【AWS】RDSを7日以上停止させてみた

RDSを7日以上停止させる方法について考えてみました。

# 背景とこの記事が紹介する仕組み
AWS初学者の私は、DBインスタンスを実際に作成したりして学習していました。
RDSしばらくの間使わないな...と思ったので停止を試みたのですが、どうやらRDSは7日経つと自動で再起動してしまうらしいです。
起動しているだけでお金もかかりますし、毎回再起動するたびに手動で停止をかけるのも面倒ですし...

AWS情報センターではcron式を用いて1日に1回など、定期的にRDSを停止をする方法が紹介されています。

https://repost.aws/ja/knowledge-center/rds-stop-seven-days

ただ、RDSの起動や停止には数分のラグがあるので、1回目は大丈夫ですが、2回目以降の再起動時刻がずれ込んでしまう可能性があります。その場合、cron式のような定期的なスケジューリングだと、どこかのタイミングで「停止の判断が起動よりも早く動作して停止できない」なんてことが起こるかもしれません。

この問題を解決するために、この記事では AmazonEventBridge のルールという機能を用い、「再起動したことを検知して、即座に自動で再停止させる」方法を紹介します。

# 解決策（方針）
実装方針は、AWS情報センターの記事に記載されている方法をベースとしています。
主な変更点としては、AmazonEventBridge のルールという機能を用い、AWS上で発生したイベントやメッセージをトリガーにRDSを停止させます。

イメージは以下のような感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3938935/463dc797-16f9-9b40-2a91-e6f85f049c0c.png)

これにより、再起動したことを検知して、即座に自動で再停止をかけることができます。

# 解決策（詳細）
## IAMポリシーの作成
**1.[ポリシーを作成]を選択**

**2.[JSON]タブに次のポリシーを入力**
```json
{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Sid": "VisualEditor0",
           "Effect": "Allow",
           "Action": [
               "rds:AddTagsToResource",
               "rds:StartDBCluster",
               "rds:StopDBCluster",
               "rds:ListTagsForResource",
               "rds:CreateDBSnapshot",
               "rds:DescribeDBInstances",
               "rds:StopDBInstance",
               "rds:DescribeDBClusters",
               "rds:StartDBInstance"
           ],
           "Resource": "*"
       }
   ]
}
```
上記では[JSON]タブでコードを記述していますが、[ビジュアル]タブで設定していただいても大丈夫です。
- rds:AddTagsToResource
- rds:CreateDBSnapshot

この2つは他のアクションと依存関係があると表示が出るので、必ず許可しておきます。
また、start系のアクションは今回関係ありませんが、~~どのアクションが不要か確認するのが面倒くさかったので~~AWS情報センターの記事のままにしておきました。


**3.ポリシー名、タグを設定**

## IAMロールの作成
**1.[ロールを作成]を選択**

**2.[AWSのサービス]、[Lambda]を選択**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3938935/e2e99f22-a6c0-460e-97f5-17d283c74e23.png)


**3.作成したポリシーと「AWSLambdaBasicExecutionRole」を選択**

**4.ロールの名前、タグを設定**

## RDS側の設定変更
**1.タグの設定**
自動停止設定を加えたいインスタンスに「key:autostop, value:yes」というタグを追加します。

## Lambda関数の作成
**1.下記項目を設定**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3938935/44126a18-d3b6-7dcf-5076-d08d7a90a0f6.png)
既存のロールの欄では、先ほど作成したIAMロールを選択します。


**2.エディタのサンプルコードを削除し、次のコードを入力**
```python
import boto3
import time

rds = boto3.client('rds')

def lambda_handler(event, context):
   dbs = rds.describe_db_instances()

   for db in dbs['DBInstances']:
    
       GetTags = rds.list_tags_for_resource(ResourceName=db['DBInstanceArn'])['TagList']
    
       for tags in GetTags:
           if tags['Key'] == 'autostop' and tags['Value'] == 'yes':

               delay = 5
               max_attempts = 24 
               attempts = 0

               while attempts < max_attempts:
                   db_status = rds.describe_db_instances(DBInstanceIdentifier=db['DBInstanceIdentifier'])['DBInstances'][0]['DBInstanceStatus']
                   print(f"{db['DBInstanceIdentifier']} の現在のステータス: {db_status}")
                   if db_status == 'stopped':
                       break
                   elif db_status == 'available':
                       try:
                            result = rds.stop_db_instance(DBInstanceIdentifier=db['DBInstanceIdentifier'])
                            print("インスタンスを停止中: {0}.".format(db['DBInstanceIdentifier']))
                       except Exception as e:
                           print("インスタンスを停止できません: {0}.".format(db['DBInstanceIdentifier']))
                           print(e)
                    
                       break        
                   else:
                       time.sleep(delay)
                       attempts += 1
           
if __name__ == "__main__":
   lambda_handler(None, None)
```
上記コードでは、対象のRDS(key:autostop, value:yesのタグを設定したRDS)に対し、ステータスが「availabel(利用可能)」の場合のみ、RDSの停止をかけるようにしています。

既にステータスが「stopped(一時的に停止済み)」になっている場合は処理をスキップし、その他のステータスになっている場合はステータスが「available(利用可能)」になるまでプログラムを待機させるという仕組みにしました。

また、一生「available(利用可能)」にならず、無限ループされても困ります。
なので、attemptsの値をループ毎に1増やし、max_attemptsの値に達したらループを終了させる設計にし、インスタンスごとにタイムアウトを設定できるようにしています。

:::note warn
入力が完了したら必ず[Deploy]ボタンを押します。
:::

**3.Lambda関数が正常に更新されたら、[設定]タブの[一般設定]からタイムアウトの設定をする。**
Lambda関数の一般設定にプログラム全体のタイムアウト設定もあるので、併せて使用することができます。
(私はLambda関数のタイムアウト設定を5分0秒に設定しました。)



# EventBridgeルールの作成
**1.Amazon EventBridgeコンソールを開き、ダッシュボードから[ルール]を選択**

**2.[ルールを作成]を押下**

**3.ルール名、ルールタイプを設定**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3938935/30afd9cb-9f7e-e783-ac72-4015eadc6540.png)

**4.イベントパターンを設定し、[パターンを編集]を選択**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3938935/e27a6ecb-682e-572b-2707-721c403443e1.png)

**5.サンプルイベントを削除し、下記を入力**
   ```json
   {
     "source": ["aws.rds"],
     "detail-type": ["RDS DB Instance Event"],
     "detail": {
       "Message": ["DB instance started"]
     }
   }
   ```

**6.ターゲットにLambda関数を設定**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3938935/3174cfa2-a740-f429-b280-1c70c294c70b.png)

**7.タグを設定**

**8.レビューを確認し、[ルールの作成]を押下**

# 結果
起動のタイミングで自動停止してくれることを確認できました。

また、以下についても確認することができました。
- 他のインスタンスのタグにも「key:autostop, value:yes」を設定した場合、複数インスタンスに対して設定することができました。
- EventBridgeのルールにおいて、有効化/無効化を簡単に切り替えることができます。

# さいごに
Step Functionsを使う方法等もあるようですが、複雑な設定が必要と聞いたので今回はあえて使わずにやってみました。

正直ここまで細かく設定する必要がないかもしれないので、その場合はEventBridgeのイベントスケジュールという機能を用い、cron式を使った設定にしても十分に機能してくれると思います。

最後まで読んでいただきありがとうございました！