はじめに
GWでまとまった時間がとれたので、前々から気になっていた、Lambda Web Adapterを使って遊んでみることにしました。
思ったよりも簡単にWebサーバーをデプロイできたので、備忘録も兼ねて記事にしてみました。
想定読者
- Goで簡単なHTTPサーバーを書いたことがある方
- AWS LambdaやDockerの基本は知っているが、Lambda Web Adapterはまだ触ったことがない方
- AWS SAMを使ってコンテナイメージLambdaを試してみたい初中級者の方
前提
今回の構成は以下です。
- Go 1.25
- Lambdaはコンテナイメージ方式
- Lambda Web Adapter を Extension として同梱
- デプロイはAWS SAM
- 公開確認はLambda Function URL
- AWS アカウントと認証情報は用意済み
リージョンはap-northeast-1を使いました。
Lambda Web Adapterとは、なぜ使うのか
Lambdaをバックエンドとして使う場合、Lambdaのイベント形式に合わせた実装を書くことが多く、普段のWebアプリの作りとは少し変わってきます。
Lambda Web Adapterを使うと、Goのnet/httpや各種Webフレームワークで作ったHTTPサーバーを大きく書き換えずにLambda上で動かせます。
コンテナ内で起動したWebサーバーに対して、Lambdaに来たリクエストをAdapter経由で転送してくれるイメージです。
今回Lambda Web Adapterを試した理由も、普段のWebアプリの作りをできるだけ崩さずにLambda上で動かしたかったからです。
そのため、以下のようなケースでは相性がよいです。
- 既存のWebアプリを大きく書き換えずにLambdaへ載せたい
- ローカルで動かしているHTTPサーバーに近い構成のまま検証したい
- Lambda向けのイベントハンドラ実装を新しく覚えるより、まずはWebアプリとして動かしたい
Lambda Web Adapter を使うときのデメリット
Lambda Web Adapterは便利ですが、常に最適解とは限りません。
特に「既存のWebアプリを活かしたい」という理由が弱い場合は、普通のLambdaハンドラ実装の方が素直なこともあります。
自分が触ってみて感じた注意点は以下です。
- 構成要素が 1 つ増える
- アプリ本体に加えてLambda Web Adapterの設定や挙動も理解する必要があります。
- Lambdaネイティブな実装より間接層が増える
- リクエストはAdapterを経由してローカルのHTTPサーバーに渡るため、処理の流れは少し追いにくくなります。
- 小さな API ではやや大げさになりやすい
- エンドポイント数が少なく、単純な JSON を返すだけなら、通常のLambdaハンドラで十分なことも多いです。
- Lambdaの制約はそのまま受ける
- Webアプリとして動かせても、実体はLambdaなので、長時間接続や常時起動前提の設計には向いていません。
- API ごとの分離運用はしにくい
- Lambda Web Adapterは、Lambda実行環境内で起動したWebアプリに対してリクエストを転送する仕組みです。そのため、複数のパスを持つWebアプリであっても、基本的には1つのLambda関数の中で処理する構成になります。
- そのため、すべての API が同じメモリ、タイムアウト、同時実行設定を共有します。特定のエンドポイントだけを個別にスケールさせたい場合や、重い処理だけ設定を分けたい場合には少し扱いづらさがあります。
- デプロイ単位もまとまりやすいため、細かく分割して運用したいケースでは通常のLambda分割構成の方が向いていることもあります。
つまりLambda Web Adapterは、「Webアプリを大きく書き換えずにLambdaに載せる」ことに強みがある一方で、最初からLambda向けに素直に組める場合は少し回り道になる可能性があります。
事前準備
ローカル環境構築
今回はDevcontainerでローカル環境を構築しています。
ソースコードも用意しているので参考にどうぞ。
必要な構成要素としては
-
awscliを追加 -
docker.ioを追加 -
samCLIを追加 - Dockerソケットをマウント
- AWS 認証情報を環境変数で渡せるようにした
sam build と sam deploy をコンテナ内から実行する場合、以下は必要でした。
- Dockerが使えること
- AWS認証情報が渡っていること
今回はアクセスキーを環境変数で渡す構成にしたため、.env.sample に以下を追加しています。
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=
AWS_PAGER=""
また、docker-compose.ymlではDockerソケットをマウントしています。
volumes:
- /var/run/docker.sock:/var/run/docker.sock
コード準備
Go アプリ
最小限のHTTPサーバーを用意します。
ポイントは /health を用意している点です。
あとでLambda Web Adapterのreadiness checkに使います。
package main
import (
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8081"
}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("Hello, Lambda Web Adapter!\n"))
})
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
})
addr := ":" + port
log.Printf("server listening on %s", addr)
if err := http.ListenAndServe(addr, mux); err != nil {
log.Fatal(err)
}
}
Dockerfile
コンテナイメージはマルチステージビルドにしました。
ポイントはここです。
- Lambda Web Adapterを
/opt/extensions/lambda-adapterに配置している - Goアプリ本体を
/var/task/serverに置いている -
arm64でビルドしている
Lambda Web AdapterはLambda Extensionとして動作し、受けたリクエストをローカルのWebサーバーへ転送してくれます。
FROM golang:1.25 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY main.go ./
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o /out/server ./main.go
FROM public.ecr.aws/docker/library/debian:bookworm-slim
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter
COPY --from=builder /out/server /var/task/server
ENV PORT=8081
WORKDIR /var/task
CMD ["/var/task/server"]
SAM テンプレート
SAM テンプレートは以下です。
ここで重要なのは環境変数です。
-
PORT- Goアプリが待ち受けるポート
-
AWS_LWA_PORT- Lambda Web Adapterが転送先として使うポート
-
AWS_LWA_READINESS_CHECK_PATH- 起動確認用のパス
PORTとAWS_LWA_PORTは一致させています。
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
GoWebAdapterFunction:
Type: AWS::Serverless::Function
Properties:
PackageType: Image
Architectures:
- arm64
MemorySize: 512
Timeout: 10
FunctionUrlConfig:
AuthType: NONE
Environment:
Variables:
PORT: "8081"
AWS_LWA_PORT: "8081"
AWS_LWA_READINESS_CHECK_PATH: "/health"
Metadata:
Dockerfile: Dockerfile
DockerContext: .
DockerTag: go-lwa-poc
Outputs:
FunctionUrl:
Description: Lambda Function URL
Value: !GetAtt GoWebAdapterFunctionUrl.FunctionUrl
デプロイ
ビルド
まずはビルドします。
今回はAWS SAMを使っているので、SAMでビルドしています。
これで.aws-sam/配下にビルド結果が作られます。
コンテナイメージを使う設定なので、SAMがDockerfileとtemplate.yamlを見てイメージを組み立てます。
sam build
AWS SAMでデプロイ
初回は guided でデプロイしました。
sam deploy --guided --resolve-image-repos
実際には以下のような設定で進めています。
Stack Name [sam-app]: go-lwa-poc
AWS Region [ap-northeast-1]: ap-northeast-1
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: y
GoWebAdapterFunction Function Url has no authentication. Is this okay? [y/N]: y
Save arguments to configuration file [Y/n]: y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
デプロイ設定はsamconfig.tomlに保存されるので、次回以降は単に以下でよいです。
sam deploy
デプロイ結果
CloudFormation OutputsとしてFunction URLが出力されました。
Key: FunctionUrl
Description: Lambda Function URL
Value: https://<your-function-url>.lambda-url.ap-northeast-1.on.aws/
ブラウザやcurlやPostmanなどお好みの方法で確認してください。
ハマりどころ
1. samはLambda Web Adapterそのものではない
samはあくまでビルド・デプロイのためのCLIです。
Lambda Web Adapter自体は、Lambda上でWebアプリを動かすためのアダプタです。
つまり役割はこう分かれます。
- Lambda Web Adapter
- WebアプリをLambdaで動かす仕組み
- AWS SAM
- その構成をビルド・デプロイする仕組み
2. AuthType: NONEは公開URLになる
今回のFunction URLは以下の設定です。
FunctionUrlConfig:
AuthType: NONE
この設定だと認証なしでアクセスできます。
PoC としては手軽ですが、本番用途ではそのまま使わない方がよいです。
まとめ
今回の最小構成でやったことをざっとまとめるとこんな感じです。
- GoでHTTPサーバーを作る
- Lambda Web AdapterをExtensionとして同梱する
- SAMテンプレートでImage Lambdaを定義する
-
sam buildとsam deployでデプロイする - Function URLで疎通確認する
Lambda Web Adapterを使うと、Goの通常のHTTPサーバーを比較的そのままLambdaに載せられます。
今回はAWS SAMでデプロイしましたが、Terraform等お好みのIaCで試してください。
参考
- AWS Lambda Web Adapter
- AWS SAM
- 今回の実装したソースコード