続・AWSのサービスクォータをなめてると痛い目に遭うぞ！（自動化CloudFormation付き）

# はじめに

**AWSサービスクォータの管理、自動化してますか？**

[前回の記事](https://qiita.com/ozzy3/items/e9691f21d431a6ceac7a)では、AWSサービスクォータの重要性と全サービスクォータを一覧化する方法を紹介しました。

https://qiita.com/ozzy3/items/e9691f21d431a6ceac7a

今回は、AWSのサービスクォータの管理を自動化する意味とその方法を解説します。
合わせて、クォータ管理を自動化するCloudFormationテンプレートのサンプルも紹介します。


# 前回のおさらい

[前回の記事](https://qiita.com/ozzy3/items/e9691f21d431a6ceac7a)では、

* **サービスクォータを意識しないと信頼性が低下してしまう可能性がある**
* サービスクォータを意識しよう、という話は **AWS Well-Architected Framework** にも書かれている
* サービスクォータは信頼性に影響するので **「信頼性の柱」** として定義されている

という話を書きました。

https://docs.aws.amazon.com/ja_jp/wellarchitected/latest/reliability-pillar/manage-service-quotas-and-constraints.html

今回は、この中の

* **クォータをモニタリングおよび管理する**
* **クォータ管理を自動化する**

の話です。


# なぜ、クォータ管理の自動化が必要なのか？

**自動化したほうが楽だから**です。（キリッ）

これ以上の説明は不要な気もしますが、説明していきましょう。

以下は[前回の記事](https://qiita.com/ozzy3/items/e9691f21d431a6ceac7a)の引用です。

> 例えば、Aさんが API Gateway - Lambda - DynamoDB を用いてサーバーレスなAPIサービスを作ったとしましょう。
> Aさんは「Lambdaは自動でスケーリングしてくれるから利用者が増えても大丈夫」と考えています。
> このAPIサービスは好評を博し、順調に利用者が増えていきました。
> すると、ある日突然、このAPIでエラーが頻発するようになりました。
> 
> なぜでしょう？
> 
> 原因は、利用者増加に伴い、Lambdaの同時実行数が増え、同時実行数が「1,000」を超えた＝**Lambdaのサービスクォータに引っ掛かってしまったため**でした。

## 問題

**Q. Aさんはどうするべきだったのでしょうか？**

1. 毎日、Lambdaの同時実行数を確認し、「800」を超過していたら、手動でクォータの引き上げをリクエストする。

2. Lambdaの同時実行数が「800」を超過したタイミングで CloudWatch Alarm と SNS (Simple Notification Service) を用いて、メールで通知する。メールを受信した後、手動でクォータの引き上げをリクエストする。

3. Lambdaの同時実行数が「800」を超過したタイミングで CloudWatch Alarm と SNS (Simple Notification Service) を用いて、別のLambdaを実行する。別のLambdaはAWS SDKを用いて自動的にクォータの引き上げをリクエストする。

※ 選択肢中の「800」は、クォータ上限に到達するまでに十分な余裕のある数値を意図しており、状況に応じて変動する値（パラメータ）と捉えてください。


## 解説

選択肢をひとつずつ見ていきましょう。

**選択肢1**
> 1. 毎日、Lambdaの同時実行数を確認し、「800」を超過していたら、手動でクォータの引き上げをリクエストする。

これは明らかに間違いです。毎日、人が確認するのはつらいですね。。
また、確認するタイミングによっては既に「1,000」を超えてしまっている可能性もあります。

**選択肢2**
> 2. Lambdaの同時実行数が「800」を超過したタイミングで CloudWatch Alarm と SNS (Simple Notification Service) を用いて、メールで通知する。メールを受信した後、手動でクォータの引き上げをリクエストする。

この選択肢は、クォータ上限である「1,000」に近づいてきたらメールで通知して対応しよう、という案です。
クォータ上限に達する前に対応することができるので悪くなさそうです。
ただし、クォータの引き上げリクエストは手動です。

**選択肢3**
> 3. Lambdaの同時実行数が「800」を超過したタイミングで CloudWatch Alarm と SNS (Simple Notification Service) を用いて、別のLambdaを実行する。別のLambdaはAWS SDKを用いて自動的にクォータの引き上げをリクエストする。

この選択肢は、クォータ上限である「1,000」に近づいてきたらLambdaを使って自動的にクォータを引き上げよう、という案です。
クォータ上限に達する前に対応することができるうえに、クォータ引き上げリクエストまで自動化されています。

というわけで正解は**選択肢3**です！

## 本当にそうでしょうか！？

落ち着いてもう一度考えてみましょう。
本当に選択肢3が唯一無二の正解なのでしょうか？

選択肢3は人の手を介さずにクォータ引き上げリクエストまで自動化されています。
しかし、これは裏を返せば、**事前に設定した条件さえ満たせば、人の判断を介さずに実行される**ということを意味します。
※ ここでは、事前に設定した条件＝Lambdaの同時実行数が「800」を超過、です。

このAPIサービスの利用者が順調に増えていった結果として、Lambdaの同時実行数が「800」を超過したのであれば、問題はないでしょう。

しかし、例えば、このAPIサービスが**DDoS攻撃を受けていた**としたらどうでしょう？[^1]
または、このサービスの開発者が**誤って負荷テスト用のテストツールを動かし続けていた**としたらどうでしょう？

そう考えると、必ずしも、選択肢3が正解とは言えないのではないでしょうか。
**通知を受けた人が状況に応じてクォータの引き上げを判断できる**選択肢2の方が好ましい場合もあります。


## 結局、どれが正解なの？

**ケースバイケース**です。

何を優先するかは、所属する組織やプロジェクトによって異なるはずなので、画一的に決めつけず、状況に応じて判断することをおすすめします。
また、今回は、Lambdaの同時実行数のクォータを例にしましたが、対象とするクォータによってもどちらを採用すべきかは異なってきます。

あえて言うなら

**自動で上限を引き上げることによるリスクがあるクォータであれば、選択肢2**
**自動で上限を引き上げることによるリスクがないクォータであれば、選択肢3**

が正解です。

:::note warn
必ずしもクォータの引き上げまで自動化するのが正解とは限らない。
通知までを自動化し、クォータの引き上げは人が判断すべき場合もある。
:::


# どのようにクォータ管理を自動化するか？

ここからは実装方法を解説していきます。

なお、モニタリング対象のクォータは引き続き「Lambdaの同時実行数」としています。
別のクォータを対象としたい場合は適宜読み替えてください。


## パターン1：クォータのモニタリングと通知

アラーム通知を受けて、人が判断して手動でクォータ引き上げをリクエストするパターンです。

![quota_notification.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2443303/148e48e1-fbad-6019-1d28-ab09a36779f3.png)


## パターン2：クォータ引き上げの自動化

アラーム通知を受けて、Lambdaが自動的にクォータ引き上げをリクエストするパターンです。
LambdaがAWS SDK ([request_service_quota_increase](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/service-quotas.html#ServiceQuotas.Client.request_service_quota_increase)) を用いてクォータ引き上げをリクエストします。



![quota_automation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2443303/1f987120-e541-1e34-79c5-fb8626d0c0c1.png)


## パターン3：パターン1＋パターン2

パターン1とパターン2の合わせ技です。
自動的にクォータ引き上げをリクエスト、かつ、メール通知もするパターンです。

![quota_notification_automation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2443303/621413e8-f65c-4be3-b698-9ab33f4f524e.png)


## CloudFormationテンプレート

パターン1とパターン2を包括するパターン3のテンプレートを載せておきます。

* CloudFormationパラメータ
  * **ThresholdConcurrentExecutions**
    Lambdaの同時実行数がこの値を超えるとクォータ引き上げをリクエストする閾値
  * **NotificationEmail**
    通知先メールアドレス

```yaml:quota_automation.yaml
#===============================================================================
# CloudFormation Template
#===============================================================================
AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFormation Template

#===============================================================================
# Metadata
#===============================================================================
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Parameters
        Parameters:
          - ThresholdConcurrentExecutions
          - NotificationEmail

#===============================================================================
# Parameters
#===============================================================================
Parameters:
  ThresholdConcurrentExecutions:
    Type: Number

  NotificationEmail:
    Type: String

#===============================================================================
# Resources
#===============================================================================
Resources:
  #-----------------------------------------------------------
  # IAM
  #-----------------------------------------------------------
  AlarmActionFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: AllowRequestServiceQuotaIncrease
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - servicequotas:RequestServiceQuotaIncrease
                Resource: "*"

  #-----------------------------------------------------------
  # Lambda
  #-----------------------------------------------------------
  AlarmActionFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${AWS::StackName}-alarm-action
      Role: !GetAtt AlarmActionFunctionRole.Arn
      Architectures:
        - arm64
      MemorySize: 256
      Timeout: 30
      Runtime: python3.8
      Handler: index.handler
      Code:
        ZipFile: |
          import boto3
          import logging

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def handler(event, context):
              logger.info('event: %s', event)

              client = boto3.client('service-quotas')
              response = client.request_service_quota_increase(
                  ServiceCode='lambda',
                  QuotaCode='L-B99A9384',
                  DesiredValue=1100
              )

              return

  AlarmActionFunctionPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt AlarmActionFunction.Arn
      Principal: sns.amazonaws.com

  #-----------------------------------------------------------
  # SNS
  #-----------------------------------------------------------
  AlarmTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Sub ${AWS::StackName}-alarm-topic

  AlarmSubscriptionForEmail:
    Type: AWS::SNS::Subscription
    Properties:
      TopicArn: !Ref AlarmTopic
      Protocol: email
      Endpoint: !Ref NotificationEmail

  AlarmSubscriptionForLambda:
    Type: AWS::SNS::Subscription
    Properties:
      TopicArn: !Ref AlarmTopic
      Protocol: lambda
      Endpoint: !GetAtt AlarmActionFunction.Arn

  #-----------------------------------------------------------
  # CloudWatch Alarm
  #-----------------------------------------------------------
  LambdaConcurrentExecutionsAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub ${AWS::StackName}-concurrent-executions
      ActionsEnabled: true
      AlarmActions:
        - !Ref AlarmTopic
      Namespace: AWS/Lambda
      MetricName: ConcurrentExecutions
      Period: 60
      EvaluationPeriods: 1
      Threshold: !Ref ThresholdConcurrentExecutions
      Statistic: Maximum
      ComparisonOperator: GreaterThanThreshold
      TreatMissingData: notBreaching
```

:::note info
* わかりやすさ重視のため、テンプレート内に直接Lambdaコードを書いていますが、実際に使う際は別ファイルで管理することをおすすめします。
* DesiredValueなどの値も固定値にしていますが、パラメータ化することをおすすします。
* 特定のLambda関数の同時実行数をモニタリング対象とする場合は Alarm にDimensionsプロパティを追加し、対象のLambda関数を指定します。
:::

:::note warn
このCloudFormationテンプレートに含まれるLambda関数を実行すると、Lambdaの同時実行数クォータの引き上げがリクエストされますので、実際に実行する際はご留意ください。
:::


# クォータ引き上げに関する注意事項

**クォータの引き上げは、リクエストすると、すぐに引き上げられるわけではないことにご注意ください。**

クォータの引き上げをリクエストすると、サポートケースが作成されます。
このサポートケースが処理される時間は対象のクォータによっても異なるようです。

また、クォータによってはAWSサポートから質問が来ることもあります。
私が実際に経験したものとしては、WAF の WCU (Web ACL Capacity Unit) のクォータ引き上げをリクエストした際に、AWSサポートから「どのようなユースケースなのか？」「対象のWeb ACLのARNを教えてほしい」といった質問が来たことがあります。（そのやりとりも含め、3日程度かかりました）

数分程度で引き上げられるクォータもありますが、クォータによってはこのような対応が必要になる可能性があることは念頭に置いておきましょう。


# まとめ
* **CloudWatch Metrics/Alarm と SNS を組み合わせて、クォータのモニタリング・通知ができる**
* **Lambda (AWS SDK) を用いてクォータの引き上げリクエストを自動化できる**
* **クォータの引き上げリクエストまで自動化すべきかは状況に応じて判断すべき**
* **クォータの引き上げリクエストはすぐに対応されるとは限らない**

最後まで読んでいただき、ありがとうございます。
この記事が少しでも役に立てば幸いです。
いいね（LGTM）いただけると励みになりますm(_ _)m


[^1]: もちろん、WAFやShieldなどを用いて対策すべきですが、本記事はクォータにフォーカスした内容なので、DDoS攻撃対策には言及しません。