EC2のスナップショットを自動的にAWS Lambdaで作成する

  • 114
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Compute_Lambda.png AWS Lambda?

  • サーバレスでプログラムを実行できるようなもの
  • 1 か月 1,000,000 リクエストは無料
    • 今回のような1日1回動かす程度なら無料でずっと使えます
  • 詳しくは → https://aws.amazon.com/jp/lambda/faqs/

やりたいこと

  • Lambdaのスケジュールイベントを使って自動的にEBSのスナップショットを作成する
    • 今回使用する言語はPython
  • スナップショットの対象は、EC2インスタンスのタグに「Backup-Generation」が付いているものとする
    • Backup-Generationの値には、0以上の数字を指定する
      • スナップショットの保存件数は、Backup-Generationの値とする
        • 0のときはスナップショットを作成しない
      • スナップショットの保存件数を超えた場合は、作成日付の古いものから削除する
  • 作成したスナップショットのDescriptionには、「EBSボリュームID」と「EC2インスタンス名」をつける
    • EC2インスタンス名がない場合は、「EBSボリュームID」のみをつける
  • 処理が失敗した場合は、CloudWatchの監視によりメール(SNS)を送る

EC2インスタンスに名前とタグをつける

名前をつける

名前をつける

  1. EC2のページのサイドメニューから「Instances」をクリック
  2. カーソルをNameの列に合わせると鉛筆アイコンが表示されるのでクリック
  3. 名前を入力

タグをつける

タグを追加

  1. 対象のインスタンスにチェックをつける
  2. 「Tags」ボタンをクリック
  3. 「Add/Edit Tags」ボタンをクリック

タグを追加

  1. 「Create Tag」ボタンをクリック
  2. Keyに「Backup-Generation」、Valueに保存件数を入力
  3. 「Save」ボタンをクリック

Lambdaの作成

Select blueprint

Select blueprint

  1. Lambdaのページの作成ボタンをクリック
  2. 「Python2.7」を選択
  3. 「hello-world-python」をクリック(動作確認後にスケジュールイベントを設定したいため)
    • ※「lambda-canary」はいきなりスケジュール設定画面から始まる

Configure function

Configure function

  1. Nameを入力
  2. Descriptionは空でもOK

Lambda function code

Lambda function code

  1. 下記のコードを貼り付ける
python
import boto3
import collections
import time
from botocore.client import ClientError
ec2 = boto3.client('ec2')

def lambda_handler(event, context):
    descriptions = create_snapshots()
    delete_old_snapshots(descriptions)

def create_snapshots():
    instances = get_instances(['Backup-Generation'])

    descriptions = {}

    for i in instances:
        tags = { t['Key']: t['Value'] for t in i['Tags'] }
        generation = int( tags.get('Backup-Generation', 0) )

        if generation < 1:
            continue

        for b in i['BlockDeviceMappings']:
            if b.get('Ebs') is None:
                continue

            volume_id = b['Ebs']['VolumeId']
            description = volume_id if tags.get('Name') is '' else '%s(%s)' % (volume_id, tags['Name'])
            description = 'Auto Snapshot ' + description

            snapshot = _create_snapshot(volume_id, description)
            print 'create snapshot %s(%s)' % (snapshot['SnapshotId'], description)

            descriptions[description] = generation

    return descriptions

def get_instances(tag_names):
    reservations = ec2.describe_instances(
        Filters=[
            {
                'Name': 'tag-key',
                'Values': tag_names
            }
        ]
    )['Reservations']

    return sum([
        [i for i in r['Instances']]
        for r in reservations
    ], [])

def delete_old_snapshots(descriptions):
    snapshots_descriptions = get_snapshots_descriptions(descriptions.keys())

    for description, snapshots in snapshots_descriptions.items():
        delete_count = len(snapshots) - descriptions[description]

        if delete_count <= 0:
            continue

        snapshots.sort(key=lambda x:x['StartTime'])

        old_snapshots = snapshots[0:delete_count]

        for s in old_snapshots:
            _delete_snapshot(s['SnapshotId'])
            print 'delete snapshot %s(%s)' % (s['SnapshotId'], s['Description']) 

def get_snapshots_descriptions(descriptions):
    snapshots = ec2.describe_snapshots(
        Filters=[
            {
                'Name': 'description',
                'Values': descriptions,
            }
        ]
    )['Snapshots']

    groups = collections.defaultdict(lambda: [])
    { groups[ s['Description'] ].append(s) for s in snapshots }

    return groups

def _create_snapshot(id, description):
    for i in range(1, 3):
        try:
            return ec2.create_snapshot(VolumeId=id,Description=description)
        except ClientError as e:
            print str(e)
        time.sleep(1)
    raise Exception('cannot create snapshot ' + description)

def _delete_snapshot(id):
    for i in range(1, 3):
        try:
            return ec2.delete_snapshot(SnapshotId=id)
        except ClientError as e:
            print str(e)
        time.sleep(1)
    raise Exception('cannot delete snapshot ' + id)

Lambda function handler and role

Lambda function handler and role

  1. 「Role」から「Basic execution role」を選択

IAM Roleの作成

IAM Roleの作成

  1. 「Role Name」を入力
  2. 「View Policy Document」をクリックしコードを表示させる
  3. 「Edit」をクリック

confirm

  1. 「OK」ボタンをクリック
  2. 下記のコードを貼り付け
  3. 「Allow」ボタンをクリック
iam.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:*:*:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:DescribeSnapshots",
                "ec2:CreateSnapshot",
                "ec2:DeleteSnapshot"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Advanced settings

Advanced settings

  1. Timeoutを「10 sec」に変更(※動作確認の「Duration」の値を見て適宜変更)
  2. 「Next」ボタンをクリック
  3. 「Create function」ボタンをクリック

Lambdaの動作確認

Test

  1. 左上の「Test」ボタンをクリック
  2. Inputデータは使わないのでそのまま「Save and test」をクリック

result

  1. 動作結果はページ最下部に表示される
    • Summary
      • Duration … 実行時間
      • Billed duration … 課金対象時間
      • Max memory used … メモリ使用量

CloudWatch

  1. Log outputの「Click here」をクリックするとCloudWatchログの画面が表示される

Lambdaのスケジュールイベントの作成

スケジュールイベントの追加

スケジュールイベントの追加

  1. 「Event sources」をクリック
  2. 「Add event source」をクリック

Add event source

Add event source

  1. Event source typeから「Scheduled Event」を選択
  2. Nameを入力
  3. Descriptionは空でもOK
  4. schedule expressionを入力 ※時間はUTC
    • 参考サイト:書き方時差計算
    • 例>毎日 03:10 (日本時間の深夜) に起動させたい場合
      • cron(10 18 * * ? *)
  5. 「Submit」ボタンをクリック

処理の失敗をCloudWatchで監視してSNSでメールを送る

Create Alarm

Create Alarm

  1. CloudWatchのページのサイドメニューから「Alarms」をクリック
  2. 「Create Alarm」ボタンをクリック

Select Metric-1

Select Metric-1

  1. Lambda Metricsの下の「By Function Name」をクリック

Select Metric-2

Select Metric-2

  1. Metric Nameが「Errors」にチェック
  2. 「Next」ボタンをクリック

Define Alarm

Define Alarm

Alarm Threshold

  1. Nameを入力
  2. Wheneverのisを「>」を選択

Alarm Preview

  1. Periodを「1 Day」を選択(※Lambdaのスケジュールイベントの設定に合わせて適宜変更)
  2. Statisticを「Sum」を選択

Actions

  1. Send notification toの「New list」をクリック(※既存の通知用のSNSがある場合はそれを選択)
  2. topic nameを入力
  3. Email listに通知するメールアドレスを入力
  4. 「Create Alarm」ボタンをクリック
  5. 承認メールが送信されるのでメール本文の「Confirm subscription」をクリック

参考サイト