推送之后,什么也没有发生
目标
在同一条提交流程中,测量 Argo CD 定期从 Git 拉取的路径,与通过 push Webhook 立即刷新的路径。 看看关掉轮询会让什么停下来,Webhook 要先确认什么才会被接受,以及在生产环境中如何把两者叠加使用。
为什么重要
GitOps 调谐器会主动拉取期望状态。触发拉取的有两种时机:按固定周期重新读取仓库的轮询, 以及仓库通知“刚刚有过 push”的事件。只用轮询,提交要等上一个周期才能生效,仓库一多, 这个周期就会变成 Git 服务器的负载。只用事件虽然快,但通知一旦丢失,提交就会悄悄停住。 而且任何人都能发送的 Webhook 会成为唤醒调谐器的攻击面,所以必须用签名确认发送方。 本实验还要确认:事件只是通知调谐器“现在就读取”,并不是部署指令。要部署什么,依然由 Git 决定。
步骤
- 创建裸仓库
/srv/bare/evt.git,克隆到/root/cgoa-evt/repo,在app/signal.yaml中提交 ConfigMapsignal(无 namespace,datarev: r1),并 push 到main。在/root/cgoa-evt/app.yaml中编写并应用 Applicationevt(argocd 命名空间,project default,repoURLhttp://githttp.gitsrv.svc.cluster.local/cgi-bin/git/evt.git,targetRevisionmain,pathapp,目标命名空间evt,自动同步 prune、selfHeal,CreateNamespace=true),确认 Synced。 - 在 ConfigMap
argocd-cm中加入timeout.reconciliation: 30s与timeout.reconciliation.jitter: 0s,然后重启argocd-application-controllerStatefulSet 和argocd-repo-serverDeployment 两者。在新的控制器 Pod 日志中找到含有appResyncPeriod的那一行,原样保存到/root/cgoa-evt/interval.txt;重启完成后,对evt执行一次 hard refresh。 - 把
app/signal.yaml的 rev 改为r2并提交、push,在既不使用 refresh 也不使用 Webhook 的情况下,等到evt的status.sync.revision变成该提交。在/root/cgoa-evt/poll.json中写入commit、pushed_at(push 之后的 Unix 秒)、synced_at(看到 revision 变化时的 Unix 秒),时间用数字表示。 - 把
argocd-cm的timeout.reconciliation改为0s,并重启控制器和 repo-server。然后把 rev 改为r3并 push,不发出任何请求,等待 40 秒以上,在/root/cgoa-evt/nopoll.json中写入commit(r3 提交)、pushed_at、checked_at(等待之后确认时的 Unix 秒)、revision_at_check(此时 evt 的 status.sync.revision)。 - 向 argocd-server 服务的
/api/webhook发送 GitHub push 事件。请求头为X-GitHub-Event: push和Content-Type: application/json,正文是一个 JSON:ref为refs/heads/main,after为 r3 提交,repository.html_url为http://githttp.gitsrv.svc.cluster.local/cgi-bin/git/evt。等到evt变为 r3 提交,然后在/root/cgoa-evt/webhook.json中写入http_status(数字)、sent_at、synced_at。 - 把用
openssl rand -hex 16生成的值保存到/root/cgoa-evt/webhook-secret,并放入 Secretargocd-secret的webhook.github.secret键。把 rev 改为r4并 push,先发送一个没有签名的 push 事件,等待 15 秒。接着,用该密钥对同一个正文文件做 HMAC-SHA256 签名,连同X-Hub-Signature-256: sha256=<hex>请求头一起发送,并等待同步。在/root/cgoa-evt/secret.json中写入commit(r4)、unsigned_status、signed_status(数字)、revision_after_unsigned(无签名请求 15 秒后 evt 的 revision)、signed_synced_at。 - Webhook 可能丢失,所以要恢复轮询。把
argocd-cm的timeout.reconciliation改为120s,删除timeout.reconciliation.jitter键,然后重启控制器和 repo-server。把新控制器日志中的appResyncPeriod行保存到/root/cgoa-evt/restored.txt,并对evt执行一次 hard refresh。Webhook 密钥保持不变。 - 在
/root/cgoa-evt/report.json中写入poll_seconds(第 3 步测得的 synced_at-pushed_at)、nopoll_synced(第 4 步确认时 r3 是否已部署,布尔值)、webhook_seconds(第 5 步的 synced_at-sent_at)、unsigned_accepted(无签名事件是否引起了部署,布尔值)、trigger(webhook)、safety_net(timeout.reconciliation)。
参考
- VM 中有 k3s、Argo CD v3.5.2、smart HTTP git 服务器(
githttp.gitsrv)。/srv/bare/<이름>.git会显示为http://githttp.gitsrv.svc.cluster.local/cgi-bin/git/<이름>.git(占位符均为仓库名称)。 - 重启:
kubectl -n argocd rollout restart statefulset argocd-application-controller与kubectl -n argocd rollout restart deployment argocd-repo-server,之后各自用rollout status等待。 - 常见错误:只重启控制器。即使每 30 秒比较一次,repo-server 也会从缓存中返回分支的旧提交,新提交几分钟都看不到。
- 时间测量用
date +%s,读取 revision 用kubectl -n argocd get app evt -o jsonpath='{.status.sync.revision}'。 - 常见错误:在第 3、4 步加上 refresh annotation。那一刻,测量对象就不再是轮询了。
- 常见错误:计算签名的字符串与实际发送的正文哪怕有一个字节不同。哪怕只是一个换行,HMAC 也会不同。
- Argo CD Webhook 配置、argocd-cm 示例(timeout.reconciliation)、GitHub Webhook 签名验证、OpenGitOps 原则
跟踪 smart HTTP 仓库的应用
创建裸仓库 /srv/bare/evt.git,克隆到 /root/cgoa-evt/repo,在 app/signal.yaml 中提交 ConfigMap signal(无 namespace,data rev: r1),并 push 到 main。在 /root/cgoa-evt/app.yaml 中编写并应用 Application evt(argocd 命名空间,project default,repoURL http://githttp.gitsrv.svc.cluster.local/cgi-bin/git/evt.git,targetRevision main,path app,目标命名空间 evt,自动同步 prune、selfHeal,CreateNamespace=true),确认 Synced。
本实验的仓库地址使用 http:// 而不是 git://,githttp 服务器用 git-http-backend 对外提供 /srv/bare 下的仓库。先用 git ls-remote 确认能看到。
把拉取周期缩短为 30 秒
在 ConfigMap argocd-cm 中加入 timeout.reconciliation: 30s 与 timeout.reconciliation.jitter: 0s,然后重启 argocd-application-controller StatefulSet 和 argocd-repo-server Deployment 两者。在新的控制器 Pod 日志中找到含有 appResyncPeriod 的那一行,原样保存到 /root/cgoa-evt/interval.txt;重启完成后,对 evt 执行一次 hard refresh。
这个设置由两个组件在启动时读取。对控制器来说,它是重新比较应用的周期;对 repo-server 来说,它是缓存分支所指提交的时长。只重启控制器的话,即使每 30 秒比较一次,repo-server 也会继续返回旧提交。缓存在 Redis 中,所以重启之前以旧的过期时间(默认 3 分钟)保存的条目仍会留着,必须用 hard refresh 重新读取一次,新的过期时间才会生效。日志中的那一行用 Go 的 time.Duration 写法显示时长。
什么都不按,干等的时间
把 app/signal.yaml 的 rev 改为 r2 并提交、push,在既不使用 refresh 也不使用 Webhook 的情况下,等到 evt 的 status.sync.revision 变成该提交。在 /root/cgoa-evt/poll.json 中写入 commit、pushed_at(push 之后的 Unix 秒)、synced_at(看到 revision 变化时的 Unix 秒),时间用数字表示。
用 date +%s 测量时间。等待期间,每隔 2 秒只读取 revision。用 annotate 请求 refresh,这次测量就不再是轮询了。
关掉轮询,提交就停住了
把 argocd-cm 的 timeout.reconciliation 改为 0s,并重启控制器和 repo-server。然后把 rev 改为 r3 并 push,不发出任何请求,等待 40 秒以上,在 /root/cgoa-evt/nopoll.json 中写入 commit(r3 提交)、pushed_at、checked_at(等待之后确认时的 Unix 秒)、revision_at_check(此时 evt 的 status.sync.revision)。
0 表示关闭周期性刷新。请看重启后控制器日志中的 appResyncPeriod 变成了什么。受管对象发生变化的集群事件,与重新读取 Git,是两回事。
一次 push 事件要几秒
向 argocd-server 服务的 /api/webhook 发送 GitHub push 事件。请求头为 X-GitHub-Event: push 和 Content-Type: application/json,正文是一个 JSON:ref 为 refs/heads/main,after 为 r3 提交,repository.html_url 为 http://githttp.gitsrv.svc.cluster.local/cgi-bin/git/evt。等到 evt 变为 r3 提交,然后在 /root/cgoa-evt/webhook.json 中写入 http_status(数字)、sent_at、synced_at。
向服务的 ClusterIP 用 https 发送,因为是自签名证书,所以要用 curl -k。Argo CD 会把事件中的仓库地址与每个 Application 的 repoURL 对照,只刷新匹配的应用。在 commits[].modified 中放入被修改的文件路径,路径对照也会通过。
没有签名的事件,挡在门外
把用 openssl rand -hex 16 生成的值保存到 /root/cgoa-evt/webhook-secret,并放入 Secret argocd-secret 的 webhook.github.secret 键。把 rev 改为 r4 并 push,先发送一个没有签名的 push 事件,等待 15 秒。接着,用该密钥对同一个正文文件做 HMAC-SHA256 签名,连同 X-Hub-Signature-256: sha256=<hex> 请求头一起发送,并等待同步。在 /root/cgoa-evt/secret.json 中写入 commit(r4)、unsigned_status、signed_status(数字)、revision_after_unsigned(无签名请求 15 秒后 evt 的 revision)、signed_synced_at。
签名必须针对发送出去的原始字节来计算。把正文做成文件,用 curl --data-binary @파일(占位符为文件名)发送,并把同一个文件放进 openssl dgst -sha256 -hmac。argocd-server 会自行重新读取 Secret 的变化。
相信 Webhook,但把轮询恢复为兜底
Webhook 可能丢失,所以要恢复轮询。把 argocd-cm 的 timeout.reconciliation 改为 120s,删除 timeout.reconciliation.jitter 键,然后重启控制器和 repo-server。把新控制器日志中的 appResyncPeriod 行保存到 /root/cgoa-evt/restored.txt,并对 evt 执行一次 hard refresh。Webhook 密钥保持不变。
在 merge patch 中,要删除某个键,就把值设为 null 发送。删掉 jitter 后会使用默认值。请确认日志行里周期和 jitter 分别显示成什么。周期为 0 期间,repo-server 用默认的缓存过期时间(24 小时)保存了分支所指的提交。即使把周期改回来,那个条目仍然留着,所以必须用 hard refresh 重新读取一次,轮询才会重新看到新提交。
报告拉取与事件各自的作用
在 /root/cgoa-evt/report.json 中写入 poll_seconds(第 3 步测得的 synced_at-pushed_at)、nopoll_synced(第 4 步确认时 r3 是否已部署,布尔值)、webhook_seconds(第 5 步的 synced_at-sent_at)、unsigned_accepted(无签名事件是否引起了部署,布尔值)、trigger(webhook)、safety_net(timeout.reconciliation)。
用前面步骤留下的 JSON 文件里的数字来计算。评分器会把同一批文件与 Argo CD 同步记录再次对照。