Edited at

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

More than 1 year has passed since last update.


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


  1. Lambdaのページの作成ボタンをクリック

  2. 「Python2.7」を選択

  3. 「hello-world-python」をクリック(動作確認後にスケジュールイベントを設定したいため)


    • ※「lambda-canary」はいきなりスケジュール設定画面から始まる




Configure function


  1. Nameを入力

  2. Descriptionは空でもOK


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


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


IAM Roleの作成


  1. 「Role Name」を入力

  2. 「View Policy Document」をクリックしコードを表示させる

  3. 「Edit」をクリック


  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


  1. Timeoutを「10 sec」に変更(※動作確認の「Duration」の値を見て適宜変更)

  2. 「Next」ボタンをクリック

  3. 「Create function」ボタンをクリック


Lambdaの動作確認


  1. 左上の「Test」ボタンをクリック

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


  1. 動作結果はページ最下部に表示される


    • Summary


      • Duration … 実行時間

      • Billed duration … 課金対象時間

      • Max memory used … メモリ使用量






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


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


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


  1. 「Event sources」をクリック

  2. 「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


  1. CloudWatchのページのサイドメニューから「Alarms」をクリック

  2. 「Create Alarm」ボタンをクリック


Select Metric-1


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


Select Metric-2


  1. Metric Nameが「Errors」にチェック

  2. 「Next」ボタンをクリック


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」をクリック


参考サイト