TT Lab
开始
学习 学习路径 课程

CNPA — 云原生平台工程助理

给 dev 的改动也出现在了 prod

在 TT Lab 中继续学习

目标

在真实的 k3s 和 Argo CD 上,用一个 ApplicationSet 从 Git 部署 dev 和 prod 两个环境。你会经历公共 base 里的一行改动同时改变两个环境的事故;接着把 prod 绑定到晋级分支,使环境之间的晋级变成 Git 操作;最后确认漂移恢复、晋级和回退分别会在仓库和集群中留下什么。

为什么重要

在 GitOps 中,Git 是各环境的期望状态,集群内的调谐器把这个状态拉取过来并使集群与之一致。因此,“prod 里有什么”应由 prod 所跟踪的 Git 引用指向的提交来回答,晋级就是移动这个引用。如果所有环境都跟踪同一个分支,晋级这一步就消失了,dev 中的实验会直接变成 prod。反过来,如果每个环境各有引用,变更到了哪里就能由仓库历史证明;即使有人手动修改集群,调谐器也会把它改回 Git 中的状态。平台团队向多个团队提供交付路径时,这种结构就是基本骨架。

步骤

  1. 创建裸仓库 /srv/bare/shop-envs.git,并克隆到 /root/cnpa-env/repo。在 base/configmap.yaml 中写入 ConfigMap shop-config(不含 namespace,data 为 FEATURE_CHECKOUT_V2: "off" 和 LOG_LEVEL: "info"),并创建包含它的 base/kustomization.yaml;在 envs/dev/kustomization.yaml 和 envs/prod/kustomization.yaml 中引用 ../../base,并分别添加把 LOG_LEVEL 改为 debug 和 warn 的补丁。提交并 push 到 main。
  2. 编写并应用 /root/cnpa-env/appset.yaml,其中是 ApplicationSet shop(argocd 命名空间)。list 生成器的元素为 {env: dev, revision: main} 和 {env: prod, revision: main};模板中名称为 shop-{{env}},project 为 default,repoURL 为 git://gitd.gitsrv.svc.cluster.local:9418/shop-envs.git,targetRevision 为 {{revision}},path 为 envs/{{env}},目标命名空间为 shop-{{env}},自动同步启用 prune 和 selfHeal,并设置 CreateNamespace=true。等待两个 Application 都变为 Synced。
  3. 为了在 dev 中启用新的结算页面,把 base/configmap.yaml 中的 FEATURE_CHECKOUT_V2 改为 "on",提交并 push。等待两个应用都同步到这个提交,然后在 /root/cnpa-env/incident.json 中写入 commit(该提交的 SHA)、dev_value 和 prod_value(各环境 ConfigMap 中 FEATURE_CHECKOUT_V2 的实际值)。
  4. 在事故发生前的提交(第 3 步提交的父提交)上创建分支 release/prod 并 push,然后把 ApplicationSet 中 prod 元素的 revision 改为 release/prod(dev 仍为 main)。确认 shop-prod 已同步到该提交,且 FEATURE_CHECKOUT_V2 又变回了 off,然后在 /root/cnpa-env/pin.json 中写入 release_prod_initial(创建分支时所用提交的 SHA)和 prod_value_after_pin。
  5. 在 shop-prod 命名空间的 ConfigMap shop-config 中,用 kubectl patch 把 LOG_LEVEL 改为 debug,并测量 Argo CD 把它恢复为 Git 中的值用了多少秒。在 /root/cnpa-env/drift.json 中写入 uid(该 ConfigMap 的 uid)、edited(debug)、restored(恢复后的值)和 seconds(整数)。
  6. 假设你已在 dev 中确认了新的结算页面,现在把它晋级到 prod。把 release/prod fast-forward push 到第 3 步的提交(禁止强制 push),并等待 shop-prod 同步到该提交、FEATURE_CHECKOUT_V2 变为 on。在 /root/cnpa-env/promote.json 中写入 from(移动前 release/prod 的 SHA)和 to(移动后的 SHA)。
  7. 在 envs/dev/kustomization.yaml 的 resources 中加入不存在的文件 missing.yaml,提交并 push。shop-dev 出现比较错误后,记下该消息,并在同一时刻读取 shop-prod 的 status.sync.revision;然后用 git revert 回退并 push,等待 shop-dev 重新变为 Synced。在 /root/cnpa-env/revert.json 中写入 bad_commit、revert_commit、dev_error(错误消息的一部分)和 prod_revision_during。
  8. 在 /root/cnpa-env/report.json 中写入 dev_tracks 和 prod_tracks(各应用的 targetRevision)、promoted_commit(第 6 步的 to)、drift_seconds(第 5 步)、bad_commit_reached_prod(第 7 步的 bad_commit 现在是否在 release/prod 的历史中,布尔值)以及 rollback(第 7 步所用的方式,revert 或 reset 之一)。

参考

两个环境放在同一个仓库里

创建裸仓库 /srv/bare/shop-envs.git,并克隆到 /root/cnpa-env/repo。在 base/configmap.yaml 中写入 ConfigMap shop-config(不含 namespace,data 为 FEATURE_CHECKOUT_V2: "off" 和 LOG_LEVEL: "info"),并创建包含它的 base/kustomization.yaml;在 envs/dev/kustomization.yaml 和 envs/prod/kustomization.yaml 中引用 ../../base,并分别添加把 LOG_LEVEL 改为 debug 和 warn 的补丁。提交并 push 到 main。

环境之间的差异只写在 overlay 中。在 kustomization 的 patches 中加入以 ConfigMap 名称为目标的小补丁即可。push 之前,先用 kubectl kustomize envs/prod 确认渲染结果。裸仓库由 git 守护进程的 Pod 读取,所以需要 chmod -R a+rX。

一个 ApplicationSet 生成两个环境

编写并应用 /root/cnpa-env/appset.yaml,其中是 ApplicationSet shop(argocd 命名空间)。list 生成器的元素为 {env: dev, revision: main} 和 {env: prod, revision: main};模板中名称为 shop-{{env}},project 为 default,repoURL 为 git://gitd.gitsrv.svc.cluster.local:9418/shop-envs.git,targetRevision 为 {{revision}},path 为 envs/{{env}},目标命名空间为 shop-{{env}},自动同步启用 prune 和 selfHeal,并设置 CreateNamespace=true。等待两个 Application 都变为 Synced。

ApplicationSet 控制器会对每个元素填充模板中的 {{...}} 来生成 Application,并通过所有者引用把它们关联起来。各环境需要不同的值,就作为键放进元素里。Argo CD 默认按固定周期读取 Git,如果不想等待,可以给 Application 添加注解 argocd.argoproj.io/refresh=hard。

dev 中加入的变更也出现在了 prod 里

为了在 dev 中启用新的结算页面,把 base/configmap.yaml 中的 FEATURE_CHECKOUT_V2 改为 "on",提交并 push。等待两个应用都同步到这个提交,然后在 /root/cnpa-env/incident.json 中写入 commit(该提交的 SHA)、dev_value 和 prod_value(各环境 ConfigMap 中 FEATURE_CHECKOUT_V2 的实际值)。

如果两个环境跟踪同一个分支并引用同一个 base,那么 base 中的一行就同时是两个环境的变更。这意味着环境之间没有晋级步骤。值可以用 kubectl -n shop-prod get cm shop-config -o jsonpath=... 读取。

把 prod 绑定到晋级分支

在事故发生前的提交(第 3 步提交的父提交)上创建分支 release/prod 并 push,然后把 ApplicationSet 中 prod 元素的 revision 改为 release/prod(dev 仍为 main)。确认 shop-prod 已同步到该提交,且 FEATURE_CHECKOUT_V2 又变回了 off,然后在 /root/cnpa-env/pin.json 中写入 release_prod_initial(创建分支时所用提交的 SHA)和 prod_value_after_pin。

如果 prod 跟踪的不是 main,而是一个单独移动的引用,那么 main 上的变更在有人移动这个引用之前,都不会到达 prod。分支可以用 git push origin <SHA>:refs/heads/release/prod 直接在远端创建(占位符为提交 SHA)。如果直接修改 Application,ApplicationSet 会按模板把它改回去,所以应该修改生成器的元素。

手动修改 prod 后,几秒内就被改了回来

在 shop-prod 命名空间的 ConfigMap shop-config 中,用 kubectl patch 把 LOG_LEVEL 改为 debug,并测量 Argo CD 把它恢复为 Git 中的值用了多少秒。在 /root/cnpa-env/drift.json 中写入 uid(该 ConfigMap 的 uid)、edited(debug)、restored(恢复后的值)和 seconds(整数)。

启用了 selfHeal 的应用,一旦它所管理的对象发生变化,就会立即重新比较,如果与 Git 不同,就向 Git 看齐。这是修改对象,而不是删除后重建,所以 uid 保持不变。请以 0.5–1 秒的间隔读取值并等待。

晋级就是一次移动分支的提交

假设你已在 dev 中确认了新的结算页面,现在把它晋级到 prod。把 release/prod fast-forward push 到第 3 步的提交(禁止强制 push),并等待 shop-prod 同步到该提交、FEATURE_CHECKOUT_V2 变为 on。在 /root/cnpa-env/promote.json 中写入 from(移动前 release/prod 的 SHA)和 to(移动后的 SHA)。

如果晋级是 Git 操作,那么谁在何时把什么部署到了 prod,会原样留在仓库历史里,回退也用同样的方式。只有移动前的提交是移动后提交的祖先时,才能 fast-forward。

弄坏 dev 的提交没有到达 prod

在 envs/dev/kustomization.yaml 的 resources 中加入不存在的文件 missing.yaml,提交并 push。shop-dev 出现比较错误后,记下该消息,并在同一时刻读取 shop-prod 的 status.sync.revision;然后用 git revert 回退并 push,等待 shop-dev 重新变为 Synced。在 /root/cnpa-env/revert.json 中写入 bad_commit、revert_commit、dev_error(错误消息的一部分)和 prod_revision_during。

无法渲染的提交,Argo CD 不会应用,而是在应用条件(status.conditions)中记录为 ComparisonError。prod 跟踪的是另一个引用,所以不会看到这个提交。回退用的是 revert,即把相反的变更作为新提交叠加上去,而不是会抹掉历史的 reset。

把环境和晋级用值记录下来

在 /root/cnpa-env/report.json 中写入 dev_tracks 和 prod_tracks(各应用的 targetRevision)、promoted_commit(第 6 步的 to)、drift_seconds(第 5 步)、bad_commit_reached_prod(第 7 步的 bad_commit 现在是否在 release/prod 的历史中,布尔值)以及 rollback(第 7 步所用的方式,revert 或 reset 之一)。

利用前面步骤的 JSON 和 git merge-base --is-ancestor 来计算。评分器会把同一批文件、仓库和 Application 再次对照。