0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GoのWebアプリをほぼそのままLambdaで動かす Lambda Web Adapter + AWS SAM

0
Posted at

はじめに

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 buildsam 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
    • 起動確認用のパス

PORTAWS_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がDockerfiletemplate.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 buildsam deployでデプロイする
  • Function URLで疎通確認する

Lambda Web Adapterを使うと、Goの通常のHTTPサーバーを比較的そのままLambdaに載せられます。
今回はAWS SAMでデプロイしましたが、Terraform等お好みのIaCで試してください。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?