「RDSデータを長期的に格納」RDSからS3にスナップショットを自動的に輸出


RDSでスナップショットが最大35日間保存できます。長期間保存するためには、S3にエクスポートする必要があります。ただし、これはAWSコンソールでの手動機能に制限されています。この記事では、自動エクスポート方法について説明します。


# 1.前提
事前に以下のサービスを用意しておきます。
* Mysqlインスタントが立ち上がる（DBエンジンが任意）
*  S3バケットが立ち上がる（デフォルト設定）

本記事で```rds-snapshot-archive-test```というS3バケットを用意ました。


# 2.構築・流れ

![Screenshot 2024-08-02 at 11.29.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/45379411-114a-281b-d063-cde8a0cacc88.png)


1. RDS自体でスナップショットを撮影した時にSNSトピックにイベント送信
2. SNSはラムダ処理にトリガー 
3. ラムダはRDSでS3輸出処理コマンドを実施（*）
4. RDSはS3へスナップショット輸出処理を行う

KMS役割はスナップショットの暗号

:::note info
S3へ輸出処理の時間は結構かかりますが、ラムダの処理ではなくRDSのです。そのため、ラムダ処理時間制限の懸念（15分以内）がありません。
:::

# 3.SNSトピック設定
#### 3.1 役割
RDSでスナップショットに関わるイベントがあれば、イベントをトピックに送信してS3をトリガーします。

#### 3.2 手順

SNSに入って→新規トピック
![SNS create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/b17535b1-e6ad-eeea-39fd-446bf119820c.png)

種類にスタンダードをしてトピック名を登録します。残り項目をデフォルトに設定したら大丈夫です。


# 4.権限設定
#### 4.1 輸出ロールを作成
RDSでS3に輸出するため権限が必要です。ロールを作成して該当するS3権限をアサインします。

1. IAMのロールに入る→新規ロールを登録
 ![Role.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/d1159073-12b8-1303-9103-5387a4ee4575.png)
2. 信頼ポリシーを選ぶ
![role 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/082279a5-884f-7ebb-69cd-ca31951986b8.png)

（２）所に以下のJSONを貼り付けてください。
意味：信頼される```export.rds.amazonaws.com```側はこちらロールとしてS3に輸出できます。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "export.rds.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```
３. ステップ2をポリシー追加

![create role step 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/f8250088-79d2-596a-a7bd-ad10c16bf68f.png)

簡単にしたら```AmazonS3FullAccess```をつけても大丈夫ですが、実際では良くない方法です。
下に最小権限ポリシーを案内します。最小権限ポリシーを付けたら一旦こちらステップを飛ばしても大丈夫です。

４. レビュ-
![create role step 3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/96148ac4-8f79-b484-fd35-6c4e6e7f88bd.png)
ロール名を登録して一応完了します。


#### 4.2 最小権限ポリシーを付け
上記のステップ２を飛ばしたらこちらで引き続きます。

1. Inline policyを作成
![Screenshot 2024-08-02 at 12.18.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/56b71730-29ab-abda-f2be-88cc27fbef66.png)

2. JSONを選ぶ
![Screenshot 2024-08-02 at 12.20.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/951a26da-3acf-d811-f472-ac5827738280.png)

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ExportPolicyToCustomS3Bucket",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject*",
                "s3:ListBucket",
                "s3:GetObject*",
                "s3:DeleteObject*",
                "s3:GetBucketLocation"
            ],
            "Resource": [
                "arn:aws:s3:::<輸出先S3バケット>",
                "arn:aws:s3:::<輸出先S3バケット>/*"
            ]
        }
    ]
}
```
本記事では私の<輸出先S3バケット>は```rds-snapshot-archive-test```

３. レビュー
![Screenshot 2024-08-02 at 12.24.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/a4f0c67a-ac1f-5162-f5d5-6c5e32b2aad3.png)
ポリシー名を登録します。

 結果
![Screenshot 2024-08-02 at 12.25.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/af8d0171-335b-588d-5d61-3fbf4ad9716b.png)



# 5.KMSキー設定
#### 5.1 役割
Snapshotを暗号するためKMSキーが必要です。
#### 5.2 手順

1. KMSに入る->新規登録
![kms create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/0fe42075-7444-56d2-16cd-e8ab67503f44.png)
2. ステップ１で以下のように設定
![kms step 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/1cb5c5e0-76d2-e8e6-c888-74380b676550.png)
3. ラベルを自由に決める
![kms step 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/9a201979-edb8-f197-40f2-df16667530d8.png)

4. キーユーザーを設定
![kms step 4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/1973422a-0ed8-061a-8caa-32494d8b3808.png)
上記の輸出ロールを指摘する。ちなにみ、ステップ３を飛ばしても大丈夫だ。


5. 最後
全て情報をチェックして登録します。

# 5.RDSでイベント登録

#### 5.1 イベント登録

1. RDSに入る→イベント登録→新規イベント登録作成

![RDS SNS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/4ea8a985-9991-6444-f8e1-796ce127e2b3.png)
2. イベント登録
![Channel create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/96a152a4-451d-7704-c790-6e1a3d10c0d1.png)


* イベント登録名前を自由に入力
* 送信先を上記のSNSトピックに設定
* イベントカテゴリを```CREATE```に設定（ラムダトリガー数を減らすため）

結果
![SNS createed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/17fc8341-942e-b7d2-2220-4d73a5432952.png)


# 6.Lambda設定

#### 6.1 新規ファンクション作成

1. ラムダに入る→新規ファンクション作成
![lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/8614e5e7-6f1f-f014-65ec-30290a611a68.png)
2. 以下のように設定します
![lambda2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/813dc4cd-79c6-dfb3-8f84-58ce03671358.png)


#### 6.2 トリガー元設定
SNSトピックにトリガー元を設定
![lambda3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/99712e7b-7012-b671-cfa1-d52416db95c1.png)
上記のSNSトピックARNを設定
![lambda4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/848439a8-4f98-cb87-cd6b-4e9600d702ef.png)


#### 6.3 処理コード

```js
import { RDSClient, StartExportTaskCommand } from "@aws-sdk/client-rds";

const REGION = "ap-northeast-1";
const rdsClient = new RDSClient({ region: REGION });

const checkSnapshotType = (eventMsg) => {
  if (eventMsg?.includes("automated") || eventMsg?.includes("Automated") ) return "AUTOMATED";
  if (eventMsg?.includes("manual") || eventMsg?.includes("Manual") ) return "MANUAL";
  return "UNKNOWN";
};

const isCreatedSnapshot = (eventMsg) => {
  if (eventMsg?.includes("created")) return true;
  return false;
};
const getCurrentTime =()=>{
  const now = new Date();
  return `${now.getUTCFullYear()}-${now.getUTCMonth()+1}-${now.getUTCDate()}-${now.getUTCHours()}-${now.getUTCMinutes()}-${now.getUTCSeconds()}`
}

export const handler = async (event) => {
  try {
    const snsEvent = event?.Records[0]?.Sns;
    //  CloudWatchでイベント詳細を閲覧
    //  Snapshot情報があります
    console.log("[INFO]:  EVENT_Records_SNS:", snsEvent);
    const eventDetails = await JSON.parse(snsEvent.Message);
    const snapshotType = checkSnapshotType(eventDetails["Event Message"]);
    // snapshot種類次第で、輸出するかどうかsnapshotTypeで処理できます
    const sourceID = eventDetails["Source ID"];
    const sourceARN = eventDetails["Source ARN"];
    const takenTime = eventDetails["Event Time"]?.slice(0,10);
    
    // create とcreated との２つイベントにS3がトリガーされるが、
    // しかし、createの時にsnapshotが作成中で輸出不可で、ラムダがこちらイベントを無視します
    if(!isCreatedSnapshot(eventDetails["Event Message"])){
      return {
        statusCode: 400,
        body: JSON.stringify("[ERROR]: Snapshot is creating, cannot export",sourceID),
      }
    }
    //　命名が任意
    const snapshotNameInS3 = `${snapshotType}-${getCurrentTime()}`
    const targetBucket = "rds-snapshot-archive-test";
    const roleArn = "arn:aws:iam::＜貴方のAWSアカウントID＞:role/Demo-RDS-Export-SS-S3";
    const kmsKeyArn = "arn:aws:kms:ap-northeast-1:＜貴方のAWSアカウントID＞:key/3dfefad9-894f-496e-93d8-47771f34ecf0";
    
    const params = {
      ExportTaskIdentifier: snapshotNameInS3,
      SourceArn: sourceARN,
      S3BucketName: targetBucket,
      IamRoleArn: roleArn,
      KmsKeyId: kmsKeyArn,
    };
    const command = new StartExportTaskCommand(params);
    const data = await rdsClient.send(command);
    console.log(`[INFO] Export task started: ${JSON.stringify(data)}`);
    const response = {
      statusCode: 200,
      body: JSON.stringify("Start export source ID",sourceID),
    };
    return response;
  } catch (error) {
    console.error(`[Error]: starting export task: ${error}`);
    return {
      statusCode: 500,
      body: JSON.stringify("Error starting export task"),
    };
  }
};

```

#### 注意点

* ```targetBucket```が貴方の輸出S3バケット先
* ```roleArn``` が 貴方の輸出ロールのARN
*  ```kmsKeyArn``` が 貴方のKMSキーのARN

#### 6.4 ポリシー追加

ラムダ上で「configuration」→「permission」→「role name」を押下→IAMRole画面を遷移

![lambda 3.5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/6a8633ab-e309-5bd6-4d0d-a3f1ac56899d.png)

ラムダのロール詳細画面に遷移された！
![Screenshot 2024-08-02 at 13.16.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/bae5c05e-f6bb-8026-8983-f023b187b815.png)

上記のインライポリシー設定のように以下のポリシーを追加してください。

1.```AWSLambdaBasicExecutionRole-2786dd6c-98c1-4fc```
こちらはCloudWatchのデフォルト権限で既存しています。

２. ```kms-access``` KMSアクセス権
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:CreateGrant",
                "kms:DescribeKey",
                "kms:RetireGrant"
            ],
            "Resource": "arn:aws:kms:ap-northeast-1:＜貴方のAWSアカウントID＞:key/3dfefad9-894f-496e-93d8-47771f34ecf0"
        }
    ]
}
```
３. ```Access-RDS-Snapshot``` snapshotアクセス権
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "RDSPolicy",
            "Effect": "Allow",
            "Action": [
                "rds:DescribeDBSnapshots",
                "rds:DescribeExportTasks",
                "rds:StartExportTask"
            ],
            "Resource": [
                "arn:aws:rds:ap-northeast-1:＜貴方のAWSアカウントID＞:snapshot:*"
            ]
        }
    ]
}
```
４. ```Lambda pass role```とはラムダは輸出ロールを投げる権
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "IamPassRoleForDemoS3TriggerRDS",
            "Effect": "Allow",
            "Action": [
                "iam:PassRole"
            ],
            "Resource": "*"
        }
    ]
}
```


# 7.テスト

#### 7.1.ラムダテストJsonを用意してテスト

テンプレートに「SNS-notification」を選び
![lambda create test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/14440663-bd7b-e437-fb3c-0c19603f52c9.png)


 ```json
{
  "Records": [
    {
      "Sns": {
        "Message": " { \"Source ID\":\"test3\",\"Source ARN\":\"<貴方のsnapshot arn>", \"Event Message\":\"Manual snapshot created\"}"
      }
    }
  ]
}

 ```



#### 7.2 RDSでスナップショットを撮って輸出過程を確認

###### スナップショットを作成

![Screenshot 2024-08-03 at 16.47.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/ee15c72f-9186-180f-7f54-f5a6a5f07539.png)

![Screenshot 2024-08-03 at 16.48.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/292f3928-78d0-0364-d196-d2e9de05bd55.png)

###### RDSのイベント

![Screenshot 2024-08-03 at 16.58.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/edb62671-f74e-4b23-12c4-5bbeb293553a.png)

スナップショットを撮るイベントが2件あってラムダをトリガーさせます。
・```Manual snapshot created```
・```Creating manual snapshot```

```Creating manual snapshot```はスナップショットが作成される段階で輸出できません。そのため、上記のラムダコードで以下の処理があります。
```js
if(!isCreatedSnapshot(eventDetails["Event Message"]))
// 。。。。。
```

##### RDSからS3に輸出進捗を監視
RDSで「Export in AWSS3」に入って過程が監視できます
![Screenshot 2024-08-03 at 16.59.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/d9e34932-6ca9-5563-e431-c73eaa2c61d1.png)

##### S3でスナップショットを確認
![Screenshot 2024-08-03 at 17.37.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703979/f83ec76b-858f-25f0-1214-1e9d32701287.png)

###### 注意点
格納バケットのデータ量が早く増えるため、Life Cycleを適用した方がいいと思います。


# ８.その他

#### 他の方法

AWSのEC2で以下のソースコードを実施してCloudFormationを作成して頂きます。CloudFormationを行うと必要なサービスが立ち上がります。

https://github.com/aws-samples/rds-snapshot-export-to-s3-pipeline/tree/master

#### 参考

https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/rds/command/StartExportTaskCommand/

https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ExportSnapshot.html


最後まで読んで頂いて有り難うございます。
役に立つを感じしたらハートやコメントやを残ってください:bow: