「アプリ用EC2の構築でやったこと(1) backup先のGolden image作成」の続きになります
前回同様にVPCまわり、セキュリティ、EC2、ユーザーアプリの話はありません
記事の内容
今回の記事では以下二つのLambda関数を説明します
- CloudformationでdeployしたAutoScalingGroupの起動テンプレートバージョンをLatestに変更する
- DLMの日次Backupで本番用AMIが更新されたら起動テンプレートのAMI Idも更新する
下図の赤枠部分に相当します
前回の続きということで図中ではBackup先リージョンでの操作を説明してますが東京にも同じ仕組みが実装されています
samでbuildとdeployをしていますが記事中では説明しません
作業環境
- Windows10 / WSL2(Ubuntu20.04)
- Python 3.8.10
- aws-cli/1.22.92 Python/3.8.10 Linux/5.13.0-1021-aws botocore/1.24.37
- SAM CLI, version 1.46.0
AutoScalingGroupの起動テンプレートバージョンをLatestに変更
今回取り組み前の自分なら「何言ってるか全然わからない」キャプションタイトルです、いや普通にすればイイでしょ的な
何故このような必要があるかと言うと、CloudformationでAutoScalingGroupをdeployする時に起動テンプレートのバージョンをLatestに指定できないからです(少なくとも自分が確認した範囲で)
AWS::AutoScaling::AutoScalingGroup LaunchTemplateSpecificationによると
Version
The version number. CloudFormation does not support specifying $Latest, or $Default for the template version number.
数値では入れることが出来ます
However, you can specify LatestVersionNumber or DefaultVersionNumber using the Fn::GetAtt function.
起動時はバージョン1なのでこのままだと1が入って終わりです
今回ユーザー部門からは、当面アプリを継続開発するので前日の開発imageで閲覧用の本番サーバーを立ち上げて欲しいという要求がありASGの起動テンプレートバージョンが初期値固定では要件(要望)を満たせない、というのがこのLambda関数の設置理由です
前置きが長くなりましたが、以下のLambda関数でAutoScalingGroupの起動テンプレートのバージョンをLatestに書き換えます
Lambdaの起動トリガーはCloudTrail経由でCreateAutoScalingGroupを検知するEventを使います
ですからLambda関数はEC2関連一式をdeployする前にBackup先リージョン(バージニア)に設置しておく必要があります
import boto3
asg = boto3.client("autoscaling")
def lambda_handler(event, context):
detail = event['detail']
asg_name = detail["requestParameters"]["autoScalingGroupName"]
tp_id = detail["requestParameters"]["launchTemplate"]["launchTemplateId"]
asg.update_auto_scaling_group(
AutoScalingGroupName = asg_name,
LaunchTemplate = {
'LaunchTemplateId': tp_id,
'Version': '$Latest'
}
)
return
Eventを設定します
{
"detail-type": ["AWS API Call via CloudTrail"],
"source": ["aws.autoscaling"],
"detail": {
"eventSource": ["autoscaling.amazonaws.com"],
"eventName": ["CreateAutoScalingGroup"]
}
}
このLambdaを設置した状態でASGの入ったtemplateをsam deployするとCreateAutoScalingGroupで発火したLambdaがASGの設定を書き換えます
DLMの日次Backupで本番用AMIが更新されたら起動テンプレートのAMI Idを更新
こちらは有難く参考記事を拝読しました
特定タグが設定されたAMIが作成された時に自動で起動テンプレートを更新する
特定のタグが設定されたAMIを作成した際にEventBridgeからSNSへ発行する
参考にしたクラメソさん記事同様にCreateImageを起動トリガーにLambdaを発火させます
ただし、EC2のstackに含まれるDLMが作成したAMIのIdが欲しいことと、stackの中でdeployされた起動テンプレートのIdはparameter storeから受けたいので2点追加してます
まずAMIの識別子として、CreateImageからのevent引数で受け取る['requestParameters']['description']を使います
詳細説明していないですがEC2用のtemplate.yamlに'EC2_BACKUP'という文字列を入れてあり
DLMがスケジュール実行されるとCreateImageで"description"の中に以下のように吐き出されるので、Event logがこの'EC2_BACKUP'を含めば対象のAMIと認識させます
'EC2_BACKUP'をLambdaでキャッチしたらAMI Idを読み取り
def lambda_handler(event, context):
detail = event['detail']
US_backup = detail['requestParameters']['description']
if "EC2_BACKUP" in US_backup:
new_ami = detail['responseElements']['imageId']
parameter storeに起動テンプレートIdを読みに行き
response = ssm.get_parameter(
Name = launch_template_Key,
WithDecryption=True,
)
launch_template_id = response['Parameter']['Value']
起動テンプレートのバージョンを更新します
response = ec2.create_launch_template_version(
LaunchTemplateId = launch_template_id,
SourceVersion="$Latest",
VersionDescription="Latest-AMI",
LaunchTemplateData={
"ImageId": new_ami
}
)
response = ec2.modify_launch_template(
LaunchTemplateId = launch_template_id,
DefaultVersion = "$Latest"
)
起動テンプレートは新しい順に3つ残すことにしました
try:
previous_version = str(int(response["LaunchTemplate"]["LatestVersionNumber"]) - 3)
response = ec2.delete_launch_template_versions(
LaunchTemplateId = launch_template_id,
Versions=[
previous_version,
]
)
except Exception as e:
print(e)
pass
Lambda関数の全体は以下のようになります
parameter storeのkeyはLambda関数の環境変数として与えます
import json, boto3, os
launch_template_Key = os.environ["launch_template_Key"]
ec2 = boto3.client("ec2")
ssm = boto3.client('ssm')
def lambda_handler(event, context):
detail = event['detail']
US_backup = detail['requestParameters']['description']
if "EC2_BACKUP" in US_backup:
new_ami = detail['responseElements']['imageId']
response = ssm.get_parameter(
Name = launch_template_Key,
WithDecryption=True,
)
launch_template_id = response['Parameter']['Value']
response = ec2.create_launch_template_version(
LaunchTemplateId = launch_template_id,
SourceVersion="$Latest",
VersionDescription="Latest-AMI",
LaunchTemplateData={
"ImageId": new_ami
}
)
response = ec2.modify_launch_template(
LaunchTemplateId = launch_template_id,
DefaultVersion = "$Latest"
)
try:
previous_version = str(int(response["LaunchTemplate"]["LatestVersionNumber"]) - 3)
response = ec2.delete_launch_template_versions(
LaunchTemplateId = launch_template_id,
Versions=[
previous_version,
]
)
except Exception as e:
print(e)
pass
return
Eventを設定します
{
"detail-type": ["AWS API Call via CloudTrail"],
"source": ["aws.ec2"],
"detail": {
"eventSource": ["ec2.amazonaws.com"],
"eventName": ["CreateImage"]
}
}
このLambdaを配置しておくことでDLMがスケジュール実行されると起動テンプレートが自動更新されます
まとめと感想
EC2を使ったWeb ServerをIaC的に構築し(1)と合わせて自動運用に必要な補助機能をLambdaで補完しました
世の中では遙かに便利な仕組みを活用していると理解していますが諸般の事情で今回このような環境を提供しました
社内外問わず同様のニーズがありそうな気もするので別テーマで扱うことがあるかもしれません
個人的には本来Lambdaはこうやって使われながら進化してきたのかな?と思う(妄想する)と今まで以上にLambdaに親近感を感じました
にわかの域を出ていないので詰めの甘い部分を修正しながらスキルアップしたいと思います
以上です
その他参考資料
Boto3 documentation /Docs/Available services/ec2/Client
Boto3 documentation /Docs/Available services/SSM/Client
AWS::Serverless::Function