Terraform 部署 AWS Lambda 完整教學:IaC 最佳實踐
Terraform 部署 AWS Lambda 完整教學:IaC 最佳實踐
手動在 AWS Console 建立 Lambda 很簡單,但當你有 10 個、50 個甚至 100 個 Lambda 函數要管理時,事情就會失控。
更別提每次部署都要手動點擊、環境之間的設定不一致、無法追蹤誰改了什麼。這些問題,Terraform 都能解決。
本文將從基礎到進階,教你如何用 Terraform 管理 AWS Lambda,建立可重複、可追蹤、可協作的基礎設施。
想先了解 Lambda 本身的概念和功能,請參考 AWS Lambda 完整指南。
為什麼用 Terraform 管理 Lambda
IaC 的核心優勢
Infrastructure as Code(IaC) 將基礎設施定義成程式碼,帶來以下好處:
| 優勢 | 傳統手動部署 | Terraform IaC |
|---|---|---|
| 版本控制 | 無法追蹤變更 | Git 完整歷史記錄 |
| 環境一致性 | 容易有設定差異 | 同一份程式碼,結果相同 |
| 協作審查 | 口頭溝通,易出錯 | Pull Request Code Review |
| 災難復原 | 重新手動設定 | terraform apply 重建 |
| 文件化 | 另外寫文件,易過時 | 程式碼即文件 |
Terraform vs CloudFormation vs CDK 比較
圖片說明:Terraform、CloudFormation、AWS CDK 三種 IaC 工具的功能比較表,包含語法類型、多雲支援、學習曲線、社群資源等維度。
| 特性 | Terraform | CloudFormation | AWS CDK |
|---|---|---|---|
| 語法 | HCL(宣告式) | YAML/JSON | TypeScript/Python/Java |
| 多雲支援 | 支援 | 僅 AWS | 僅 AWS |
| 學習曲線 | 中等 | 中等 | 較陡(需懂程式語言) |
| 社群資源 | 非常豐富 | AWS 官方 | 快速成長中 |
| 狀態管理 | 需自行管理 State | AWS 自動管理 | 基於 CloudFormation |
| 偵錯體驗 | 較好 | 錯誤訊息較難懂 | 較好 |
選擇建議:
- 多雲環境或團隊已熟悉 Terraform → 選 Terraform
- 純 AWS 且團隊熟悉 TypeScript/Python → 選 CDK
- 企業環境需要 AWS 官方支援 → 選 CloudFormation
基礎設定
Provider 設定
首先建立 provider.tf:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
# 建議:使用遠端 State 儲存
backend "s3" {
bucket = "my-terraform-state"
key = "lambda/terraform.tfstate"
region = "ap-northeast-1"
}
}
provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = {
Environment = var.environment
ManagedBy = "terraform"
Project = "my-lambda-project"
}
}
}
Lambda 必要資源
一個完整的 Lambda 部署需要三個核心資源:
圖片說明:Terraform 部署 Lambda 的資源關係圖,顯示 aws_lambda_function、aws_iam_role、aws_iam_role_policy_attachment 三者的關聯。
1. IAM 執行角色(Execution Role)
# IAM 角色 - Lambda 的執行身份
resource "aws_iam_role" "lambda_role" {
name = "${var.function_name}-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
# 附加基本執行政策(CloudWatch Logs 權限)
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
2. Lambda 函數
resource "aws_lambda_function" "this" {
function_name = var.function_name
role = aws_iam_role.lambda_role.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
memory_size = 256
timeout = 30
environment {
variables = {
NODE_ENV = var.environment
}
}
}
3. 程式碼打包
data "archive_file" "lambda_zip" {
type = "zip"
source_dir = "${path.module}/src"
output_path = "${path.module}/dist/lambda.zip"
}
完整範例
簡單 Lambda 函數
完整的單一 Lambda 部署範例:
# main.tf
locals {
function_name = "hello-world-${var.environment}"
}
# 程式碼打包
data "archive_file" "lambda_zip" {
type = "zip"
source_dir = "${path.module}/src"
output_path = "${path.module}/dist/lambda.zip"
}
# IAM 角色
resource "aws_iam_role" "lambda" {
name = "${local.function_name}-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_logs" {
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# Lambda 函數
resource "aws_lambda_function" "hello" {
function_name = local.function_name
role = aws_iam_role.lambda.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
memory_size = 128
timeout = 10
environment {
variables = {
ENVIRONMENT = var.environment
}
}
}
# 輸出
output "function_arn" {
value = aws_lambda_function.hello.arn
}
output "function_name" {
value = aws_lambda_function.hello.function_name
}
Lambda + API Gateway
建立 HTTP API 整合:
# API Gateway HTTP API
resource "aws_apigatewayv2_api" "api" {
name = "${var.project_name}-api"
protocol_type = "HTTP"
cors_configuration {
allow_origins = ["*"]
allow_methods = ["GET", "POST", "PUT", "DELETE"]
allow_headers = ["Content-Type", "Authorization"]
max_age = 300
}
}
# Lambda 整合
resource "aws_apigatewayv2_integration" "lambda" {
api_id = aws_apigatewayv2_api.api.id
integration_type = "AWS_PROXY"
integration_uri = aws_lambda_function.api.invoke_arn
integration_method = "POST"
payload_format_version = "2.0"
}
# 路由設定
resource "aws_apigatewayv2_route" "default" {
api_id = aws_apigatewayv2_api.api.id
route_key = "$default"
target = "integrations/${aws_apigatewayv2_integration.lambda.id}"
}
# Stage 部署
resource "aws_apigatewayv2_stage" "default" {
api_id = aws_apigatewayv2_api.api.id
name = "$default"
auto_deploy = true
}
# Lambda 執行權限
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.api.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.api.execution_arn}/*/*"
}
output "api_endpoint" {
value = aws_apigatewayv2_stage.default.invoke_url
}
更多 API Gateway 整合細節,請參考 API Gateway 整合概念。
Lambda + EventBridge 定時任務
建立每小時執行的排程任務:
# CloudWatch Event Rule(使用 EventBridge)
resource "aws_cloudwatch_event_rule" "schedule" {
name = "${var.function_name}-schedule"
description = "每小時觸發一次"
schedule_expression = "rate(1 hour)"
# 或使用 Cron 表達式
# schedule_expression = "cron(0 * * * ? *)"
}
# Event Target
resource "aws_cloudwatch_event_target" "lambda" {
rule = aws_cloudwatch_event_rule.schedule.name
target_id = "TriggerLambda"
arn = aws_lambda_function.scheduled.arn
}
# Lambda 執行權限
resource "aws_lambda_permission" "eventbridge" {
statement_id = "AllowEventBridgeInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.scheduled.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.schedule.arn
}
更多事件驅動架構內容,請參考 EventBridge 事件驅動架構。
想設計更完善的 IaC 架構?預約架構諮詢,讓專家幫你規劃。
Terraform Lambda Module
使用現成 Module
社群維護的 terraform-aws-modules/lambda 是最受歡迎的 Lambda Module:
module "lambda_function" {
source = "terraform-aws-modules/lambda/aws"
version = "~> 6.0"
function_name = "my-lambda-function"
description = "My awesome lambda function"
handler = "index.handler"
runtime = "nodejs20.x"
source_path = "./src"
# VPC 設定
vpc_subnet_ids = var.private_subnet_ids
vpc_security_group_ids = [aws_security_group.lambda.id]
# 環境變數
environment_variables = {
DATABASE_URL = var.database_url
NODE_ENV = var.environment
}
# CloudWatch Logs
cloudwatch_logs_retention_in_days = 14
# 效能設定
memory_size = 512
timeout = 60
# 併發限制
reserved_concurrent_executions = 100
tags = var.common_tags
}
Module 的優點:
- 自動建立 IAM Role 和 Policy
- 內建程式碼打包功能
- 支援 Layers、VPC、Alias 等進階功能
- 持續維護更新
自建 Module 結構
圖片說明:自建 Terraform Lambda Module 的目錄結構圖,展示 modules/lambda 下的 main.tf、variables.tf、outputs.tf 等檔案組織方式。
當你需要客製化更多邏輯時,可以自建 Module:
modules/
└── lambda/
├── main.tf
├── variables.tf
├── outputs.tf
└── iam.tf
variables.tf
variable "function_name" {
description = "Lambda 函數名稱"
type = string
}
variable "handler" {
description = "Lambda handler"
type = string
default = "index.handler"
}
variable "runtime" {
description = "執行環境"
type = string
default = "nodejs20.x"
}
variable "source_dir" {
description = "程式碼目錄"
type = string
}
variable "memory_size" {
description = "記憶體大小 (MB)"
type = number
default = 256
}
variable "timeout" {
description = "執行超時 (秒)"
type = number
default = 30
}
variable "environment_variables" {
description = "環境變數"
type = map(string)
default = {}
}
variable "tags" {
description = "資源標籤"
type = map(string)
default = {}
}
outputs.tf
output "function_arn" {
description = "Lambda 函數 ARN"
value = aws_lambda_function.this.arn
}
output "function_name" {
description = "Lambda 函數名稱"
value = aws_lambda_function.this.function_name
}
output "invoke_arn" {
description = "Lambda 調用 ARN"
value = aws_lambda_function.this.invoke_arn
}
output "role_arn" {
description = "IAM 角色 ARN"
value = aws_iam_role.lambda.arn
}
變數與輸出設計原則
- 必要參數不設預設值:強制呼叫者提供
- 合理預設值:常用設定提供預設,減少重複程式碼
- 類型明確定義:使用
type強制類型檢查 - 描述清楚:每個變數都有
description - 輸出有用資訊:ARN、Name、Invoke URL 等常用屬性
進階技巧
管理 Lambda 程式碼
方式一:archive_file(小型專案)
data "archive_file" "lambda" {
type = "zip"
source_dir = "${path.module}/src"
output_path = "${path.module}/dist/lambda.zip"
excludes = ["node_modules", "*.test.js"]
}
方式二:S3 儲存(大型專案)
resource "aws_s3_object" "lambda_code" {
bucket = aws_s3_bucket.deployment.id
key = "lambda/${var.function_name}/${var.code_version}.zip"
source = "${path.module}/dist/lambda.zip"
etag = filemd5("${path.module}/dist/lambda.zip")
}
resource "aws_lambda_function" "this" {
function_name = var.function_name
role = aws_iam_role.lambda.arn
handler = "index.handler"
runtime = "nodejs20.x"
s3_bucket = aws_s3_bucket.deployment.id
s3_key = aws_s3_object.lambda_code.key
# ...
}
方式三:Container Image(複雜相依)
resource "aws_lambda_function" "container" {
function_name = var.function_name
role = aws_iam_role.lambda.arn
package_type = "Image"
image_uri = "${aws_ecr_repository.lambda.repository_url}:${var.image_tag}"
memory_size = 1024
timeout = 300
}
版本與別名管理
版本與別名讓你實現藍綠部署和流量切換。詳細的版本管理概念,請參考 Lambda 版本與別名說明。
# 發布新版本
resource "aws_lambda_function" "this" {
# ...
publish = true # 每次更新自動發布新版本
}
# 建立別名
resource "aws_lambda_alias" "live" {
name = "live"
description = "Production traffic"
function_name = aws_lambda_function.this.function_name
function_version = aws_lambda_function.this.version
# 流量切換(金絲雀部署)
routing_config {
additional_version_weights = {
"${aws_lambda_function.this.version}" = 0.1 # 10% 流量到新版本
}
}
}
環境變數與 Secrets 管理
圖片說明:Lambda 取得 Secrets 的三種方式比較:環境變數(簡單但不安全)、SSM Parameter Store(適中)、Secrets Manager(最安全但較貴)。
方式一:SSM Parameter Store
# 建立參數
resource "aws_ssm_parameter" "api_key" {
name = "/${var.environment}/lambda/api-key"
type = "SecureString"
value = var.api_key
}
# Lambda 取得參數的權限
resource "aws_iam_role_policy" "ssm_read" {
name = "ssm-read"
role = aws_iam_role.lambda.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"ssm:GetParameter",
"ssm:GetParameters"
]
Resource = aws_ssm_parameter.api_key.arn
}]
})
}
# 傳遞參數名稱給 Lambda
resource "aws_lambda_function" "this" {
# ...
environment {
variables = {
API_KEY_PARAM = aws_ssm_parameter.api_key.name
}
}
}
方式二:Secrets Manager
data "aws_secretsmanager_secret" "db_credentials" {
name = "prod/database/credentials"
}
resource "aws_iam_role_policy" "secrets_read" {
name = "secrets-read"
role = aws_iam_role.lambda.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = data.aws_secretsmanager_secret.db_credentials.arn
}]
})
}
DevOps 流程需要優化? CI/CD、多環境部署、Secrets 管理都有很多眉角。 預約架構諮詢,讓我們幫你設計最佳 DevOps 流程。
CI/CD 整合
GitHub Actions 範例
# .github/workflows/deploy-lambda.yml
name: Deploy Lambda
on:
push:
branches: [main]
paths:
- 'lambda/**'
- 'terraform/**'
env:
AWS_REGION: ap-northeast-1
TF_VERSION: 1.6.0
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Build Lambda
run: |
cd lambda
npm ci
npm run build
- name: Terraform Init
run: |
cd terraform
terraform init
- name: Terraform Plan
id: plan
run: |
cd terraform
terraform plan -no-color -out=tfplan
continue-on-error: true
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && steps.plan.outcome == 'success'
run: |
cd terraform
terraform apply -auto-approve tfplan
GitLab CI 範例
# .gitlab-ci.yml
stages:
- validate
- plan
- apply
variables:
TF_ROOT: ${CI_PROJECT_DIR}/terraform
AWS_DEFAULT_REGION: ap-northeast-1
.terraform_template: &terraform_template
image: hashicorp/terraform:1.6
before_script:
- cd ${TF_ROOT}
- terraform init
validate:
<<: *terraform_template
stage: validate
script:
- terraform validate
- terraform fmt -check
plan:
<<: *terraform_template
stage: plan
script:
- terraform plan -out=plan.tfplan
artifacts:
paths:
- ${TF_ROOT}/plan.tfplan
expire_in: 1 hour
apply:
<<: *terraform_template
stage: apply
script:
- terraform apply -auto-approve plan.tfplan
dependencies:
- plan
only:
- main
when: manual
自動部署流程設計
圖片說明:Terraform Lambda CI/CD 部署流程圖,從程式碼推送到 GitHub,經過 Build、Test、Plan、Apply 階段,最終部署到 AWS。
建議的流程:
- 開發階段:本地開發 +
terraform plan預覽 - Code Review:Pull Request + Plan 結果自動留言
- 合併後:自動執行
terraform apply - 監控:CloudWatch 警示 + Slack 通知
多環境策略:
terraform/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ │ ├── main.tf
│ │ └── terraform.tfvars
│ └── prod/
│ ├── main.tf
│ └── terraform.tfvars
└── modules/
└── lambda/
或使用 Terraform Workspace:
terraform workspace new dev
terraform workspace new prod
terraform workspace select prod
terraform apply -var-file="prod.tfvars"
常見問題與解法
程式碼更新不生效
症狀:修改了 Lambda 程式碼,terraform apply 顯示沒有變更。
原因:Terraform 只追蹤 filename 和 source_code_hash,檔案路徑沒變就不會偵測到變更。
解法:確保使用 source_code_hash:
resource "aws_lambda_function" "this" {
filename = data.archive_file.lambda.output_path
source_code_hash = data.archive_file.lambda.output_base64sha256 # 關鍵!
# ...
}
IAM 權限問題
症狀:Lambda 執行時出現 AccessDenied 錯誤。
解法:
- 確認 IAM Role 有正確的 Trust Policy
- 檢查是否附加了必要的 Policy
- 使用最小權限原則,只給必要權限
# 額外權限範例:允許存取 DynamoDB
resource "aws_iam_role_policy" "dynamodb" {
name = "dynamodb-access"
role = aws_iam_role.lambda.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query"
]
Resource = aws_dynamodb_table.this.arn
}]
})
}
VPC 設定問題
症狀:Lambda 放入 VPC 後無法存取網路。
原因:VPC Lambda 沒有公網存取能力。
解法:
resource "aws_lambda_function" "vpc" {
# ...
vpc_config {
subnet_ids = var.private_subnet_ids # 使用 Private Subnet
security_group_ids = [aws_security_group.lambda.id]
}
}
# 確保 Private Subnet 有 NAT Gateway 路由
# 或使用 VPC Endpoints 存取 AWS 服務
想深入了解 Lambda@Edge 的 Terraform 部署,可參考 Lambda@Edge Terraform 部署。
常見問題 FAQ
Terraform Lambda 部署需要多久?
首次部署約 1-3 分鐘,後續更新通常在 30 秒內完成。主要時間花在程式碼上傳和 IAM 權限傳播。使用 S3 儲存程式碼可以加速大型專案的部署。
Terraform State 應該存在哪裡?
強烈建議使用 S3 + DynamoDB 作為遠端 Backend。本地 State 檔案在團隊協作時會造成衝突,且有遺失風險。設定方式請參考本文的 Provider 設定段落。
如何處理 Lambda 程式碼和基礎設施的版本同步?
建議將 Lambda 程式碼和 Terraform 放在同一個 Repository,使用統一的 CI/CD Pipeline 部署。這樣可以確保程式碼變更和基礎設施變更同步進行,避免版本不一致。
AWS CDK 和 Terraform 哪個更適合部署 Lambda?
如果團隊熟悉 TypeScript/Python 且專案只用 AWS,CDK 是不錯的選擇,程式語言的靈活性可以減少重複程式碼。如果需要管理多雲環境或團隊已經有 Terraform 經驗,Terraform 會是更好的選擇。
結語
Terraform 管理 Lambda 的核心價值在於 可重複性 和 可追蹤性。
從本文學到的重點:
- 基礎設定:Provider、IAM Role、Lambda Function 三件套
- Module 使用:善用社群 Module 減少重複程式碼
- 進階技巧:版本管理、Secrets 處理、多種程式碼部署方式
- CI/CD 整合:自動化部署流程設計
下一步建議:
- 從一個簡單的 Lambda 開始實作
- 逐步加入 Module 和 CI/CD
- 建立團隊的 Terraform 最佳實踐規範
IaC 架構需要專業規劃?
如果你正在:
- 導入 Terraform 管理 AWS 資源
- 設計 CI/CD 自動部署流程
- 規劃多環境的基礎設施
預約架構諮詢,我們會在 24 小時內回覆你。 正確的 IaC 設計能大幅降低維運成本與風險。
相關文章
Lambda@Edge 完整指南:CDN 邊緣運算應用與實戰
Lambda@Edge 是什麼?本文完整解析 CDN 邊緣運算,包含執行時機點、限制、實戰應用(URL 重寫、A/B 測試、圖片優化),幫你在 CloudFront 上實現進階功能。
AWS LambdaAWS Lambda 費用計算完整指南:Free Tier、計價方式與省錢攻略【2025】
AWS Lambda 怎麼收費?本文詳解 Lambda 計費模式、Free Tier 免費額度、Memory Size 成本關係,並提供實用省錢攻略,幫你找出最佳成本效益。
AWS LambdaAWS Lambda + API Gateway 整合教學:建立 REST API 完整指南
如何用 Lambda + API Gateway 建立 REST API?本文完整教學 Lambda Proxy Integration、Lambda Authorizer 設定,並比較 Function URL 的適用場景。