背景
個人向けに最低費用で運用するため、Ec2をスポットインスタンスで使用しているが、時々スポットリクエストが中断される。その都度、再度スポットリクエストで立ち上げなおしているが、この処理を自動化したい。
大まかな流れ
スポットインスタンスの中断はイベント通知されるので、それをトリガとして、立ち上げ処理を行う。
①普段の処理
- 起動時に使用するAMIの最新IDを取得しておく。
- バックアップ(AWS Backup)でAMIが作成された際、CreateImageイベントが発生。
- EventBridgeでイベントを拾い、Lambda関数にて該当のAMIのIDを取得し、ParameterStoreへ保存しておく。
②中断イベント発生時の流れ
- スポットインスタンスの中断イベントが通知される。
- EventBridgeでイベントを拾う。
- イベントに連動して、Step Functionsへ連携。
- Step Functions内で以下の流れを作る。
- ParameterStoreから最新のAMI IDを取得し、スポットリクエストを発行。(Lambda関数)
- 起動状況を確認。
- ダメだったら、AZやインスタンスタイプを変更して再トライ。
- 起動に成功したか判定し、成功していればElastic IPを付け替える。(Lambda関数)
- 処理結果を通知。(LINE通知)
※AZとインスタンスタイプのパターンはリスト化して持っておく。(ParameterStore)
- ParameterStoreから最新のAMI IDを取得し、スポットリクエストを発行。(Lambda関数)
記事の全体
要素に分けて全5回で記載していきます。
調べたこと
-
boto3でAMI IDを取得する方法。
https://qiita.com/handa3/items/8ea3ee59145ccf55d525 -
そもそもAWS BackupでAMIが作成された際に、CreateImageが通知されるので、EventBridge経由でLambdaを起動し、ParameterStoreに保存すればよい。
https://zenn.dev/chittai/articles/20210325-update-ps-for-dr
2.の方法がよさそうなので、そちらで進める。
[参考]
BackupにてAMIが作成されると、CreateImageのEventが発行される。
{
"version": "0",
"id": "*******-*****-****-****-*********",
"detail-type": "EC2 AMI State Change",
"source": "aws.ec2",
"account": "アカウントIDの番号",
"time": "2023-08-05T03:34:14Z",
"region": "ap-northeast-1",
"resources": [
"arn:aws:ec2:ap-northeast-1::image/ami-********"
],
"detail": {
"RequestId": "********-****-****-****-**********",
"State": "available",
"ImageId": "ami-*********",
"ErrorMessage": ""
}
}
AMIのIDは取れるものの、どのインスタンスのAMIなのかが判別できない。
AWS Backupで、AMIのタグにあるNameに取得元インスタンス名称を入れているので、それを取得する。
AMI情報取得にはアカウントIDを指定するが、以下の方法でコードに記載することなく取得可能。
{
"Images": [
{
"Architecture": "x86_64",
"CreationDate": "2023-08-05T04:05:47.000Z",
"ImageId": "ami-***********",
"ImageLocation": "*********/AwsBackup_i-*************_********-****-****-****-******",
"ImageType": "machine",
"Public": false,
"OwnerId": "*********",
"PlatformDetails": "Linux/UNIX",
"UsageOperation": "RunInstances",
"ProductCodes": [
{
"ProductCodeId": "aw0evgkw8e5c1q413zgy5pjce",
"ProductCodeType": "marketplace"
}
],
"State": "available",
"BlockDeviceMappings": [
{
"DeviceName": "/dev/sda1",
"Ebs": {
"DeleteOnTermination": true,
"SnapshotId": "snap-********",
"VolumeSize": 20,
"VolumeType": "gp2",
"Encrypted": true
}
}
],
"Description": "This image is created by the AWS Backup service.",
"EnaSupport": true,
"Hypervisor": "xen",
"Name": "AwsBackup_i-*************_********-****-****-****-******",
"RootDeviceName": "/dev/sda1",
"RootDeviceType": "ebs",
"SriovNetSupport": "simple",
"Tags": [
{
"Key": "Name",
"Value": "EC2のホスト名を入れておく"
},
{
"Key": "aws:backup:source-resource",
"Value": "i-********:*******_********-****-****-****-******"
},
{
"Key": "AWSBackup",
"Value": "EC2"
}
],
"VirtualizationType": "hvm"
}
]
CreateImageイベント受信、ParameterStoreの登録用Lambda
import boto3
import logging
import json
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Region
REGION = 'ap-northeast-1'
HOSTNAME = 'EC2のTagsのNameに登録しているホスト名'
def lambda_handler(event, context):
logger.info('event[' + json.dumps(event) + ']')
image_id = event['detail']['ImageId']
ec2 = boto3.client('ec2', region_name=REGION)
# アカウントIDを取得
Owner_id = boto3.client('sts').get_caller_identity().get('Account')
response = ec2.describe_images(
Owners = [Owner_id],
ImageIds = [image_id]
)
logger.info('image info[' + json.dumps(response) + ']')
ssm = boto3.client('ssm', region_name=REGION)
for idx, keyvalue in enumerate(response['Images'][0]['Tags']):
# TagsのNameがホスト名と一致しているかチェック
if( keyvalue['Key'] == 'Name' and keyvalue['Value'] == HOSTNAME ):
# パラメータストアへAMIのimageIDを保存
response_put = ssm.put_parameter(
Name = '/EC2Backup/latestAMI/' + HOSTNAME,
Value = image_id,
Type = 'String',
Overwrite = True
)
#
if( keyvalue['Key'] == 'aws:backup:source-resource' ):
# パラメータストアへEC2インスタンスIDを保存
response_put = ssm.put_parameter(
Name = '/EC2Backup/latestAMI/source-resource' ,
Value = keyvalue['Value'].split(':')[0] ,
Type = 'String',
Overwrite = True
)
Lambdaのロールには、AWSLambdaBasicExecutionRoleと、AMI情報取得、ParameterStoreの操作に必要そうな許可ポリシーを入れた。(合っている?)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ec2:DescribeImages",
"ssm:PutParameter",
"sts:AssumeRole",
"ssm:GetParameter"
],
"Resource": "*"
}
]
}
EventBridgeへの登録方法
{
"source": ["aws.ec2"],
"detail-type": ["EC2 AMI State Change"],
"detail": {
"State": ["available"]
}
}
Eventを受信したら、用意したLambdaが実行されるようにターゲットを指定。