法人のAWS Lambdaの歩き方 (令和 2026版)
はじめまして @shimma です。業務支援として複数社、インフラを中心に直接手を動かして、社内で横展開できるような設計・コードベースをご提供しています。
- 枯れた技術で
- コード行数少なく
- 運用コストかからず
- 8-9割くらいのことを解決できる
こちらが私の設計がポリシーです。
はじめに
AWS Lambdaについて、そのポテンシャルにもかかわらず、簡単なスクリプトを作る程度の事例が占めており、本格的にチーム開発、DevOps、アプリケーション開発者の全観点からシステムに組み込む解説記事がないと感じ、今回は具体事例を1個公開します。
想定読者
- 社内で全てのインフラはTerraform管理されている
- 一部AWS Lambdaは使っているが、ライブラリ入れるのが手間でzipにまとめたPython/JSを使っている程度の活用しかしていない
- 本番運用はECSである
結論: Dockerイメージでデプロイ管理する
AWS Lambdaは2020年ごろにコンテナイメージがサポートされ、RustからGoまで一通り動きます。
コンテナで動かす場合、Lambda対応イメージを含める必要はありますが、これも最新provided.al2023の指定すれば、殆どバイナリサイズも大きくなりません。(過去はLambda化するとイメージサイズがプラスで400MBほど膨れていましたが、今は純粋なバイナリに近いサイズ感になります)
ローカルにてDockerイメージで好きな言語で開発して、テストコードまでしっかり書いて、あとはそれをECRにPushするのみです。本番環境でインフラコストがほぼかからない夢のシステムを運用可能です。
Lambdaの立ち位置的に、しっかり管理しようとすると
- デプロイ管理の手間などからKlayers + Pythonだったり
- TerraformのArchive Provider使って簡単なJSスクリプト程度
で収まりがちですが、Docker化すれば全部がシンプルになります。AWS公式のLambda Layerは全く使う必要がありません。
コード開発編: Go + Docker (Lambda向け)
今回は特定のGETリクエストをレスポンスする、というユースケースで実装します。LambdaはPublic Function URLという外部公開用のHTTPSエンドポイントを簡単に作れますので、それを利用します。
ローカル開発でも動き、Lambdaでも同様に起動するイメージとなります。
流れとしては以下のように組むことで、ローカルでも同じように開発出来ます。
- Local HTTPリクエストならば、
LambdaFunctionURLRequestにその場で変換 - Lambda Eventとして共通で処理する
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum* ./
RUN go mod download && go mod tidy
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/bootstrap .
FROM public.ecr.aws/lambda/provided:al2023
COPY --from=builder /app/bootstrap /var/task/bootstrap
RUN chmod +x /var/task/bootstrap
ENTRYPOINT [ "/var/task/bootstrap" ]
func main() {
if os.Getenv("LOCAL_MODE") == "true" {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/", localHandler)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
} else {
lambda.Start(handler)
}
}
func handler(ctx context.Context, request events.LambdaFunctionURLRequest) (events.LambdaFunctionURLResponse, error) {
if request.RequestContext.HTTP.Method == "GET" {
return events.LambdaFunctionURLResponse{
StatusCode: http.StatusOK,
Body: `{"status": "ok!”}`,
Headers: map[string]string{
"Content-Type": "application/json",
},
}, nil
}
}
テスト編: ローカル開発環境をPublic公開する
あとはcompose.yamlに以下のように準備します。
services:
dev:
image: golang:1.21-alpine
environment:
- LOCAL_MODE=true
- PORT=8080
volumes:
- .:/app
working_dir: /app
command: sh -c "go mod download && go run main.go"
ports:
- "8080:8080"
cloudflared:
image: cloudflare/cloudflared:latest
command:
- tunnel
- --no-autoupdate
- run
environment:
- TUNNEL_TOKEN=${TUNNEL_TOKEN:-}
depends_on:
- dev
後は以下のように実行するだけで、ローカル開発環境でPublic URLが出力され、n8nなり自由に外部ツールとのWebhookなどをローカルで実装検証できます。
docker compose up dev cloudflared
デプロイ編: Terraform
基本はTerraformで管理されているので、これを統合して利用します。AWS公式が提供するAWS SAMやServerless Frameworkは個人的に使いにくいため利用しません。
null_resourceを利用して、local execでビルドからデプロイまで行います。変更対象ファイルhashを取ることで、差分がある時だけデプロイが動くようにします。
locals {
app_image_tag = substr(sha256(join("", [
filebase64sha256("${path.module}/aws-lambda/app/Dockerfile"),
filebase64sha256("${path.module}/aws-lambda/app/main.go"),
filebase64sha256("${path.module}/aws-lambda/app/go.mod"),
filebase64sha256("${path.module}/aws-lambda/app/go.sum")
])), 0, 7)
}
resource "null_resource" "app_ecr" {
triggers = {
repository_url = aws_ecr_repository.app.repository_url
image_tag = local.app_image_tag
}
provisioner "local-exec" {
working_dir = "${path.module}/aws-lambda/app"
command = <<-EOT
set -e
docker build --platform linux/amd64 --provenance=false \
-t app-for-terraform:latest .
aws ecr get-login-password --region ${local.aws_region} \
| docker login --username AWS --password-stdin \
${data.aws_caller_identity.self.account_id}.dkr.ecr.${local.aws_region}.amazonaws.com
docker tag app-for-terraform:latest \
${aws_ecr_repository.app.repository_url}:${local.app_image_tag}
docker tag app-for-terraform:latest \
${aws_ecr_repository.app.repository_url}:latest
docker push ${aws_ecr_repository.app.repository_url}:${local.app_image_tag}
docker push ${aws_ecr_repository.app.repository_url}:latest
EOT
}
}
resource "aws_lambda_function" "app" {
function_name = "${local.app_env}-app"
role = aws_iam_role.app_lambda.arn
package_type = "Image"
image_uri = "${aws_ecr_repository.app.repository_url}:${local.app_image_tag}"
timeout = 30
memory_size = 256
depends_on = [null_resource.app_ecr]
environment {
variables = {
SOME_ENV_VAR = "<redacted>"
}
}
}
resource "aws_lambda_function_url" "app" {
function_name = aws_lambda_function.app.function_name
authorization_type = "NONE"
cors {
allow_credentials = false
allow_origins = ["*"]
allow_methods = ["POST"]
allow_headers = ["*"]
expose_headers = []
max_age = 0
}
}
よりデプロイが高頻度なプロダクトであれば、Lambda版ecspressoであるlambrollを利用しGithub Actions経由のデプロイによるイメージ差し替えをおすすめします。
終わりに
「SendGridの開封確認のコールバックをメインサーバで受ける設計にしたところ、サービス提供に影響受けるレベルで負担がかかってしまった」など、そういうケースも今回のようなLambdaに流すことでコスト削減とシステム安定化が可能です。是非、高度な活用事例をお知らせ下さい。
ポップアップストアや催事イベント向けの商業スペースを簡単に予約できる「SHOPCOUNTER」と商業施設向けリーシングDXシステム「SHOPCOUNTER Enterprise」を運営しています。エンジニア採用強化中ですので、興味ある方はお気軽にご連絡ください! counterworks.co.jp/
Discussion