LoginSignup
3
0

More than 3 years have passed since last update.

これでスポットリクエストのキャンセルし忘れとさらばだ!

Posted at

概要

 開発環境にEC2のスポットインスタンスを利用することはよくあると思います。その中でも「永続的リクエスト」を使っているとついついインスタンスだけを「終了」し、スポットリクエストをキャンセルし忘れることありませんか?リクエスト有効期間内であれば、自動的に同じタイプのインスタンスがゾンビのごとくのように再作成され、無駄な課金が発生します。
 インスタンスの削除時にスポットリクエストからキャンセルするように運用手順で決めていてもついつい忘れがちなので、自動化する方法を紹介します。

前提条件のまとめ

  • スポットインスタンスを使用している
  • 永続的リクエストである
  • リクエスト有効期限内である

構成

EventBridgeを使用することで、インスタンス終了イベント時に指定したLambdaを実行し、スポットリクエストをキャンセルします。

image.png

EventBridge

イベントとしてインスタンスの状態変更通知で「Terminated」を追加します。

image.png

eventbridge.json
{
  "detail-type": [
    "EC2 Instance State-change Notification"
  ],
  "source": [
    "aws.ec2"
  ],
  "detail": {
    "state": [
      "terminated"
    ]
  }
}

Lambda

Lambdaにアタッチされているロールに以下の権限(インスタンスの情報取得、スポットリクエストのキャンセル)を含めればOKです。

role.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ec2:CancelSpotInstanceRequests",
                "ec2:DescribeInstances"
            ],
            "Resource": "*"
        }
    ]
}

EventBridgeから渡される変数は以下になります。
同時に複数のインスタンスを削除した場合でも、1つずつのイベントとして送られますので、
Lambda側では基本的に単数を想定した実装でよいでしょう。

event.json
{
  "version": "0",
  "id": "xxxxxxxxxxxxxxxxxxxxxx",
  "detail-type": "EC2 Instance State-change Notification",
  "source": "aws.ec2",
  "account": "xxxxxxxxxx",
  "time": "2020-10-26T09:28:11Z",
  "region": "ap-northeast-1",
  "resources": [
    "arn:aws:ec2:ap-northeast-1:xxxxxx:instance/i-xxxxxxxxxxxx"
  ],
  "detail": {
    "instance-id": "i-xxxxxxxxxxxx",
    "state": "terminated"
  }
}

eventのinstance-idからインスタンスの情報を取得し、関連するスポットリクエストをキャンセルする。

cancle_spotrequest.py

import json
import boto3

def lambda_handler(event, context):
    '''
    EC2インスタンス削除時に関連するスポットリクエストをキャンセルする
    '''

    print('--------event----------')
    print(event)
    print('------------------')

    client = boto3.client('ec2')

    event_detail = event['detail']
    if event_detail['state'] != 'terminated':
        return

    # インスタンスIDで終了済みのスポットインスタンスを取得する
    response = client.describe_instances(
                    Filters=[{
                        'Name': 'instance-state-name',
                        'Values': ['terminated']
                    },{
                        'Name': 'instance-lifecycle',
                        'Values': ['spot']
                    }],
                    InstanceIds=[event_detail['instance-id']]
                )

    # 対象がなければ、終了
    if len(response['Reservations']) <= 0:
        return

    spot_request_id = ''
    for reservations in response['Reservations']:
        for ins in reservations['Instances']:
            spot_request_id = ins['SpotInstanceRequestId']

    print('--------cancel id----------')
    print(spot_request_id)
    print('------------------')

    # スポットリクエストをキャンセル
    client.cancel_spot_instance_requests(SpotInstanceRequestIds=[spot_request_id])

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0