一、无服务器部署的特殊约束
1.1 与传统容器部署的差异
维度 容器部署 无服务器
部署单元 image + 编排清单 function 包 + 配置
启动延迟 秒级(冷启动可忽略) 毫秒到秒级(冷启动敏感)
状态 进程常驻 无状态、随时回收
版本模型 滚动更新 版本 + 别名(Lambda)
配置方式 ConfigMap/Secret 环境变量 + 参数存储
体积上限 数百 MB 常见 压缩包 50MB / 解压 250MB
回滚 重新调度旧 image 切换别名指向旧版本
无服务器的「版本 + 别名」模型是它最大的优势:发布不需要重建环境,只需把别名从 v41 指到 v42,回滚同理,秒级生效且零冷启动惩罚。
1.2 流水线的通用骨架
1) 安装依赖(带缓存)
2) 静态检查与单元测试
3) 构建打包(bundle / zip / image)
4) 用 OIDC 换取云凭证
5) 部署到非生产别名并冒烟测试
6) 晋级到生产别名(或按流量比例灰度)
7) 归档制品与部署元数据
二、AWS Lambda 的凭证准备
2.1 用 OIDC 替代 AccessKey
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-lambda-deploy
role-session-name: gha-${{ github.run_id }}
aws-region: ap-northeast-1
- name: Verify identity
run: aws sts get-caller-identity
2.2 信任策略收紧
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
}
}
}
]
}
sub 一定要限定到仓库与分支,用 repo:my-org/* 这类通配等于把角色开放给组织内所有仓库。
2.3 部署所需的最小权限
lambda:UpdateFunctionCode / UpdateFunctionConfiguration
lambda:PublishVersion / UpdateAlias / GetAlias / GetFunction
lambda:InvokeFunction # 冒烟测试直调函数时必需
s3:PutObject / GetObject # 存放部署包
Resource 收窄到具体函数:
arn:aws:lambda:ap-northeast-1:123456789012:function:my-func*
三、AWS Lambda 的三条部署路径
3.1 SAM
- uses: aws-actions/setup-sam@v2
with:
use-installer: true
- name: Build and deploy
run: |
sam build --use-container
sam deploy \
--stack-name my-app-prod \
--s3-bucket my-artifacts \
--capabilities CAPABILITY_IAM \
--no-confirm-changeset \
--no-fail-on-empty-changeset \
--parameter-overrides Stage=prod
已有 template.yaml、团队熟悉 CloudFormation 时最省事;sam local 还能本地调试。代价是变更集较慢,回滚依赖 CFN 回滚而非别名切换。
3.2 CDK
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Diff then deploy
run: |
npm ci
npx cdk diff MyStack
npx cdk deploy MyStack --require-approval never
用 TypeScript 描述基础设施,条件分支与循环能力最强,可复用构造器;代价是需要 bootstrap 环境,权限边界要单独收紧。
3.3 SST
- name: Deploy with SST
run: npx sst deploy --stage prod
env:
AWS_REGION: ap-northeast-1
全栈应用、需要把前端与函数一起部署时体验最好,内置 Live Lambda 本地实时调试;抽象层较厚,对版本较敏感。
维度 SAM CDK SST
抽象层 中 高 最高
本地调试 好(sam local)一般 好(Live Lambda)
回滚方式 CFN 回滚 CFN 回滚 框架封装
学习成本 低 中 低
生态绑定 AWS AWS AWS + 前端框架
3.4 Serverless Framework 仍然可用
- name: Deploy
run: |
npm i -g serverless@3
serverless deploy --stage prod --region ap-northeast-1
注意三点
1) serverless v4 起对组织使用有授权要求,迁移前先确认许可
2) 它本质也是 CloudFormation 的封装,回滚语义与 CFN 一致
3) 大量插件依赖 Node 版本,锁定 node-version 避免踩坑
四、Lambda 的打包形态
4.1 压缩包与层
mkdir -p dist && cp -r src/* dist/
cd dist && zip -r ../function.zip . -x '*.test.js'
aws lambda update-function-code \
--function-name my-func \
--zip-file fileb://function.zip \
--publish
依赖体积大(如 pandas)时抽到层里,可显著缩小函数包并复用缓存:
mkdir -p layer/nodejs
cd layer && npm install --omit=dev --prefix ./nodejs
zip -r layer.zip nodejs
aws lambda publish-layer-version \
--layer-name my-deps \
--zip-file fileb://layer.zip \
--compatible-runtimes nodejs20.x
4.2 容器镜像
FROM public.ecr.aws/lambda/nodejs:20
COPY package*.json ${LAMBDA_TASK_ROOT}/
RUN npm ci --omit=dev
COPY src/ ${LAMBDA_TASK_ROOT}/
CMD [ "index.handler" ]
aws ecr get-login-password --region ap-northeast-1 \
| docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-func:$GITHUB_SHA .
docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-func:$GITHUB_SHA
选择建议
纯代码 + 依赖不多 zip 包,冷启动最快
依赖体积大(如 pandas) 层,复用缓存
依赖原生库 / 自定义运行时 容器镜像,上限 10GB
注意:镜像函数冷启动通常比 zip 慢 1 秒以上
五、版本、别名与灰度回滚
5.1 发布版本并指向别名
aws lambda update-function-code --function-name my-func --zip-file fileb://function.zip
aws lambda publish-version --function-name my-func --description "sha-abc1234"
VERSION=$(aws lambda list-versions-by-function \
--function-name my-func \
--query 'Versions[-1].Version' --output text)
aws lambda update-alias \
--function-name my-func --name live --function-version "$VERSION"
5.2 加权别名做灰度
- name: Canary 10 percent
run: |
aws lambda update-alias --function-name my-func --name live \
--function-version "$VERSION" \
--routing-config "{\"AdditionalVersionWeights\":{\"$PREV\":0.9}}"
- name: Smoke test canary
run: ./scripts/smoke.sh https://api.example.com
- name: Promote to 100 percent
run: |
aws lambda update-alias --function-name my-func --name live \
--function-version "$VERSION" --routing-config '{}'
5.3 回滚
aws lambda update-alias --function-name my-func --name live --function-version "$PREV"
回滚要点
1) 别名切换是原子的,不需要重新部署代码
2) 版本不可变,因此旧版本永远可回
3) 给函数配置版本保留,避免历史版本无限堆积
4) 如果改的是环境变量,回滚别名不会回滚配置,需要单独处理
六、Cloudflare Workers 部署
6.1 wrangler-action
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v4
- name: Deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy
6.2 wrangler.toml 与绑定
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2026-09-01"
compatibility_flags = ["nodejs_compat"]
[vars]
ENVIRONMENT = "production"
[[kv_namespaces]]
binding = "CACHE"
id = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "11111111-2222-3333-4444-555555555555"
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "my-assets"
[observability]
enabled = true
绑定类型速记
KV 最终一致的键值存储,读多写少
D1 基于 SQLite 的关系库,支持迁移
R2 对象存储,兼容 S3 API,无出口流量费
Durable Objects 强一致的有状态单元
Queues 消息队列
6.3 预览环境与灰度回滚
PR 场景下用 wrangler versions upload 生成预览版本而不影响线上:
- name: Upload preview version
if: github.event_name == 'pull_request'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: versions upload
npx wrangler versions deploy <new-version-id>@20% <old-version-id>@80%
npx wrangler deployments list
npx wrangler rollback
Workers 的版本模型与 Lambda 别名类似:
1) 每次 upload 产生一个不可变版本
2) deploy 决定哪个版本接收多少流量
3) 回滚不需要重新构建,直接切版本
6.4 冷启动与体积约束
体积上限(免费 / 付费)
压缩后 3 MiB / 10 MiB,这是硬约束,打包时必须做 tree-shaking
冷启动
基于 V8 isolate,通常 5ms 以内
但首次加载大 bundle 有解析开销,控制在 1MB 以内最佳
常见踩坑
1) 引入 Node 内置模块需开启 nodejs_compat 并注意兼容日期
2) 打包时误把 devDependencies 打进去,体积翻倍
3) 大 JSON 常量直接 import 导致 bundle 膨胀,应改用 KV
# 检查打包体积,定位膨胀源
npx wrangler deploy --dry-run --outdir=dist && du -sh dist/*
七、环境变量与密钥管理
7.1 分层存放
非敏感配置 wrangler.toml 的 [vars] / Lambda 的环境变量
敏感配置 GitHub Secrets + 部署时注入
运行时可轮换 Secrets Manager / Parameter Store,启动时拉取
Cloudflare 密钥 wrangler secret put / Dashboard 加密变量
npx wrangler secret put DATABASE_URL
npx wrangler secret list
7.2 只在需要的环境注入
jobs:
deploy-prod:
environment:
name: production
steps:
- uses: actions/checkout@v4
- run: npx sst deploy --stage prod
env:
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
用 environment secret 而非仓库 secret,可以让未绑定 production 环境的 job 完全拿不到生产凭证。
7.3 与 Vercel、Netlify 的对比
平台 部署动作 预览环境 回滚
Cloudflare wrangler deploy 版本上传 + 流量比例 版本切换
Vercel vercel deploy --prod 每个 PR 一个预览 URL 重新提升旧部署
Netlify netlify deploy Deploy Preview 重新发布旧构建
AWS Lambda 别名切换 别名 + 加权路由 别名指回旧版本
四者的共同点是「不可变部署 + 原子切换」,差异在预览环境的默认行为:Vercel 与 Netlify 默认给每个 PR 生成独立 URL,Cloudflare 与 Lambda 需要自己实现。
八、部署后冒烟测试
8.1 直调函数与走公网
aws lambda invoke \
--function-name my-func:live \
--payload '{"path":"/health","httpMethod":"GET"}' \
--cli-binary-format raw-in-base64-out \
response.json
grep -q '"statusCode":200' response.json
for i in $(seq 1 10); do
code=$(curl -s -o /dev/null -w '%{http_code}' https://api.example.com/health)
if [ "$code" = "200" ]; then echo "healthy after $i attempts"; exit 0; fi
sleep 3
done
echo "smoke test failed with $code"; exit 1
8.2 失败即回滚
- name: Rollback on failure
if: failure()
run: |
aws lambda update-alias --function-name my-func --name live \
--function-version "${{ env.PREV_VERSION }}"
冒烟测试设计原则
1) 只测关键路径:健康检查、一次真实读写
2) 设置重试与退避,无服务器冷启动可能让首次请求超时
3) 断言具体字段而非只看状态码
4) 失败必须触发回滚,不能只打印日志
5) 冒烟脚本独立成文件,本地可复用
总结
无服务器部署把「环境」变成了「版本加别名」,流水线的重心随之从滚动更新编排转向不可变制品与原子切换。AWS Lambda 侧用 OIDC 换临时凭证替代长期 AccessKey,按团队熟悉度选 SAM、CDK、SST 或 Serverless Framework,打包按依赖体积在 zip、层、容器镜像之间取舍,发布时先 publish-version 再更新别名,灰度靠加权路由,回滚只需把别名指回旧版本。Cloudflare Workers 侧用 wrangler-action 部署,配置写在 wrangler.toml,KV、D1、R2 通过绑定暴露,预览环境用版本上传,灰度与回滚用 wrangler versions deploy 的流量比例。两者共同的收尾动作是部署后冒烟测试与失败自动回滚,这一环缺了,前面所有的版本与别名机制都只是摆设。
延伸阅读:
- GitHub Actions 部署到 Vercel — 前端平台的部署与预览
- GitHub Actions OIDC 云认证 — 免密上云完整配置
- GitHub Actions 容器化 CI/CD — 镜像构建与推送
- GitHub Actions Environment 审批门禁 — 生产发布审批
- GitHub Actions 安全加固 — 权限最小化实践
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。