push したのに何も起きなかった
目標
Argo CDがGitを定期的に取りに行く経路と、pushのWebhookですぐに更新する経路を、同じコミットの流れの中で測ります。 ポーリングを切ると何が止まるのか、Webhookは何を確認してからでないと受け入れないのか、運用で両者をどう重ねて使うのかを見ます。
なぜ重要なのか
GitOpsの調整器は、望ましい状態を自分で取りに行きます。取りに行くきっかけは2つあります。決まった周期ごとにリポジトリを読み直すポーリングと、 リポジトリが「たった今pushがあった」と知らせるイベントです。ポーリングだけを使うと、コミットが反映されるまで周期の分だけ待つことになり、リポジトリが増えると その周期がそのままGitサーバーの負荷になります。イベントだけを使うと速いものの、通知が一度失われるとコミットが静かに止まります。 そして、誰でも送れるWebhookは調整器を起こす攻撃面になるので、署名で送信元を確認しなければなりません。 イベントは調整器に「今読んで」と知らせるだけで、デプロイ命令ではないという点も、このラボで確認します。何をデプロイするかは、依然としてGitが決めます。
ステップ
- ベアリポジトリ
/srv/bare/evt.gitを作成して/root/cgoa-evt/repoにクローンし、app/signal.yamlにConfigMapsignal(namespaceなし、datarev: r1)をコミットしてmainへpushしてください。/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-controllerのStatefulSetとargocd-repo-serverのDeploymentを両方とも再起動してください。新しいコントローラーPodのログからappResyncPeriodが入っている行を探して、そのまま/root/cgoa-evt/interval.txtに保存し、再起動が終わったらevtを1回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のServiceの
/api/webhookにGitHubのpushイベントを送ってください。ヘッダーはX-GitHub-Event: pushとContent-Type: application/jsonで、本文は、refにrefs/heads/main、afterにr3のコミット、repository.html_urlにhttp://githttp.gitsrv.svc.cluster.local/cgi-bin/git/evtを入れたJSONです。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を1回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を付けることです。その時点で、測定の対象がポーリングではなくなります。
- よくある間違い: 署名を計算した文字列と、実際に送った本文が1バイトでも違うことです。改行1つでも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)をコミットしてmainへpushしてください。/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を確認してください。
このラボのリポジトリのアドレスは、git://ではなくHTTP(http://)です。githttpサーバーが、/srv/bare以下のリポジトリをgit-http-backendで公開しています。まず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を1回hard refreshしておいてください。
この設定は、2つのコンポーネントが起動時に読みます。コントローラーにとってはアプリを比較し直す周期で、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イベント1回で何秒か
argocd-serverのServiceの/api/webhookにGitHubのpushイベントを送ってください。ヘッダーはX-GitHub-Event: pushとContent-Type: application/jsonで、本文は、refにrefs/heads/main、afterにr3のコミット、repository.html_urlにhttp://githttp.gitsrv.svc.cluster.local/cgi-bin/git/evtを入れたJSONです。evtがr3のコミットに変わるまで待って、/root/cgoa-evt/webhook.jsonにhttp_status(数値)、sent_at、synced_atを書いてください。
Serviceの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を1回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の同期の記録をもう一度突き合わせます。