CloudWatch Events -EC2 Create Snapshot API 呼び出し②-

こんにちは。katoです。

前回に引き続き、CloudWatch Eventsの組み込みターゲットである「EC2 Create Snapshot API 呼び出し」について紹介させていただきます。

今回はlambdaと連携して、events targetの自動登録、取得したスナップショットの世代管理を行っていきます。

 

CloudWatch Events Tergetの自動登録

EBSボリュームのタグ作成

本記事内のlambda関数では、「Daily_Gens」というタグのついたEBSボリュームがスナップショットの取得・管理対象となります、Keyに「Daily_Gens」、Valueに世代数を入力してください。

 

 

IAMロールの準備

events targetの自動登録を行うために必要なlambda用のIAMロールの作成を行います。
本記事内のlambda関数をご利用される場合は、下記の権限を持ったIAMロールが必要となります。

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:Describe*",
                "events:PutRule",
                "events:DisableRule",
                "events:PutTargets",
                "events:List*"
            ],
            "Resource": "*"
        }
    ]
}

 

lambda function作成

IAMロールの作成が完了したら、events target登録用のlambda関数を作成致します。
※アカウントIDの箇所は適宜変更してください。

 

import boto3
import re

ec2 = boto3.client('ec2')
ins = boto3.resource('ec2')
client = boto3.client('events')

def lambda_handler(event, context):
        get_volume_tags()

def add_targets(id, instancename, rulename):
        response = client.list_rules()
        eventnames = []
        for num in range(len(response[u'Rules'])):
                eventnames.append(response[u'Rules'][num][u'Name'])
        if rulename in eventnames:
                pass
        else:
                createrule = client.put_rule(Name=rulename,ScheduleExpression='cron(30 9 * * ? *)')
                disabledrule = client.disable_rule(Name=rulename)
    
        targets = client.list_targets_by_rule(Rule=rulename)
        targetnames = []
        for num in range(len(targets[u'Targets'])):
            targetnames.append(targets[u'Targets'][num][u'Id'])
        if instancename in targetnames:
            pass
        else:
            addtarget = client.put_targets(
                    Rule=rulename,
                    Targets=[{'Input': '"arn:aws:ec2:ap-northeast-1:<your-acount-id>:volume/' + id + '"', 'Id': instancename, 'Arn': 'arn:aws:automation:ap-northeast-1:<your-acount-id>:action/EBSCreateSnapshot/' + instancename }]
            )
        


def get_volume_tags():
    vol = ec2.describe_volumes()
    volumes = vol['Volumes']
    length = len(volumes)
    for num in range(length):
        volume = volumes[num]
        AZ = volume['AvailabilityZone']
        VOL = "".join(map(str,volume.keys()))
        pattern1 = "Tags"
        repattern1 = re.compile(pattern1)
        match1 = repattern1.search(VOL)
        if match1 is None:
            pass
        else:
            tag = volume['Tags']
            id = volume['VolumeId']
            for num2 in range(len(tag)):
                tag_value = tag[num2]['Value']
                if tag[num2]['Key'] == "Daily_Gens":
                        ataches = volume['Attachments']
                        instanceid = ataches[0]['InstanceId']
                        tagdata = ins.Instance(instanceid)
                        rulename = tagdata.tags[0][u'Value'] + "_daily"
                        instancename = tagdata.tags[0][u'Value'] + "_" + id + "_daily"
                        add_targets(id, instancename, rulename)

 

上記関数は「Daily_Gens」というタグが付与されたEBSボリュームの情報を取得し、日次のスケジュールが設定されたevents ruleにtarget登録するという流れになっております。

インスタンスごとにルールを分けており、ルールの作成も自動で行われます。

ただし、新規で追加されたルールへのIAMロールの割り当ては手動で行って頂く必要が御座います。
こちらに関しては、コンソール上からの操作しか現状対応していないようで、関数中で割当てることができませんでした。。。
また、新規に追加されたルールに関しては、関数の中でルールの無効化を行っており、他のルールと判別がつくようにしております。IAMロールの割り当て作業後、手動で有効化を行ってください。

割当てるIAMロール情報に関しては前回の記事をご参照ください。

lambda関数の作成が完了したら、cloudwatch eventsにてトリガー設定を行い、作業完了となります。
関数の実行後にevents targetが追加されます。

 

 

スナップショットの世代管理

IAMロールの準備

スナップショットの世代管理を行うために必要なlambda用のIAMロールの作成を行います。

本記事内の関数においては下記権限を持ったIAMロールが必要となります。

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:Describe*",
                "ec2:CreateTags",
                "ec2:DeleteSnapshot"
            ],
            "Resource": "*"
        }
    ]
}

 

lambda function作成

 

IAMロールの作成が完了したら、世代管理用のlambda関数を作成致します。

import boto3
import re

ec2 = boto3.client('ec2')
client = boto3.resource('ec2')

def lambda_handler(event, context):
    get_volume_tags()

def get_volume_tags():
    vol = ec2.describe_volumes()
    volumes = vol['Volumes']
    length = len(volumes)
    for num in range(length):
        volume = volumes[num]
        AZ = volume['AvailabilityZone']
        VOL = "".join(map(str,volume.keys()))
        pattern1 = "Tags"
        repattern1 = re.compile(pattern1)
        match1 = repattern1.search(VOL)
        if match1 is None:
            pass
        else:
            tag = volume['Tags']
            id = volume['VolumeId']
            for num2 in range(len(tag)):
                tag_value = tag[num2]['Value']
                if tag[num2]['Key'] == "Daily_Gens":
                        snapshot_manage(volume, id, tag_value)

def snapshot_manage(volume, id, tag_value):
    removeflag = 0
    ataches = volume['Attachments']
        instanceid = ataches[0]['InstanceId']
        tagdata = client.Instance(instanceid)
        snapshots = ec2.describe_snapshots(Filters=[{'Name':'volume-id', 'Values':[id]}])
        snapshot = snapshots[u'Snapshots']
        instancename = tagdata.tags[0][u'Value'] + "_daily_" + id
        dates = []
        deldates = []
        for num3 in range(len(snapshot)):
            time = snapshot[num3][u'StartTime']
                dates.append(time)
                tagcheck = "".join(map(str,snapshot[num3].keys()))
                pattern3 = "Tags"
                repattern3 = re.compile(pattern3)
                match3 = repattern3.search(tagcheck)
                if match3 is None:
                    deltime = snapshot[num3][u'StartTime']
                        deldates.append(deltime)
                else:
                    snaptag = snapshot[num3][u'Tags'][0][u'Value']
                        if snaptag == instancename:
                            deltime = snapshot[num3][u'StartTime']
                                deldates.append(deltime)
        if len(deldates) == 0:
            deldates.append("000")
        elif len(deldates) = int(tag_value):
            pass
        else:
            removeflag += 1
        for num4 in range(len(snapshot)):
            count = 0
                if dates[num4] == min(deldates):
                    count += 1
                else:
                    pass
                snapid = snapshot[num4][u'SnapshotId']
                snapdata = client.Snapshot(snapid)
                connect = "".join(map(str,snapshot[num4].keys()))
                pattern4 = "Tags"
                repattern4 = re.compile(pattern4)
                match4 = repattern4.search(connect)
                if match4 is None:
                        snapdata.create_tags(Tags=[{'Key': 'Name','Value': instancename },])
                else:
                        if count == 1 and removeflag == 1:
                                snapdata.delete()
                        else:
                                pass

 

上記関数は「Daily_Gens」というタグが付与されたEBSボリュームの情報を取得し、スナップショットへのタグ付け、世代数を超えたスナップショットの削除を行っております。

世代数を超えない限りスナップショットは削除されませんので、スナップショットの取得に失敗した場合にスナップショットが世代数を下回ってしまうというようなことは御座いません。
ただし、スナップショットの削除に失敗した場合に、世代数を超えて保持し続けるという状況が発生してしまいます。

この問題に関しては、関数のトリガーを15分間隔や毎時等に設定し、create snapshotの呼び出し回数よりも上記関数が呼び出される回数が上回るよう設定することで、設定した世代数で管理を続けることが可能になります。

その他、上記関数利用時の注意点をご確認下さい。

・スナップショットに空のNameタグが付与されている場合は、タグ付けが行われない
・特定のスナップショットを対象から外す場合は、事前に別のタグをスナップショットに付与する必要がある
・スナップショットへの説明の追加は不可

lambda関数の作成が完了したら、cloudwatch eventsにてトリガー設定を行い、作業完了となります。
関数の実行後にスナップショットが作成されます。

正常な場合、以下の様な形でスナップショットが管理されていきます。
※testは関数での管理対象外のスナップショットです。

 

 

Nameタグの値は「インスタンスdailyボリュームID」となっております。
上記は検証のため、cloudwatch eventsのスケジュールを毎時に変更しスナップショットを取得しております。

 

まとめ

これまでの説明でcloudwatch eventsを利用したスナップショットの取得から管理までが可能になります。

「これだけ作業あるなら最初っからlambdaだけで良くね?」って思うかもしれませんが、cloudwatch eventsを使うメリットももちろんあります。

スナップショットの取得をボリューム単位に切り離せる点は、sleepやloop処理を考えている場合や、統計情報を取得したいと思っている場合には有益でしょう。

メトリクスを監視することで、エラーが発生したボリュームでのみスナップショットの取得を再試行させることも可能にもなります。

「スナップショットの自動取得はlambda」と断定せずに一度、cloudwatch eventsでの取得も考えてみて下さい。

用途や要件によっては、lambdaよりも簡単に実現出来たり、lambdaの問題点を解決できるかもしれません。

 

 

 

アプリケーション開発バナー