インフィニットループ×クラスメソッド×クリプトン合同勉強会で「AWS Lambdaで ”ソンナコ”を実装してみた」について発表しました #ilcmcr

2015.12.03

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

サーモン大好き横山です。

今回「 インフィニットループ×クラスメソッド×クリプトン合同勉強会 」で発表させていただきました。

ブログ記事、【お知らせ】1クリックで毎日スナップショットを自動取得 〜 ソンナコトモアロウカト v1.4 リリース から、Auto Scalingで制御しているEC2の部分をAWS Lambdaに置き換えた話をしました。

発表資料

[slideshare id=55766232&doc=awslambda-151203041554-lva1-app6891]

補足

今回のデモで「予め作っておいたAWS Lambdaがこちら」となっていた部分を補足致します。

デモでは、 Event soucesを rate(10 minutes) で、PythonコードのBACKUP_RETENTIONは { 'minutes': 30 } で設定していました。
この部分を Event soucesを cron(0 15 ? * * *) でPythonコードのBACKUP_RETENTIONは { 'days': 7 } で設定をしますとソンナコと同じように動作することが出来ます。

AWS Lambda作成

大まかな手順としまして以下のとおり通りです。

  • 「Select blueprint」で hello-world-python を選択
  • IAM RoleをPowerUserRoleと同じものを設定
  • 「Lambda function code」にコードをコピー&ペースト
  • 「Event sources」で「Scheduled Event」を rate(10 minutes)

細かな設定方法は、 AWS LambdaからPythonでEC2のインスタンスを起動するスケジュールを設定してみた #reinvent に詳しく書いていますのでこちらをご参照ください。

使用したIAM Role

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "NotAction": "iam:*",
      "Resource": "*"
    }
  ]
}

ソースコード

#!/usr/bin/env python
# encoding: utf-8

from __future__ import print_function
from datetime import datetime, timedelta
import time
import boto3

import pprint

BACKUP_RETENTION = {
    'days': 7,
}
dt_now = datetime.now()
dt_backup_retention = dt_now + timedelta(**BACKUP_RETENTION)

def output_log(title, body):
    print('----------[{}]----------'.format(title))
    pprint.pprint(body)
    print('-'*(22+len(title)))

def create_snapshot_ebs(ec2):
    describe_volumes = ec2.describe_volumes()
    output_log('describe_volumes', describe_volumes)
    volume_ids = [ volume['VolumeId'] for volume in describe_volumes['Volumes'] ]

    describe_tags = ec2.describe_tags(Filters=[
                        {
                            'Name': 'resource-type',
                            'Values': [ 'volume' ],
                        },
                        {
                            'Name': 'resource-id',
                            'Values': volume_ids
                        },
                        {
                            'Name': 'key',
                            'Values': [ 'Backup' ],
                        }
                    ])
    output_log('describe_tags',describe_tags)

    create_snapshots = []
    for tag in describe_tags['Tags']:
        if tag['Value'] != 'true':
            continue

        instance_ids = [ attachment['InstanceId'] for attachment in volume['Attachments']
                                                  for volume in describe_volumes['Volumes']
                                                  if volume['VolumeId'] == tag['ResourceId'] ]
        if not instance_ids:
            continue

        tag['InstanceId'] = instance_ids[0]
        tag['DateCurrent'] = dt_now.strftime('%Y-%m-%d')
        description = "ec2ab_{ResourceId}_{DateCurrent} ({InstanceId})".format(**tag)

        create_snapshot = ec2.create_snapshot(
                              VolumeId=tag['ResourceId'],
                              Description=description
                          )
        create_snapshots.append(create_snapshot)
        output_log('create_snapshot[{}]'.format(description), create_snapshot)


    unixtime = str(int(time.mktime(dt_backup_retention.timetuple())))
    ec2.create_tags(
        Resources = [ snapshot['SnapshotId'] for snapshot in create_snapshots ],
        Tags = [
            { 'Key': 'PurgeAllow', 'Value': 'true' },
            { 'Key': 'PurgeAfter', 'Value': unixtime },
        ],
    )

def purge_snapshot_ebs(ec2):
    describe_tags = ec2.describe_tags(Filters=[
                        {
                            'Name': 'resource-type',
                            'Values': [ 'snapshot' ],
                        },
                        {
                            'Name': 'key',
                            'Values': [ 'PurgeAllow', 'PurgeAfter' ],
                        }
                    ])
    output_log('describe_tags(purge)', describe_tags)
    snapshot_id_groups = {}
    for tag in describe_tags['Tags']:
        groups_key = tag['ResourceId']
        if snapshot_id_groups.has_key(groups_key):
            snapshot_id_groups[groups_key].append(tag)
        else:
            snapshot_id_groups[groups_key] = [tag]

    for key, values in snapshot_id_groups.items():
        tag_values = { v['Key']: v['Value'] for v in values 
                                            if v['Key'] in ['PurgeAllow', 'PurgeAfter']}

        if len(tag_values) != 2:
            continue

        if tag_values['PurgeAllow'] != 'true':
            continue

        dt_purgeafter = datetime.fromtimestamp(int(tag_values['PurgeAfter']))
        output_log('dt', [dt_now, dt_backup_retention, dt_purgeafter])
        if dt_now > dt_purgeafter:
            output_log('delete snapshot: {}'.format(key), values)
            ec2.delete_snapshot(
                SnapshotId=key
            )

def handler():
    ec2 = boto3.client('ec2')

    create_snapshot_ebs(ec2)
    purge_snapshot_ebs(ec2)

def lambda_handler(event, context):
    handler()

おわりに

今回”ソンナコ”をテーマにAWS Lambdaに書き換えて見ましたが、他にもEC2で処理している部分をAWS Lambdaに置き換える事が可能だと思います。
これからも機会を見つけてブログや勉強会で発信していきたいです。

でもまずは、AWS LambdaのCFn対応が一番待ち遠しいです。