カレーのライスをtech忘れ

odmishienのtechメモ

Amazon EC2 Auto Scaling のライフサイクルフックを完了させる時の lifecycle-action-result とは

この記事は DeNA 21新卒×22新卒内定者 Advent Calendar 2021 #DeNA21x22AdCal の23日目の記事です。

この記事で書くこと / 書かないこと

書くこと

  • Amazon EC2 Auto Scaling のライフサイクルフックを完了させる AWS CLI コマンド complete-lifecycle-actionlifecycle-action-result 引数(CONTINUE / ABANDON)について
    • どのような挙動をするのか
      • Launch / Terminate 時
      • ライフサイクルアクションが1つ / 複数ある時
    • どのような場面で指定すべきか

書かないこと

Amazon EC2 Auto Scaling のライフサイクルフック

Amazon EC2 Auto Scalingでは ライフサイクルフックという仕組みが使える。ドキュメントには以下のように紹介されている。

Amazon EC2 Auto Scaling は、Auto Scaling グループにライフサイクルフックを追加する機能を提供します。これらのフックにより、Auto Scaling グループは Auto Scaling インスタンスライフサイクルのイベントを認識し、対応するライフサイクルイベントが発生したときにカスタムアクションを実行することを有効にします。

docs.aws.amazon.com

ドキュメントにある図*1が分かりやすい。

f:id:odmishien:20210803190119p:plain

  • Pending:Wait / Terminating:Wait の間にやりたい処理を実行する
  • 処理が終わったら、ライフサイクルアクションを完了させる
    • これは aws autoscaling complete-lifecycle-action という AWS CLI のコマンドで実行できる
  • Pending:Proceed / Terminating: Proceed に移行する
  • その後 InService / Terminated となる

というような流れである。Pending:Wait 時は プロビジョニングなどやるし、Terminating:Wait 時はログの収集などをやるだろうか。何かと便利。

lifecycle-action-result について分からないこと

先述した aws autoscaling complete-lifecycle-action のオプションに lifecycle-action-result というものがある。これに関してドキュメントにはこう書いてある。

lifecycle-action-result (string) The action for the group to take. This parameter can be either CONTINUE or ABANDON .

CONTINUEABANDON という値が取れることは分かった。しかし以下の 2点がよく分からない。

  • どのような挙動をするのか?
  • どのような場面で指定すべきか?

ドキュメントを漁ってみても、あまりめぼしい情報が出てこなかったので、手元のAmazon EC2 Auto Scaling 環境で実験してみることにした。

環境を用意

今回は以下の 4パターンにおける CONTINUE / ABANDON の挙動を確認する。

  • EC2 インスタンスを Launch する時
    • ライフサイクルフックが 1 つだけ作成されている時
    • ライフサイクルフックが複数作成されている時
  • EC2 インスタンスを Terminate する時
    • ライフサイクルフックが 1 つだけ作成されている時
    • ライフサイクルフックが複数作成されている時

上の挙動確認のために、 2つの AutoScalingGroupを用意した。

  1. ライフサイクルフックを Launch / Terminate 時のそれぞれ 1 つずつ作成した AutoScalingGroup
  2. ライフサイクルフックを Launch / Terminate 時のそれぞれ 2 つずつ作成した AutoScalingGroup

まず Auto Scaling の起動設定を作成。

❯ aws autoscaling create-launch-configuration \
  --launch-configuration-name test-as-conf \
  --image-id ami-xxxxx \
  --security-groups sg-xxxxx \
  --instance-type t2.micro

この起動設定からインスタンスを作成/削除する Auto Scaling グループを作成。上述した 2つの環境を設定にそれぞれ書いておく。

❯ vim test-asg.json
{
    "AutoScalingGroupName": "test-asg",
    "LaunchConfigurationName": "test-as-conf",
    "LifecycleHookSpecificationList": [
        {
            "LifecycleHookName": "launch-hook",
            "LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING"
        },
        {
            "LifecycleHookName": "terminate-hook",
            "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING"
        }
    ],
    "MinSize": 1,
    "MaxSize": 1,
    "DesiredCapacity": 1,
    "VPCZoneIdentifier": "subnet-xxxxxx"
}

❯ vim test-asg-multiple-hooks.json
{
    "AutoScalingGroupName": "test-asg-multiple-hooks",
    "LaunchConfigurationName": "test-as-conf",
    "LifecycleHookSpecificationList": [
        {
            "LifecycleHookName": "launch-hook-1",
            "LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING"
        },
        {
            "LifecycleHookName": "launch-hook-2",
            "LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING"
        },
        {
            "LifecycleHookName": "terminate-hook-1",
            "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING"
        },
        {
            "LifecycleHookName": "terminate-hook-2",
            "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING"
        }
    ],
    "MinSize": 1,
    "MaxSize": 1,
    "DesiredCapacity": 1,
    "VPCZoneIdentifier": "subnet-xxxxxx"
}

❯ aws autoscaling create-auto-scaling-group \
  --cli-input-json file://./test-asg.json

❯ aws autoscaling create-auto-scaling-group \
  --cli-input-json file://./test-asg-multiple-hooks.json

本来はこのライフサイクルフックに NotificationTargetARN を指定することで、SQS にジョブをキューしたり、Lambda を呼び出したりして、実行したい処理を呼び出す。今回はライフサイクルフック完了を CLI から直接行うだけなので、特に通知先は指定しない。

インスタンスを Launchさせる時

以上の設定で、ライフサイクルフックが 1つの環境も複数の環境も共に、既にインスタンスが 1台立ち上がり、ライフサイクルフックの完了を待っている(Pending:Wait) 状態になっている。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-01651d39583878c03",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Pending:Wait",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg-multiple-hooks \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-06f48fba796571cbc",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Pending:Wait",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

CONTINUE

ライフサイクルフックが 1つの場合

lifecycle-action-resultCONTINUE を指定して、launch-hook という名前で作成したライフサイクルフックを完了させてみる。

❯ aws autoscaling complete-lifecycle-action \
    --lifecycle-hook-name "launch-hook" \
    --auto-scaling-group-name test-asg \
    --lifecycle-action-result CONTINUE \
    --instance-id i-01651d39583878c03

インスタンスの状態を確認してみると LifecycleStateInService になっている。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg \
| jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-01651d39583878c03",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "InService",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

ライフサイクルフックが複数の場合

lifecycle-action-resultCONTINUE を指定して、2つのうちの 1つのライフサイクルフック(launch-hook-1) 完了させてみる。

❯ aws autoscaling complete-lifecycle-action \
    --lifecycle-hook-name "launch-hook-1" \
    --auto-scaling-group-name test-asg-multiple-hooks \
    --lifecycle-action-result CONTINUE \
    --instance-id i-06f48fba796571cbc

インスタンスの状態を確認してみると、ライフサイクルフックが 1つの場合は InService していたのに対して、まだ Pending:Wait のままである。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg-multiple-hooks \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-06f48fba796571cbc",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Pending:Wait",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

では続いて、残りのもう 1つのライフサイクルフック(launch-hook-2) を、同じく lifecycle-action-resultCONTINUE を指定して完了させてみる。

❯ aws autoscaling complete-lifecycle-action \
    --lifecycle-hook-name "launch-hook-2" \
    --auto-scaling-group-name test-asg-multiple-hooks \
    --lifecycle-action-result CONTINUE \
    --instance-id i-06f48fba796571cbc

すると、全てのライフサイクルフックが完了したので、LifecycleStateInService となる。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg-multiple-hooks \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-06f48fba796571cbc",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "InService",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

ABANDON

ライフサイクルフックが 1つの場合

今度は ABANDON を指定してみる。

❯ aws autoscaling complete-lifecycle-action \
    --lifecycle-hook-name "launch-hook" \
    --auto-scaling-group-name test-asg \
    --lifecycle-action-result ABANDON \
    --instance-id i-06088732b011ec77c

インスタンスTerminating:Wait となって、InService にはならない。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-06088732b011ec77c",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Terminating:Wait",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

ライフサイクルフックが複数の場合

lifecycle-action-resultABANDON を指定して、2つのうちの 1つのライフサイクルフック(launch-hook-1) 完了させてみる。

❯ aws autoscaling complete-lifecycle-action \
    --lifecycle-hook-name "launch-hook-1" \
    --auto-scaling-group-name test-asg-multiple-hooks \
    --lifecycle-action-result ABANDON \
    --instance-id i-05f20b0925768e65a

ABANDON が指定されたため、1つ目を完了させた時点で2つ目のライフサイクルフックは無視され、Terminating:Wait となった。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg-multiple-hooks \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-05f20b0925768e65a",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Terminating:Wait",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

インスタンスを Terminate させる時

勘の良い方はもう大体挙動が理解できたと思われるが、続いてインスタンスを Terminate させる時の挙動について確かめてみる。インスタンスは先ほどの実験の残りカスが Terminating:Wait のまま待機しているので、それを利用する。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-078955cc41ee3f010",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Terminating:Wait",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

CONTINUE

ライフサイクルフックが 1つの場合

lifecycle-action-resultCONTINUE を指定して、terminate-hook という名前で作成したライフサイクルフックを完了させてみる。

❯ aws autoscaling complete-lifecycle-action \
    --lifecycle-hook-name "terminate-hook" \
    --auto-scaling-group-name test-asg \
    --lifecycle-action-result CONTINUE \
    --instance-id i-06088732b011ec77c

その後インスタンスを確認してみると、LifecycleStateTerminateing:Proceed となる。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-06088732b011ec77c",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Terminating:Proceed",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

少し待てば完全に Terminate され、AutoScalingGroup からいなくなる。

ライフサイクルフックが複数の場合

lifecycle-action-resultCONTINUE を指定して、2つのうちの 1つのライフサイクルフック(terminate-hook-1) 完了させてみる。

❯ aws autoscaling complete-lifecycle-action \
    --lifecycle-hook-name "terminate-hook-1" \
    --auto-scaling-group-name test-asg-multiple-hooks \
    --lifecycle-action-result CONTINUE \
    --instance-id i-05f20b0925768e65a

CONTINUE したので、Terminate 時でも、もう1つのライフサイクルフックが完了するまで Terminating:Wait のままである。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg-multiple-hooks \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-05f20b0925768e65a",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Terminating:Wait",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

続いて2つ目(terminate-hook-2)を完了させる。

aws autoscaling complete-lifecycle-action \
    --lifecycle-hook-name "terminate-hook-2" \
    --auto-scaling-group-name test-asg-multiple-hooks \
    --lifecycle-action-result CONTINUE \
    --instance-id i-05f20b0925768e65a

全てのライフサイクルフックが完了したので、Terminating:Proceed となり、しばらくしたら LifecycleStateTerminatedとなる。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg-multiple-hooks \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-05f20b0925768e65a",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Terminating:Proceed",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

ABANDON

ライフサイクルフックが 1つの場合

ABANDON を指定して terminate-hook を完了させる。

❯ aws autoscaling complete-lifecycle-action \
    --lifecycle-hook-name "terminate-hook" \
    --auto-scaling-group-name test-asg \
    --lifecycle-action-result ABANDON \
    --instance-id i-078955cc41ee3f010

Terminate させる時は、CONTINUE と同じく、Terminating:Proceed となる。 ABANDON は 「中断」という意味だが、インスタンスを減らすことを中断するわけではない。

❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-078955cc41ee3f010",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Terminating:Proceed",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

ライフサイクルフックが 複数の場合

ABANDON を指定して 2つのうちの 1つのライフサイクルフック(terminate-hook-1) を完了させる。

❯ aws autoscaling complete-lifecycle-action \
    --lifecycle-hook-name "terminate-hook-1" \
    --auto-scaling-group-name test-asg-multiple-hooks \
    --lifecycle-action-result ABANDON \
    --instance-id i-0aa497f0729a2a0bf
❯ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-name test-asg-multiple-hooks \
    | jq ".AutoScalingGroups[].Instances[]"
{
  "InstanceId": "i-0aa497f0729a2a0bf",
  "InstanceType": "t2.micro",
  "AvailabilityZone": "us-east-2a",
  "LifecycleState": "Terminating:Proceed",
  "HealthStatus": "Healthy",
  "LaunchConfigurationName": "test-autoscale",
  "ProtectedFromScaleIn": false
}

こちらも Launch 時と同じく、複数のライフサイクルフックがあったとしても、ABANDON を指定した場合はその他のライフサイクルフックは無視される。問答無用でインスタンスTerminating:Proceed へと移行し、やがて Terminated となる。

まとめ

Amazon EC2 Auto Scaling のライフサイクルフックを完了させる時の lifecycle-action-result の挙動について、実際にコマンドを打ちながら確認した。まとめると以下のような感じ。

CONTINUE ABANDON
Launch × 1つのライフサイクルフック InService Terminated
Launch × 複数のライフサイクルフック インスタンスは他のライフサイクルフックが完了されるのを待つ → 全てのライフサイクルフックが完了すると InService Terminated
Terminate × 1つのライフサイクルフック Terminated Terminated
Terminate × 複数のライフサイクルフック インスタンスは他のライフサイクルフックが完了されるのを待つ → 全てのライフサイクルフックが完了すると Terminated Terminated
  • CONTINUE

    • ライフサイクルフックを次のステップに進める
      • ライフサイクルフックが複数あるなら、残りのライフサイクルフックの結果を待つ
      • 完了していないライフサイクルフックが無くなったら、 InService / Terminated になる
    • フックした処理が成功した場合に指定することが多い
  • ABANDON

    • とにかく全てのライフサイクルフックを中断し、現在のインスタンスTerminated にする
    • フックした処理が失敗した場合に指定することが多い
      • 例えばChef などのプロビジョニングツールでの処理をフックしたが失敗した場合、そのようなインスタンスはサービスで使えないので、 ABANDON で terminate させてしまう → 違うインスタンスが立ち上がる

それでは皆様もよい AutoScaling ライフを!

参考

docs.aws.amazon.com

docs.aws.amazon.com