2023年2月23日
TerraformでAPI Gatewayのスロットリングを設定する
AWS API Gateway のスロットリングを Terraform を使って設定する方法を見つけるまでに少し手間取ったのでメモ。
AWS マネジメントコンソールでの場所
今回 Terraform で設定するのは、マネジメントコンソールの各ステージの設定画面内の「デフォルトのメソッドスロットリング」に該当する箇所です。
そもそも API Gateway のスロットリングとは
API Gateway では API が1秒あたりに処理できるリクエスト数や同時に処理できるリクエスト数を制限することができます。これによって負荷が高くなりすぎたり、攻撃によって意図しない高額請求を受けてしまったりするリスクを減らすことができます。
ただし、この設定はベストエフォートで提供されるため、この設定値がリクエスト上限になるわけではないという点に注意は必要です。
API のスロットリングおよびクォータを設定して、多すぎるリクエストで API の負荷が高くなりすぎないように保護できます。スロットルとクォータの両方はベストエフォートベースで適用されるため、これらは保証されたリクエスト上限ではなく、目標として考える必要があります。
AWS デベロッパーガイドより引用。
設定できる値
以下の2つがあります。これらのリクエスト数の制限を上回った時、429 Too Many Requests
が返るようになります。
- レート:1秒あたりに処理できるリクエスト数
- バースト:同時に処理できるリクエスト数
スコープ
次のスコープの順序で適用されます(1が最も優先される)。
- 使用量プランで API ステージに設定したクライアントあたり、またはメソッドあたりのスロットリング制限
- API ステージに設定したメソッドあたりのスロットリング制限
- リージョンごとのアカウントレベルのスロットリング
- AWS リージョンのスロットリング
AWS デベロッパーガイドより引用。
1はクライアント(API キー)単位の設定です。これに関しては今回の記事のスコープ外ですので、試してみたい方は AWS デベロッパーガイド API キーを使用した使用量プランの作成と使用のページをご覧ください。
2が今回設定する項目になります。今回はあるステージにおけるすべてのメソッドを対象にした設定を行いますが、パスごとメソッドごとの設定を行うことも可能です。
3は AWS アカウント単位の設定になります。今回の記事のスコープ外です。
4は AWS が設定しているリージョンごとの設定になります。ユーザーは変更することができません。また、1〜3の設定値は4の値よりも高くすることはできません。
Terraform での設定方法
Terraform で API Gateway のあるステージにおけるすべてのメソッドを対象にスロットリングの設定を行うには以下のように書けば OK です。
resource "aws_api_gateway_method_settings" "example" { rest_api_id = aws_api_gateway_rest_api.example.id stage_name = aws_api_gateway_stage.example.stage_name method_path = "*/*" settings { throttling_rate_limit = 2 throttling_burst_limit = 1 } }
この settings
には上記2つ以外にも API Gateway 関連の設定ができますので気になる方は Terraform 社ドキュメント Resource: aws_api_gateway_method_settings をご覧ください。
サンプル
下記サンプルは API Gateway REST API OpenAPI Example を基に作成しています。
注意点として、Terraform 社ドキュメントに次の記載があります。
We recommend using this resource in conjunction with the
aws_api_gateway_stage
resource instead of a stage managed by theaws_api_gateway_deployment
resource optionalstage_name
argument. Stages managed by theaws_api_gateway_deployment
resource are recreated on redeployment and this resource will require a second apply to recreate the method settings.
訳:このリソース(訳註:aws_api_gateway_method_settings
)をaws_api_gateway_stage
リソースとあわせて使うことをおすすめします。aws_api_gateway_deployment
リソースの任意の引数stage_name
で管理されるステージと一緒に使うことはおすすめしません。aws_api_gateway_deployment
で管理されるステージは再デプロイ時に再作成され、メソッドの設定を再作成するために再度 apply する必要が出てきます。
Terraform 社ドキュメント より引用。
もしすでに API Gateway に関する Terraform 設定があり、aws_api_gateway_deployment
の stage_name
引数を使っている場合は、aws_api_gateway_stage
を使って書くように修正したほうが良いでしょう。
その際、
triggers = { redeployment = sha1(jsonencode(aws_api_gateway_rest_api.example.body)) } lifecycle { create_before_destroy = true }
あたりが関連する設定になっていると思います。私の実際のプロジェクトの場合はこのサンプルと異なり、Lambda との統合を行っていたので、sha1(jsonencode(aws_api_gateway_rest_api.example.body))
に相当する書き方がわからず、triggers
lifecycle
は消しちゃいました。。(今のところは大丈夫そうですが、もし更新がありましたら追記します)
# # Variables # variable "aws_region" { default = "ap-northeast-1" description = "AWS Region to deploy example API Gateway REST API" type = string } variable "rest_api_name" { default = "api-gateway-rest-api-openapi-example" description = "Name of the API Gateway REST API (can be used to trigger redeployments)" type = string } variable "rest_api_path" { default = "/path1" description = "Path to create in the API Gateway REST API (can be used to trigger redeployments)" type = string } # # Provider # provider "aws" { region = var.aws_region } # # API Gateway # resource "aws_api_gateway_rest_api" "example" { body = jsonencode({ openapi = "3.0.1" info = { title = var.rest_api_name version = "1.0" } paths = { (var.rest_api_path) = { get = { x-amazon-apigateway-integration = { httpMethod = "GET" payloadFormatVersion = "1.0" type = "HTTP_PROXY" uri = "https://ip-ranges.amazonaws.com/ip-ranges.json" } } } } }) name = var.rest_api_name endpoint_configuration { types = ["REGIONAL"] } } resource "aws_api_gateway_deployment" "example" { rest_api_id = aws_api_gateway_rest_api.example.id triggers = { redeployment = sha1(jsonencode(aws_api_gateway_rest_api.example.body)) } lifecycle { create_before_destroy = true } } # # Stage and Stage Settings # resource "aws_api_gateway_stage" "example" { deployment_id = aws_api_gateway_deployment.example.id rest_api_id = aws_api_gateway_rest_api.example.id stage_name = "example" } resource "aws_api_gateway_method_settings" "example" { rest_api_id = aws_api_gateway_rest_api.example.id stage_name = aws_api_gateway_stage.example.stage_name method_path = "*/*" settings { throttling_rate_limit = 2 throttling_burst_limit = 1 } }
export AWS_ACCESS_KEY_ID= export AWS_SECRET_ACCESS_KEY=
terraform init terraform plan terraform apply
curl で同時に複数のリクエストを投げてみて429が返ってくるかを確かめます。curl の --parallel
を使うために、同じ内容のリクエストを12個作成します。
for i in 0 1 2 3 4 5 6 7 8 9 10 11; do echo 'url = "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/example/path1"' >> data.txt; echo 'output = "/dev/null"' >> data.txt; echo 'write-out = "%{http_code}\n"' >> data.txt; echo 'silent' >> data.txt; done
time curl --parallel --parallel-immediate --parallel-max 4 --config data.txt
実行結果は毎回変わりますが、以下のような結果になります。きっかりレート2・バースト1になっていなさそうなので、やはりベストエフォートなのだなということが伺えます。
200 200 200 200 429 429 200 200 429 200 200 200 real 0m1.527s user 0m0.114s sys 0m0.151s