AWS利用料が閾値を超過したらサイトをクローズする

# はじめに
個人でAWS上でWEBサイトを運営していますが、アクセスに応じてスケールするサービスを使っているので、月の利用料金が怖いです。

ということで、今回は設定した利用料金を超えたら、サイトをクローズする設定を導入しました。

# 設計

#### 概要
サービスクローズの方法は、Route53に設定したサービスのAレコードを上書きし、URLのアクセス先をS3バケットに保存したメンテナンスページに向けることで実現する。
AWS利用料監視にはCloudWatchのEstimatedChargesメトリクスを使用し、このメトリクスの閾値超過をトリガーに、Route53のレコードが上書きされるLambdaを実行する。

#### 構成図
AWS利用料が閾値を超えてからサービスクローズまでのイメージを以下に示します。
![名称未設定ファイル.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374911/ecd32d93-64b5-9c3a-6687-597878b680b2.png)

#### 機能設計
* CloudWatch
AWS利用料を監視する。利用料のメトリクスであるEstimatedChargesに閾値を設定し、それを超えると発砲されるアラームを作成する。
* SNS
CloudWatchアラームをEメール配信するために利用する。トピックに紐づいたサブスクリプションには管理者のEメールアドレスを設定する。
* EventBridge
CloudWatchアラームの状態が「ALARM」になるイベントを検知した際に、Lambdaを呼び出すために利用する。
* Lambda
Route53に設定したレコード情報を上書きするAPIを実行するために利用する。

# 設定
CloudWatch、SNSのアラート設定は、以下の記事が参考になりますので割愛します。
[CloudWatchで請求アラームを作成する](https://zenn.dev/mn87/articles/ec5a57228c491c)

### S3
まずは、メンテナンスページを格納するS3バケットを作成します。
バケットは以下の点に注意して作成します。
* レコード名とバケット名を同一にする。
* バケットの静的ウェブサイトホスティング設定を有効にする。
* バケットのパブリックアクセスを許可する。

### EventBridge
* イベントパターン
イベントパターンに以下を指定すると、作成したCloudWatchアラームの状態が「OK」→「ALARM」に変化した際に設定したターゲットが実行されます。
```json
{
  "source": ["aws.cloudwatch"],
  "detail-type": ["CloudWatch Alarm State Change"],
  "detail": {
    "alarmName": ["CloudWatchアラームの名前"],
    "state": {
      "value": ["ALARM"]
    }
  }
}
```
<br>

* ターゲット
ここでは後の紹介で作成するLambdaを指定します。
EventBridgeからLambdaの実行は非同期のため、再試行回数や、デッドレターキューの使用などの設定も行います。

※参考
[Amazon EventBridge (CloudWatch Events) でAWS Lambdaを使用する](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-cloudwatchevents.html)

### Lambda
コードは以下です。
pythonのAWS SDKで、Route53に登録されているレコードを編集するAPIを実行しています。
```python
import json
import boto3

client = boto3.client('route53')

def lambda_handler(event, context):
    client.change_resource_record_sets(
        HostedZoneId='Route53のホストゾーンID',
        ChangeBatch={
            'Changes': [
                {
                    'Action': 'UPSERT',
                    'ResourceRecordSet': {
                        'Name': 'レコード名(WEBサイトのURL)',
                        'Type': 'A',
                        'AliasTarget': {
                            'HostedZoneId': 'Z2M4EHUR26P7ZW',
                            'DNSName': 's3-website-ap-northeast-1.amazonaws.com.',
                            'EvaluateTargetHealth': True
                        },
                    }
                },
            ]
        }
    )
```

※以下はS3東京リージョンのウェブサイトエンドポイントのホストゾーンIDで、固有の値です。
[Amazon Simple Storage Service エンドポイントとクォータ](https://docs.aws.amazon.com/ja_jp/general/latest/gr/s3.html)
```
'HostedZoneId': 'Z2M4EHUR26P7ZW',
```
<br>

* 権限
権限まわりの設定は、LambdaがRoute53のリソースにアクセスするため、実行ロールに許可を与える必要があります。特にRoute53側のリソースを絞らない場合は、以下のポリシーでOK です。
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets",
                "route53:ListResourceRecordSets"
            ],
            "Resource": "*"
        }
    ]
}
```

Lambdaへのアクセス権限は、EventBridgeで関数をターゲットに指定した際に、自動で設定されます。(※AWSコンソール操作の場合)

# おわりに
今回は月のAWS利用料に応じて、サイトのURLをS3に保存したメンテナンスページにアクセスするよう変更する設定を行いました。
このおかげで、AWSサービスのオートスケール機能の恩恵を受けつつ、安心してWEBサイトの運用ができそうです。

# おまけ
本記事の機能は以下のサービスで利用されています。

#### 観た映画を登録、管理できるサービス　MyMovie
![映画一覧.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374911/8d8ac46a-8305-937f-5225-fb1a834309ad.png)

サービスURL : [mymovie.jp](https://mymovie.jp/)
紹介記事 : [【個人開発】みた映画を管理/共有するアプリを作りました。](https://qiita.com/haruya_hamasaki/items/ae1b8cd5d94ea053b76e)