当一家公司同时使用 AWS 做主要业务、GCP 做数据分析、Azure 做企业集成时,“部署"就不再是一条线性流水线,而是一张有依赖关系的有向图:数据库要先于应用、主区域要先于从区域、网络层要先于计算层。更难的是,基础设施会被人在控制台上"手动改一下”,导致声明式配置(IaC)与真实状态之间产生漂移(drift)——而漂移如果不被检测,下一次 apply 就会把这些手改覆盖掉,或者产生冲突。
本文聚焦两个问题:如何用 GitHub Actions 编排跨云的多目标部署,以及如何把漂移检测变成一条持续运行的流水线。
一、多云部署的复杂度来源
1.1 三块复杂度
| 复杂度 | 具体表现 |
|---|---|
| 身份 | 每个云有自己的联合身份机制 |
| 状态 | 每个云/区域有独立的 state 与锁 |
| 编排 | 依赖顺序、并行度、部分失败处理 |
把这三块分开治理,是多云编排能被维护的前提。
1.2 不要试图"统一一切"
多云编排里最常见的反模式,是追求一个"万能抽象层"来抹平三家云的差异。结果是抽象层越来越厚、越来越难调试。务实的做法是:只统一编排层(workflow 的 job 图),不统一实现层(每个云用各自原生工具)。
统一编排层:GitHub Actions job 图(needs / matrix / environment)
│
├── AWS → terraform + aws-cli + OIDC(assume-role)
├── GCP → terraform + gcloud + OIDC(WIF)
└── Azure → terraform + az-cli + OIDC(federated credential)
二、统一编排的抽象
2.1 用矩阵表达"多目标"
多云部署最自然的抽象是矩阵:把"云 + 区域 + 环境"展开成组合。
jobs:
deploy:
strategy:
fail-fast: false
matrix:
include:
- cloud: aws
region: us-east-1
env: prod
- cloud: aws
region: eu-west-1
env: prod
- cloud: gcp
region: us-central1
env: prod
- cloud: azure
region: eastus
env: prod
runs-on: ubuntu-latest
environment: ${{ matrix.env }}-${{ matrix.cloud }}
steps:
- uses: actions/checkout@v4
- name: Deploy
run: ./scripts/deploy.sh --cloud=${{ matrix.cloud }} --region=${{ matrix.region }}
fail-fast: false 让一个云失败不取消其他云——多云场景下,你能接受"AWS 成功、GCP 失败",但不能接受"GCP 失败导致 AWS 被取消在中间状态"。
2.2 用 reusable workflow 收敛差异
把每个云的部署逻辑封装成可复用工作流,调用方只传参数:
# .github/workflows/deploy-aws.yml
on:
workflow_call:
inputs:
region:
required: true
type: string
env:
required: true
type: string
jobs:
apply:
runs-on: ubuntu-latest
environment: ${{ inputs.env }}-aws
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/gha-deploy
aws-region: ${{ inputs.region }}
- run: terraform -chdir=infra/aws apply -auto-approve
调用方:
jobs:
aws:
uses: ./.github/workflows/deploy-aws.yml
with:
region: us-east-1
env: prod
可复用工作流的价值在于把"每个云各写一遍"的重复逻辑收敛到一处,调用方只关心参数。
2.3 用 environment 隔离权限
每个云 + 环境组合对应一个 GitHub Environment,在其中配置该环境的密钥与审批规则:
prod-aws → AWS OIDC role + 需要 2 人审批
prod-gcp → GCP WIF + 需要审批
staging-aws → AWS OIDC role + 无需审批
这样"权限"与"审批"都绑定在 environment 上,workflow 本身不持有任何长期密钥。环境门禁的机制可参考 /github-actions-environment-gates/。
三、OIDC 与多云端身份联邦
3.1 为什么必须用 OIDC
长期密钥(AWS Access Key、GCP Service Account JSON)一旦泄漏就是灾难,且轮换麻烦。OIDC 让 GitHub 在运行时签发一个短期令牌,云厂商验证该令牌的签发者与声明后,换发临时凭证。核心前提是:信任策略必须收紧到 repo 与分支。
3.2 三家云的差异
| 维度 | AWS | GCP | Azure |
|---|---|---|---|
| 机制名 | OIDC + AssumeRoleWithWebIdentity | Workload Identity Federation | Federated Credentials |
| 主体标识 | repo:org/repo:ref | attribute.repository | repo:org/repo:ref |
| 配置位置 | IAM Role 信任策略 | Workload Identity Pool | App Registration |
| 认证 Action | aws-actions/configure-aws-credentials | google-github-actions/auth | azure/login |
3.3 AWS 配置
信任策略里必须精确限制 sub:
{
"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"
}
}
}]
}
StringLike 里的 ref:refs/heads/main 是安全关键——如果写成 repo:my-org/my-repo:*,那么任何分支(包括攻击者的 PR 分支)都能扮演该角色。
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
aws-region: us-east-1
3.4 GCP 配置
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123/locations/global/workloadIdentityPools/gha/providers/github
service_account: gha-deploy@my-project.iam.gserviceaccount.com
GCP 的 WIF 需要配置 attribute.repository 的映射,把 GitHub 的 claim 映射为 GCP 的属性,再据此授予权限。
3.5 Azure 配置
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
注意:Azure 的 client-id 等本身不是密钥(它们不是机密),真正的机密是联邦凭证的绑定关系——即该 App 只信任来自指定 repo/分支的令牌。更系统的 OIDC 跨云认证可参考 /github-actions-oidc-cloud-auth/。
四、并行与串行编排
4.1 fan-out / fan-in
多云部署的典型形状是"先并行铺开,再汇总验证":
validate(串行,先做 plan 审阅)
│
├────────┬────────┬────────┐
▼ ▼ ▼ ▼
aws-eu aws-us gcp-us azure-eu ← fan-out 并行
│ │ │ │
└────────┴────────┴────────┘
│
▼
verify(fan-in 汇总) ← 统一冒烟测试
jobs:
validate:
runs-on: ubuntu-latest
steps: [...]
deploy:
needs: validate
strategy:
fail-fast: false
matrix: { ... }
steps: [...]
verify:
needs: deploy
if: always()
runs-on: ubuntu-latest
steps:
- run: ./scripts/smoke-test-all-regions.sh
verify 用 if: always() 保证即使部分部署失败,也会执行汇总验证并给出完整状态。
4.2 依赖顺序
有依赖关系的部署用 needs 表达:
jobs:
network:
runs-on: ubuntu-latest
steps: [ ... ] # 先建 VPC / 子网
database:
needs: network
runs-on: ubuntu-latest
steps: [ ... ] # 再建数据库
app:
needs: database
strategy:
matrix: { ... }
steps: [ ... ] # 最后部署应用
4.3 并发控制
同环境的部署不能并发,否则 state 锁会冲突:
concurrency:
group: deploy-prod-aws
cancel-in-progress: false
cancel-in-progress: false 对 IaC 部署尤其重要——中途取消可能留下"资源建了一半"的状态。
4.4 金丝雀与分批
跨区域发布可以分批:先发一个区域观察,再发其余。
jobs:
canary:
environment: prod-aws-canary
steps: [ ... ] # 只发 us-east-1
rollout:
needs: canary
strategy:
matrix:
region: [eu-west-1, ap-southeast-1]
steps: [ ... ] # 观察通过后再发其余
五、基础设施漂移检测
5.1 什么是漂移
漂移指真实基础设施状态与IaC 声明的期望状态不一致。来源通常是:有人在控制台手改了安全组、有人手动扩容了实例、或某个自动化脚本绕过 IaC 改了资源。
漂移的危险在于它让 IaC 不再可信:下次 apply 要么覆盖手改(可能造成故障),要么与之冲突。
5.2 用 plan 检测漂移
Terraform 的 plan 天然就是漂移检测器:如果配置没变但 plan 显示有变更,说明发生了漂移。
terraform plan -detailed-exitcode -out=tfplan
# exit code: 0=无变更 1=出错 2=有变更(含漂移)
-detailed-exitcode 是关键,它让 CI 能区分"无变更"与"有变更":
- name: Drift check
id: plan
run: |
set +e
terraform plan -detailed-exitcode -out=tfplan
code=$?
set -e
echo "exitcode=$code" >> "$GITHUB_OUTPUT"
if [ "$code" -eq 2 ]; then
echo "::warning::检测到漂移或待应用的变更"
elif [ "$code" -eq 1 ]; then
echo "::error::plan 执行失败"; exit 1
fi
5.3 定时漂移巡检
把漂移检测做成定时任务,而不是只在部署时才发现:
name: Drift Detection
on:
schedule:
- cron: "0 6 * * *" # 每日 06:00 UTC
workflow_dispatch:
jobs:
drift:
strategy:
fail-fast: false
matrix:
target: [aws-prod, gcp-prod, azure-prod]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/drift-check.sh ${{ matrix.target }}
- name: Report drift
if: steps.check.outputs.drift == 'true'
run: |
gh issue create \
--title "基础设施漂移: ${{ matrix.target }}" \
--body "定时巡检发现 ${{ matrix.target }} 存在漂移,请核查。" \
--label "infra,drift"
把漂移结果落成 issue,而不是直接自动 apply 修复——漂移修复必须人工确认,因为漂移背后可能是有人为了救火而做的紧急变更。
5.4 漂移的三种处置
| 处置 | 适用 | 风险 |
|---|---|---|
| 接受并回写配置 | 变更合理 | 需更新 IaC 代码 |
| 用 apply 覆盖 | 变更是误操作 | 可能中断业务 |
| 隔离并调查 | 来源不明 | 需人工介入 |
无论哪种,都不应该在无人知晓的情况下自动覆盖。
5.5 多云漂移的聚合
多云环境下,每个云的目标单独检测,最后聚合:
drift(aws-prod) ─┐
drift(gcp-prod) ─┼─► drift-summary(聚合,生成统一报告)
drift(azure-prod)─┘
聚合 job 负责把各云结果汇总成一张表,避免团队去翻三个云的控制台。
六、状态一致性与锁
6.1 每云独立 state
多云项目的 state 必须按云/区域切分,绝不能共用一个 state 文件:
infra/aws/us-east-1/ → s3://tf-state/aws/us-east-1/terraform.tfstate
infra/aws/eu-west-1/ → s3://tf-state/aws/eu-west-1/terraform.tfstate
infra/gcp/us-central1/→ gcs://tf-state/gcp/us-central1/default.tfstate
独立 state 的好处:一个区域的故障不会污染另一个区域的 state,plan/apply 可以完全并行。
6.2 锁与并发
后端锁防止两个 workflow 同时操作同一份 state:
terraform {
backend "s3" {
bucket = "tf-state"
key = "aws/us-east-1/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tf-lock"
encrypt = true
}
}
配合 workflow 的 concurrency,形成"CI 层 + 后端层"的双重防并发。状态与锁的更多细节可参考 Terraform 专题
。
6.3 部分失败的处置
多云部署最常见的故障是"部分成功":AWS 成功、GCP 失败。处置原则:
- 不回滚已成功的云——回滚本身是高风险操作,可能引入新故障;
- 让失败的云可重入——IaC 的
apply应当幂等,重跑不会重复建资源; - 明确记录状态——在 issue 或 PR 中标注哪些云已成功、哪些待重试。
七、故障处理与回滚
7.1 幂等是前提
所有部署脚本必须是幂等的。terraform apply 天然幂等,但自定义脚本(如数据库迁移、缓存预热)往往不是。这类脚本要加"是否已执行"的判断。
7.2 回滚策略
| 层 | 回滚方式 |
|---|---|
| 应用 | 重新部署上一个镜像 tag |
| 配置 | 回退 IaC 代码并 apply |
| 数据 | 依赖备份,谨慎 |
数据层的回滚最难,因此数据库迁移要设计成向前兼容(先加列、后删列),而不是依赖回滚。
7.3 手动门禁
生产环境的多云部署应当在关键节点加人工审批:
environment:
name: prod-aws
# 在 GitHub Environment 设置中配置 required reviewers
审批发生在 apply 之前,而不是之后。审批人应当能看到 plan 输出,据此判断是否放行。
八、落地清单
- 编排层统一、实现层各用原生工具;
- 每个云+环境对应独立 GitHub Environment 与权限;
- OIDC 信任策略收紧到具体 repo 与分支;
- state 按云/区域切分,后端锁已启用;
-
concurrency防止同环境并发部署; - 漂移检测定时运行,结果落成 issue 而非自动修复;
- 部分失败不回滚、可重入、状态可见;
- 生产 apply 前有人工审批。
总结
多云部署的难点不在"连上三个云",而在编排与一致性:用矩阵和 reusable workflow 统一编排层、用 OIDC 让每个云各自验证短期令牌、用独立 state 与后端锁保证并发安全、用 plan -detailed-exitcode 把漂移检测变成每日巡检。其中最容易被忽略的是漂移处置原则——检测可以自动化,修复必须人工确认,因为漂移背后往往藏着一次无人记录的手工救火。把 AWS 侧的落地细节做扎实之后,IaC 流水线的通用模式可进一步参考 /github-actions-iac-terraform/。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。