🍽️ レストランのスタッフシフトで例えると超わかりやすい!
Auto Scalingは「レストランのスタッフシフト管理」のようなもの!
お客様が増えれば新しいスタッフを追加(スケールアウト)、
お客様が減ればスタッフを減らす(スケールイン)。
でも、いきなり出勤・退勤させるわけにはいきません!
身だしなみチェック(起動準備)
や
引継ぎ作業(終了処理)
が必要です。
これが
LifeCycle Hook
の役割✨
インスタンスの起動・終了時に「ちょっと待って!」と止めて、
必要な準備や後片付けをする時間を作ります。
📋 インスタンスのライフサイクル全体像
• IPアドレスが割り当てられる
• OSが起動する
• 基本的な初期化処理
• アプリケーションのデプロイ
• 設定ファイルのダウンロード
• 依存パッケージのインストール
• ヘルスチェックの準備
• 最大60分間待機可能
• データベース接続設定
• キャッシュのウォームアップ
• モニタリングエージェントの設定
• アプリケーションがリクエストを処理
• ヘルスチェックに合格している状態
• Auto Scalingのキャパシティにカウント
• 本番稼働中!
• ロードバランサーから切り離し開始
• 新しいリクエストは受け付けない
• 既存の処理は継続中
• 処理中のリクエストを完了
• ログをS3にアップロード
• セッション情報を保存
• キャッシュデータのバックアップ
• データベース接続のグレースフルシャットダウン
• 最大60分間待機可能
• セッション情報のRedisへ移行
• 進行中のジョブの完了待機
• メトリクスの最終送信
• リソースが解放される
• IPアドレスが開放
• 課金が停止
• Auto Scalingグループから削除
🎯 LifeCycle Hookの詳細
インスタンスが起動し、Pending状態になった直後
🎯 目的:
トラフィックを受ける前に必要な準備を完了させる
💡 ユースケース:
• アプリケーションのデプロイ
• 設定ファイルの取得と適用
• データベーススキーマの初期化
• キャッシュのプリロード
• 依存サービスとの接続確立
• ログ収集エージェントの設定
⏱️ デフォルトタイムアウト:
3600秒(60分)
✅ 完了アクション:
•
CompleteLifecycleAction
APIを呼び出す
• CONTINUE を送信 → InServiceへ移行
• ABANDON を送信 → インスタンス終了
インスタンスの終了が決定し、Terminating状態になった直後
🎯 目的:
データやログを失わないよう、安全に終了する
💡 ユースケース:
• 処理中リクエストの完了待機
• ログファイルのS3へのアップロード
• セッション情報の永続化
• キャッシュデータの保存
• メトリクスの最終送信
• 外部システムへの終了通知
• データベース接続のグレースフルクローズ
⏱️ デフォルトタイムアウト:
3600秒(60分)
✅ 完了アクション:
•
CompleteLifecycleAction
APIを呼び出す
• CONTINUE を送信 → Terminatedへ移行
• ABANDON を送信 → 即座に終了
📊 ライフサイクル状態比較表
| 状態 | ステータス | トラフィック | カスタム処理 | レストランのたとえ |
|---|---|---|---|---|
| Pending | 起動中 | ❌ なし | ❌ 不可 | 更衣室で着替え中 |
| Pending:Wait | 準備中 | ❌ なし | ✅ 可能 | 店長の身だしなみチェック |
| InService | 稼働中 | ✅ あり | ❌ 不可 | 接客フロアで勤務中 |
| Terminating | 終了中 | ⚠️ 切断中 | ❌ 不可 | 退勤指示を受けた |
| Terminating:Wait | 後処理中 | ❌ なし | ✅ 可能 | 引継ぎ・後片付け作業 |
| Terminated | 終了済 | ❌ なし | ❌ 不可 | 退勤完了 |
💼 実践的なユースケース
アプリケーションデプロイ
Launching Hook を使用
2. 環境変数を設定
3. 依存パッケージをインストール
4. ヘルスチェックが成功するまで待機
5. CompleteLifecycleAction で完了通知
ログ保存
Terminating Hook を使用
2. ローカルログをS3にアップロード
3. CloudWatch Logsに最終メトリクスを送信
4. 終了通知をSlackに送信
5. CompleteLifecycleAction で終了許可
セキュリティスキャン
Launching Hook を使用
2. 脆弱性スキャンを実行
3. 必要なパッチを適用
4. コンプライアンスチェック
5. 合格したらInServiceへ、不合格なら終了
セッション管理
Terminating Hook を使用
2. セッションデータをRedisに移行
3. 既存ユーザーのリクエスト完了を待機
4. セッション移行完了を確認
5. 安全に終了
通常の処理時間の2〜3倍を設定。ネットワーク遅延や負荷を考慮する。
2. 冪等性を確保:
同じスクリプトを複数回実行しても問題ないように設計。リトライ時の安全性。
3. エラーハンドリングを徹底:
失敗時は必ず ABANDON を返す。中途半端な状態でInServiceにしない。
4. ログを詳細に記録:
CloudWatch Logsに詳細なログを出力。デバッグが圧倒的に楽になる。
5. Lambda + EventBridge を活用:
インスタンス内スクリプトより、Lambdaで処理する方が管理しやすい。
6. CompleteLifecycleAction を必ず呼ぶ:
忘れるとタイムアウトまで待機してしまい、スケーリングが遅れる。
7. デッドレターキューを設定:
失敗したメッセージを保存して後で分析できるようにする。
8. テスト環境で十分に検証:
本番環境でいきなり使わず、開発環境で動作を確認してから導入。
import boto3 import json autoscaling = boto3.client('autoscaling') s3 = boto3.client('s3') def lambda_handler(event, context): # EventBridgeからのLifeCycle Hookイベントを処理 detail = event['detail'] instance_id = detail['EC2InstanceId'] lifecycle_hook_name = detail['LifecycleHookName'] asg_name = detail['AutoScalingGroupName'] lifecycle_token = detail['LifecycleActionToken'] try: # カスタム処理を実行(例:アプリケーションデプロイ) if 'launching' in lifecycle_hook_name.lower(): # 起動時の処理 deploy_application(instance_id) elif 'terminating' in lifecycle_hook_name.lower(): # 終了時の処理 save_logs_to_s3(instance_id) # 処理成功 - CONTINUEを送信 autoscaling.complete_lifecycle_action( LifecycleHookName=lifecycle_hook_name, AutoScalingGroupName=asg_name, LifecycleActionToken=lifecycle_token, LifecycleActionResult='CONTINUE', InstanceId=instance_id ) return { 'statusCode': 200, 'body': json.dumps('LifeCycle Hook completed successfully') } except Exception as e: print(f"Error: {str(e)}") # 処理失敗 - ABANDONを送信 autoscaling.complete_lifecycle_action( LifecycleHookName=lifecycle_hook_name, AutoScalingGroupName=asg_name, LifecycleActionToken=lifecycle_token, LifecycleActionResult='ABANDON', InstanceId=instance_id ) raise def deploy_application(instance_id): # アプリケーションデプロイロジック print(f"Deploying application to {instance_id}") # SSM Run Commandなどを使用して実行 pass def save_logs_to_s3(instance_id): # ログをS3に保存するロジック print(f"Saving logs from {instance_id} to S3") pass
# 起動時のLifeCycle Hook作成 aws autoscaling put-lifecycle-hook \ --lifecycle-hook-name launching-hook \ --auto-scaling-group-name my-asg \ --lifecycle-transition autoscaling:EC2_INSTANCE_LAUNCHING \ --default-result CONTINUE \ --heartbeat-timeout 600 \ --notification-target-arn arn:aws:events:region:account:rule/my-rule # 終了時のLifeCycle Hook作成 aws autoscaling put-lifecycle-hook \ --lifecycle-hook-name terminating-hook \ --auto-scaling-group-name my-asg \ --lifecycle-transition autoscaling:EC2_INSTANCE_TERMINATING \ --default-result CONTINUE \ --heartbeat-timeout 300 \ --notification-target-arn arn:aws:events:region:account:rule/my-rule
🎮 LifeCycle Hook の3つの必須コマンド
PutLifecycleHook
• heartbeat-timeout : タイムアウト時間(秒)
• default-result : タイムアウト時の動作(CONTINUE/ABANDON)
• notification-target-arn : 通知先(SNS/EventBridge)
CompleteLifecycleAction
- CONTINUE : 次のステップへ進む(通常はこれ)
- ABANDON : インスタンスを終了
• LifecycleActionToken : イベントから受け取ったトークン
• AutoScalingGroupName : ASG名
• LifecycleHookName : Hook名
RecordLifecycleActionHeartbeat
• データベースマイグレーション実行中
• 複数のAPIコールを順次実行中
• 長時間かかるビルド処理中
🔄 コマンド実行フロー
⚙️ Phase 1: 初期設定(1回だけ)
LifeCycle Hookのルールを設定
✓ 設定完了!以降は自動的に適用される
🚀 Phase 2: 実行フェーズ(インスタンスごと)
• ログ保存
• etc...
→ タイムアウトがリセットされる
• 成功 → CONTINUE
• 失敗 → ABANDON
⏰ タイムアウト時の挙動
• ABANDON に設定: インスタンスを終了
# ======================================== # Step 1: LifeCycle Hook の設定(初回のみ) # ======================================== aws autoscaling put-lifecycle-hook \ --lifecycle-hook-name "my-launching-hook" \ --auto-scaling-group-name "my-asg" \ --lifecycle-transition "autoscaling:EC2_INSTANCE_LAUNCHING" \ --default-result "ABANDON" \ --heartbeat-timeout 1800 \ --notification-target-arn "arn:aws:events:ap-northeast-1:123456789012:rule/my-lifecycle-rule" # ======================================== # Step 2: Lambda関数での処理(インスタンスごと) # ======================================== import boto3 import time autoscaling = boto3.client('autoscaling') def lambda_handler(event, context): # イベントから必要な情報を取得 detail = event['detail'] instance_id = detail['EC2InstanceId'] hook_name = detail['LifecycleHookName'] asg_name = detail['AutoScalingGroupName'] token = detail['LifecycleActionToken'] try: # 長時間処理の場合、ハートビートを送信 for i in range(3): # 処理を実行 perform_deployment_step(instance_id, i) # タイムアウト前にハートビート送信 if i < 2: # 最後は不要 autoscaling.record_lifecycle_action_heartbeat( LifecycleHookName=hook_name, AutoScalingGroupName=asg_name, LifecycleActionToken=token, InstanceId=instance_id ) print(f"Heartbeat sent for step {i+1}") time.sleep(300) # 5分待機 # 処理成功 - CONTINUE で完了通知 autoscaling.complete_lifecycle_action( LifecycleHookName=hook_name, AutoScalingGroupName=asg_name, LifecycleActionToken=token, LifecycleActionResult='CONTINUE', InstanceId=instance_id ) return {'status': 'success'} except Exception as e: print(f"Error: {str(e)}") # 処理失敗 - ABANDON で完了通知 autoscaling.complete_lifecycle_action( LifecycleHookName=hook_name, AutoScalingGroupName=asg_name, LifecycleActionToken=token, LifecycleActionResult='ABANDON', InstanceId=instance_id ) raise def perform_deployment_step(instance_id, step): print(f"Performing deployment step {step} for {instance_id}") # デプロイ処理の実装 pass
❓ よくある質問
Q1: CompleteLifecycleAction を呼び忘れたらどうなる?
A: タイムアウト時間(デフォルト3600秒)まで待機し続けます。その後、 default-result の設定に従って自動的に処理されます。しかし、 必ず明示的に呼ぶべき です!タイムアウト待ちはスケーリングの遅延につながります。
Q2: RecordLifecycleActionHeartbeat はいつ送るべき?
A: 処理が タイムアウト時間の半分 を超えそうな場合に送信します。例:timeout=1800秒なら、15分ごとに送信。ハートビートを送ると、タイムアウトカウンターがリセットされます。
Q3: CONTINUE と ABANDON の使い分けは?
A:
•
CONTINUE
: 処理が成功し、インスタンスを次の状態(InServiceまたはTerminated)へ進める
•
ABANDON
: 処理が失敗し、インスタンスを終了させる(Launchingの場合)または即座に終了する(Terminatingの場合)
Q4: ハートビートは何回まで送れる?
A: 回数制限はありませんが、 最大待機時間は48時間 です。ただし、長時間の待機は推奨されません。処理が長引く場合は設計を見直しましょう。
Q5: PutLifecycleHook は何回も実行しても大丈夫?
A: 大丈夫です。同じHook名で実行すると 上書き更新 されます。設定を変更したい場合は、再度 PutLifecycleHook を実行すればOKです。
🎓 まとめ
🍽️ レストランスタッフ = EC2インスタンス
Auto Scalingのライフサイクルは、レストランのスタッフ管理と同じ!
LifeCycle Hook
で「準備」と「引継ぎ」の時間を確保し、
安全で確実なスケーリングを実現✨
出勤前の
身だしなみチェック
準備万端で接客開始
フロアで
活躍中!
お客様対応中
退勤前の
引継ぎ作業
安全に退勤
🎯
LifeCycle Hookの黄金ルール:
必ず CompleteLifecycleAction を呼ぶ!
成功なら CONTINUE、失敗なら ABANDON
これで安全なスケーリングが実現!🎉