【データ基盤構築/AWS】RDSのスナップショットをLambdaを使ってS3に定期的にエクスポートする仕組みを作る

# 今回の課題
下記の記事を参考にしながら、EC2にてRDSを連携させたRailsのアプリを動かすことができたため、
今回は、AWS Lambdaを使用して、RDS内のデータのスナップショットをS3に定期的にエクスポートする機能を作る。

S3にエクスポートしたデータは、今後Snowflakeにしたいと考えている。

https://zenn.dev/ito110/scraps/81b8dd657a5c6b




# RDSスナップショットをS3にエクスポートする機能を実装していく
## 実装手順
### 1)S3にてバケットを作成
RDSのスナップショットをエクスポートするためのS3バケットを作成しておく。

### 2)RDSのスナップショットを作成
`DBスナップショットの取得`ページの、
`DBインスタンス`でスナップショットを取得したいRDS内のDBを指定し、
`スナップショット名`でDBスナップショットの識別子を指定しておく。

※DBスナップショットの識別子は、スナップショットを復元する時などに使用するもので、今回の記事では使用しない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/1054a3c7-e126-618d-d445-62ec6cde0fee.png)

### 3)RDSスナップショットをS3にエクスポートするために必要なポリシー&ロールの作成
#### 3)-1.ポリシーの作成
RDSがエクスポート先のS3にアクセスしてエクスポートできるようにするためのポリシーを作成しておく。
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject*",
                "s3:GetObject*",
                "s3:DeleteObject*"
            ],
            "Resource": [
                "arn:aws:s3:::[エクスポート先のバケット名]",
                "arn:aws:s3:::[エクスポート先のバケット名]/*"
            ]
        }
    ]
}
```

#### 3)-2.ロールの作成
ロールの作成画面にて、
信頼されたエンティティタイプを`AWSのサービス`に指定し、
ユースケースは`RDS - Add Role to Database`に指定して`次へ`をクリック。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/8d340dd1-0947-5bfe-0a54-ead352eb8117.png)

次のページで3)-1で作成したポリシーを選択して、ロールの作成を完了する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/46277ed8-83ec-aa17-0e58-a33bfce5795a.png)


#### 3)-3.作成したロールの`信頼関係`を書き換える
ロールの編集画面に飛び、`信頼関係の編集`タブをクリックし、
`信頼ポリシーを編集`をクリックして下記に書き換える。
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "export.rds.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```
これで、RDSスナップショットをS3にエクスポートするための権限の準備が完了した。

### 4)KMSの作成
KMSのページの`カスタマー管理型のキー`を選択し、デフォルトの設定でKMSを作成する。
ここで作成したキーは、Lambdaを作成後に使用する。

### 5)Lambdaの作成

#### 5)-1.関数の作成を行う
Lambdaのページから`関数の作成`をクリックして下記のページに飛び、
ランタイムは`Python 3.9`を選択し、
実行ロールは`基本的なLambdaアクセス権限で新しいロールを作成`を選択して関数の作成を実施する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/f8aa4ebb-8e1b-2936-48db-10da5c53caec.png)


#### 5)-2.ポリシーとロールを作成
5)-1で`基本的なLambdaアクセス権限で新しいロールを作成`を選択したので、
`AWSLambdaBasicExecutionRole-********-****-****-****-************`というポリシー名を所持した`[関数名]role-********`というロール名で、新しいロールが作成されている。

そこで、下記のポリシーを別途作成し、
上記のLambda作成時に同時に作成した`[関数名]role-********`というロールにアタッチする。
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:PassRole",
                "rds:StartExportTask"
            ],
            "Resource": "*"
        }
    ]
}
```



#### 5)-3.Lambdaコードを作成
RDSスナップショットをS3にエクスポートするためのコードを作成する。
```python
import json
import boto3
from datetime import datetime

SOURCE_ARN="arn:aws:rds:ca-central-1:************:snapshot:rails-ec2-rds" # エクスポート対象とするRDSスナップショットのARN
S3_BUCKET_NAME="snowflake-data-practice" # 出力先のS3バケット名
IAM_ROLE_ARN="arn:aws:iam::************:role/RDSToS3_export" # S3にエクスポートする際に使用するロールのARN
KMS_KEY_ID="arn:aws:kms:ca:central-1:************:key/********-****-****-****-**********" # 作成したKMSキーのARN

client = boto3.client('rds')

def lambda_handler(event, context):

    export_task_identifier="mysnapshot" + datetime.now().strftime("%Y%m%d%H%M%S")

    response = client.start_export_task(
        ExportTaskIdentifier=export_task_identifier,
        SourceArn=SOURCE_ARN,
        S3BucketName=S3_BUCKET_NAME,
        IamRoleArn=IAM_ROLE_ARN,
        KmsKeyId=KMS_KEY_ID,
        )
```

### 6)KMSのキーユーザーにLambdaのロールを追加
5)-2で作成したLambdaのロールを、4)で作成したKMSの`キーユーザー`に追加する。

※Lambda実行時に`An error occurred (KMSKeyNotAccessibleFault)`というエラーが発生するのを防ぐため

### 7)定期実行されるように設定する
ここまでで、Lambdaを実行することでRDSスナップショットがS3にエクスポートされる仕組みを実装することができた。
最後に、RDSスナップショットがS3に定期的にエクスポートされるように設定を行う。

まずは、5)で作成したLambdaの関数のページに進み、`トリガーを追加`をクリックする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/d3395224-9e7b-636c-a66a-4d87467be3dc.png)

トリガーに`EventBridge(CloudWatch Events)`を選択して、
スケジュール式を選択すれば、指定した時間おきにRDSスナップショットがS3にエクスポートされるようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/0df9f83d-04be-d067-1903-f7f942672163.png)





##### 参考記事

https://www.seeds-std.co.jp/blog/creators/2022-07-22-143712/

https://qiita.com/hmdsg/items/a948b8e30eb5503438af

# まとめ
以上のように進めることで、
RDSのスナップショットをLambdaを使ってS3に定期的にエクスポートする仕組みを構築することができた。

# おまけ
## LambdaのPythonコードを理解する
Lambdaで実行したPythonコードに、どういったことが書かれているかしっかり理解するために調べてみた。
特に下記の部分が理解不足だった。
```python
client = boto3.client('rds')

def lambda_handler(event, context):

    export_task_identifier="mysnapshot" + datetime.now().strftime("%Y%m%d%H%M%S")

    response = client.start_export_task(
        ExportTaskIdentifier=export_task_identifier,
        SourceArn=SOURCE_ARN,
        S3BucketName=S3_BUCKET_NAME,
        IamRoleArn=IAM_ROLE_ARN,
        KmsKeyId=KMS_KEY_ID,
        )

```
### 1)RDSにアクセスできるようにする
```python
client = boto3.client('rds')
```


### 2)S3にエクスポートされた際のファイル名を設定しておく
```python
export_task_identifier="mysnapshot" + datetime.now().strftime("%Y%m%d%H%M%S")
```
この場合、`mysnapshot[現在日時]`と命名される。

### 3)Lambdaが実行されたときに呼び出すコードを設定しておく
Lambdaが実行されたときに、下記の関数が呼び出される。
```python
def lambda_handler(event, context):
```
※参考：[PythonのLambda関数ハンドラー](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-handler.html)

### 4)RDSスナップショットをS3にエクスポートするための設定を行う
`start_export_task`内で、エクスポート元やエクスポート先やエクスポート字のファイル名などを設定する。
```python
response = client.start_export_task(
    ExportTaskIdentifier=export_task_identifier, # S3にエクスポート時のファイル名を指定
    SourceArn=SOURCE_ARN, # RDSスナップショットを指定
    S3BucketName=S3_BUCKET_NAME, # エクスポート先のS3バケット名を指定
    IamRoleArn=IAM_ROLE_ARN, # S3をエクスポートするために使用するロールを指定
    KmsKeyId=KMS_KEY_ID, # KMSキーを指定
    )
```