CloudWatchとAWS Lambdaを用いてAmazon EFSのスループットモード変更を自動化してみた

# はじめに

業務で使用している`Amazon EFS`の`BurstCreditBalance`が2週間程度で枯渇するため、これまではその都度手動でスループットモードを変更するという対応を取っていたのですが、`CloudWatch`と`AWS Lambda`を用いて対応を自動化してみましたのでその際に設定した内容を記します。

## 注意事項
- この記事の内容は執筆時点のものであるため、最新の設定方法とは異なっている可能性があります。
- 入力が任意の設定項目については記載を割愛している場合があります。
- 自身の備忘録も兼ねて記載しているので内容の誤りや、より効率的に実現する方法がある可能性があります。気になる点などがありましたらお気軽にコメントいただけますと幸いです。。。！

# やったこと

1. Amazon SNS - トピック作成
1. CloudWatch - アラーム作成
1. AWS Lambda - 関数作成
1. IAM - 許可ポリシー追加

## Amazon SNS

Lambda関数を実行させるトリガとしてトピックを2つ作成しました。

| トピックの名前               | トピックの用途                                                         |
| ------------------ | ------------------------------------------------------------ |
| `EFS-to-bursting`    | EFSのスループットモードをバーストに変更するLambda関数のトリガ |
| `EFS-to-provisioned` | EFSのスループットモードをプロビジョニング済みに変更するLambda関数のトリガ |

### 作成手順

1. `AWSマネジメントコンソール`より`Amazon SNS コンソール`にアクセス
1. 左のサイドメニューより`トピック`をクリック
1. 画面右側の`トピックの作成`をクリック
1. 以下の通り設定し、`トピックの作成`をクリック
    - `詳細`
        - `タイプ`: `スタンダード`
        - `名前`: `<上記表の名前>`

## CloudWatch

`Amazon EFS`の`BurstCreditBalance`が閾値を下回った時と回復した時に、前述の手順で作成したトピックに対して通知されるようにアラームを2つ作成しました。

| アラームの名前                                              | アラームの用途                                                         |
| ------------------------------------------------- | ------------------------------------------------------------ |
| `EFS BurstCreditBalance is under threshold`         | `BurstCreditBalance`が閾値を下回った時にEFSのスループットモードをプロビジョニング済みに変更する際に使用するアラーム |
| `EFS BurstCreditBalance returned to the threshold` | `BurstCreditBalance`が閾値を上回った(回復した)時にEFSのスループットモードをバーストに変更する際に使用するアラーム |

### 作成手順

#### プロビジョニング済みに変更するアラーム (`EFS BurstCreditBalance is under threshold`)

1. `AWSマネジメントコンソール`より`CloudWatch コンソール`にアクセス
1. 左のサイドメニューより`すべてのアラーム`をクリック
1. 画面右側の`アラームの作成`をクリック
1. 以下の通り設定し、アラームを作成
1. ステップ１ メトリクスと条件の指定
以下の通り設定し、`次へ`をクリック
    - メトリクス: `すべて`>`EFS`>`ファイルシステムメトリクス`

    | FileSystemId                      | メトリクス名       |
    | --------------------------------- | ------------------ |
    | `<変更対象のEFSのファイルシステムID>` | `BurstCreditBalance` |

    - 条件
        - `閾値の種類`: `静的`
        - `BurstCreditBalance が次の時...`: `以下`
        - `... よりも`: `<任意の値>` (私の環境では`200000000000`と設定しました。)

1. ステップ2 アクションの設定
以下の通り設定し、`次へ`をクリック

    - 通知
        - `アラーム状態トリガー`: `アラーム状態`
        - `次の SNS トピックに通知を送信`: `既存の SNS トピックを選択`
        - `通知の送信先`: `EFS-to-provisioned`

1. ステップ3 名前と説明を追加
以下の通り設定し、`次へ`をクリック

    - 名前と説明
        - `アラーム名`: `EFS BurstCreditBalance is under threshold`

1. ステップ4 プレビューと作成
アラームの内容を確認し、問題がなければ`アラームの作成`をクリックします。

#### バーストに変更するアラーム (`EFS BurstCreditBalance returned to the threshold`)

1. `AWSマネジメントコンソール`より`CloudWatch コンソール`にアクセス
1. 左のサイドメニューより`すべてのアラーム`をクリック
1. 画面右側の`アラームの作成`をクリック
1. 以下の通り設定し、アラームを作成
1. ステップ１ メトリクスと条件の指定
以下の通り設定し、`次へ`をクリック
    - メトリクス: `すべて`>`EFS`>`ファイルシステムメトリクス`

    | FileSystemId                      | メトリクス名       |
    | --------------------------------- | ------------------ |
    | `<変更対象のEFSのファイルシステムID>` | `BurstCreditBalance` |

    - 条件
        - `閾値の種類`: `静的`
        - `BurstCreditBalance が次の時...`: `以下`
        - `... よりも`: `<任意の値>` (私の環境では`2200000000000`と設定しました。)

1. ステップ2 アクションの設定
以下の通り設定し、`次へ`をクリック

    - 通知
        - `アラーム状態トリガー`: `OK`
        - `次の SNS トピックに通知を送信`: `既存の SNS トピックを選択`
        - `通知の送信先:`:`EFS-to-bursting`

1. ステップ3 名前と説明を追加
以下の通り設定し、`次へ`をクリック

    - 名前と説明
        - `アラーム名`: `EFS BurstCreditBalance returned to the threshold`

1. ステップ4 プレビューと作成
アラームの内容を確認し、問題がなければ`アラームの作成`をクリックします。

## AWS Lambda

`Amazon EFS`のスループットモードをプロビジョニング済みに変更するものと、バーストに変更するもので関数を2つ作成しました。

### 作成手順

#### プロビジョニング済みに変更する関数 (`EFS-to-provisioned`)

1. `AWSマネジメントコンソール`より`AWS Lambda コンソール`にアクセス
1. 左のサイドメニューより`関数`をクリック
1. 画面右側の`関数の作成`をクリック
1. 以下の通り設定し、関数を作成

    - `関数の作成`: `一から作成`
    - `基本的な情報`
        - `関数名`: `EFS-to-provisioned`
        - `ランタイム`: `Python 3.9`
        - `アーキテクチャ`: `x86_64`
        - `実行ロール`: `基本的な Lambda アクセス権限で新しいロールを作成`
    - `詳細設定`
        - `VPCを有効化`: `✓`
        - `VPC`: `<変更対象のEFSと同じVPC>`
        - `サブネット`: `<変更対象のEFSと同じサブネット>`
        - `セキュリティグループ`: `<変更対象のEFSにアクセスできる設定を有したセキュリティグループ>`

1. 作成された関数の画面から`設定`タブを選択し、以下の通り設定を変更する
    - `一般設定`
        - `タイムアウト`: `10 秒`
    - `トリガー`
        - `ソースを選択`: `SNS`
        - `SNS トピック`: `EFS-to-provisioned`

1. 作成された関数の画面から`コード`タブを選択し、以下のコードを貼り付け一部修正後に`Deploy`をクリックする

```python
# スループットモードをプロビジョニング済みに変更
import sys
import json
import boto3
import logging

# ログ設定
logger = logging.getLogger()
for h in logger.handlers:
  logger.removeHandler(h)
h = logging.StreamHandler(sys.stdout)
FORMAT = '[%(levelname)s] %(message)s'
h.setFormatter(logging.Formatter(FORMAT))
logger.addHandler(h)
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    try:
        client = boto3.client('efs')
        
        response = client.update_file_system(
            ############################ 環境に合わせて適宜変更 #############################
            FileSystemId='fs-xxxxxxxxxxxx', # 変更対象のEFSのファイルシステムID
            ThroughputMode='provisioned', # スループットモードを`プロビジョニング済み`に指定
            ProvisionedThroughputInMibps = 15.0 # プロビジョニングされたスループット (MiB/秒)
            ###############################################################################
        )
        logger.info(response)
        return True
    except:
        logger.exception('failed to lambda_handler')
        return False
```

#### バーストに変更する関数 (`EFS-to-bursting`)

1. `AWSマネジメントコンソール`より`AWS Lambda コンソール`にアクセス
1. 左のサイドメニューより`関数`をクリック
1. 画面右側の`関数の作成`をクリック
1. 以下の通り設定し、関数を作成

    - `関数の作成`: `一から作成`
    - `基本的な情報`
        - `関数名`: `EFS-to-bursting`
        - `ランタイム`: `Python 3.9`
        - `アーキテクチャ`: `x86_64`
        - `実行ロール`: `既存のロールを使用する`
        - `既存のロール`: `<前述の手順で作成されたロールを選択>`
    - `詳細設定`
        - `VPCを有効化`: `✓`
        - `VPC`: `<変更対象のEFSと同じVPC>`
        - `サブネット`: `<変更対象のEFSと同じサブネット>`
        - `セキュリティグループ`: `<変更対象のEFSにアクセスできる設定を有したセキュリティグループ>`

1. 作成された関数の画面から`設定`タブを選択し、以下の通り設定を変更する
    - `一般設定`
        - `タイムアウト`: `10 秒`
    - `トリガー`
        - `ソースを選択`: `SNS`
        - `SNS トピック`: `EFS-to-bursting`

1. 作成された関数の画面から`コード`タブを選択し、以下のコードを貼り付け一部修正後に`Deploy`をクリックする

```python
# スループットモードをバーストに変更
import sys
import json
import boto3
import logging

# ログ設定
logger = logging.getLogger()
for h in logger.handlers:
  logger.removeHandler(h)
h = logging.StreamHandler(sys.stdout)
FORMAT = '[%(levelname)s] %(message)s'
h.setFormatter(logging.Formatter(FORMAT))
logger.addHandler(h)
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    try:
        client = boto3.client('efs')
        
        response = client.update_file_system(
            ############################ 環境に合わせて適宜変更 #############################
            FileSystemId='fs-xxxxxxxxxxxx', # 変更対象のEFSのファイルシステムID
            ThroughputMode='bursting', # スループットモードを`バースト`に指定
            ###############################################################################
        )
        logger.info(response)
        return True
    except:
        logger.exception('failed to lambda_handler')
        return False
```

## IAMロール 許可ポリシー追加

Lambda関数の作成手順の中で作成されたロールに`Amazon EFS`の設定を変更できる権限を付与しました。
(※ 取り急ぎ動作を確認するために作成したので、必要以上の権限が付与されている可能性があります。)

### 追加手順

1. `AWSマネジメントコンソール`より`IAM コンソール`にアクセス
1. 左のサイドメニューより`ロール`をクリック
1. 前述の手順で作成されたロールを検索し、ロール名をクリック
1. `許可を追加`をクリックし、以下のポリシーにチェックを入れた後に`ポリシーをアタッチ`をクリック
    - `AmazonElasticFileSystemFullAccess`
    - `AmazonElasticFileSystemClientFullAccess`

# おわりに

今回は`CloudWatch`と`AWS Lambda`を組み合わせて`AWS EFS`のスループットモード変更を自動化する実装について説明しました。
手動で対応していた時もそこまで時間のかかる作業ではありませんでしたが、アラームが通知される度に対応するというひと手間が解消でき、他の作業に時間を費やせるようになって良かったと思います。
この記事が同じような悩みを抱えている方の助けになれば幸いです！

# 参考

https://docs.aws.amazon.com/ja_jp/efs/latest/ug/performance.html?_fsi=PDabqRpc

https://zenn.dev/optfit/articles/bc257d3eeef6a9

https://www.seeds-std.co.jp/blog/creators/2019-08-26-132438/

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/efs.html#EFS.Client.update_file_system