Amazon RDSの自動起動を抑制したい

# やりたいこと
停止しても１週間後に自動起動してくるRDSだが、
自己学習中とかで１週間以上停止しておきたいこともあるかと思います。

今回は自動起動したRDSを検知して、再度停止させることが目的となります。

# 使うサービス
- Amazon EventBridge
- AWS Step Functions
  - Amazon EventBridge スケジューラで実現する方法もあるが、同じ時間に停止しようとすると前回停止したRDSが実行時点で起動していなくて1日ラグが発生する為、StepFunctionsを採用

# 参考ドキュメント
① https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/rds-cloud-watch-events.html 
② https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/rds-cloud-watch-events.html
③ https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/USER_Events.Messages.html
# 動作イメージ
![スクリーンショット 2023-07-06 19.36.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472762/b61a1225-5100-e84d-25da-36411a24a266.png)


①EventBridgeで自動起動を検知します。
②StepFunctionsを実行します。
③RDSを停止します。

# 実装
### StepFunctionsの作成
**③RDSを停止します。**
こちらを実現するためにStepFunctionsのステートマシンを作成します。
ステートマシンには参考ドキュメント①に記載されているようなイベントが送られてきます。
それを踏まえて、定義を下記のように記載します。
<details><summary>定義</summary><div>

```json
{
  "Comment": "A description of my state machine",
  "StartAt": "TypeCheck",
  "States": {
    "TypeCheck": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.detail-type",
          "StringEquals": "RDS DB Instance Event",
          "Next": "DescribeDBInstances"
        },
        {
          "Variable": "$.detail-type",
          "StringEquals": "RDS DB Cluster Event",
          "Next": "DescribeDBClusters"
        }
      ]
    },
    "DescribeDBInstances": {
      "Type": "Task",
      "Parameters": {},
      "Resource": "arn:aws:states:::aws-sdk:rds:describeDBInstances",
      "Next": "DbInstanceStatCheck",
      "ResultPath": "$.Result",
      "OutputPath": "$.Result.DbInstances[?(@.DbInstanceIdentifier == $.detail.SourceIdentifier)]"
    },
    "DbInstanceStatCheck": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$[0].DbInstanceStatus",
          "StringEquals": "available",
          "Next": "StopDBInstance"
        }
      ],
      "Default": "InstanceBootPending"
    },
    "InstanceBootPending": {
      "Type": "Wait",
      "Seconds": 600,
      "Next": "DbInstanceIdentifierPass"
    },
    "DbInstanceIdentifierPass": {
      "Type": "Pass",
      "Next": "DescribeDBInstances",
      "Parameters": {
        "detail": {
          "SourceIdentifier.$": "$[0].DbInstanceIdentifier"
        }
      }
    },
    "DescribeDBClusters": {
      "Type": "Task",
      "Next": "DbClusterStatCheck",
      "Parameters": {},
      "Resource": "arn:aws:states:::aws-sdk:rds:describeDBClusters",
      "ResultPath": "$.Result",
      "OutputPath": "$.Result.DbClusters[?(@.DbClusterIdentifier == $.detail.SourceIdentifier)]"
    },
    "DbClusterStatCheck": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$[0].Status",
          "StringEquals": "available",
          "Next": "StopDBCluster"
        }
      ],
      "Default": "ClusterBootPending"
    },
    "ClusterBootPending": {
      "Type": "Wait",
      "Seconds": 600,
      "Next": "DbClusterIdentifierPass"
    },
    "DbClusterIdentifierPass": {
      "Type": "Pass",
      "Next": "DescribeDBClusters",
      "Parameters": {
        "detail": {
          "SourceIdentifier.$": "$[0].DbClusterIdentifier"
        }
      }
    },
    "StopDBInstance": {
      "Type": "Task",
      "Parameters": {
        "DbInstanceIdentifier.$": "$[0].DbInstanceIdentifier"
      },
      "Resource": "arn:aws:states:::aws-sdk:rds:stopDBInstance",
      "End": true
    },
    "StopDBCluster": {
      "Type": "Task",
      "End": true,
      "Parameters": {
        "DbClusterIdentifier.$": "$[0].DbClusterIdentifier"
      },
      "Resource": "arn:aws:states:::aws-sdk:rds:stopDBCluster"
    }
  }
}
```

</div></details>

グラフはこんな感じです。
![stepfunctions_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472762/6f7f8879-fca2-d8fe-962e-55404e5db964.png)
- TypeCheck
  - インスタンスとクラスターで、停止のために呼び出すAPIが変わるため、detail-typeを使用して分岐しています。
- DescribeDBInstances
  - ステータスが**available**にならないと停止ができないため、現在の状態を取得します。
  - RDSのDescribeはリソース名などでfilterができないので、一旦全インスタンスを取得しています。
  - ResultPathでinputのデータと取得したデータを合わせています。
  - OutputPathで上記で合わせたデータを使用して、inputで受け取ったインスタンスIDと取得したインスタンスIDが一致するデータのみを出力しています。
- DbInstanceStatCheck
  - DbInstanceStatusの状態によって処理を分岐しています。
  - ステータスがavailableの場合、インスタンスを停止します。
  - available以外の場合、待機します。
- InstanceBootPending
  - 10分待機します。
- DbInstanceIdentifierPass
  - 再度DescribeDBInstancesに戻すための下準備をしています。
  - DescribeDBInstancesのOutputPathで\\$.Result.DbInstances配下のみを出力していて、OutputPathでfilterをかけるための\\$.detail.SourceIdentifier配下の情報がなくなってしまっている為、detail配下を新規で作成して、パラメータとして渡しています。
- StopDBCluster
  - 検知したインスタンスを停止しています。

- クラスターの方もやっていることはインスタンスと同じです。

### EventBridgeの作成
**①EventBridgeで自動起動を検知します。**
**②StepFunctionsを実行します。**
これらを実現するためにEventBridgeを作成します。

1. ルールタイプは「イベントパターンを持つルール」を選択
1. 作成のメソッドで「カスタムパターン」を選択
1. 参考ドキュメントを参考にイベントパターンを記述。
下記のようになります。
```json
{
  "source": ["aws.rds"],
  "detail-type": ["RDS DB Instance Event"],
  "detail": {
    "EventID": ["RDS-EVENT-0154"]
  }
}
```

①のドキュメントでどういったイベントが来るのかを確認します。
②のドキュメントで検知したいイベントを特定します。今回は自動起動したものを検知したいので、「RDS-EVENT-0154」となります。
![スクリーンショット 2023-07-06 20.14.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472762/6d26a591-b21b-3a83-baa3-3eb15c051291.png)
4. ターゲットに作成したStepFunctionsのステートマシンを指定します。
5. Auroraクラスターの方も③のドキュメントで検知したいイベントを特定し、ルールを作成します。
```json
{
  "source": ["aws.rds"],
  "detail-type": ["RDS DB Cluster Event"],
  "detail": {
    "EventID": ["RDS-EVENT-0153"]
  }
}
```

# 確認
実際に停止から１週間後に自動起動してくるのを待ちます。
![stepfunctions_graph (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472762/ddf7c210-31fa-f9c4-ed4c-0a482b6a32a0.png)
エラーがなく、RDS,Auroraが止まっていれば成功です。

ステートマシンのみの確認であれば1週間待たずに、下記のようなイベントを入力に指定して実行することで確認できます。
<details><summary>入力イベント</summary><div>

```json
{
  "version": "0",
  "id": "12a345b6-78c9-01d2-34e5-123f4ghi5j6k",
  "detail-type": "RDS DB Cluster Event",
  "source": "aws.rds",
  "account": "**********",
  "time": "2023-07-04T21:03:28Z",
  "region": "ap-northeast-1",
  "resources": [
    "arn:aws:rds:ap-northeast-1:*********:cluster:*******"
  ],
  "detail": {
    "EventCategories": [
      "notification"
    ],
    "SourceType": "CLUSTER",
    "SourceArn": "arn:aws:rds:ap-northeast-1:*********:cluster:*********",
    "Date": "2023-07-04T21:03:28.114Z",
    "Message": "DB cluster is being started due to it exceeding the maximum allowed time being stopped.",
    "SourceIdentifier": "*********",
    "EventID": "RDS-EVENT-0153"
  }
}
```
</div></details>

# 最後に
今回StepFunctionsを使用してみて、Lambdaを噛ませずに直接APIを呼び出すことができることに驚きました。。。
最初はLambdaを使用して実現することを検討していましたが、APIを呼び出すだけの処理であれば、StepFuntionsや、Amazon EventBridge スケジューラを使用することで実現できないか検討していこうと思いました。