CNPA — クラウドネイティブプラットフォームエンジニアリングアソシエイト
dev に入れた変更が prod にも現れた
目標
本物のk3sとArgo CDで、ApplicationSet 1つを使い、devとprodの2つの環境をGitからデプロイします。共通のbaseの1行が、2つの環境を同時に変更してしまう事故を体験し、 prodをプロモーション用ブランチに結び付けて、環境間のプロモーションをGitの操作に変えたうえで、ドリフトの復旧・プロモーション・巻き戻しが、それぞれリポジトリとクラスターにどう残るかを確認します。
なぜ重要なのか
GitOpsでは、Gitが各環境の望ましい状態であり、クラスター内のReconcilerが、その状態を引き寄せて合わせます。そのため、「何がprodにあるのか」は、 prodが追跡するGit参照が指すコミットで答える必要があり、プロモーションとは、その参照を移す作業になります。すべての環境が同じブランチを見ていると、 プロモーションの段階がなくなり、devでの実験がそのままprodになります。逆に、環境ごとに参照を分けると、変更がどこまで進んだかがリポジトリの履歴で証明され、 誰かがクラスターを手で変更しても、ReconcilerがGitに元に戻します。プラットフォームチームが複数のチームに配信経路を提供するとき、この構造が基本の骨格です。
ステップ
- ベアリポジトリ
/srv/bare/shop-envs.gitを作成し、/root/cnpa-env/repoにクローンしてください。base/configmap.yamlにConfigMapshop-config(namespaceなし、dataFEATURE_CHECKOUT_V2: "off"、LOG_LEVEL: "info")と、それを含むbase/kustomization.yamlを、envs/dev/kustomization.yamlとenvs/prod/kustomization.yamlには../../baseを読み込んで、LOG_LEVELをそれぞれdebug・warnに変更するパッチを置いてください。コミットしてmainにpushします。 /root/cnpa-env/appset.yamlにApplicationSetshop(argocdネームスペース)を作成して適用してください。listジェネレーターの要素は{env: dev, revision: main}と{env: prod, revision: main}、テンプレートは、名前shop-{{env}}、project default、repoURLgit://gitd.gitsrv.svc.cluster.local:9418/shop-envs.git、targetRevision{{revision}}、pathenvs/{{env}}、ターゲットネームスペースshop-{{env}}、自動同期のprune・selfHeal、CreateNamespace=trueです。2つのApplicationがSyncedになるまで待ってください。- devで新しい決済画面を有効にするために、
base/configmap.yamlのFEATURE_CHECKOUT_V2を"on"に変更して、コミット・pushしてください。2つのアプリがそのコミットに同期されるまで待ってから、/root/cnpa-env/incident.jsonにcommit(そのコミットのSHA)、dev_value、prod_value(各環境のConfigMapのFEATURE_CHECKOUT_V2の実際の値)を書いてください。 - 事故の直前のコミット(ステップ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を書いてください。 shop-prodネームスペースのConfigMapshop-configで、LOG_LEVELをkubectl patchでdebugに変更し、Argo CDがGitの値に戻すまでにかかった秒数を測ってください。/root/cnpa-env/drift.jsonにuid(そのConfigMapのuid)、edited(debug)、restored(戻ってきた値)、seconds(整数)を書きます。- devで新しい決済画面を確認できたとして、prodにプロモーションしてください。
release/prodをステップ3のコミットにfast-forwardでpushし(強制pushは禁止)、shop-prodがそのコミットに同期されて、FEATURE_CHECKOUT_V2がonになるまで待ってください。/root/cnpa-env/promote.jsonにfrom(移す前のrelease/prodのSHA)、to(移したあとのSHA)を書きます。 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を書きます。/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(revertまたはresetのうち、ステップ7で使った方式)を書いてください。
参考
- VM内にk3s、Argo CD v3.5.2、gitデーモン(
gitd.gitsrv)があります。/srv/bare/<이름>.gitがgit://gitd.gitsrv.svc.cluster.local:9418/<이름>.gitとして見えます(プレースホルダーはリポジトリ名です)。 - すぐに再読み込み:
kubectl -n argocd annotate app <앱> argocd.argoproj.io/refresh=hard --overwrite(プレースホルダーはアプリ名です)。 - レンダリングの確認:
kubectl kustomize envs/<환경>(プレースホルダーは環境名です)。 - よくある間違い: ApplicationSetが作成したApplicationを直接変更することです。コントローラーがテンプレートどおりに戻します。
- よくある間違い: プロモーションを
git push --forceで行うことです。履歴が変わると、何がいつprodに反映されたかを証明できなくなります。 - ApplicationSet List Generator・Automated Sync Policy(selfHeal)・Kustomize・OpenGitOpsの原則・CNCF Platforms White Paper
1つのリポジトリに2つの環境
ベアリポジトリ/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に変更するパッチを置いてください。コミットしてmainにpushします。
環境ごとの違いは、overlayに差分だけを書きます。kustomizationのpatchesに、ConfigMapの名前を対象にした小さなパッチを入れればよいです。pushする前に、kubectl kustomize envs/prodでレンダリング結果を確認してください。ベアリポジトリはgitデーモンのPodが読むため、chmod -R a+rXが必要です。
ApplicationSet 1つで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です。2つのApplicationがSyncedになるまで待ってください。
ApplicationSetコントローラーは、要素ごとにテンプレートの{{...}}を埋めてApplicationを作成し、オーナー参照で結び付けます。環境ごとに違う必要のある値は、要素にキーとして置きます。Argo CDはデフォルトの周期でGitを読むので、待ちたくなければ、Applicationにargocd.argoproj.io/refresh=hardアノテーションを付けます。
devに入れた変更がprodにも現れた
devで新しい決済画面を有効にするために、base/configmap.yamlのFEATURE_CHECKOUT_V2を"on"に変更して、コミット・pushしてください。2つのアプリがそのコミットに同期されるまで待ってから、/root/cnpa-env/incident.jsonにcommit(そのコミットのSHA)、dev_value、prod_value(各環境のConfigMapのFEATURE_CHECKOUT_V2の実際の値)を書いてください。
2つの環境が同じブランチを追跡し、同じbaseを読み込むなら、baseの1行が、そのまま2つの環境の変更です。環境の間にプロモーションの段階がないということです。値は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でリモートに直接作成できます。Applicationを直接変更すると、ApplicationSetがテンプレートどおりに戻すので、ジェネレーターの要素を変更する必要があります。
prodを手で変更したら、数秒後に元に戻った
shop-prodネームスペースのConfigMap shop-configで、LOG_LEVELをkubectl patchでdebugに変更し、Argo CDがGitの値に戻すまでにかかった秒数を測ってください。/root/cnpa-env/drift.jsonにuid(そのConfigMapのuid)、edited(debug)、restored(戻ってきた値)、seconds(整数)を書きます。
selfHealが有効なアプリは、管理しているオブジェクトが変わるとすぐに再比較し、Gitと違えばGit側に合わせます。オブジェクトを削除して作り直すのではなく修正するので、uidはそのままです。0.5–1秒間隔で値を読みながら待ってください。
プロモーションは、ブランチを移すコミット1回
devで新しい決済画面を確認できたとして、prodにプロモーションしてください。release/prodをステップ3のコミットにfast-forwardでpushし(強制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は別の参照を追跡するので、このコミットを見ることはありません。巻き戻しは、履歴を消すresetではなく、反対の変更を新しいコミットとして積むrevertです。
環境とプロモーションを値として残す
/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(revertまたはresetのうち、ステップ7で使った方式)を書いてください。
前のステップのJSONと、git merge-base --is-ancestorで計算します。採点ツールは、同じファイルとリポジトリ・Applicationをもう一度照合します。