最近仕事でEC2を使ってユーザー部門向けWeb Serverを準備する機会がありました
直接運用に関わる立場ではないので、不具合が起きた場合にlog見てもらいつつも基本はstackのdeleteとdeploy(あとはaws lambda invoke)くらいで対応できるようにIaC化前提で構築しました
運用を考えたServer構築経験がないため調べることが多々あり印象に残った点を数回に分けて記録に残したいと思います
尚、VPCまわり、セキュリティ、EC2、ユーザーアプリの話はありません
全体構成
今回詳細説明はしませんが投稿の背景となる全体構成について最初に説明します
通常は東京リージョンのEC2上でユーザー部門のアプリが動いています
EC2は開発用と本番用に分けていて、DLMによる開発用AMIの日次copyを本番用起動imageとしてLaunchTemplateに登録しています
DLMの日次copyではCrossRegionCopyをバージニアに作成します
障害等で東京のEC2上でアプリが動かせなくなった場合には
①バージニアにある最新コピーからGolden imageを作成
②東京と同じstackをバージニアにdeployして起動
を運用の担当者がcliで実行することにしました
記事の内容
今回の記事では
- Backup先でGolden imageを作成する
- AMI Idをparameter storeに出力する
Lambda関数を説明します
下図の赤枠部分に相当します
samでstackの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
Golden imageの作成
一つ目のLambda関数では、東京から日次CrossRegionCopyされているAMIのLatestを選びGolden imageとしてコピーします
過去のGolden imageの削除
まず、過去にバージニアにBackupを立ち上げてGolden imageが残っていたらダブるので消去します
バージニアのAMIを参照して
def delete_old_ami():
image_id = ""
response = ec2.describe_images(
Owners = [Owner_id]
)
最後のlambda_handlerで出てきますがバージニアGolden imageは"DevAMIBackup"と名前を付けますので、このNameに紐づいたAMIが既にあれば削除します
AMIが見つかったらIdとSnapshot idを取得します
for image in response['Images']:
if "DevAMIBackup" in image['Name']:
image_id = image['ImageId']
ebs = image['BlockDeviceMappings'][0]
snapshot_id = ebs['Ebs']['SnapshotId']
AMI登録を解除してSnapshotを削除します
ec2.deregister_image(
ImageId = image_id,
)
ec2.delete_snapshot(
SnapshotId = snapshot_id,
)
return
CrossRegionCopyのNameとIdの取得
次に東京リージョンのDLM Policyで作成されたCrossRegionCopyを探してAMI名とAMI Idのリストを作ります
def get_list_ami_name():
response = ec2.describe_images(
Owners = [Owner_id]
)
for image in response['Images']:
if "DLM_policy" in image['Name']:
list_ami_name.append(image['Name'])
list_image_id.append(image['ImageId'])
return
Latest AMI Idの取得
取得したAMI名からLatestのタイムスタンプを持つAMI Idを探します
CrossRegionCopy作成時のタイムスタンプは直接Key:Valueで拾えなかったのでAMI名内のdatetime情報から拾いました
東京のDLMは02:00(JST)に起動する設定にしてあり、昼間の勤務時間中に最新のAMIを取得しようとするとGMTでは昨日のAMIを探せば良いことになります
まず、AMI名内のdatetime情報がMM.ddなのに合わせて昨日の月日をyesterday = MM.ddとして準備します
def set_information():
dt = datetime.datetime.now()
if len(str(dt.day)) == 1:
yesterday = "%s.%s" %(str(dt.month), "0" + str(dt.day-1))
else:
yesterday = "%s.%s" %(str(dt.month), str(dt.day-1))
yesterdayの文字列を含むAMIを探して更にHH:MMを取得します
listにして複数HH:MMを取得してるのは開発側で複数CrossRegionCopyを作った場合を想定した対策です
IDs = []
HHs = []
mms = []
IDs_max = []
HHs_max = []
mms_max = []
ID = ""
for ami_name, amiid in zip(list_ami_name, list_image_id):
if yesterday in ami_name:
IDs.append(amiid)
HHs.append(int(ami_name.split(".")[2][-2:]))
mms.append(int(ami_name.split(".")[3]))
LatestのAMI Idを特定して返します
hh_max = max(HHs)
mm_max = max(mms)
for amiid, hh, mm in zip(IDs, HHs, mms):
if hh == hh_max:
IDs_max.append(amiid)
HHs_max.append(hh)
mms_max.append(mm)
for amiid, mm in zip(IDs_max, mms_max):
if len(IDs_max) == 1:
ID = amiid
elif mm == mm_max:
ID = amiid
return ID
Golden imageの作成
LatestのCrossRegionCopyに'DevAMIBackup'と名前を付けてGolden imageを作成します
def lambda_handler(event, context):
delete_old_ami()
get_list_ami_name()
ami_ID = set_information()
ec2.copy_image(
Name ='DevAMIBackup',
SourceImageId = ami_ID,
SourceRegion = Source_Region,
)
return
Golden imageをcopyするLambda関数の全体は以下のようになります
import datetime, boto3, os
ec2 = boto3.client("ec2")
list_ami_name = []
list_image_id = []
list_TP_version = []
Owner_id = os.environ['Owner_id']
Source_Region = os.environ['Source_Region']
def delete_old_ami():
image_id = ""
response = ec2.describe_images(
Owners = [Owner_id]
)
for image in response['Images']:
if "DevAMIBackup" in image['Name']:
image_id = image['ImageId']
ebs = image['BlockDeviceMappings'][0]
snapshot_id = ebs['Ebs']['SnapshotId']
ec2.deregister_image(
ImageId = image_id,
)
ec2.delete_snapshot(
SnapshotId = snapshot_id,
)
return
def get_list_ami_name():
response = ec2.describe_images(
Owners = [Owner_id]
)
for image in response['Images']:
if "DLM_policy" in image['Name']:
list_ami_name.append(image['Name'])
list_image_id.append(image['ImageId'])
return
def set_information():
t_delta = datetime.timedelta(hours=9)
JST = datetime.timezone(t_delta, 'JST')
dt = datetime.datetime.now(JST)
if len(str(dt.day)) == 1:
yesterday = "%s.%s" %(str(dt.month), "0" + str(dt.day-1))
else:
yesterday = "%s.%s" %(str(dt.month), str(dt.day-1))
IDs = []
HHs = []
mms = []
IDs_max = []
HHs_max = []
mms_max = []
ID = ""
for ami_name, amiid in zip(list_ami_name, list_image_id):
if yesterday in ami_name:
IDs.append(amiid)
HHs.append(int(ami_name.split(".")[2][-2:]))
mms.append(int(ami_name.split(".")[3]))
hh_max = max(HHs)
mm_max = max(mms)
for amiid, hh, mm in zip(IDs, HHs, mms):
if hh == hh_max:
IDs_max.append(amiid)
HHs_max.append(hh)
mms_max.append(mm)
for amiid, mm in zip(IDs_max, mms_max):
if len(IDs_max) == 1:
ID = amiid
elif mm == mm_max:
ID = amiid
return ID
def lambda_handler(event, context):
delete_old_ami()
get_list_ami_name()
ami_ID = set_information()
ec2.copy_image(
Name ='DevAMIBackup',
SourceImageId = ami_ID,
SourceRegion = Source_Region,
)
return
AMI Idをparameter storeに出力
バージニアに東京と同じEC2をdeployする為のtemplate.yamlではAMI Idをparameter store経由で参照する仕様にしました
その為に前項でcopyしたGolden imageのAMI Idをparameter storeに出力します
AMI Idを探す操作は前項と同じです
parameterのkeyは'Latest_AMI_Id_us-east-1'としてstringで出力しました
import boto3, os
ec2 = boto3.client("ec2")
ssm = boto3.client('ssm')
Owner_id = os.environ['Owner_id']
def get_ami_id():
image_id = ""
response = ec2.describe_images(
Owners = [Owner_id]
)
for image in response['Images']:
if "DevAMIBackup" in image['Name']:
image_id = image['ImageId']
print(image_id)
return image_id
def lambda_handler(event, context):
image_id = get_ami_id()
try:
ssm.delete_parameter(
Name='Latest_AMI_Id_us-east-1'
)
except Exception as e:
pass
ssm.put_parameter(
Name='Latest_AMI_Id_us-east-1',
Description='Latest backup My_EC2 AMI Id of us-east-1',
Value=image_id,
Type='String',
)
Lambda関数の実行
東京リージョンのEC2と通信できなくなった場合は、バージニアにVPC、NW、EC2をdeployする前に今回説明した二つのLambda関数を実行します
AMIのCopy
最初にLatestのBackupをcopyします
$ aws lambda invoke --function-name Copy-Latest-Image-Function --region us-east-1 responce.json
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
正常に実行してAMIが作成できました
AMI Idの出力
続いてAMI Idをparameter storeに格納します
$ aws lambda invoke --function-name Export-Latest-AMIId-Function --region us-east-1 responce.json
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
パラメータストアに出力ができました
まとめ
EC2を使ったWeb Serverを自動運用するためにGolden imageをcopyする補助機能を作りました
先人の記事を参照してもっと今っぽく自動化できたかもしれません、確認しきれなかった反省点として留めておきます
EC2 Auto Scaling ゴールデンイメージの更新と Auto Scaling グループのインスタンス更新を自動化しました
こちらの方法も手順習得しておきたいと思います
アプリ用EC2周りの構築でやったこと(2) AutoScalingGroupの起動テンプレートVer設定に続きます
その他の参考資料
Boto3 documentation /Docs/Available services/ec2/Client
Boto3 documentation /Docs/Available services/SSM/Client
AWS::Serverless::Function