部署的是标签 v1,出来的却是 v2
目标
创建分别跟踪分支、标签、提交 SHA 的 Argo CD Application,通过实际的同步记录,确认哪些名称会移动、哪些名称不会移动。 然后看看在 GitOps 中,为什么回滚必须是一个新提交。
为什么重要
OpenGitOps 的第二条原则说,期望状态必须“带版本且不可变(versioned and immutable)”地存储。
Git 提交是内容的哈希,不会改变,但分支和标签只是指向提交的名称,任何人都能移动它们。
targetRevision: v1 表示的是“v1 这个名称当前所指的东西”,而不是“最初评审的那个提交”。
不了解这一差别,未经评审的内容就会在已通过评审的发布名称之下被部署,而审计记录里仍然写着 v1。
回滚也出自同样的原则。只把集群还原到旧状态,Git 与集群就会分叉;
把 Git 的历史删掉,就没有了“什么时候部署了什么”的证据。
步骤
- 创建裸仓库
/srv/bare/rev.git,克隆到/root/cgoa-rev/repo。在app/release.yaml中提交 ConfigMaprelease(不使用 namespace,dataversion: v1),push 到main,并给该提交打上标签v1,同时 push 这个标签。把该提交的 40 位 SHA 以一行写入/root/cgoa-rev/v1.sha。 - 在
/root/cgoa-rev/apps.yaml中编写并应用三个 Application。名称与目标命名空间分别是rev-branch、rev-tag、rev-sha,source 是git://gitd.gitsrv.svc.cluster.local:9418/rev.git的app路径,targetRevision 依次是main、v1,以及v1.sha中记录的 40 位 SHA。三个都使用 projectdefault,开启自动同步(prune、selfHeal)和CreateNamespace=true,并确认三个应用都变为 Synced。 - 在
/root/cgoa-rev/repo中,把app/release.yaml的 version 改为v2并提交,push 到main(不要动标签)。对三个应用执行 hard refresh 后,把各应用的status.sync.revision以{"branch": ..., "tag": ..., "sha": ...}的形式写入/root/cgoa-rev/after-commit.json。branch 必须指向新提交,tag 和 sha 必须仍停留在 v1 提交。 - 把标签
v1强制移动到 main 的最新提交(v2)并执行git push -f,然后对rev-tag执行 hard refresh。将rev-tag的status.history[].revision依次以{"target": "v1", "revisions": [...]}的形式写入/root/cgoa-rev/tag-history.json。同一个名称v1之下必须记录下两个不同的提交,rev-tag命名空间的 ConfigMap 必须变为 v2,rev-sha必须仍是 v1。 - 先把标签
v1移回v1.sha的原始提交并 push,让rev-tag重新同步到该提交。然后在/srv/bare/rev.git/hooks/pre-receive中放一个可执行的钩子,拒绝修改或删除已存在的refs/tags/*的 push,允许创建新标签和更新分支。最后尝试再次移动v1的 push,把被拒绝的完整输出保存到/root/cgoa-rev/guard.txt。 - main 现在是 v2。不要删除历史,而是用
git revert创建回滚 v2 提交的新提交并 push。rev-branch必须同步到这个新提交,使 ConfigMap 回到 v1。在/root/cgoa-rev/rollback.json中写入revert_sha(新的 main SHA)、deployed(rev-branch 命名空间 ConfigMap 的 version 值)、equals_v1_sha(新 SHA 是否与 v1.sha 相同,布尔值)。 - 保持自动同步开启的原样,执行
argocd app rollback rev-branch 0 --core,把包含拒绝消息在内的完整输出保存到/root/cgoa-rev/rollback-refused.txt。core 模式会在 kubeconfig 的当前命名空间中寻找 Argo CD 配置,所以要把 k3s kubeconfig 复制到/root/cgoa-rev/kubeconfig,把当前命名空间改为argocd,并通过KUBECONFIG指定后执行。不要为了让 rollback 成功而关闭自动同步。结束时rev-branch必须仍然同步在 main 的最新提交上,处于 Synced。 - 在
/root/cgoa-rev/report.json中写入mutable_refs(移动过的引用种类两个,以branch、tag的形式,数组)、immutable_ref(commit-sha)、pinned_revision(现在rev-sha的 status.sync.revision)、tag_guard(pre-receive)、rollback(git-revert)。评分器会在检查文件值的同时,重新确认三个应用的当前状态。
参考
- VM 中运行着 k3s、Argo CD v3.5.2 和集群内的 git 守护进程(
gitd.gitsrv)。/srv/bare下的裸仓库都会显示为git://gitd.gitsrv.svc.cluster.local:9418/<이름>.git(占位符为仓库名称)。 - 不想等待新提交时,使用
kubectl -n argocd annotate app <이름> argocd.argoproj.io/refresh=hard --overwrite(占位符为应用名称)。 - 读取状态:
kubectl -n argocd get app -o custom-columns=N:.metadata.name,S:.status.sync.status,R:.status.sync.revision - 常见错误:只在本地移动标签,忘了
git push -f origin v1。远程标签没变,所以什么都不会发生。 - 常见错误:在第 5 步先放钩子再去回移标签。钩子会把那次 push 也拒绝。
- OpenGitOps 原则、Argo CD 跟踪策略、argocd app rollback、githooks
给 v1 提交打标签并记下 SHA
创建裸仓库 /srv/bare/rev.git,克隆到 /root/cgoa-rev/repo。在 app/release.yaml 中提交 ConfigMap release(不使用 namespace,data version: v1),push 到 main,并给该提交打上标签 v1,同时 push 这个标签。把该提交的 40 位 SHA 以一行写入 /root/cgoa-rev/v1.sha。
空的裸仓库用 git init --bare 创建。git 守护进程会直接对外提供 /srv/bare,所以不需要另行注册。标签必须与分支分开 push,远程才会有。SHA 用 git rev-parse 获取。
分别跟踪分支、标签、SHA 的三个应用
在 /root/cgoa-rev/apps.yaml 中编写并应用三个 Application。名称与目标命名空间分别是 rev-branch、rev-tag、rev-sha,source 是 git://gitd.gitsrv.svc.cluster.local:9418/rev.git 的 app 路径,targetRevision 依次是 main、v1,以及 v1.sha 中记录的 40 位 SHA。三个都使用 project default,开启自动同步(prune、selfHeal)和 CreateNamespace=true,并确认三个应用都变为 Synced。
三个应用只有 targetRevision 一行不同。现在三个名称都指向同一个提交,所以 status.sync.revision 也应该相同。
把 v2 推到 main 上,谁会跟上
在 /root/cgoa-rev/repo 中,把 app/release.yaml 的 version 改为 v2 并提交,push 到 main(不要动标签)。对三个应用执行 hard refresh 后,把各应用的 status.sync.revision 以 {"branch": ..., "tag": ..., "sha": ...} 的形式写入 /root/cgoa-rev/after-commit.json。branch 必须指向新提交,tag 和 sha 必须仍停留在 v1 提交。
请求 refresh 的方法是 annotation argocd.argoproj.io/refresh=hard。要写入文件的值不要猜测,请从 Application 状态中读取。
部署的是标签 v1,起来的却是 v2
把标签 v1 强制移动到 main 的最新提交(v2)并执行 git push -f,然后对 rev-tag 执行 hard refresh。将 rev-tag 的 status.history[].revision 依次以 {"target": "v1", "revisions": [...]} 的形式写入 /root/cgoa-rev/tag-history.json。同一个名称 v1 之下必须记录下两个不同的提交,rev-tag 命名空间的 ConfigMap 必须变为 v2,rev-sha 必须仍是 v1。
标签只是名称,不是提交。history 的每一项中,都会同时留下当时的 source.targetRevision 和实际的 revision。
禁止移动已存在的标签
先把标签 v1 移回 v1.sha 的原始提交并 push,让 rev-tag 重新同步到该提交。然后在 /srv/bare/rev.git/hooks/pre-receive 中放一个可执行的钩子,拒绝修改或删除已存在的 refs/tags/* 的 push,允许创建新标签和更新分支。最后尝试再次移动 v1 的 push,把被拒绝的完整输出保存到 /root/cgoa-rev/guard.txt。
pre-receive 会从标准输入接收 옛SHA 새SHA 참조이름(占位符依次为旧 SHA、新 SHA 与引用名称)这样的行。新建引用的旧 SHA 是 40 个 0,删除引用的新 SHA 也是如此。钩子以非 0 值结束时,整个 push 都会被拒绝。移回标签的 push 要在放置钩子之前做。
回滚是一个新提交
main 现在是 v2。不要删除历史,而是用 git revert 创建回滚 v2 提交的新提交并 push。rev-branch 必须同步到这个新提交,使 ConfigMap 回到 v1。在 /root/cgoa-rev/rollback.json 中写入 revert_sha(新的 main SHA)、deployed(rev-branch 命名空间 ConfigMap 的 version 值)、equals_v1_sha(新 SHA 是否与 v1.sha 相同,布尔值)。
先 reset 再强制 push,内容也会变成 v1,但 v2 提交会从 main 历史中消失。请选择能留下“谁在什么时候回滚了什么”的做法。
自动同步应用的 rollback 为什么被拒绝
保持自动同步开启的原样,执行 argocd app rollback rev-branch 0 --core,把包含拒绝消息在内的完整输出保存到 /root/cgoa-rev/rollback-refused.txt。core 模式会在 kubeconfig 的当前命名空间中寻找 Argo CD 配置,所以要把 k3s kubeconfig 复制到 /root/cgoa-rev/kubeconfig,把当前命名空间改为 argocd,并通过 KUBECONFIG 指定后执行。不要为了让 rollback 成功而关闭自动同步。结束时 rev-branch 必须仍然同步在 main 的最新提交上,处于 Synced。
Argo CD 不会把开启了自动同步的应用回滚到旧 revision。因为即使回滚,下一次调谐也会回到 Git。kubectl config set-context --current --namespace=... 只会修改指定的 kubeconfig 文件。
报告会变化的名称与不变的名称
在 /root/cgoa-rev/report.json 中写入 mutable_refs(移动过的引用种类两个,以 branch、tag 的形式,数组)、immutable_ref(commit-sha)、pinned_revision(现在 rev-sha 的 status.sync.revision)、tag_guard(pre-receive)、rollback(git-revert)。评分器会在检查文件值的同时,重新确认三个应用的当前状态。
请回想前面的步骤中实际移动的是什么,以及始终没有移动的应用是哪个。pinned_revision 要从状态中读取。