OPS

新卒2年目女性エンジニアの奮闘記②「AWSLambdaでRDSの手動バックアップを自動化させてみた~前編~」

2019.07.16

本記事のポイント

RDSの自動バックアップでは要件を満たせない場合に、Lambda+CloudWatchEventsで手動バックアップを自動化させる方法についてハマったポイントも併せてご紹介します。

はじめに

まえおき

実際に案件でAWS Lambdaを触る機会があったので、今回はその実装方法をご紹介します!

AWS Lambdaは、2014年の11月に発表されたサービスで、イベントドリブンなアプリケーションを簡単に開発できるサーバレスなプラットフォームです。
サーバについて意識する必要がない、実行時間にしか課金されない、等メリットが沢山あります。

今回、AWS Lambdaを使用して何を自動化させたのかというとRDSのバックアップです。

え??

RDSってデフォルトでバックアップ機能あるでしょ!

と思った方がほとんどかと思います。

その通りです。

(後ほどデフォルトの自動バックアップ機能について紹介します)

その通りなのですが、一旦要件を見てみます。

*要件
・1日2回バックアップをとりたい→× (1回/日のみ)
・バックアップは50日間保存したい→× (35日間まで)
・DR対策のためバージニア北部リージョンにスナップショットをコピーしておき災害時はスナップショットから復元できるようにしたい→× (デフォルトでは不可)

?!なんと、
残念ながら、いただいた要件は全てデフォルト機能では実現できないようです!!
 

要件を満たすためには、手動でスナップショットの取得&コピーを毎日決まった時間で行う必要があります。

これらのバックアップ運用を【Lambda+CloudwatchEvents】の組み合わせで自動化させたいと思います!

 

実現したいこと

今回実現したいことを大きく2つに分けました。

①東京リージョンにたてたRDSのスナップショットを毎日2時に取得し、50日間保管後削除
②バージニア北部リージョンへリージョン間コピーを行い1日間保管後削除。

本記事では①「東京リージョンでスナップショットを作成し、日次50世代保管する仕組み」についてご紹介します!
 

前提知識~RDSバックアップ機能について~

RDSはデフォルト機能として自動バックアップ機能を備えています。

今回の実装を行うにあたり知っていたほうがいい知識となるため簡単にバックアップ機能を紹介したいと思います。

項目 デフォルト 設定変更
バックアップサイクル 1回/日 ×
バックアップ保持期間 7日 (0日〜35日)

 

*バックアップサイクル
デフォルトで1回/日までと決まっておりこの回数を増やすことはできないようです。

 

*バックアップ保持期間

デフォルトで7日となっておりこの日数は0~35日まで設定変更が可能です。

0日にすると自動バックアップは実行されません。(自動バックアップの無効化)

また、最大日数は35日間となっておりこの上限を緩和することはできないようです。

※手動バックアップであれば明示的に削除しない限り永続的にスナップショットは残るため36日以上保存可能となります。

実際の手順

事前準備

■RDSをたてておきましょう

今回は「rds-backup」というインスタンス名で作成しています

 

■Lambda用のIAMロールを用意しておきましょう

今回は「dbsnapshot-lambda-role」というロール名で作成しています

<付与しているポリシー>

・AmazonRDSFullAccess:RDSの削除、作成、一覧表示させるなどのために必要

・AWSLambdaBasicExecutionRole:CloudwatchLogsにLambdaの実行ログを出すために必要

 

Lambda基本設定

1,関数の作成をクリックします

 

2,言語とテンプレートを選択します

今回はpythonを使用してコードを作成します。

「設計図の使用」を選択しフィルタで「hello」と検索し「hello-world-python」をクリックします。

 

3,関数名を入力しロールを選択します

今回は以下の通り指定しました。

・関数名:rds-CreateSnapshot

・実行ロール:既存のロールを使用する

・既存のロール:dbsnapshot-lambda-role

※事前に作成しておいたロールを指定しましょう。

 

4,関数の作成をクリックします

 
5,基本設定からタイムアウト値を延長します

デフォルトでは3秒で設定されていますが、これだとLambda実行時にタイムアウトとなってしまい誤って同じ処理が複数回実行されてしまうため1分に延長しておきます

 
コードをかく

このような画面が表示されていると思うので早速コードをかいていきます。
 

 

コード内容は以下の通りです。

import json
import boto3
import time
from botocore.client import ClientError
from datetime import datetime, timedelta, tzinfo

rds = boto3.client('rds')

class JST(tzinfo):
    def utcoffset(self, dt):
        return timedelta(hours=9)
    def dst(self, dt):
        return timedelta(0)
    def tzname(self, dt):
        return 'JST'

def delete_snapshots(prefix, days):
    snapshots = rds.describe_db_snapshots()
    now = datetime.utcnow().replace(tzinfo=None)
    for snapshot in snapshots['DBSnapshots']:
        if 'SnapshotCreateTime'not in snapshot:
            continue
        delta = now - snapshot['SnapshotCreateTime'].replace(tzinfo=None)
        if snapshot['DBSnapshotIdentifier'].startswith(prefix) and delta.days >= days:
            rds.delete_db_snapshot(DBSnapshotIdentifier=snapshot['DBSnapshotIdentifier'])

def create_snapshot(prefix, instanceid):
    newsnapshotid = "-".join([prefix, datetime.now().strftime("%Y-%m-%d-%H-%M")])
    rds.create_db_snapshot(
        DBSnapshotIdentifier=newsnapshotid,
        DBInstanceIdentifier=instanceid
    )

def lambda_handler(event, context):
    snapshot_prefix = "rds-backup"
    instance = "rds-backup"
    delete_days = 50
    create_snapshot(snapshot_prefix, instance)
    delete_snapshots(snapshot_prefix, delete_days)

 

Pythonでは def 構文を使って、必要な関数を自由に作成することができます。

ここからコード内の説明を簡単に行っていきたいと思います。

*1~5行目

import json
import boto3
import time
from botocore.client import ClientError
from datetime import datetime, timedelta, tzinfo

import 〓モジュール名〓

上記では使用するモジュールのインポートを行います。

boto3とはAWS (Amazon Web Services) を Python から操作するためのライブラリのことで、事前に用意されている関数の使用方法などが英語で説明されています。

boto3マニュアル

 

*17~25行目

def delete_snapshots(prefix, days):
    snapshots = rds.describe_db_snapshots()
    now = datetime.utcnow().replace(tzinfo=None)
    for snapshot in snapshots['DBSnapshots']:
        if 'SnapshotCreateTime'not in snapshot:
            continue
        delta = now - snapshot['SnapshotCreateTime'].replace(tzinfo=None)
        if snapshot['DBSnapshotIdentifier'].startswith(prefix) and delta.days >= days:
            rds.delete_db_snapshot(DBSnapshotIdentifier=snapshot['DBSnapshotIdentifier'])

ここではスナップショットの世代管理を行っています。

今回はlambda_handler関数内で定義している通り50日以前のものを削除する仕様としています。

delete_days = 50

※delete_snapshots関数の実行時にdaysという変数に50が格納されます

20~25行目はfor文を使い繰り返し処理を行っています。

22行目でcontinueと出てきていますが、

continue文は、for文などの繰り返しを行う処理の中で使われます。

continue文を使うと、条件に一致した場合それ以降の処理が実行されずにスキップされ、次のループに進みます。

今回であればdescribe_db_snapshots関数実行時のレスポンスの値を順にsnapshotという変数に格納しSnapshotCreateTimeの結果が格納されたときにcontinue以降を実行しています。

 

*27~32行目

def create_snapshot(prefix, instanceid):
    newsnapshotid = "-".join([prefix, datetime.now().strftime("%Y-%m-%d-%H-%M")])
    rds.create_db_snapshot(
        DBSnapshotIdentifier=newsnapshotid,
        DBInstanceIdentifier=instanceid
    )

 

ここではスナップショットの作成を行っています。

DBSnapshotIdentifier=〓生成されたスナップショットにつける名前〓

DBInstanceIdentifier=〓スナップショットの作成対象となるインスタンス名〓

今回はlambda_handler関数内で指定している「rds-backup」というインスタンスからスナップショットを作成する仕様としています。

instance = “rds-backup”

※create_snapshots関数の実行時にinstanceidという変数にrds-backupが格納されます

 

*34~39行目

def lambda_handler(event, context):
    snapshot_prefix = "rds-backup"
    instance = "rds-backup"
    delete_days = 50
    create_snapshot(snapshot_prefix, instance)
    delete_snapshots(snapshot_prefix, delete_days)

メインの部分です。

以下のように記述することで作成した関数「create_snapshot」と「delete_snapshots」を呼び出しています。

create_snapshot(snapshot_prefix, instance)
delete_snapshots(snapshot_prefix, delete_days)

CloudWatchEventsによる定期実行の設定

コードを作成しおおよそのLambda実装は終わりましたが、これだけでは自動でのバックアップ取得はできません。

手動で毎朝2時に起きて実行ボタンをおさなければいけません。(笑)

深夜帯に自動でLambdaが実行されるようにするためにはCloudWatchEventsを使用します。

CloudWatchEventsの設定は簡単ですので以下3ステップで説明していきます。

 

1,Lambda画面のトリガー追加で、CloudWatchEventsを選択します

 

2,CloudWatchEventsの設定を行います

今回は以下の通り指定しました。

・ルール:新規ルールの作成

・ルール名:test

・ルールの説明:テスト

・ルールタイプ:スケジュール式

・cron形式で指定します

・今回は毎朝2時(JST)に取得したいため下記の通りUTCで20時に設定しました。

cron(0 20 * * ? *)

 

3,追加をクリックします

まとめ

今回はRDSの自動バックアップ機能では要件を満たせない場合に、Lambda+CloudWachEventsを使いスナップショット取得を決まった時間で実行させる仕組みを実装しました。

pythonは触ったことがないため調べながらの作業でしたがテストを何度も実行できるので【テスト実行→エラー確認→調査→修正→テスト実行】という流れを繰り返し完成させました。

ハマった点としては、

翌日スナップショットが生成されているか確認した際に本来は1つできるはずが、3つ生成されていた点です。

原因は、実行時間をデフォルトのままにしていたためでした。

コード内の関数を全て処理できないうちにタイムアウトしてしまい再実行されたようです。
(2回のリトライを含め、合計3回実行される決まりです)

また、もう一つ忘れずに設定してほしいのがIAMロールです。

CloudwatcLogsに実行ログを出力させるためには「AWSLambdaBasicExecutionRole」ポリシーの付与を忘れないようにしましょう!(最初設定してなかったためにタイムアウトエラーにも気づけませんでした。)

 

Lambdaはサーバレスのサービスで基本的な設定とコードをかくことができれば簡単に実装できるのでぜひ皆さん使ってみてください。