RDSを低コストでDRリージョンにバックアップ・リストアする

#概要
以下サイト内容を実施してみました。
簡単に言うと、RDSの障害時にLambdaを使って、
DRリージョンにスナップショットからRDSを復元します。

https://aws.amazon.com/jp/blogs/database/cross-region-automatic-disaster-recovery-on-amazon-rds-for-oracle-database-using-db-snapshots-and-aws-lambda/

#構成図
3つのLambdaを使用しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/8b720be4-9870-e9cd-f877-353c020c88ba.png)

#前提条件
RDSはソースリージョンで作成しておきます。
ターゲットリージョンではDBサブネットグループのみ作成しておきます。

#目次
1. [Lambda用IAMロールの作成](#anchor1)
1. [【①RDSスナップショット取得】のLambda](#anchor2)
    1. Lambda作成 
    1. CloudWatch イベントでスケジュールの設定をする
1. [【②DRリージョンにスナップショットコピー】のLambda](#anchor3)
    1. Lambda作成 
    1. SNSトピックを作成
    1. RDSイベントサブスクリプションを作成
1. [【③DRリージョンにRDS復元】のLambda](#anchor4)
    1. Lambda作成 
    1. SNSトピックを作成
    1. RDSイベントサブスクリプションを作成



#手順
<a id="anchor1"></a>
###1. Lambda用IAMロールの作成
まずはIAMロールを作っておきます。

今回は以下のポリシーを適用します。
　(実際に使う場合は最小権限にしてください)
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/00651937-8653-0302-bd5d-1bed03c4c0f2.png width=40%>

ロール名を入力してロールの作成を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/474df1a2-1fc9-ffb2-d329-17028ccbaf24.png)


<a id="anchor2"></a>
###2.【①RDSスナップショット取得】のLambda
それでは以下の部分を作っていきましょう。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/cbbc549a-cdbe-57d3-966c-1899975f3002.png width=80%>

####2-1.Lambda作成
Lambda作成の際は、1で作ったIAMロールを付与します。

コードは冒頭のサイト参照

```
import botocore  
import datetime  
import re  
import logging
import boto3

#以下は自環境に合わせて書き換えてください
region='ap-northeast-1'  
db_instance_class='db.t2.micro'  
db_subnet='default-vpc-077737b0bf23840f2'  
instances = ['irikura-db-1']
 
print('Loading function')
 
def lambda_handler(event, context):  
     source = boto3.client('rds', region_name=region)
     for instance in instances:
         try:
             #timestamp1 = '{%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now())
             timestamp1 = str(datetime.datetime.now().strftime('%Y-%m-%d-%H-%-M-%S')) + "lambda-snap"
             snapshot = "{0}-{1}-{2}".format("mysnapshot", instance,timestamp1)
             response = source.create_db_snapshot(DBSnapshotIdentifier=snapshot, DBInstanceIdentifier=instance)
             print(response)
         except botocore.exceptions.ClientError as e:
             raise Exception("Could not create snapshot: %s" % e)
```


####2-2.CloudWatch イベントでスケジュールの設定をする
CloudWatchのイベント→ルールに移動します。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/5f46dc8d-2655-9648-a306-5673c61de171.png width=30%>

ルールの作成を押下し、
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/ca298ca9-2c62-52c2-999f-9ec8c4c76ad0.png width=20%>

イベントソースはスケジュールにチェックを入れ、任意の間隔を指定します。
ターゲットは上記で作成したLambda関数を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/8ac305f2-6e22-a3ea-83c7-b21da0162b84.png)

ルールが作成できたら、
指定の間隔でRDSのスナップショットが作成されることを確認しておきます。

<a id="anchor3"></a>
###3.【②DRリージョンにスナップショットコピー】のLambda
スナップショットが作成された際のRDSイベントから、
SNSでLambdaを起動してDRリージョンにスナップショットをコピーします。

<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/ed038d1f-0506-2aab-6f2b-231d48ad5e39.png width=40%>



####3-1.Lambda作成
Lambda作成の際は、1で作ったIAMロールを付与します。

コードは冒頭のサイト参照

```
import boto3  
import botocore  
import datetime  
import re
import json

#以下は自環境に合わせて書き換えてください	
SOURCE_REGION = 'ap-northeast-1'  
TARGET_REGION = 'us-east-1'  
iam = boto3.client('iam')  
instances = ['irikura-db-1']

print('Loading function')

def byTimestamp(snap):  
  if 'SnapshotCreateTime' in snap:
    return datetime.datetime.isoformat(snap['SnapshotCreateTime'])
  else:
    return datetime.datetime.isoformat(datetime.datetime.now())

def lambda_handler(event, context):  
    print("Received event: " + json.dumps(event, indent=2))
    account_ids = []
    try:
        iam.get_user()
    except Exception as e:
        account_ids.append(re.search(r'(arn:aws:sts::)([0-9]+)', str(e)).groups()[1])
        account = account_ids[0]

    source = boto3.client('rds', region_name=SOURCE_REGION)

    for instance in instances:
        source_instances = source.describe_db_instances(DBInstanceIdentifier= instance)
        source_snaps = source.describe_db_snapshots(DBInstanceIdentifier=instance)['DBSnapshots']
        source_snap = sorted(source_snaps, key=byTimestamp, reverse=True)[0]['DBSnapshotIdentifier']
        source_snap_arn = 'arn:aws:rds:%s:%s:snapshot:%s' % (SOURCE_REGION, account, source_snap)
        target_snap_id = (re.sub('rds:', '', source_snap))
        print('Will Copy %s to %s' % (source_snap_arn, target_snap_id))
        target = boto3.client('rds', region_name=TARGET_REGION)

        try:
            response = target.copy_db_snapshot(
            SourceDBSnapshotIdentifier=source_snap_arn,
            TargetDBSnapshotIdentifier=target_snap_id,
            CopyTags = True)
            print(response)
        except botocore.exceptions.ClientError as e:
            raise Exception("Could not issue copy command: %s" % e)
        copied_snaps = target.describe_db_snapshots(SnapshotType='manual', DBInstanceIdentifier=instance)['DBSnapshots']
```
####3-2.SNSトピックを作成

以下のSNSトピックを作成し、
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/3b1ab415-1a8d-766a-14b3-2475d80d4ec7.png width=80%>

サブスクリプションに3-1で作成したLambdaを指定します。

####3-3.RDSイベントサブスクリプションを作成
RDSに移動しイベントサブスクリプションを選択する。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/9429b042-81ed-283e-97e8-63d8d7019c9f.png width=40%>

イベントサブスクリプションの作成で名前を入力し、
ターゲットに3-2で作成したSNSトピックを指定する。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/e4ae097b-766c-a6c0-cb36-1644ea79c47b.png width=70%>


ソースは以下のように設定することで、スナップショット作成時に
SNSに通知がいき、SNSからLambdaを起動することができます。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/b3441b06-bf65-4483-14a7-9af39ed0346f.png width=70%>


<a id="anchor4"></a>
###4.【③DRリージョンにRDS復元】のLambda
3の手順と同じように障害などのRDSイベントからSNSを利用してLambdaを起動させ、
DRリージョンでスナップショットからRDSを復元します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/a4899d2a-8c0e-9d43-dc11-7186b3b221af.png)

####4-1.Lambda作成
Lambda作成の際は、1で作ったIAMロールを付与します。

コードは冒頭のサイト参照

```
import boto3  
import botocore  
import datetime  
import re  
import logging

#以下は自環境に合わせて書き換えてください	
region='us-east-1'  
db_instance_class='db.t2.micro' 
db_subnet='test'  
#db_subnet='subnet-12345d6a'
instances = ['irikura-db-1']

print('Loading function')

def byTimestamp(snap):  
  if 'SnapshotCreateTime' in snap:
    return datetime.datetime.isoformat(snap['SnapshotCreateTime'])
  else:
    return datetime.datetime.isoformat(datetime.datetime.now())

def lambda_handler(event, context):  
    source = boto3.client('rds', region_name=region)
    for instance in instances:
        try:
            source_snaps = source.describe_db_snapshots(DBInstanceIdentifier = instance)['DBSnapshots']
            print ("DB_Snapshots:", source_snaps)
            source_snap = sorted(source_snaps, key=byTimestamp, reverse=True)[0]['DBSnapshotIdentifier']
            snap_id = (re.sub( '-\d\d-\d\d-\d\d\d\d ?', '', source_snap))
            print('Will restore %s to %s' % (source_snap, snap_id))
            response = source.restore_db_instance_from_db_snapshot(DBInstanceIdentifier=snap_id,DBSnapshotIdentifier=source_snap,DBInstanceClass=db_instance_class,DBSubnetGroupName=db_subnet,MultiAZ=False,PubliclyAccessible=False)
            print(response)

        except botocore.exceptions.ClientError as e:
            raise Exception("Could not restore: %s" % e)
```
####4-2.SNSトピックを作成
以下のSNSトピックを作成し、
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/431f5c1e-dfe4-0234-90f1-e73cd6a78db6.png width=80%>

サブスクリプションに4-1で作成したLambdaを指定します。

####4-3.RDSイベントサブスクリプションを作成
RDSに移動しイベントサブスクリプションを選択する。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/9429b042-81ed-283e-97e8-63d8d7019c9f.png width=40%>

イベントサブスクリプションの作成で名前を入力し、
ターゲットに4-2で作成したSNSトピックを指定する。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/ac563f14-90b3-7aeb-134a-295cf8bdf694.png width=70%>


ソースは以下のように設定することで、スナップショット作成時に
SNSに通知がいき、SNSからLambdaを起動することができます。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138583/8ff84992-1fde-01fa-12fd-17c67653cc10.png width=70%>



####詰まったところ
IAMロールはサービスリンクドロールをLambdaに使用すると、
CloudWatch logsにログが出力されていませんでした。
なので最初にIAMロールを作っておきましょう。

以下参照

https://qiita.com/arara_tepi/items/98f23d91eb805c6d4d71

####気になったところ
　RDSのイベント通知が複数回くるので(例えばスナップショット取得開始・完了でそれぞれ)、その度にLambdaが起動している。
　SNSサブスクリプションフィルターで指定の値のみ通知するようなフィルター機能はあるが、部分一致などの指定はできない。Lambda内に処理を加えた方がよいかも。