終了通知が届いたスポットインスタンスをLambdaでELBから取り外してみた

Cloudwatchイベントに終了通知が届いたスポットインスタンスを、2分の猶予期間の間にLambda関数を利用してELBから除外、サービス影響の回避できるか試みてみました。
2018.12.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

AWSチームのすずきです。

スポットインスタンスはオンデマンドと比較して70〜80%廉価な料金で利用する事ができますが、 AWS側の在庫状況により強制中断される事があります。

今回、強制中断の2分前を知らせる通知をAmazon CloudWatch イベントで受け取り、 ELB(ALB)からの自動デタッチを行うLambda関数を作成。 その動作を確認する機会がありましたので紹介させて頂きます。

Lambda 処理内容

インスタンスID

  • 停止対象となるEC2のインスタンスIDを取得します。
  instance_id = event['detail']['instance-id']

オートスケールグループ情報

  • 停止対象のEC2インスタンスのタグ情報からオートスケールグループ名を取得します。
  client = boto3.client('ec2')
  response = client.describe_instances(Filters=[{'Name':'instance-id', 'Values':[instance_id]}])
  StateName = response['Reservations'][0]['Instances'][0]['State']['Name']
  if StateName == 'running':
    for tag in response['Reservations'][0]['Instances'][0]['Tags']:
      if tag['Key'] == 'aws:autoscaling:groupName':
        asg_name = tag['Value']

ターゲットグループ情報

  • オートスケールで設定された、ALBのターゲットグループ情報を取得します。
  • CLB対応は未実装です。
  client = boto3.client('autoscaling')
  response = client.describe_auto_scaling_groups(AutoScalingGroupNames=[asg_name])
  targetgroup_arns = response['AutoScalingGroups'][0]['TargetGroupARNs']

  • ターゲットグループのアタッチ情報からデタッチ対象を取得します。
  client = boto3.client('elbv2')
  response = client.describe_target_health(
    TargetGroupArn = targetgroup_arn
  )
  target_ids = []
  for a in response['TargetHealthDescriptions']:
    if a['Target']['Id'] == instance_id:
      if a['TargetHealth']['State'] == 'healthy':
        target_ids.append(a['Target'])

インスタンスの取り外し

  • ターゲットグループから停止対象のインスタンスを取り外します。
  client = boto3.client('elbv2')
  response = client.deregister_targets(
    TargetGroupArn = targetgroup_arn,
    Targets = target_ids,
  )

確認

  • Instance-teminated-no-capacity が発生し、強制終了したスポットインスタンスの動作ログを確認してみました。

00:56:42

  • スポット中断2分前を示す、EC2 Spot Instance Interruption Warning 発生
  • Cloudwatch イベント通知内容
{
  "version": "0",
  "id": "11111111-2222-3333-4444-555555555555",
  "detail-type": "EC2 Spot Instance Interruption Warning",
  "source": "aws.ec2",
  "account": "000000000000",
  "time": "2018-12-26T00:56:42Z",
  "region": "ap-northeast-1",
  "resources": [
    "arn:aws:ec2:ap-northeast-1c:instance/i-0000000000000cc6c"
  ],
  "detail": {
    "instance-id": "i-0000000000000cc6c",
    "instance-action": "terminate"
  }
}

00:56:45

  • Cloudwatch Event連携のLambda起動

00:56:46 (経過時間:1秒)

DeregisterTargets (ELB)

  • 停止対象EC2をELBからデタッチされ、正常ホスト数が減少

00:57:06 (経過時間:21秒)

  • 20秒の登録解除の遅延時間経過し、ELB→EC2の転送終了

00:58:45 (経過時間:120秒)

  • スポットインスタンスの停止

01:01:11 (経過時間:266秒)

  • 代替インスタンスがオートスケールで起動し、正常ホスト数回復

まとめ

強制停止対象となったスポットインスタンス、Lambdaを利用して猶予時間内にELBから除外処理を行う事で、 エラー応答の発生などサービス影響を回避できる事が確認できました。

適切なオートスケール設定を施す事で、廉価に利用できるスポットインスタンスでも高い可用性を実現可能です。 負荷に応じてインスタンス台数が大きく変化するオートスケール環境では、リザーブドインスタンス(RI)の利用が難しい場合がありますが、 EC2費用の抑制手段の一つとしてスポットインスタンスを活用頂ければと思います。

Savings Summary

テンプレートサンプル

今回の検証、以下のCloudFormationテンプレートを利用して実施しました。

  • 下記のサンプルの「instance-id」をオートスケールで起動したEC2のIDに変更し、Lambdaのテストイベントして登録する事でテストが可能です。
{
    "version": "0",
    "id": "12345678-1234-1234-1234-123456789012",
    "detail-type": "EC2 Spot Instance Interruption Warning",
    "source": "aws.ec2",
    "account": "123456789012",
    "time": "yyyy-mm-ddThh:mm:ssZ",
    "region": "us-east-2",
    "resources": ["arn:aws:ec2:us-east-2:123456789012:instance/i-1234567890abcdef0"],
    "detail": {
        "instance-id": "i-1234567890abcdef0",
        "instance-action": "action"
    }
}