[アップデート]API Gateway REST APIでカスタムドメイン名を使用する場合にHTTPヘッダー値でルーティングできるようになりました
お疲れさまです。とーちです。
API Gateway REST APIでカスタムドメイン名を使用する場合にルーティングルールが使用できるようになったというアップデートがありました。これは面白そうなアップデートですね。実際に試してみたので紹介します。
どういうアップデート?
以下の図が今回のアップデートを分かりやすく表現しています。
※Amazon API Gateway ルーティングルールを使用した動的リクエストルーティング | AWS コンピューティングブログより引用
API Gatewayには元々カスタムドメイン名をつける設定がありました。このカスタムドメイン名設定の中にルーティングルールという新たな要素が追加された形です。
従来、API Gateway REST APIではパスベースのルーティング設定をすることはできました。以下のような形ですね。
例えばホスト名が example.com
だった場合、https://example.com/v1/
と指定した場合にv1用のAPI Gateway REST APIに送信されるといった形です。
この方法ではパスも意識した設計をする必要がありました。
今回のアップデートにより、パスとHTTPヘッダー値を組み合わせてルーティング条件を作成できるようになりました。
上記の例でいうと、/v1/
などのバージョン用のパス設計をせずとも、任意のHTTPヘッダー値に基づき送信先のAPI Gateway REST APIを決めることができるようになったということです。
これにより、APIバージョンの移行などがよりシンプルに実現できるようになりました。
なお、この新しい機能はパブリックとプライベートの両方の REST API でサポートされ、かつ、AWS GovCloud (米国) リージョンを含むすべてのAWS リージョンでサポートされています。
やってみた
事前準備:API Gateway REST APIの作成
まずは以下のようにシンプルなAPI Gateway REST APIを2つ用意します。
以下のTerraformコードで準備しました。デプロイさえ出来れば良いので内容は適当です。
API Gateway RESTAPIを作るTerraformコード
// API Gateway本体(REST API)
resource "aws_api_gateway_rest_api" "sample" {
name = "sample-api"
description = "Sample REST API created by Terraform"
}
// ルート(/)のGETメソッド
resource "aws_api_gateway_method" "root_get" {
rest_api_id = aws_api_gateway_rest_api.sample.id
resource_id = aws_api_gateway_rest_api.sample.root_resource_id
http_method = "GET"
authorization = "NONE"
}
// Lambda関数(hello-v1)のソースファイルをローカルに作成
resource "local_file" "lambda_hello_v1_src" {
filename = "${path.module}/lambda_function.py"
content = file("${path.module}/lambda_function.py")
}
// Lambda関数(hello-v1)のZIPアーカイブ作成
resource "archive_file" "lambda_hello_v1_zip" {
type = "zip"
source_file = local_file.lambda_hello_v1_src.filename
output_path = "${path.module}/lambda_function_v1.zip"
}
// Lambda関数(hello-v1)
resource "aws_lambda_function" "hello_v1" {
filename = archive_file.lambda_hello_v1_zip.output_path
function_name = "hello-v1"
handler = "lambda_function.lambda_handler"
runtime = "python3.12"
source_code_hash = archive_file.lambda_hello_v1_zip.output_base64sha256
role = aws_iam_role.lambda_exec.arn
}
// Lambda実行用IAMロール
resource "aws_iam_role" "lambda_exec" {
name = "lambda_exec_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}
// Lambdaに基本権限を付与
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
// ルート(/)のGETメソッドとLambdaの統合
resource "aws_api_gateway_integration" "root_get" {
rest_api_id = aws_api_gateway_rest_api.sample.id
resource_id = aws_api_gateway_rest_api.sample.root_resource_id
http_method = aws_api_gateway_method.root_get.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.hello_v1.invoke_arn
}
// API GatewayからLambdaの呼び出し許可
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.hello_v1.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.sample.execution_arn}/*/*"
}
// API Gatewayのデプロイメント
resource "aws_api_gateway_deployment" "sample" {
depends_on = [aws_api_gateway_integration.root_get]
rest_api_id = aws_api_gateway_rest_api.sample.id
triggers = {
redeployment = sha1(jsonencode(aws_api_gateway_rest_api.sample))
}
lifecycle {
create_before_destroy = true
}
}
// devステージの作成
resource "aws_api_gateway_stage" "dev" {
stage_name = "dev"
rest_api_id = aws_api_gateway_rest_api.sample.id
deployment_id = aws_api_gateway_deployment.sample.id
depends_on = [aws_api_gateway_deployment.sample]
}
# 以下、v2用のリソース定義も同様に作成...
API Gatewayから呼び出されるLambdaもシンプルに以下のようにしました。
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': 'hello v1' # もう一方のAPI Gatewayから呼び出されるLambdaは 'hello v2'と返すよう設定
}
curlで実行すると以下のようにレスポンスが返ってくる状態です。
> curl -X GET https://h6yc47f563.execute-api.ap-northeast-1.amazonaws.com/dev/
hello v1⏎
> curl -X GET https://lvhwqhwtyd.execute-api.ap-northeast-1.amazonaws.com/v2/
hello v2⏎
カスタムドメイン名の作成
ここからはマネージメントコンソールで作業していきます。
まずはカスタムドメイン名をAPI Gatewayに追加します。カスタムドメイン機能自体は結構古くからある機能ですね。(参考)
以下のような設定で作成しました。なお、ACM証明書やRoute53のホステッドゾーンは既に用意してあるものとします。
赤枠で囲った箇所が今回のアップデートで追加された部分です。
ルーティングモードについて
ルーティングモードは以下の3つから選ぶことができます。今回は「ルーティングルールのみ」にしました。「ルーティングルールのみ」が推奨とのことなので、今後新しく作成する際はこちらで作ったほうが良さそうです。
-
APIマッピングのみ
- 従来と同じベースパスマッピングを使用
- ルーティングルールを一切使用しない場合に使用
-
ルーティングルール、次にAPIマッピング
- ルーティングルールを使用しながら、ベースパスマッピングをフォールバックとして維持
- ルーティングルールが常に優先され、一致しないリクエストはベースパスマッピングに対して評価される
- このモードは、APIをルーティングルールに段階的に移行する場合に役立つとのこと
- 既存のベースパスマッピングによるルーティングを維持しながら、ルーティングルールを段階的に作成していくことが可能
-
ルーティングルールのみ
- ルーティングルールのみを使用
- これが推奨されるルーティングモードなので、新しいカスタムドメインを開始する場合や、既存のカスタムドメインでAPIマッピングからルーティングルールへの移行が完了した場合に使用しましょう。
Route53でのDNS設定
カスタムドメイン名が追加されたのでRoute53ホステッドゾーンにドキュメントに従いAエイリアスレコードを追加します。
追加されたカスタムドメイン名の以下の部分をチェックしておきましょう。
Route53の画面に移動し、以下のようにレコードを作成します。赤枠の部分が上で確認した内容と合ってるかチェックしましょう。
一応、digで紐づけが出来てるか確認しておきます。以下のようにANSWER SECTIONにIPアドレスが表示されていれば問題ないです(以下は機微な情報をマスクしたものを記載しています)。
> dig test.example.com
<中略>
;; ANSWER SECTION:
test.example.com. 60 IN A XX.XXX.XX.XXX
test.example.com. 60 IN A XX.XXX.XX.XXX
ルーティングルールの作成
続いて、今回のアップデートの肝であるルーティングルールを作成します。
ルーティングルールの構成要素
先にルーティングルールの要素を説明しておきましょう。ルーティングルールには、以下の3つの設定プロパティがあります。
-
Conditionsプロパティ:アクションを実行するために満たす必要がある条件を定義
- ルールには最大 2 つのヘッダー条件と 1 つのベースパス条件を含めることができ、指定されたすべての条件が満たされるとアクションがトリガー
- ルールに条件が定義されていない場合、そのルールはすべてのリクエストに一致するルールとして機能
-
Actionsプロパティ:ルール条件が満たされたときに実行されるアクションを定義
- 今回のリリース時点でサポートされているアクションは、同一アカウントおよび同一リージョン内の任意のREST APIの任意のステージの呼び出しのみとのこと
- 今後のアップデートでさらなるアクションが追加されるかもしれませんね
-
Priorityプロパティ:ルールの評価順序を定義
1つ目のルーティングルール作成
ルーティングルールの概要が掴めたところで、ルールを作成していきます。今回はシンプルにヘッダーのみでルーティングをしてみましょう。x-versionヘッダがv1のときは、v1用のRESTAPIに、v2のときはv2用のRESTAPIにルーティングするよう設定してみます。
上の条件の箇所でヘッダー条件やパスベース条件を指定します。アクションの箇所では、どのAPI Gateway RESTAPIのどのステージにリクエストを送るかを指定します。
ベースパスをストリップという設定が気になったので調べてみると公式ドキュメント曰く以下のような設定のようです。
- 例として
https://example.com/PetStoreShopper/dogs
にクライアントからアクセスするとします - ルールが/PetStoreShopperに一致し「ベースパスをストリップ」が有効な場合、ターゲットAPIはパスを
/dogs
として受け取る - 「ベースパスをストリップ」が無効な場合、ベースパス全体をそのままターゲットAPIに転送
続いてルールの優先度を指定します。
1が最高優先度、1,000,000が最低優先度となります。
公式ブログでは、将来新しいルールを追加しやすくするために、連続するルールの間に十分な間隔を空けることを推奨するとのことでした。例えば、1、2、3ではなく、100、200、300といった形です。
これで一つめのルールが追加されたので、まずはこの状態で動作確認してみます。
> curl -H "x-version: v1" https://test.example.com
hello v1⏎
おっ、ちゃんと返ってきて安心しました。
2つ目のルーティングルール作成
続けて、2つめのルールを登録します。
ヘッダー条件については、RFC7230に準拠しており、ヘッダー名では大文字と小文字が区別されませんが、ヘッダー値では大文字と小文字が区別されるとのことです。
また、ヘッダー値では、プレフィックス、サフィックス、および包含の一致にワイルドカードを使用することもできます。例としては以下のような形です。
- 完全一致:
alpha-v2-latest
- プレフィックス一致:
*latest
そこであえて、ヘッダーの値を大文字でV2*
としてみました。
ちなみにパス条件でも大文字と小文字は区別されるとのことです。
優先度は200で設定します。
ルーティング動作の確認
この状態で試してみると以下のような結果になりました。
> curl -H "x-version: v2" https://test.example.com
{"message":"Forbidden"}⏎
> curl -H "x-version: V2" https://test.example.com
hello v2⏎
> curl -H "x-version: V2-test" https://test.example.com
^C⏎
> curl -H "x-version: V2test" https://test.example.com
hello v2⏎
"x-version: v2"
だとルールに一致しないのでForbiddenという応答になっていますね。
また、何故かは不明ですが、"x-version: V2-test"
で初回アクセスしたときに妙に時間がかかりました。以降はすぐにレスポンスが返ってくるようになりましたが、デプロイした直後は軽くテストしたほうがいいかもしれませんね。
フォールバックルールの追加
以下のルールも追加してみましょう。
優先度は300としました。
この状態で、先ほどForbiddenとなった curl -H "x-version: v2" https://test.example.com
コマンドを実行してみます。
> curl -H "x-version: v2" https://test.example.com
hello v1⏎
hello v1
で応答が返ってきましたね。ルーティングルールに条件を定義しない場合、全てのリクエストが条件に合致したものとして判断されます。
まとめ
というわけで、API Gateway REST APIでカスタムドメイン名を使用する場合にルーティングルールが使用できるようになったアップデートの紹介でした。
従来のパスベースのルーティングに加えて、HTTPヘッダー値を組み合わせた柔軟なルーティングが可能になりました。これにより、APIバージョンの管理やA/Bテスト、段階的なマイグレーションなどがより簡単に実現できるようになったのではないでしょうか。
個人的にはREST API側のアップデートだったのが少し意外でした。API GatewayのHTTP APIとREST APIは今後も共存させる考えなのかもしれないですね。
以上、とーちでした。