Techfirm Cloud Architect Blog

テックファーム株式会社クラウドインフラグループのブログ

Terraformで作ったHTTP APIにServerless Frameworkからデプロイしてみた

以前、API GatewayのREST APIをTerraformから作成し、Serverless Frameworkからデプロイする方法の1つを紹介しました。

今回は、同じようにTerraformで作成したHTTP APIに対してデプロイします。

はじめに

以前にREST APIで行ったように、なるべくTerraform側に設定を寄せていきます。
ただし、APIのURLに関わる部分等はServerless Frameworkで設定します。

イメージとしては、

  • ログ出力や監視等の設定はインフラ担当がTerraformで行う
  • APIの実装とURLパスの設定は開発担当がServerless Frameworkで行う

という状況です。

REST APIとの違い

比較概要

API GatewayのHTTP APIはREST APIよりも安価ですが、機能も少なくなっています。
両者の違いはAWSの以下のページにまとめられています。

ここにないところで、AWSのサービス統合で対応しているサービスもHTTP APIの方が少ないようです。
何ができて何ができないのか把握した上で選択する必要があります。

以前のブログ記事でREST APIへデプロイしたときとの主な違いは以下のようになります。

RESTAPIとの比較

以下の点について説明します。

  • ステージ作成のためにデプロイメント不要(オートデプロイも可能)
  • APIのパスはルートで設定(REST APIより簡易的)
  • $defaultステージを作れる
  • MOCK統合がない

ステージ作成のためにデプロイメント不要(オートデプロイも可能)

ステージを設定するためにデプロイメントが必須では無くなります。
アクセスログの設定はステージで行うため、ステージの作成が簡単になるのは嬉しいところです。

REST APIでは、ステージを作成するためにデプロイメントを指定する必要がありました。
さらに、デプロイメント作成のためには何かしらのAPIが存在する必要もあり、Terraformのコード量が増えてしまいます。

HTTP APIでは、URLを何も定義していない状態でデプロイせずにステージを作成できます。
REST APIのときよりも、Terraformのコード量は圧倒的に少なくなります。

さらに、HTTP APIではオートデプロイ機能も存在するため、デプロイメントを気にする必要はありません。

APIのパスはルートで設定(REST APIより簡易的)

/test/test1というGET APIを作成するとき、REST APIでは以下のようにAPIのURLとメソッドを定義します。

  1. ルートリソースに紐づいたtestリソースを定義する
  2. testリソースに紐づいたtest1リソースを定義する
  3. test1リソースに紐づいたGETメソッドを定義する

Terraformや手で作成するのは面倒な部分です。
それに対して、HTTP APIでは以下のようになります。

  1. GET /test/test1ルートを定義する

REST APIよりもシンプルで分かりやすい設定になります。

また、HTTP APIでは$defaultという特別なルートを作成し、他のルートに当てはまらないリクエストを処理することも可能です。

$defaultステージを作れる

ステージにはdevprod等の名前をつけますが、HTTP APIでは$defaultという特別なステージを作成できます。
$defaultステージでは、API Gatewayデフォルトのエンドポイント(URL)にステージ名が入りません。

たとえばdevステージの場合、前述の/test/test1APIに対してAPI Gatewayは以下のようなURLを生成します。
https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/test/test1
これが$defaultステージの場合は以下のようになります。
https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/test/test1
ステージ名がURLに含まれません。

MOCK統合がない

REST APIでは、固定値の応答をAPI Gatewayだけで行えます。そのときに使うのがMOCK統合です。

HTTP APIではMOCK統合が存在しません。
APIのモックを作成するときもバックエンドが必要そうです。

作成するリソースの境界

ここから、実際にHTTP APIを作成していきたいと思います。
今回、API Gatewayに関しては以下のようなリソースを作成します。

作成するリソース

デプロイメントがないため、REST APIのときと比べると凄くシンプルになっています。

Terraformで作成するのはaws_apigatewayv2_apiaws_apigatewayv2_stageだけです。
Serverless Frameworkで作成されるのはAWS::ApiGatewayV2::IntegrationAWS::ApiGatewayV2::Routeだけです。
TerraformでAPIを作成してデプロイしていたのに比べるとTerraformのコード量はかなり少なくなります。

気を付けるのは、Serverless Frameworkはデプロイメントを作成しないということです。
そのため、ステージにはオートデプロイを設定しておきます。

次から実際の設定を見ていきます。

Terraformの設定

まず始めに、HTTP APIとステージを作成するコードです。

# HTTP API
resource "aws_apigatewayv2_api" "api" {
  name          = "${var.env}-test-http-api"
  protocol_type = "HTTP"
}

resource "aws_apigatewayv2_stage" "default" {
  api_id      = aws_apigatewayv2_api.api.id
  name        = "$default"
  auto_deploy = true

  default_route_settings {
    throttling_burst_limit   = 1
    throttling_rate_limit    = 1
  }
}

ステージにauto_deployを設定しているのがポイントです。
アクセスログの出力等はいれていませんので、必要に応じて設定をいれてください。
今回は、念のためスロットリングのみ設定しています。

リソースの作成はここまでですが、最後にServerless Frameworkに渡す必要のある値をoutputします。

output "apigw_api_id" {
  value = aws_apigatewayv2_api.api.id
}

Serverless Frameworkの設定に必要なので、次へ進む前にapplyして実際にHTTP APIを作成してください。

serverless.ymlの設定

Serverless Frameworkのserverless.ymlに、上で作成したHTTP APIを設定します。
特別に必要な設定は以下の箇所のみです。

provider:
  httpApi:
    id: "<<HTTP APIのID(Terraformでoutputとしたapigw_api_id)>>"

作成したAPIのIDを設定するだけです。
あとは、REST APIのログ設定等を入れないように気を付けながら通常通りserverless.ymlの設定をおこなってください。
たとえば、以下のようになります。

service: apigwhttptest
frameworkVersion: '3'
provider:
  name: aws
  runtime: python3.11
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
  httpApi:
    id: xxxxxxxxxx
custom:
  defaultStage: dev
package:
  individually: true
  excludeDevDependencies: true
  patterns:
    - '!**'
functions: ${file(./conf/functions.yml)}

実際のLambda関数の定義はfunctions.ymlという別ファイルにしています。
内容は以下の通りです。

test1:
  handler: functions/test/test.handler
  role: arn:aws:iam::123456789012:role/${self:provider.stage}-api-lambda-test
  disableLogs: true
  package:
    patterns:
      - functions/test/**
  events:
    - httpApi:
        path: /test/test1
        method: GET

eventsでAPIのパスとメソッドを設定しているのがポイントです。
REST APIのときとの違いもevents以下だけになります。
REST APIでは以下のようになっていました。

  events:
    - http:
        path: /test/test1
        method: GET

REST APIのときにhttpだったものがhttpApiになっているだけです。

Lambda統合のペイロード形式バージョン

HTTP APIをLambda統合で使うとき、ペイロードの形式のバージョンを選択できます。
これは、Lambda関数に渡されるイベントとLambda関数が出力するレスポンスの形式です。

1.0がREST APIと同じ形式です。

この設定はServerless Frameworkのserverless.yml(またはそこから読み込まれるファイル)で行います。

providerの下に設定することで、全体の共通設定を行うことができます。
設定にするには、先ほどAPI IDを設定したところにpayloadを記載します。 以下の例では、REST APIと同じ形式である1.0を設定しています。

provider:
  httpApi:
    id: xxxxxxxxxx
    payload: '1.0'

API個別に設定する場合は、payloadをLambda関数の設定に記載します。
(上記providerの設定をAPI個別に上書きできます)

test1:
  handler: functions/test/test.handler
  role: arn:aws:iam::123456789012:role/${self:provider.stage}-api-lambda-test
  disableLogs: true
  package:
    patterns:
      - functions/test/**
  httpApi:
    payload: '1.0'
  events:
    - httpApi:
        path: /test/test1
        method: GET

packageeventsの間に、payloadを含むhttpApiの設定を追加しています。

なお、両方とも省略した場合は2.0が選択されるようです。
(Serverless Frameworkのバージョン等により異なる場合があります)

最後に

今回は、Terraformで構築したHTTP APIにServerless Frameworkからデプロイする方法について紹介しました。
TerraformのコードはREST APIのときよりも少なく、簡単に構築が可能です。
さらにHTTP APIの方がREST APIより安価でもあるので、積極的に使っていきたい気持ちはあります。

しかし、REST APIよりも機能が限定的なため慎重に考える必要があります。
HTTP APIでは、リソースポリシーによる接続元IPアドレスの制限もAWS WAFによるセキュリティ対策も行えません。
これでは、利用できるシーンが限られてしまうのではないでしょうか。
AWS WAFだけでも対応してくれれば思います。