Terraform で AWS Lambda をデプロイしようとする際にぶつかる現実
はじめに
AWSリソースを管理する IaC ツールの中でも高いシェア率を誇っている Terraform で Lambda 関数をデプロイをしようとすると、sls deploy
、 cdk deploy
などと同じメンタルモデルで「 terraform apply
すれば OK! 」というわけにはいかない という気付きがあり、その内容について書き残しておこうと思いました。
Lambda のデプロイに必要なことをおさらい
ここでは、TypeScript(実行環境=Nodejs)、Go(実行環境=Amazon Linux) などのビルドが必要なプログラミング言語を前提として書いています。(Python などでも依存モジュールのダウンロードなどが必要なのでおおよそ変わらないはずです)
そもそも、Lambda をデプロイする方法としては、
- (ZIP形式)ZIP化してアップロードする
- (ZIP形式)S3にアップロードして経由でアップロードする
- (コンテナ形式)コンテナイメージを作成して ECR などのコンテナレジストリを使ってデプロイする
の大きく3パターンあります。
それぞれの方法の特徴は、S3 を経由することで 50 MB を超えるZIPファイルをアップロードすることができ、コンテナイメージを使うと圧縮前 250 MB というサイズ制限に引っ掛からなくなることや コンテナをそのまま動かせるメリットなどがあります。
ここでは最も一般的であろう ZIP化してアップロードしてデプロイ方式 についてのみを対象とします。
ZIP化してアップロードしてデプロイ方式 は以下のステップが必要です。
- ビルドする
- ビルド成果物をZIP化する
- ZIPファイルをアップロードする(
aws lambda update-function-code --function-name funcA --zip-file fileb://lambda.zip
相当)
Terraform でも terraform apply だけで Lambda の更新をしたい!
そもそも Terraform は Lambda デプロイの何をしてくれるか?
Terraform の aws_lambda_function リソースでは主に以下のことをすることができます
- ZIPファイル を入力として Lambda 関数を作成・更新する
-
aws lambda update-function-code
相当
-
- Lambda 関数の設定(メモリ、タイムアウト、環境変数、IAMロールなど)を管理する
-
aws lambda update-function-configuration
相当
-
公式ドキュメント にあるサンプルのように ZIPファイルを指定する必要があります。
data "archive_file" "example" {
type = "zip"
source_file = "${path.module}/lambda/index.js"
output_path = "${path.module}/lambda/function.zip"
}
# Lambda function
resource "aws_lambda_function" "example" {
filename = data.archive_file.example.output_path
function_name = "example_lambda_function"
role = aws_iam_role.example.arn
handler = "index.handler"
source_code_hash = data.archive_file.example.output_base64sha256
runtime = "nodejs20.x"
environment {
variables = {
ENVIRONMENT = "production"
LOG_LEVEL = "info"
}
}
tags = {
Environment = "production"
Application = "example"
}
}
つまり、ビルドとZIPファイル化については Terraform の外で実行する必要があります。
local-execという選択肢
Terraform だけで Lambda をデプロイする方法はないかと探していると、 local-exec を使う方法が見つかりました。詳細は以下の記事に書かれています。
provisioner "local-exec" {
command = "GOARCH=amd64 GOOS=linux go build -o ${local.golang_binary_local_path} ${local.golang_codedir_local_path}/*.go"
}
provisioner "local-exec" {
command = "zip -j ${local.golang_zip_local_path} ${local.golang_binary_local_path}"
}
のように、 local-exec を定義することで terraform apply
コマンドにビルドやZIP化を内包することができます。
しかし、Terraform のドキュメントには、
と書かれており、 local-exec の利用は一時的な回避策とすべきであるようです。
HCP Terraform(Terraform Cloud) 上での選択肢
local-exec はローカル環境の node や go の実行環境を使う方法手段になりますが、クラウド上で terraform apply 等を行う HCP Terraform では少し勝手が違ってきます。
以下のドキュメントのように npm install する方法の紹介例などはあるので HCP Terraform 上でもビルド&ZIP化をする方法はあるかもしれません。しかし、この方法も開発時の回避策として紹介されているもので、このアプローチはエラーが起きやすいため、しばしば推奨されないとあります。HCP Terrraform 自体が Node.js や Go言語の実行環境を提供しているわけではないので相性が悪く思います。
結論: Terraform では、 terraform apply だけで Lambda 関数を継続的に更新していくことは難しい
では、どうするのか?
1つの解決方針としては、Terraform で Lambda の定義を管理し、Lambda関数の更新は別の方法で実現するという方針です。
具体的にどういうステップになるかというと以下です。
-
terraform apply
でひとまず Lambda をデプロイしておく(dummyもしくは最小のコード) - GitHub Actions などの CI/CD 環境でビルドする
- GitHub Actions などの CI/CD 環境でビルドしたものをZIP化する
- GitHub Actions などの CI/CD 環境でZIPファイルを使って Lambda 関数を更新する
Lambda 関数の更新をする手段の選択肢としては、
- AWS CLI
- lambroll
- aws-actions/aws-lambda-deploy (GitHub Actions)
などがあります。 Lambda 関数の更新だけに限定するか、 Lambda の設定更新も行うかは自由度があります。lambroll については、少し記事を書いたことがあるので参考にしてください。
aws-actions/aws-lambda-deploy は、2025/08 に AWS がリリースした Lambda 関数デプロイ用のGitHub Action です。このモジュールがリリースした背景にはこの記事で取り上げている課題を解決しようとするものがあるのかもしれません。
各ステップの詳細なイメージ
最初にデプロイする terraform のコードは以下のようなものになります。
resource "aws_lambda_function" "function" {
lifecycle {
ignore_changes = [
filename,
source_code_hash,
]
}
function_name = "go-lambda-function"
filename = data.archive_file.dummy.output_path
role = aws_iam_role.lambda_role.arn
handler = "bootstrap" # Go AL2ランタイムではハンドラーは使用されませんが必要
runtime = "provided.al2" # Go AL2用のカスタムランタイム
timeout = 30
environment {
variables = {
EXAMPLE_VAR = "example_value"
}
}
}
data "archive_file" "dummy" {
type = "zip"
output_path = "${path.module}/dummy.zip"
source {
content = "dummy"
filename = "bootstrap"
}
depends_on = [
null_resource.main
]
}
resource "null_resource" "main" {}
ここでは、dummyのZIPファイルを作成していますが、ビルドした成果物を用意しておいても構いません。GitHub Actions の定義は以下のようなものになります。
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.25'
- name: Install dependencies
run: |
go mod download
- name: Build Go application for Lambda
run: |
GOOS=linux GOARCH=arm64 go build -o bootstrap main.go
- name: Create deployment package
run: |
zip lambda-function.zip bootstrap
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ""
role-session-name: ""
aws-region: ""
- name: Update Lambda function
run: |
aws lambda update-function-code \
--function-name my-lambda-function \
--zip-file fileb://lambda-function.zip
この構成の良さ
Lambda の設定などの定義の管理と Lambda コードの管理が別々になるため、ライフサイクルがそれぞれ独立したものになります。Lambda の設定を更新したい頻度と Lambda の本体のソースコードを変更したい頻度は異なることが多いと思うため、 Lambda 自体の定義と Lambda のコード変更タイミングでデプロイできるため責務を明確に分けたい場合はメリットになる構成だと思います。
Lambda ではない別のサービスの例だと、 ECS のクラスタの定義と ECS サービス(タスク)の更新を分離するために Terraform と ecspresso を組み合わせて使うというよく見られる構成と捉えることもできるかと思います。
しかし、サーバーレス、Function as a Service としての Lambda というサービスの特徴やちょっとした処理を Lambda に任せたいシーンなどで考えるとやや too much というか煩雑だなあと個人的には感じました。
まとめ
このように、Terraform で Lambda 関数を継続的にデプロイしていく仕組みを作ろうとすると、 terraform apply
だけでは完結せず、 Terraform による AWS リソースの管理と Lambda 関数デプロイに責務を分ける必要が出てきます。
もし、そんなことはせず、 Terraform × Lambda でももっと簡単にデプロイできるいい方法を知っている方がいたら教えてください。
以上、Terraform で AWS Lambda を扱う際にぶつかる現実について紹介しました。どなたかのお役に立てれば幸いです。
Discussion
以下moduleがありますね。日本語の情報が少なくてとっつきづらいですが。