適用した値が戻ってしまう — フィールドの持ち主を探す
目標
kwokクラスターで、サーバーサイド適用によってマネージャーを2つ作り、managedFieldsを直接読みます。衝突と強制、フィールドの手放し、scaleサブリソースの衝突まで実物で起こしてみて、最後にArgo CDの無視ルールとつなげます。
なぜ重要なのか
GitOpsを有効にしても、クラスターのオブジェクトに書く主体は1つではありません。Argo CDが書き、オートスケーラーがreplicasを書き、Webhookがコンテナを差し込み、人が急ぐときにkubectlで書きます。サーバーサイド適用は、「誰がこのフィールドの持ち主か」をAPIサーバーが記録として持つ仕組みです。この記録を読めて初めて、3つの質問に答えられます。なぜ自分の適用が拒否されるのか、なぜ強制で押し込むと次に相手が拒否されるのか、なぜ値を手放したのに以前の値に戻らないのか。そして、この記録はArgo CD側につながります。無視ルールのmanagedFieldsManagersは、フィールドのパスではなくこの所有の記録で比較の対象を選ぶからです。
ステップ
- kwokクラスターにネームスペース
ga-ssaを作り、/root/ga-ssa/deploy.yamlにDeploymentwebを書いてください。spec.replicasは2、セレクターとラベルはapp: web、コンテナは1つ(web、イメージnginx:1.25)です。kubectl apply --server-side --field-manager=gitopsで適用してください。 kubectl -n ga-ssa get deploy web -o json --show-managed-fieldsで所有の記録を読み、マネージャーごとに1行ずつ、<관리자> | <연산>の形式で/root/ga-ssa/managers.txtに保存してください(プレースホルダーは、順にマネージャーと操作です)。gitopsがApply操作として入っている必要があります。/root/ga-ssa/hotfix.yamlに同じDeploymentを書き、spec.replicasは5、イメージはnginx:1.27にしてください。--field-manager=hotfixでサーバーサイド適用を試み、失敗の出力を標準エラー出力まで合わせて/root/ga-ssa/conflict.txtに保存してください。--force-conflictsは、まだ使いません。- 同じ適用を、
--force-conflictsを付けてもう一度行い、成功の出力を/root/ga-ssa/force.txtに保存してください。これでクラスターのイメージはnginx:1.27になり、そのフィールドの持ち主はhotfixになります。 /root/ga-ssa/hotfix-slim.yamlを作ってください。hotfix.yamlと同じですが、spec.replicasの行がないものにします。--field-manager=hotfixでサーバーサイド適用したあと、spec.replicasを所有するマネージャーの一覧を/root/ga-ssa/release.txtに保存してください(誰もいなければ空のファイル)。クラスターのreplicasがいくつになるかを確認してください。/root/ga-ssa/web-csa.yamlにDeploymentweb-csaを書き(同じネームスペース、ラベルとコンテナ名はweb-csa、イメージnginx:1.25)、--server-sideなしの普通のkubectl applyで上げてください。2つのDeployment(webとweb-csa)のmetadata.annotationsのキーの一覧を、/root/ga-ssa/csa.txtに保存してください。/root/ga-ssa/scale-deploy.yamlにDeploymentweb-scale(レプリカ2、ラベルとコンテナ名web-scale、イメージnginx:1.25)を書いて、--field-manager=gitopsでサーバーサイド適用してください。そのあとkubectl -n ga-ssa scale deploy web-scale --replicas=4で増やし、同じファイルを同じマネージャー名でもう一度適用してみてください。失敗の出力を/root/ga-ssa/scale-conflict.txtに保存します。/root/ga-ssa/live.yamlに、クラスターのwebを所有の記録まで含めてYAMLで保存してください(-o yaml --show-managed-fields)。/root/ga-ssa/argocd-cm.yamlにキーresource.customizations.ignoreDifferences.apps_Deploymentを置いて、managedFieldsManagersにhotfix、jsonPointersに/spec/replicasを書いてください。argocd admin settings resource-overrides ignore-differences /root/ga-ssa/live.yaml --argocd-cm-path /root/ga-ssa/argocd-cm.yamlの出力を、/root/ga-ssa/ignore.txtに保存してください。
参考
kubectl get ... -o jsonは、managedFieldsを隠します。--show-managed-fieldsを付けてください。--field-managerでマネージャー名を決めます。デフォルトはコマンドごとに違います。- 衝突のメッセージは、どのフィールドが誰のものかを教えてくれます。その一覧を先に読んでください。
- よくあるミスは、
--force-conflictsを習慣のように付けることです。所有権が移って、次に相手が止められます。 - よくあるミスは、マニフェストからフィールドを除けば以前の値に戻ると期待することです。APIのデフォルトになります。
- 参考: https://kubernetes.io/docs/reference/using-api/server-side-apply/
名前を明かして適用する
kwokクラスターにネームスペースga-ssaを作り、/root/ga-ssa/deploy.yamlにDeploymentwebを書いてください。spec.replicasは2、セレクターとラベルはapp: web、コンテナは1つ(web、イメージnginx:1.25)です。kubectl apply --server-side --field-manager=gitopsで適用してください。
--field-managerは、「この変更を誰が行ったか」をAPIサーバーに知らせる名前です。サーバーサイド適用は、この名前ごとに、どのフィールドを所有するかを記録しておきます。クライアントサイド適用にはない概念です。
所有の記録を読む
kubectl -n ga-ssa get deploy web -o json --show-managed-fieldsで所有の記録を読み、マネージャーごとに1行ずつ、<관리자> | <연산>の形式で/root/ga-ssa/managers.txtに保存してください(プレースホルダーは、順にマネージャーと操作です)。gitopsがApply操作として入っている必要があります。
このオプションを外すと、.metadata.managedFieldsがnullで出ます。kubectlがデフォルトで隠すからです。jqで配列を回しながら、managerとoperationを取り出してください。
2つ目のマネージャーが同じフィールドを書こうとすると
/root/ga-ssa/hotfix.yamlに同じDeploymentを書き、spec.replicasは5、イメージはnginx:1.27にしてください。--field-manager=hotfixでサーバーサイド適用を試み、失敗の出力を標準エラー出力まで合わせて/root/ga-ssa/conflict.txtに保存してください。--force-conflictsは、まだ使いません。
サーバーは、「このフィールドは別の人が持っている」として拒否し、どのフィールドかを一覧で教えてくれます。この拒否がなければ、2つの自動化が互いの値を静かに上書きしてしまいます。解答の中で失敗して止まらないように気をつけてください。
所有権を奪う
同じ適用を、--force-conflictsを付けてもう一度行い、成功の出力を/root/ga-ssa/force.txtに保存してください。これでクラスターのイメージはnginx:1.27になり、そのフィールドの持ち主はhotfixになります。
強制は、衝突をなくすのではなく所有権を移すものです。以前の持ち主はそのフィールドから外れるので、次に以前の持ち主が自分の値をもう一度適用すると、今度はそちらが衝突に出会います。自動化2つが強制を交互に使うと、終わらない争いになります。
フィールドを手放しても、値は持ち主に戻らない
/root/ga-ssa/hotfix-slim.yamlを作ってください。hotfix.yamlと同じですが、spec.replicasの行がないものにします。--field-manager=hotfixでサーバーサイド適用したあと、spec.replicasを所有するマネージャーの一覧を/root/ga-ssa/release.txtに保存してください(誰もいなければ空のファイル)。クラスターのreplicasがいくつになるかを確認してください。
マニフェストからフィールドを除くと、そのマネージャーは所有の一覧からそのフィールドを手放します。ところが、値は以前の持ち主の値には戻りません。誰も所有しないフィールドは、APIのデフォルトになります。所有者の一覧は、jqで.fieldsV1."f:spec"."f:replicas"がnullではない項目を選べば求められます。
クライアントサイド適用は何を残すか
/root/ga-ssa/web-csa.yamlにDeploymentweb-csaを書き(同じネームスペース、ラベルとコンテナ名はweb-csa、イメージnginx:1.25)、--server-sideなしの普通のkubectl applyで上げてください。2つのDeployment(webとweb-csa)のmetadata.annotationsのキーの一覧を、/root/ga-ssa/csa.txtに保存してください。
クライアントサイド適用は、「自分が前回送ったマニフェスト」を1つのアノテーションにまるごと入れておいて、それと比べて何を消すかを決めます。そのため、持ち主という概念がなく、2つのツールが同じオブジェクトを扱うと、互いのフィールドを消します。サーバーサイドで上げたほうには、そのアノテーションがありません。
kubectl scaleは別の入口から入ってくる
/root/ga-ssa/scale-deploy.yamlにDeploymentweb-scale(レプリカ2、ラベルとコンテナ名web-scale、イメージnginx:1.25)を書いて、--field-manager=gitopsでサーバーサイド適用してください。そのあとkubectl -n ga-ssa scale deploy web-scale --replicas=4で増やし、同じファイルを同じマネージャー名でもう一度適用してみてください。失敗の出力を/root/ga-ssa/scale-conflict.txtに保存します。
kubectl scaleは、オブジェクト全体ではなくscaleというサブリソースに書きます。そのため、所有の記録にマネージャーkubectlとサブリソースscaleが別に残り、衝突のメッセージもその事実を知らせます。オートスケーラーが作る衝突が、まさにこの形です。
持ち主の名前で比較から除く
/root/ga-ssa/live.yamlに、クラスターのwebを所有の記録まで含めてYAMLで保存してください(-o yaml --show-managed-fields)。/root/ga-ssa/argocd-cm.yamlにキーresource.customizations.ignoreDifferences.apps_Deploymentを置いて、managedFieldsManagersにhotfix、jsonPointersに/spec/replicasを書いてください。argocd admin settings resource-overrides ignore-differences /root/ga-ssa/live.yaml --argocd-cm-path /root/ga-ssa/argocd-cm.yamlの出力を、/root/ga-ssa/ignore.txtに保存してください。
Argo CDで「オートスケーラーが書いたフィールドは比較しない」を表現する方法が、この2つです。パスで除くか、マネージャー名で除くか。ただし、プレビューのコマンドは、パスのルールだけをレンダリングするので、マネージャー名のルールが実際に何を除くかは、所有の記録を人が読んで判断する必要があります。