🔄 Auto Scaling ライフサイクル

レストランのスタッフ勤務で理解する!インスタンスの一生とLifeCycle Hook

🍽️ レストランのスタッフシフトで例えると超わかりやすい!

Auto Scalingは「レストランのスタッフシフト管理」のようなもの!

お客様が増えれば新しいスタッフを追加(スケールアウト)、
お客様が減ればスタッフを減らす(スケールイン)。

でも、いきなり出勤・退勤させるわけにはいきません!
身だしなみチェック(起動準備) 引継ぎ作業(終了処理) が必要です。

これが LifeCycle Hook の役割✨
インスタンスの起動・終了時に「ちょっと待って!」と止めて、
必要な準備や後片付けをする時間を作ります。

📋 インスタンスのライフサイクル全体像

👔
状態 1
Pending
起動中
⏰ いつ: 起動開始直後 🔧 何を: EC2起動処理
🍽️ レストランのたとえ
新人スタッフが出勤!更衣室で制服に着替えている状態。
💻 実際の処理
• EC2インスタンスが起動開始
• IPアドレスが割り当てられる
• OSが起動する
• 基本的な初期化処理
⏸️
状態 2
Pending:Wait
起動時ライフサイクルフック待機中
⏰ いつ: Pending直後 🔧 何を: カスタム処理実行 🎯 LifeCycle Hook!
🍽️ レストランのたとえ
店長が身だしなみチェック!髪型、爪、制服がOKか最終確認。準備が整うまで接客フロアには出さない。
💻 実際の処理
EC2_INSTANCE_LAUNCHING フックが発動
• アプリケーションのデプロイ
• 設定ファイルのダウンロード
• 依存パッケージのインストール
• ヘルスチェックの準備
最大60分間待機可能
📝 実行例
• S3からアプリケーションをダウンロード
• データベース接続設定
• キャッシュのウォームアップ
• モニタリングエージェントの設定
状態 3
InService
稼働中
⏰ いつ: 準備完了後 🔧 何を: 通常業務
🍽️ レストランのたとえ
スタッフが接客フロアで活躍中!お客様の注文を受け、料理を運び、満面の笑顔でサービス提供。
💻 実際の処理
• ロードバランサーにトラフィックが流れる
• アプリケーションがリクエストを処理
• ヘルスチェックに合格している状態
• Auto Scalingのキャパシティにカウント
本番稼働中!
🎯 重要なポイント
この状態のインスタンスが増減することで、システムの処理能力が調整される
📤
状態 4
Terminating
終了中
⏰ いつ: 終了開始時 🔧 何を: 終了準備
🍽️ レストランのたとえ
お客様が減ってきたので、スタッフに「そろそろ上がっていいよ」と伝えた状態。
💻 実際の処理
• Auto Scalingが終了を決定
• ロードバランサーから切り離し開始
• 新しいリクエストは受け付けない
• 既存の処理は継続中
⏸️
状態 5
Terminating:Wait
終了時ライフサイクルフック待機中
⏰ いつ: 終了直前 🔧 何を: 後処理実行 🎯 LifeCycle Hook!
🍽️ レストランのたとえ
退勤前の引継ぎタイム!担当していたテーブルを他のスタッフに引き継ぎ、レジ金を確認し、後片付けをする。
💻 実際の処理
EC2_INSTANCE_TERMINATING フックが発動
• 処理中のリクエストを完了
• ログをS3にアップロード
• セッション情報を保存
• キャッシュデータのバックアップ
• データベース接続のグレースフルシャットダウン
最大60分間待機可能
📝 実行例
• アプリケーションログのS3保存
• セッション情報のRedisへ移行
• 進行中のジョブの完了待機
• メトリクスの最終送信
💤
状態 6
Terminated
終了完了
⏰ いつ: 最終 🔧 何を: インスタンス削除
🍽️ レストランのたとえ
スタッフが更衣室で着替えて、タイムカードを押して退勤。お疲れさまでした!
💻 実際の処理
• EC2インスタンスが完全に停止
• リソースが解放される
• IPアドレスが開放
• 課金が停止
• Auto Scalingグループから削除

🎯 LifeCycle Hookの詳細

🚀 EC2_INSTANCE_LAUNCHING
📍 発動タイミング:
インスタンスが起動し、Pending状態になった直後

🎯 目的:
トラフィックを受ける前に必要な準備を完了させる

💡 ユースケース:
• アプリケーションのデプロイ
• 設定ファイルの取得と適用
• データベーススキーマの初期化
• キャッシュのプリロード
• 依存サービスとの接続確立
• ログ収集エージェントの設定

⏱️ デフォルトタイムアウト:
3600秒(60分)

✅ 完了アクション:
CompleteLifecycleAction APIを呼び出す
• CONTINUE を送信 → InServiceへ移行
• ABANDON を送信 → インスタンス終了
🛑 EC2_INSTANCE_TERMINATING
📍 発動タイミング:
インスタンスの終了が決定し、Terminating状態になった直後

🎯 目的:
データやログを失わないよう、安全に終了する

💡 ユースケース:
• 処理中リクエストの完了待機
• ログファイルのS3へのアップロード
• セッション情報の永続化
• キャッシュデータの保存
• メトリクスの最終送信
• 外部システムへの終了通知
• データベース接続のグレースフルクローズ

⏱️ デフォルトタイムアウト:
3600秒(60分)

✅ 完了アクション:
CompleteLifecycleAction APIを呼び出す
• CONTINUE を送信 → Terminatedへ移行
• ABANDON を送信 → 即座に終了

📊 ライフサイクル状態比較表

状態 ステータス トラフィック カスタム処理 レストランのたとえ
Pending 起動中 ❌ なし ❌ 不可 更衣室で着替え中
Pending:Wait 準備中 ❌ なし 可能 店長の身だしなみチェック
InService 稼働中 ✅ あり ❌ 不可 接客フロアで勤務中
Terminating 終了中 ⚠️ 切断中 ❌ 不可 退勤指示を受けた
Terminating:Wait 後処理中 ❌ なし 可能 引継ぎ・後片付け作業
Terminated 終了済 ❌ なし ❌ 不可 退勤完了

💼 実践的なユースケース

📦

アプリケーションデプロイ

Launching Hook を使用

実装内容:
1. S3からアプリケーションをダウンロード
2. 環境変数を設定
3. 依存パッケージをインストール
4. ヘルスチェックが成功するまで待機
5. CompleteLifecycleAction で完了通知
📊

ログ保存

Terminating Hook を使用

実装内容:
1. 実行中のリクエスト完了を待機(最大30秒)
2. ローカルログをS3にアップロード
3. CloudWatch Logsに最終メトリクスを送信
4. 終了通知をSlackに送信
5. CompleteLifecycleAction で終了許可
🔐

セキュリティスキャン

Launching Hook を使用

実装内容:
1. セキュリティエージェントをインストール
2. 脆弱性スキャンを実行
3. 必要なパッチを適用
4. コンプライアンスチェック
5. 合格したらInServiceへ、不合格なら終了
💾

セッション管理

Terminating Hook を使用

実装内容:
1. アクティブなセッション情報を取得
2. セッションデータをRedisに移行
3. 既存ユーザーのリクエスト完了を待機
4. セッション移行完了を確認
5. 安全に終了
💡 LifeCycle Hook 実装のベストプラクティス
1. タイムアウトは余裕を持って設定:
通常の処理時間の2〜3倍を設定。ネットワーク遅延や負荷を考慮する。

2. 冪等性を確保:
同じスクリプトを複数回実行しても問題ないように設計。リトライ時の安全性。

3. エラーハンドリングを徹底:
失敗時は必ず ABANDON を返す。中途半端な状態でInServiceにしない。

4. ログを詳細に記録:
CloudWatch Logsに詳細なログを出力。デバッグが圧倒的に楽になる。

5. Lambda + EventBridge を活用:
インスタンス内スクリプトより、Lambdaで処理する方が管理しやすい。

6. CompleteLifecycleAction を必ず呼ぶ:
忘れるとタイムアウトまで待機してしまい、スケーリングが遅れる。

7. デッドレターキューを設定:
失敗したメッセージを保存して後で分析できるようにする。

8. テスト環境で十分に検証:
本番環境でいきなり使わず、開発環境で動作を確認してから導入。
Lambda関数実装例(Python)
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 CLI)
# 起動時の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

🍽️ レストランのたとえ
「出勤時は必ず店長チェックを受ける」「退勤時は必ず引継ぎをする」という ルールを設定 する作業。
📋 役割: LifeCycle Hookを 作成または更新 する
⏰ 実行タイミング: Auto Scalingグループの設定時(初回のみ、または設定変更時)
🎯 主な設定項目: lifecycle-transition : LAUNCHING か TERMINATING
heartbeat-timeout : タイムアウト時間(秒)
default-result : タイムアウト時の動作(CONTINUE/ABANDON)
notification-target-arn : 通知先(SNS/EventBridge)
💡 ポイント
このコマンドは1回実行すれば、その後すべてのインスタンスに自動適用される

CompleteLifecycleAction

🍽️ レストランのたとえ
店長チェックや引継ぎ作業が 「完了しました!」 と報告するアクション。これを言わないと永遠に待たされる。
📋 役割: LifeCycle Hookの処理が 完了したことを通知
⏰ 実行タイミング: カスタム処理(デプロイ、ログ保存など)が完了した時
🎯 必須パラメータ: LifecycleActionResult :
- CONTINUE : 次のステップへ進む(通常はこれ)
- ABANDON : インスタンスを終了
LifecycleActionToken : イベントから受け取ったトークン
AutoScalingGroupName : ASG名
LifecycleHookName : Hook名
⚠️ 超重要
このコマンドを呼ばないと、タイムアウトまで待機状態が続く! 必ず成功時も失敗時も呼ぶこと
💓

RecordLifecycleActionHeartbeat

🍽️ レストランのたとえ
引継ぎ作業が長引いている時に「まだ作業中です!もう少し待ってください」と 定期的に報告 して、タイムアウトを延長してもらう。
📋 役割: 処理がまだ続いていることを通知し、 タイムアウトをリセット
⏰ 実行タイミング: 処理に時間がかかる場合、タイムアウト前に定期的に送信
🎯 使用例: • 大容量ログのS3アップロード中
• データベースマイグレーション実行中
• 複数のAPIコールを順次実行中
• 長時間かかるビルド処理中
💡 推奨パターン
タイムアウト時間の半分が経過したら送信。例:timeout=600秒なら、5分ごとにハートビート送信

🔄 コマンド実行フロー

⚙️ Phase 1: 初期設定(1回だけ)

1️⃣ PutLifecycleHook
Auto Scalingグループに対して
LifeCycle Hookのルールを設定
✓ 設定完了!以降は自動的に適用される

🚀 Phase 2: 実行フェーズ(インスタンスごと)

インスタンス起動/終了時に自動発動
⏸️ 待機状態に入る
Pending:Wait または Terminating:Wait
▶️ カスタム処理開始
• アプリケーションデプロイ
• ログ保存
• etc...
2️⃣ RecordLifecycleActionHeartbeat(必要に応じて)
処理が長引く場合、定期的に送信
→ タイムアウトがリセットされる
3️⃣ CompleteLifecycleAction(必須!)
処理完了を通知
• 成功 → CONTINUE
• 失敗 → ABANDON
✅ 次のステップへ進む
InService または Terminated へ移行

タイムアウト時の挙動

CompleteLifecycleAction を呼ばずにタイムアウトした場合:

default-result の設定に従う
CONTINUE に設定: 次のステップへ進む
ABANDON に設定: インスタンスを終了
⚠️ 注意
タイムアウトに依存せず、必ず CompleteLifecycleAction を明示的に呼ぶべき!
完全な実装例:3つのコマンドの使用方法
# ========================================
# 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 で「準備」と「引継ぎ」の時間を確保し、
安全で確実なスケーリングを実現✨

🚀
Launching Hook

出勤前の
身だしなみチェック
準備万端で接客開始
InService

フロアで
活躍中!
お客様対応中
🛑
Terminating Hook

退勤前の
引継ぎ作業
安全に退勤

🎯 LifeCycle Hookの黄金ルール:
必ず CompleteLifecycleAction を呼ぶ!
成功なら CONTINUE、失敗なら ABANDON
これで安全なスケーリングが実現!🎉

Created by SSuzuki1063

AWS SAP Learning Resources