TT Lab
はじめる
学ぶ 学習パス コース

CNPA — クラウドネイティブプラットフォームエンジニアリングアソシエイト

dev に入れた変更が prod にも現れた

TT Labで続きを見る

目標

本物のk3sとArgo CDで、ApplicationSet 1つを使い、devとprodの2つの環境をGitからデプロイします。共通のbaseの1行が、2つの環境を同時に変更してしまう事故を体験し、 prodをプロモーション用ブランチに結び付けて、環境間のプロモーションをGitの操作に変えたうえで、ドリフトの復旧・プロモーション・巻き戻しが、それぞれリポジトリとクラスターにどう残るかを確認します。

なぜ重要なのか

GitOpsでは、Gitが各環境の望ましい状態であり、クラスター内のReconcilerが、その状態を引き寄せて合わせます。そのため、「何がprodにあるのか」は、 prodが追跡するGit参照が指すコミットで答える必要があり、プロモーションとは、その参照を移す作業になります。すべての環境が同じブランチを見ていると、 プロモーションの段階がなくなり、devでの実験がそのままprodになります。逆に、環境ごとに参照を分けると、変更がどこまで進んだかがリポジトリの履歴で証明され、 誰かがクラスターを手で変更しても、Reconcilerが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に変更するパッチを置いてください。コミットしてmainにpushします。
  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になるまで待ってください。
  3. 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の実際の値)を書いてください。
  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で、LOG_LEVELをkubectl patchでdebugに変更し、Argo CDがGitの値に戻すまでにかかった秒数を測ってください。/root/cnpa-env/drift.jsonにuid(そのConfigMapのuid)、edited(debug)、restored(戻ってきた値)、seconds(整数)を書きます。
  6. 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)を書きます。
  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(revertまたはresetのうち、ステップ7で使った方式)を書いてください。

参考

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をもう一度照合します。