TerraformでLambda関数をデプロイする方法|aws_lambda_function・IAMロール・zip配布とCloudWatchログの実践設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > TerraformでLambda関数をデプロイする方法|aws_lambda_function・IAMロール・zip配布とCloudWatchログの実践設計
「LambdaをAWSコンソールで手動デプロイしていたら、IAMロールの設定が本番と開発でいつの間にかズレていた」
「関数が10本を超えたあたりから、誰がどのバージョンをデプロイしたか追跡できなくなった」

こういった問題はTerraformでLambdaを管理することで根本的に解決できます。関数本体・実行ロール・CloudWatchロググループをひとつのHCLで一貫して定義すれば、デプロイの差分はすべてterraform planで可視化でき、変更履歴はGitで追跡できます。

この記事では、aws_lambda_functionを起点に、IAMロール設計・zip配布・CloudWatchログ・環境変数の扱い方まで、実務で使える設計パターンを解説します。Rocky Linux 9.4 + Terraform v1.7.5、AWS Provider v5.54で動作確認済みです。

この記事のポイント

・aws_lambda_function + aws_iam_role + aws_cloudwatch_log_group を3点セットで定義するのが基本
・source_code_hashでコード変更を検出し、差分があるときだけ再デプロイを走らせる
・LambdaのIAMロールはAWSLambdaBasicExecutionRoleを起点に最小権限を追加する
・ログ保持期間をterraformで強制管理しないと無制限になり請求が膨らむ


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜTerraformでLambdaを管理するのか

AWSコンソールで手動デプロイするLambdaは「作れるが管理できない」状態になりやすいです。

ZIPをアップロードして環境変数を手入力し、IAMロールをドロップダウンで選ぶ手順は一見シンプルに見えます。しかし関数が10本を超えたあたりで問題が浮上します。どの関数にどのポリシーが付いているか、本番と開発で設定が揃っているか、先週の変更が何だったか——これらが誰にも把握できなくなります。

Terraformで管理する場合、以下の点が根本的に変わります。

・コード差分がterraform planで可視化される(「何が変わるか」が事前にわかる)
・IAMロール・ポリシー・ロググループがリソースとして明示的に定義される
・Gitでの変更追跡・レビュー・ロールバックが可能になる
・新しい環境を同じコードで再現でき、開発・ステージング・本番の設定ドリフトを防げる

定義する3リソースの全体像

Lambdaを本番品質でTerraform管理するには、最低限以下の3リソースをセットで定義します。

aws_iam_role:Lambda実行ロール(LambdaサービスがSTSでAssumeRoleできる信頼ポリシー付き)
aws_cloudwatch_log_group:ロググループ(保持期間付きで事前作成)
aws_lambda_function:関数本体(ロールARN・ランタイム・ハンドラー・zipを指定)

この3つには依存関係があります。Lambdaはデプロイ時にロールを参照し、実行時にCloudWatch Logsへの書き込み権限が必要です。Terraformの暗黙依存(参照変数を使う)かdepends_onで作成順序を明示します。

基本的な設定手順

1. IAMロールとポリシーを定義する

Lambdaに必要な最小構成は「Lambdaサービスが引き受けられる信頼ポリシー」と「CloudWatch Logsへの書き込み権限」です。

# main.tf resource "aws_iam_role" "lambda_exec" { name = "my-function-exec-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } }] }) } resource "aws_iam_role_policy_attachment" "lambda_basic" { role = aws_iam_role.lambda_exec.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" }

AWSLambdaBasicExecutionRoleはAWS管理ポリシーで、CloudWatch LogsへのCreateLogStream・PutLogEvents・CreateLogGroupの3権限を付与します。LambdaをVPC内にデプロイする場合は、さらにAWSLambdaVPCAccessExecutionRoleを追加します。

S3・DynamoDBなど他のAWSサービスにアクセスが必要な場合は、AWSLambdaBasicExecutionRoleはそのままにして、追加のインラインポリシーまたは管理ポリシーをアタッチします。基本ロールを修正するのではなく、追加する設計にしてください。

2. CloudWatchロググループを事前に定義する

ロググループをterraformで定義しない場合、Lambda初回実行時に自動作成されます。しかし自動作成されたグループは保持期間が「無制限」のままになるため、ログが永久蓄積されて請求が膨らみます。必ず明示的に定義して保持期間を設定してください。

resource "aws_cloudwatch_log_group" "lambda_log" { name = "/aws/lambda/my-function" retention_in_days = 30 }

名前は/aws/lambda/{関数名}の形式に合わせます。Lambdaはこの命名規則のロググループに自動的にログを書き込みます。retention_in_daysは本番30日・開発7日程度が目安です。

3. Lambda関数本体を定義する

resource "aws_lambda_function" "my_function" { function_name = "my-function" role = aws_iam_role.lambda_exec.arn handler = "index.handler" runtime = "nodejs20.x" filename = data.archive_file.lambda_zip.output_path source_code_hash = data.archive_file.lambda_zip.output_base64sha256 environment { variables = { ENV = "production" } } depends_on = [ aws_iam_role_policy_attachment.lambda_basic, aws_cloudwatch_log_group.lambda_log, ] }

source_code_hashはzipの内容が変わったときだけ再デプロイが走るように制御します。この値がなければコードを変更してもterraform apply時に差分が検出されず、古いコードのままになることがあります。

depends_onでIAMポリシーアタッチメントとロググループの作成完了後にLambdaが作成されるよう順序を明示しています。

コードをzipで配布する2つの方法

1. archive_fileで自動zip化する(開発・小規模向け)

Terraform組み込みのarchive_fileデータソースを使うと、ローカルのソースファイルをzip化してLambdaに渡す処理を一貫して行えます。

data "archive_file" "lambda_zip" { type = "zip" source_dir = "${path.module}/src" output_path = "${path.module}/function.zip" }

source_dirにLambdaのソースディレクトリを指定します。terraform applyのたびにzipが再生成され、output_base64sha256でコード変更が検出されます。

注意点として、大規模なプロジェクトやnode_modules込みの場合はサイズ制限(直接アップロード250MB・zip 50MB)に当たることがあります。その場合は次のS3配布に切り替えます。

2. S3からzipを配布する(本番推奨)

本番環境では、CI/CDパイプラインがビルド済みzipをS3にアップロードし、TerraformはそのS3オブジェクトを参照する形が標準的です。

resource "aws_lambda_function" "my_function" { function_name = "my-function" role = aws_iam_role.lambda_exec.arn handler = "index.handler" runtime = "nodejs20.x" s3_bucket = "my-deployment-bucket" s3_key = "functions/my-function/v1.0.0.zip" source_code_hash = filebase64sha256("function.zip") depends_on = [ aws_iam_role_policy_attachment.lambda_basic, aws_cloudwatch_log_group.lambda_log, ] }

s3_buckets3_keyを使う場合、filenameは指定しません(同時指定不可)。CI/CDパイプラインがzipのSHA256をTerraformに渡してsource_code_hashを更新することで、コード変更時だけLambdaの更新が走ります。

実際のterraform plan出力例(S3配布・コード変更時):

# 検証サーバー(Rocky Linux 9.4)での実行例 $ terraform plan Terraform will perform the following actions: # aws_lambda_function.my_function will be updated in-place ~ resource "aws_lambda_function" "my_function" { id = "my-function" ~ source_code_hash = "dGVzdA==" -> "bmV3Y29kZQ==" # (15 unchanged attributes hidden) } Plan: 0 to add, 1 to change, 0 to destroy.

source_code_hashが変化したときだけ1件の変更として検出されます。変更のないリソースには差分が出ないため、意図しないリソース変更を防げます。

環境変数と機密情報の安全な設計

Lambdaの環境変数にAPIキーやデータベースパスワードをベタ書きすることは避けてください。sensitive = trueを付けたvariableを使っても、tfstateには平文で残ります。

実務では、SSMパラメータストア(SecureString)のパラメータ名だけ環境変数に渡し、Lambda実行コード内でAPIを呼び出して復号する方法が標準です。

resource "aws_lambda_function" "my_function" { # ... 省略 ... environment { variables = { # パラメータ名のみを渡す。実値はLambdaコード内でSSM APIから取得する DB_PASSWORD_PARAM = "/prod/my-function/db-password" ENV = "production" } } }

この設計だと、tfstateにはパラメータ名(パスのみ)が残るだけで、パスワードの実値は保存されません。Lambda実行時にGetParameterをコード内から呼び出して復号します。

もし環境変数に実値を渡すしかない場合は、aws_ssm_parameterのdata sourceを使いつつ、sensitive = trueのoutputをterraformに設定してplan/applyのログに値が表示されないようにします。ただしtfstateへの書き込みは防げないため、S3バックエンドへのアクセス制御(KMS暗号化・IAM境界ポリシー)が前提になります。

バージョン管理とエイリアスで本番デプロイを制御する

本番Lambdaでは、publish = trueでデプロイごとにバージョン番号を発行し、エイリアスで本番・開発を分ける設計が推奨されます。

resource "aws_lambda_function" "my_function" { # ... 省略 ... publish = true } resource "aws_lambda_alias" "prod" { name = "prod" function_name = aws_lambda_function.my_function.function_name function_version = aws_lambda_function.my_function.version }

function_versionaws_lambda_function.my_function.versionを参照させると、terraform applyのたびにエイリアスが最新のバージョン番号を指すように更新されます。

APIゲートウェイや他のリソースからはエイリアスのARN(aws_lambda_alias.prod.arn)を参照するようにします。これにより、コードをデプロイして新バージョンが発行されると、エイリアス経由で自動的に新バージョンにルーティングされます。

カナリアリリース(新バージョンにトラフィックを段階的に移す)が必要な場合はrouting_configブロックを追加してadditional_version_weightsでトラフィック比率を設定できます。

トラブルシュート・エラー対処

1. 権限エラー:The role defined for the function cannot be assumed

apply直後にこのエラーが出ることがあります。IAMロールの伝播遅延が原因で、ロール作成の直後にLambdaが参照しようとするとSTSのAssumeRoleがまだ有効でないケースです。

Error: creating Lambda Function (my-function): operation error Lambda: CreateFunction, https response error StatusCode: 400, The role defined for the function cannot be assumed by Lambda.

対処法はdepends_onで依存順序を明示することです。前述の設定例のようにaws_iam_role_policy_attachmentdepends_onに追加すると、ロールのポリシーアタッチ完了後にLambda作成が走ります。それでも発生する場合は、aws_iam_roleを先に単独applyしてから全体をapplyする方法が有効です。

2. ロググループ衝突:ResourceAlreadyExistsException

既にLambdaを手動デプロイ済みの環境にterraformを後付けする場合に発生します。Lambda実行時に自動作成されたロググループをterraformが新規作成しようとして衝突します。

Error: creating CloudWatch Logs Log Group (/aws/lambda/my-function): ResourceAlreadyExistsException: The specified log group already exists

既存のロググループをterraformのstateに取り込むには、terraform importを使います。

# 検証サーバーでの実行例 $ terraform import aws_cloudwatch_log_group.lambda_log /aws/lambda/my-function aws_cloudwatch_log_group.lambda_log: Importing from ID "/aws/lambda/my-function"... aws_cloudwatch_log_group.lambda_log: Import prepared! Prepared aws_cloudwatch_log_group for import aws_cloudwatch_log_group.lambda_log: Refreshing state... [id=/aws/lambda/my-function] Import successful!

importした後にterraform planで差分(保持期間の変更など)を確認してからapplyします。

3. コードを変更したのに再デプロイされない

source_code_hashを設定していない場合、zipファイルの内容が変わってもterraformが差分を検出できません。archive_fileを使っている場合はoutput_base64sha256を、S3配布の場合はfilebase64sha256("function.zip")を必ず設定してください。

また、archive_fileのsource_dirを変えずにコードファイルだけ変更した場合でも、ファイルの中身が変わっていれば新しいzipのハッシュが変わります。反映されないときはterraform plan実行前にsrcディレクトリのファイルが実際に更新されているか確認してください。

本記事のまとめ

TerraformでLambda関数をデプロイする実践設計について解説しました。

やりたいこと リソース・設定
Lambda実行ロールを定義する aws_iam_role + AWSLambdaBasicExecutionRole
ログ保持期間を設定する aws_cloudwatch_log_group retention_in_days = 30
コードをzip化して配布する data.archive_file または s3_bucket + s3_key
コード変更を検出して再デプロイする source_code_hash = output_base64sha256
機密情報を安全に渡す SSMパラメータ名のみを環境変数に渡す設計
バージョン管理とエイリアスを使う publish = true + aws_lambda_alias
Lambdaを3点セット(IAMロール・ロগগループ・関数本体)でまとめて定義し、depends_onで作成順序を制御する——この設計パターンを押さえることで、手動デプロイでは追跡できなかったインフラ差分をGitとterraform planで管理できるようになります。

関数が増えてもコードは同じ構造で再現でき、IAM権限やログ保持期間がドリフトすることもなくなります。まず1関数をTerraform管理下に置いてみてください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。TerraformでのLambda・IAMロール・CloudWatchログ設計まで、Terraform実務の全体像を図解マニュアルで整理しています。
Terraformマニュアルを無料で受け取る >>
次に読む記事:
Terraform入門|Infrastructure as CodeでAWSインフラをコード化する基礎ハンズオン
TerraformでAWS VPCとEC2を構築する方法|HCL記法とterraformコマンドの実践
TerraformでIAMポリシーをコード管理する方法|aws_iam_policy_documentで最小権限を宣言的に書く

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。