0
Help us understand the problem. What are the problem?

posted at

updated at

アプリ用EC2周りの構築でやったこと(1) Backup先のGolden image作成

最近仕事で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 architecture.PNG

記事の内容

今回の記事では

  • Backup先でGolden imageを作成する
  • AMI Idをparameter storeに出力する

Lambda関数を説明します

下図の赤枠部分に相当します

samでstackのbuildとdeployをしていますが記事中では説明しません

copy_ami_export_amiid.PNG

作業環境

  • 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関数の全体は以下のようになります

app.py (Copy Image)
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で出力しました

app.py (Export AMI Id)
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

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?