企业项目:私网高可用 SQL、异步投影与受控发布
验证:静态检查 · 核验于 2026-10-13
- 适用版本
- 云环境版本未实测;适用条件见正文
- 验证范围
- 已检查文档构建、链接与脚本语法;未执行云端实验。
复用 订单参考工程,增加私网 HA SQL、异步投影和受控发布。尚未完成目标云环境的端到端验收;逐服务入门实验见 订单项目。
1. 环境与代码#
从本仓库已提交版本开始,在仓库终端执行。独立 worktree 保留真实提交号;未提交修改不会进入构建。不要只复制源码目录,否则发布脚本无法取得 Git 提交记录。运行产生的秘密、状态和计划文件不得提交。
使用独立计费项目、区域、Terraform 远端状态桶、Artifact Registry 私有仓库与短期登录。需要 gcloud、Terraform ≥1.11、Docker、Python 3。区域须同时支持所选 Cloud SQL 与 Firestore 位置。预计主要费用来自区域数据库及 Cloud Run 最小实例;设置预算,完成后按第 7 节收尾。
export SOURCE_REPO=$(git rev-parse --show-toplevel)
export WORKDIR=$(mktemp -d /tmp/gcp-orders.XXXXXX)
git -C "$SOURCE_REPO" worktree add --detach "$WORKDIR" HEAD
cd "$WORKDIR/static/examples/enterprise-orders"
git rev-parse HEAD
export GOOGLE_CLOUD_PROJECT=REPLACE_ISOLATED_PROJECT
export REGION=REPLACE_REGION
export TF_VAR_project="$GOOGLE_CLOUD_PROJECT"
export TF_VAR_region="$REGION"
export TF_VAR_invoker='user:REPLACE_CORPORATE_EMAIL'
export TF_VAR_notification_email='REPLACE_APPROVED_ONCALL_EMAIL'
gcloud config set project "$GOOGLE_CLOUD_PROJECT"
gcloud auth application-default loginTerraform 配套 gcp/main.tf 创建专属 VPC(虚拟私有云)、Private Services Access、REGIONAL Cloud SQL、持续日志与备份、独立 API/worker/scheduler Service Account、秘密容器、私有归档桶、Firestore 数据库、Cloud Run 与调度。provider 固定 7.27.0,提交生成的锁文件;企业流水线用 Workload Identity Federation代替用户登录,不创建 SA(Service Account)key。
2. 构建门禁与基础设施#
先由平台管理员创建构建资源;与后续工作负载权限分开。已有受控资源则复用,不重复创建。下面在同一项目执行,BUILD_SA 是已有的短期构建身份,来源见 无密钥 CI。
export ARTIFACT_REPO=orders-build
export STATE_BUCKET="${GOOGLE_CLOUD_PROJECT}-orders-tfstate"
export BUILD_SA='REPLACE_BUILD_SA_EMAIL'
gcloud services enable artifactregistry.googleapis.com cloudkms.googleapis.com storage.googleapis.com
gcloud artifacts repositories create "$ARTIFACT_REPO" --location="$REGION" --repository-format=docker --immutable-tags
gcloud storage buckets create "gs://$STATE_BUCKET" --location="$REGION" --uniform-bucket-level-access --public-access-prevention
gcloud storage buckets update "gs://$STATE_BUCKET" --versioning
gcloud kms keyrings create orders-signing --location="$REGION"
gcloud kms keys create image-signing --keyring=orders-signing --location="$REGION" --purpose=asymmetric-signing --default-algorithm=ec-sign-p256-sha256
gcloud artifacts repositories add-iam-policy-binding "$ARTIFACT_REPO" --location="$REGION" --member="serviceAccount:$BUILD_SA" --role=roles/artifactregistry.writer
gcloud kms keys add-iam-policy-binding image-signing --keyring=orders-signing --location="$REGION" --member="serviceAccount:$BUILD_SA" --role=roles/cloudkms.signerVerifier
export KMS_KEY_URI="gcpkms://projects/$GOOGLE_CLOUD_PROJECT/locations/$REGION/keyRings/orders-signing/cryptoKeys/image-signing/cryptoKeyVersions/1"
export PUBLIC_KEY_FILE="$PWD/image-signing.pub"
gcloud kms keys versions get-public-key 1 --key=image-signing --keyring=orders-signing --location="$REGION" --output-file="$PUBLIC_KEY_FILE"
gcloud auth configure-docker "$REGION-docker.pkg.dev"Terraform 执行身份在 state 桶需要对象读写权限;不授予构建身份 state 权限。首次人工实验由获授权操作者执行;流水线切换至 BUILD_SA 时同时配置 gcloud 与 Application Default Credentials,不能只设置 CLI impersonation 后假定 cosign 也使用该身份。记录公钥指纹并从可信渠道校验。
配套 ci/publish-gcp.sh 构建一次,再对同一镜像运行数据库/业务测试、Trivy 扫描、推送和 cosign 签名验签。可信单执行器节点需安装 Docker、gcloud、Trivy、cosign,设置批准的 BASE_IMAGE/POSTGRES_IMAGE 摘要、ARTIFACT_REPO、KMS_KEY_URI 和 PUBLIC_KEY_FILE;提前用 gcloud 配置该区域 Docker 凭据助手。短期构建身份只具备本仓库推送、指定 KMS 签名权限;仓库标签启用不可变。执行 bash ci/publish-gcp.sh 后读取 evidence/image.txt 填入 IMAGE。失败测试、扫描不可用、签名失败均不得进入部署;生产不重新构建。
export IMAGE='REPLACE_REGION-docker.pkg.dev/REPLACE_PROJECT/REPLACE_REPO/orders@sha256:REPLACE_DIGEST'
export TF_VAR_image="$IMAGE"
terraform -chdir=gcp init -backend-config="bucket=$STATE_BUCKET" -backend-config='prefix=orders-enterprise'
terraform -chdir=gcp plan -out=foundation.tfplan
terraform -chdir=gcp apply foundation.tfplan默认 deploy_services=false,先建数据库和秘密容器,再初始化。Cloud SQL 两层删除保护、14 份备份、7 天事务日志;应用并发 8、最多 6 实例,加 worker/运维预留后核对实际 max_connections。数据加密使用服务管理密钥;需要 CMEK(客户管理加密密钥)时先配置密钥授权及恢复演练,再建实例,不能事后假定已有。
3. 私网迁移与秘密初始化#
使用本项目内私有迁移机,通过 IAP(身份感知代理)SSH 隧道访问 SQL,数据库不分配公网地址。操作员预先具备 IAP 隧道、OS Login 和对应实例管理权限;新 VM 不附带云 Service Account。
export ZONE=REPLACE_REGION_ZONE
export DB_IP=$(terraform -chdir=gcp output -raw database_ip)
gcloud compute firewall-rules create orders-iap-ssh --network=orders-enterprise --allow=tcp:22 --source-ranges=35.235.240.0/20 --target-tags=orders-migration
gcloud compute instances create orders-migration --zone="$ZONE" --subnet=orders-enterprise --no-address --no-service-account --no-scopes --machine-type=e2-small --image-family=debian-12 --image-project=debian-cloud --metadata=enable-oslogin=TRUE --tags=orders-migration
# 保持运行,另一个终端继续初始化。
gcloud compute ssh orders-migration --zone="$ZONE" --tunnel-through-iap -- -N -L "127.0.0.1:15432:$DB_IP:5432"新终端在相同目录与项目环境中:
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
export CONFIRM_NEW_INSTANCE="$GOOGLE_CLOUD_PROJECT/orders-enterprise"
python3 gcp/bootstrap.py初始化脚本会重设 postgres 密码并迁移数据库,只能用于本篇新建实例。 它把运行角色 DSN、token 哈希和实例 CA 写入 Secret Manager,不写入 Terraform state。将
secrets/owner-*和备用站点密码保存到企业保险库;既有实例的凭据轮换走独立变更,不能重跑初始化代替。
连接使用实例专属 CA 的 verify-ca 验证及私网 IP;不是关闭证书校验。若改为共享 CA 模式,必须同时配置可验证的实例 DNS 名和 verify-full,不能照搬该连接字符串。Cloud SQL 的管理员身份与 API/worker 角色分离。
4. 运行实际异步处理#
terraform -chdir=gcp plan -var=deploy_services=true -out=runtime.tfplan
terraform -chdir=gcp apply runtime.tfplan
export BASE_URL=$(terraform -chdir=gcp output -raw api_url)
# 服务保持 IAM 私有;调用者只具备该服务的 run.invoker。
umask 077
gcloud auth print-identity-token > secrets/cloud-run-id-token
export CLOUD_RUN_ID_TOKEN_FILE=secrets/cloud-run-id-token
export TOKEN_FILE=secrets/token
# 先启动一次 worker;后续 Scheduler 每分钟启动,事务锁防止重复领取。
gcloud run jobs execute orders-worker --region="$REGION" --async
python3 tests/acceptance.py
gcloud run jobs execute orders-projector --region="$REGION" --wait验收脚本通过 X-Serverless-Authorization 携带 Cloud Run 身份 token,Authorization 留给应用租户 token;过期后重新签发。服务入口虽允许网络到达,但没有 allUsers 授权,匿名调用必须 401/403。
worker 先提交 SQL 账本与投影事件;projector 自动把完成订单写入 Storage 和 Firestore,再确认投影事件。归档使用 ifGenerationMatch=0,Firestore 使用仅创建条件,重放时验证已有内容完全一致;中途崩溃会重试,不会用覆盖写掩盖冲突。Firestore IAM(身份与访问管理)条件仅授权 orders-projection 数据库,仍需验证其他数据库访问被拒绝。
通过验收输出的 ORDER_ID 对账:
export ORDER_ID=REPLACE_ACCEPTANCE_OUTPUT_ID
gcloud storage cat "gs://$GOOGLE_CLOUD_PROJECT-orders-enterprise-archive/orders/$ORDER_ID.json"
curl --fail -H "Authorization: Bearer $(gcloud auth print-access-token)" "https://firestore.googleapis.com/v1/projects/$GOOGLE_CLOUD_PROJECT/databases/orders-projection/documents/orders/$ORDER_ID"
# 用只读运维连接检查待投影数;SQL 应最终返回 0。
# SELECT count(*) FROM cloud_projection WHERE NOT done;IAM 最小授权:API 只能读自己的三个秘密;worker 不能修改/删除归档;Scheduler 只能运行两个 Job。秘密使用数字版本,不用 latest;轮换时创建新版本、更新 Terraform 参数、部署并验证后才撤销旧版本。
5. 发布、告警与故障验收#
配套 monitoring.tf 已创建两个 Job 失败/10 分钟执行缺失、SQL CPU/磁盘持续超 80% 告警与指定邮件渠道;先至少成功运行一次 Job,缺失告警才有已知时间序列。验证收件人后再故障注入。另监控 Cloud Run 5xx、延迟、SQL 连接/复制状态;订单积压需通过私有指标采集或定时 SQL 检查接入告警,规则和闭环步骤见 SRE 实验。先建立通知渠道,再做故障注入。
首次部署后记录旧 revision;后续用同一镜像摘要生成无流量候选,避免 Terraform apply 直接切生产:
export OLD_REVISION=REPLACE_CURRENT_SERVING_REVISION
export NEW_IMAGE=REPLACE_APPROVED_IMAGE_AT_DIGEST
gcloud run deploy orders-enterprise --region="$REGION" --image="$NEW_IMAGE" --no-traffic --tag=candidate
# 查询 tagged URL,重签发适用身份令牌,执行完整业务验收。
gcloud run services describe orders-enterprise --region="$REGION" --format='yaml(status.traffic)'
# 候选通过并完成指标观察后,按实际候选 revision 提升。
export NEW_REVISION=REPLACE_CANDIDATE_REVISION
gcloud run services update-traffic orders-enterprise --region="$REGION" --to-revisions="$OLD_REVISION=95,$NEW_REVISION=5"先完成金丝雀观察与业务对账,再决定继续发布或回滚。下面只在故障或本次演练明确要求回滚时执行,不能紧接切流一起粘贴:
# 观察窗口内业务错误或对账不一致,恢复先前记录的版本。
gcloud run services update-traffic orders-enterprise --region="$REGION" --to-revisions="$OLD_REVISION=100"此阶段暂停 Terraform 对该服务的写入,只允许单一发布流程;通过后更新 IaC 的 image/traffic 至批准状态,执行 plan 确认没有意外流量变化后恢复管理,不能让两个工具并发争用。首次演练按上例故意回退;正式全量另审批 NEW_REVISION=100。
故障演练:暂停 projector 的 Scheduler、继续创建订单,核对执行缺失告警和 SQL 待投影计数;恢复后事件清零且三个存储金额一致。在隔离环境运行 gcloud sql instances failover orders-enterprise,记录错误窗口、连接恢复和账本一致性。PITR(时间点恢复)必须恢复到独立实例并核对数据,见 GCP 灾备;高可用不是误删除恢复。
6. 接入已有服务体系#
域名、负载均衡、Cloud Armor、证书和 IAP 员工入口继续使用 ProjectProduction 的独立章节;本服务默认 IAM 私有,不为接入 LB 直接授予 allUsers。企业身份网关应验证最终用户并换取受控服务身份,应用仍执行租户授权。
7. 清理与证据#
停止两个 Scheduler→等待 Job 结束→停止合成流量→核对 SQL/outbox/归档→创建并验证保留备份。先记录资源清单,再移除本次迁移 VM 与 IAP 防火墙规则。删除运行服务和数据资源须分别关闭相应删除保护并审查 plan;对象桶非空不会强删。不要清空远端 state 桶,也不要把 terraform destroy 当数据库备份方案。