AWS Lambda 现在可以直接引用客户自有 S3 存储桶中的部署包。这个变化解决的是账户在单个 Region 内累计存放多少函数代码的问题,而不是放宽单个函数部署包的大小限制。与此同时,Lambda 托管代码存储的默认配额也从 75 GB 提升到了 300 GB。
这两个数字很容易让人误判:团队可以保存更多函数版本,并不代表某个 ZIP 包可以变得更大。
变化发生在存储层,而不是函数打包层
过去,函数版本、Layer 和持续发布产生的部署包会不断消耗 Lambda 的区域级代码存储配额。函数数量多、发布频率高,或者长期保留旧版本的账户,容易在 CI/CD 发布时撞上配额上限。
自主管理代码存储把部署包留在客户控制的 S3 存储桶中,由 Lambda 引用对应对象。它带来两个直接结果:
- 使用自主管理模式的代码不再受 Lambda 每 Region 代码存储总配额约束。
- 继续使用 Lambda 托管存储的账户,默认可用容量从 75 GB 增加到 300 GB。
但现有的单函数部署包限制没有变化。遇到依赖过重、原生库体积庞大等问题时,仍然需要通过裁剪依赖、拆分 Layer、延迟加载资源或调整部署形态来处理,不能把区域配额提升当成函数体积提升。
覆盖 S3 对象不等于发布新代码
另一个关键边界是部署触发机制。即使函数引用的是 S3 对象,直接覆盖同一个对象键也不会自动让 Lambda 加载新内容。上传完成后,仍然必须调用 UpdateFunctionCode。
因此,不建议让发布流水线长期覆盖 releases/function.zip。更稳妥的办法是使用提交哈希、构建号或内容摘要生成不可变对象键,再显式更新函数代码。这样既能避免 S3 缓存与并发覆盖问题,也能准确追踪某个函数版本对应哪个制品。
可以这样实践:用不可变对象键发布
下面的 Bash 示例假设目标 Lambda 函数、S3 存储桶以及必要的 IAM 权限已经配置完成。运行前需要修改 FUNCTION_NAME、BUCKET 和 AWS_REGION。示例创建一个最小 Python 函数,将 ZIP 包上传到带时间戳的对象键,然后显式调用更新接口。
#!/usr/bin/env bash
set -euo pipefail
FUNCTION_NAME="orders-handler"
BUCKET="my-lambda-artifacts"
AWS_REGION="us-east-1"
BUILD_ID="$(date -u +%Y%m%dT%H%M%SZ)"
S3_KEY="lambda/${FUNCTION_NAME}/${BUILD_ID}.zip"
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
cat > "$WORK_DIR/lambda_function.py" <<'PY'
import json
def handler(event, context):
return {
"statusCode": 200,
"body": json.dumps({"build": "self-managed-storage-example"}),
}
PY
(
cd "$WORK_DIR"
zip -q function.zip lambda_function.py
)
aws s3 cp \
"$WORK_DIR/function.zip" \
"s3://${BUCKET}/${S3_KEY}" \
--region "$AWS_REGION"
aws lambda update-function-code \
--function-name "$FUNCTION_NAME" \
--s3-bucket "$BUCKET" \
--s3-key "$S3_KEY" \
--region "$AWS_REGION"
aws lambda wait function-updated \
--function-name "$FUNCTION_NAME" \
--region "$AWS_REGION"
aws lambda get-function-configuration \
--function-name "$FUNCTION_NAME" \
--region "$AWS_REGION" \
--query '{FunctionName:FunctionName,LastModified:LastModified,CodeSize:CodeSize}'
这段流程最重要的不是 ZIP 命令,而是两个部署约束:每次构建产生新的 S3 键,并且每次上传后都执行 update-function-code。如果改为覆盖固定键,也仍然需要调用更新接口;仅执行 aws s3 cp 不会完成发布。
在生产环境中,还可以启用 S3 Versioning,并把对象版本 ID 写入部署记录。制品生命周期规则也应谨慎设置:如果 Lambda 仍需读取某个对象,就不能提前删除对应版本。
Terraform 暂时不要假设已有原生字段
Terraform Provider 对这一能力的支持仍是一个待处理的增强请求。因此,现阶段不应猜测尚未发布的资源参数,也不应把未经验证的字段写进基础设施模块。
可以把职责暂时拆开:Terraform 管理函数、IAM、S3 存储桶和加密策略,发布系统使用 AWS CLI 或 SDK 上传制品并调用 UpdateFunctionCode。如果必须把部署动作接入 Terraform,可以用外部脚本过渡,但要明确它会引入本地工具依赖、状态不可见和重复执行等问题。
例如,下面是一个可改造的过渡方案。它假设前面的发布脚本保存为 scripts/deploy-lambda.sh:
variable "artifact_digest" {
type = string
description = "SHA-256 digest of the Lambda deployment artifact"
}
resource "terraform_data" "deploy_lambda" {
triggers_replace = {
artifact_digest = var.artifact_digest
}
provisioner "local-exec" {
command = "bash scripts/deploy-lambda.sh"
}
}
这个配置只适合作为短期桥接。Provider 提供正式支持后,应优先迁移到可声明、可比较并能进入 Terraform 状态的资源配置。
采用前检查四件事
引入自主管理代码存储前,建议确认以下边界:
- 当前瓶颈是否真的是区域级累计代码存储,而不是单函数包大小。
- Lambda 服务主体和部署角色是否具备读取目标 S3 对象及 KMS 解密所需的权限。
- 发布流水线是否会在上传后显式调用
UpdateFunctionCode。 - S3 Versioning、生命周期、跨区域部署和制品回滚策略是否与函数版本管理一致。
对于函数和版本数量较多的账户,这项能力能把代码制品纳入现有的 S3 治理体系,并解除区域累计容量压力。对于只维护少量函数的团队,300 GB 的托管默认配额可能已经足够,继续使用托管模式反而更省运维成本。选择标准应该是制品规模、治理需求和发布复杂度,而不是单纯追求新的存储方式。