Amazon EC2 Auto Scallingのスケールイン時にインスタンスメタデータを利用してEC2インスタンス内のログを退避させる

2022.03.28

コンサル部のとばち(@toda_kk)です。

先日、EC2 Auto Scalingのライフサイクル状態をインスタンスメタデータから取得から取得できるようになるアップデートがありました。

ライフサイクルの遷移をトリガーにして特定の処理を実行したい場合、従来はライフサイクルフックとEventBridgeを組み合わせるなどの方法を取る必要がありました。

例えば下記ページのように、Auto Scalingグループで管理するEC2インスタンスがスケールインによって終了する際に、ローカルのログをS3バケットに転送するといった処理を実現できます。

そこで、今回はインスタンスメタデータを利用して上記の内容と同等の処理を実現させてみたいと思います。

仕組みとしては、AWS公式で用意されているチュートリアルのドキュメントと同じように、インスタンスメタデータサービス(IMDS)をポーリングするユーザーデータを設定し、ライフサイクルの状態が Terminated になった際にローカルのログをS3バケットに転送するようなスクリプトを実行します。

必要な手順

  1. ユーザーデータを設定した起動テンプレートを作成する
  2. 作成した起動テンプレートを使用したAuto Scalingグループを作成する
  3. Auto Scalingグループにライフサイクルを追加する

なお、ログの転送先となるS3バケットや、EC2に必要な権限(S3へのPutObjectや、ライフサイクルアクション完了のコールバック送信)を付与したIAMロールおよびインスタンスプロファイルについては、既に作成済みの前提とします。

ユーザーデータを設定した起動テンプレートを作成する

まず、Auto Scalingグループで指定する起動テンプレートを作成します。起動テンプレートには、下記のようにスクリプトを実行するユーザーデータを設定します。

下記の変数については、適宜変更してください。

  • S3バケット名: bucket
  • ライフサイクルフック名: lifecycle_hook_name
  • Auto Scalingグループ名: group_name
  • 転送したいログのパス: log_path

ユーザーデータ

#!/bin/bash

function get_target_state {
    echo $(curl -s http://169.254.169.254/latest/meta-data/autoscaling/target-lifecycle-state)
}

function get_instance_id {
    echo $(curl -s http://169.254.169.254/latest/meta-data/instance-id)
}

function get_region {
    echo $(curl -s http://169.254.169.254/latest/meta-data/placement/region)
}

function export_logs {
    instance_id=$(get_instance_id)
    region=$(get_region)
    bucket='auto-scaling-terminated-log-bucket'
    lifecycle_hook_name='terminated-hook'
    group_name='test-auto-scaling-group'
    log_path='/var/log/cloud-init-output.log'
 
    echo $instance_id
    echo $region

    # export logs
    sudo aws s3 cp $log_path s3://$bucket/$group_name-$instance_id/

    # send callback
    echo $(aws autoscaling complete-lifecycle-action \
      --lifecycle-hook-name $lifecycle_hook_name \
      --auto-scaling-group-name $group_name \
      --lifecycle-action-result CONTINUE \
      --instance-id $instance_id \
      --region $region)
}

function main {
    while true
    do
        target_state=$(get_target_state)
        echo $target_state
        if [ \"$target_state\" = \"Terminated\" ]; then
            export_logs
        fi
        echo $target_state
        sleep 5
    done
}

main

起動テンプレートをマネジメントコンソールから作成する場合は、「EC2 Auto Scaling で使用できるテンプレートをセットアップする際に役立つガイダンスを提供」にチェックを入れると必須や推奨の項目が表示されるので便利です。

また、ユーザーデータは「高度な詳細」から設定できます。

「ユーザーデータは既に base64 エンコードされています」のチェックがありますが、スクリプトを直接記載する場合は外した状態で設定します。

作成した起動テンプレートを使用したAuto Scalingグループを作成する

先ほど作成した起動テンプレートを指定して、Auto Scalingグループを作成します。

上述のユーザーデータで指定したAuto Scalingグループ名(group_name)を設定しておきます。

また、後ほどスケールインの動作確認をするために「希望する容量」「最小キャパシティ」「最大キャパシティ」を「2」に設定しておきます。

Auto Scalingグループにライフサイクルを追加する

続いて、Auto Scalingグループにライフサイクルフックを追加します。

上述のユーザーデータで指定していたライフサイクルフック名(lifecycle_hook_name)を設定しておきます。

「ライフサイクル移行」を「インスタンス終了」に設定することで、スケールイン時の動作を制御することができます。

「ハートビートタイムアウト」は、EC2インスタンスがスケールインの通知を受け取ってから停止するまでの待機時間を設定します。この間に、インスタンスのローカルに保存されたログファイルをS3バケットに転送することになります。デフォルトでは3600秒ですが、任意の秒数に変更してください。

「デフォルトの結果」を「CONTINUE」に設定します。

Auto Scalingグループの起動数を変更してテストしてみる

最後に、動作確認してみます。Auto Scalingグループの「希望する容量」「最小キャパシティ」「最大キャパシティ」から起動数を「2」から「1」に変更することで、スケールインによりEC2インスタンスを1台停止します。

マネジメントコンソールから、ライフサイクルの状態が「InService」から「Terminating:Wait」や「Terminating:Proceed」に変化し、スケールインが進んでいることがわかります。

しばらくするとスケールインが完了し、インスタンスが停止します。

ログの転送先として指定したS3バケットを確認すると、期待通りにファイルが保存されています。

既存の方法よりも管理するAWSリソースが少なくて済む

Auto Scalingグループにおけるスケールイン/アウト時の操作を制御したい際に、従来はEventBridgeやLambdaといったAWSリソースが必要になっていました。

ライフサイクルの状態をEC2インスタンス上でインスタンスメタデータから取得できるようになったことで、操作をEC2インスタンス上で完結でき、必要なAWSリソースが少なくて済むようになりました。

ただし、今回のようにインスタンスメタデータサービスをポーリングすることで実現する場合、ポーリングしているプロセスが停止してしまった際のケアなどを考慮する必要がありますので、ご注意ください。

以上、コンサル部のとばち(@toda_kk)でした。