マニフェストリポジトリを作りドリフトを捕まえる
目標
マニフェストを入れたgitリポジトリを作ってクラスターに適用し、手で作った変更(ドリフト)を検知して、リポジトリを基準に元に戻せるようになります。
なぜ重要なのか
GitOpsのルールは1文です。リポジトリが正しく、クラスターがそれに従う。このルールを守った瞬間から、「今プロダクションで何が動いているか」という質問がgit logで答えられる質問になり、ロールバックはgit revertという平凡な作業になります。反対に、クラスターを直接直す習慣が1つでも残っていると、リポジトリは現実を説明できない文書になり、その瞬間から再現が不可能になります。このラボでkubectl diffを繰り返し使う理由がここにあります。diffは、「宣言と実際がどれだけ離れたか」を終了コードで答えてくれるドリフト計測器であり、Argo CDが画面にOutOfSyncと表示するのとまったく同じ判断を、人の手で行うことです。この環境にはArgo CDコントローラーが動いていないので、そのコントローラーが代わりにやってくれることを、最後のステップで自分でスクリプトに書いてみます。
ステップ
/root/gitops/repoディレクトリを作ってgit initし、そのリポジトリにuser.nameとuser.emailを設定してください。そのあと/root/gitops/repo/README.mdを作って、最初のコミットを残してください(コミットが最低1つ必要です)。/opt/lab/fixtures/gitops/seed/deployment.yamlとservice.yamlを/root/gitops/repo/apps/web/にコピーしてください。Deploymentはmetadata.labelsにapp.kubernetes.io/managed-by: gitopsを持ち、spec.selector.matchLabelsのapp.kubernetes.io/nameはwebであり、spec.replicasはデフォルト値に任せず明示する必要があります(ここでは2から始めます)。コンテナイメージはnginx:1.27のようにタグが固定された値でなければなりません(:latestは失敗扱いになります)。README.mdもそのまま残っている必要があります。apps/web/deployment.yaml、apps/web/service.yaml、README.mdの3つのファイルをすべてgit addして追跡させ、10文字以上の意味のあるメッセージでコミットしてください。終わったら、git status --porcelainの出力が空である必要があります。kubectl apply -n gitops-lab -f /root/gitops/repo/apps/web/で適用し、その出力全体を/root/gitops/out/apply.txtに保存してください。適用後、gitops-labネームスペースにDeploymentwebとServicewebがあり、Deploymentにapp.kubernetes.io/managed-by=gitopsラベルが付いている必要があります。- リポジトリの
apps/web/deployment.yamlでspec.replicasを3に直し、コミットメッセージにreplicasという単語を入れてコミットしたあとで、クラスターにもう一度適用してください。終わったら、コミットが2つ以上あり、リポジトリとクラスターのreplicasがどちらも3で、作業ツリーがきれいである必要があります。 - 今回はリポジトリに触れず、
kubectl scale deploy web -n gitops-lab --replicas=5でドリフトを作ってください。その状態でkubectl diff -n gitops-lab -f /root/gitops/repo/apps/web/の出力を/root/gitops/out/drift-diff.txtに、そのコマンドの終了コードを/root/gitops/out/drift-exit.txtに保存してください。そして/root/gitops/out/drift-note.txtに、手で行った変更が次の適用で元に戻されて消えるという内容を、韓国語で2、3行書いてください。 - リポジトリを基準にもう一度適用して、ドリフトをなくしてください。そのあと
kubectl diff -n gitops-lab -f /root/gitops/repo/apps/web/をもう一度実行し、そのときの終了コードを/root/gitops/out/clean-exit.txtに保存してください。手で作った5をリポジトリに反映してはいけません。リポジトリのreplicasは3で、作業ツリーはきれいである必要があります。 /root/gitops/sync.shを作って、実行権限を与えてください。このスクリプトは、(a)kubectl diffで適用前の差を確認し、(b)kubectl applyで適用し、(c)git rev-parse HEADでどのコミットを適用したかを記録する必要があります。実行結果として、/root/gitops/out/sync-report.jsonにrepo_commit(現在のHEADの完全なハッシュ)、drift(ブール値のfalse)、applied(適用したオブジェクトの数、2以上)の3つのキーを入れてください。
参考
- 同じコミットがいつ適用されても同じ結果を出してこそ、GitOpsです。そのため、イメージのタグを固定し、replicasのようにデフォルト値が存在するフィールドも、あえて明示します。明示していない値は、適用した時点のクラスターのデフォルトが決めることになり、その瞬間にリポジトリは状態を定義できなくなります。
kubectl diffは、差があれば終了コード1、なければ0を返します。kubectl apply --dry-run=serverと違い、実際のサーバーのマージ結果とライブの状態を比較してくれます。- 終了コードは、コマンドの直後に
echo $?で読む必要があります。&&でつなぐと、失敗したとき後ろの部分がそもそも実行されないので、명령 > 파일; echo $? > 코드파일のように;でつなぐほうが安全です(プレースホルダーは、順にコマンド、ファイル、コード用ファイルです)。 - 現在のHEADのハッシュは、
git -C /root/gitops/repo rev-parse HEADで得られます。短いハッシュではなく、完全なハッシュである必要があります。 - よくあるミス1は、ステップ6で手で作った
5を、リポジトリにも反映してしまうことです。そうすると、ドリフトを解決したのではなく、事故をコードに昇格させたことになります。その値が正しいなら、別のコミットで正式に反映する必要があり、このラボでは元に戻すほうが正解です。 - よくあるミス2は、
sync.shやout/をリポジトリの中(/root/gitops/repo)に置いてしまうことです。コミットしなければ作業ツリーが汚れ、コミットすればレポートに書いたHEADのハッシュがたちまちずれます。成果物は、/root/gitops/の下の、リポジトリの外に置きます。
宣言を入れるリポジトリを初期化する
/root/gitops/repoディレクトリを作ってgit initし、そのリポジトリにuser.nameとuser.emailを設定してください。そのあと/root/gitops/repo/README.mdを作って、最初のコミットを残してください(コミットが最低1つ必要です)。
GitOpsでは、コミット履歴がそのまま監査記録です。リポジトリに誰が変えたかが残るには、user.nameとuser.emailがそのリポジトリに設定されている必要があり、コミットが最低1つ必要です。
アプリごとのマニフェストディレクトリを作る
/opt/lab/fixtures/gitops/seed/deployment.yamlとservice.yamlを/root/gitops/repo/apps/web/にコピーしてください。Deploymentはmetadata.labelsにapp.kubernetes.io/managed-by: gitopsを持ち、spec.selector.matchLabelsのapp.kubernetes.io/nameはwebであり、spec.replicasはデフォルト値に任せず明示する必要があります(ここでは2から始めます)。コンテナイメージはnginx:1.27のようにタグが固定された値でなければなりません(:latestは失敗扱いになります)。README.mdもそのまま残っている必要があります。
フィクスチャをコピーして使いますが、リポジトリは人も読みます。そして、デフォルト値に頼らず、個数とイメージのタグを明示してください。同じコミットが違う結果を生めば、そのリポジトリは状態を定義できません。セレクターがPodのラベルと合っているかも確認してください。
宣言をコミットで固定する
apps/web/deployment.yaml、apps/web/service.yaml、README.mdの3つのファイルをすべてgit addして追跡させ、10文字以上の意味のあるメッセージでコミットしてください。終わったら、git status --porcelainの出力が空である必要があります。
追跡されていないファイルが残っていると、リポジトリはクラスターを定義できません。コミットメッセージは、数か月後にこの1行だけを見て、元に戻すかどうかを判断することになる文章です。
リポジトリの宣言をクラスターに適用する
kubectl apply -n gitops-lab -f /root/gitops/repo/apps/web/で適用し、その出力全体を/root/gitops/out/apply.txtに保存してください。適用後、gitops-labネームスペースにDeploymentwebとServicewebがあり、Deploymentにapp.kubernetes.io/managed-by=gitopsラベルが付いている必要があります。
ディレクトリ全体を一度に適用できます。マニフェストにネームスペースが書かれていないので、コマンドで指定する必要があり、適用結果はファイルに残す必要があります。
変更をコミットを経由して反映する
リポジトリのapps/web/deployment.yamlでspec.replicasを3に直し、コミットメッセージにreplicasという単語を入れてコミットしたあとで、クラスターにもう一度適用してください。終わったら、コミットが2つ以上あり、リポジトリとクラスターのreplicasがどちらも3で、作業ツリーがきれいである必要があります。
順序が核心です。リポジトリを先に直してコミットし、そのあと適用します。クラスターを先に直せば、それは変更ではなくドリフトです。
手で変えてドリフトを作ってみる
今回はリポジトリに触れず、kubectl scale deploy web -n gitops-lab --replicas=5でドリフトを作ってください。その状態でkubectl diff -n gitops-lab -f /root/gitops/repo/apps/web/の出力を/root/gitops/out/drift-diff.txtに、そのコマンドの終了コードを/root/gitops/out/drift-exit.txtに保存してください。そして/root/gitops/out/drift-note.txtに、手で行った変更が次の適用で元に戻されて消えるという内容を、韓国語で2、3行書いてください。
今回はわざとリポジトリに触れず、クラスターだけを変えます。差を示すコマンドの終了コードが何かがこのステップの核心で、そのコードはコマンドの直後に読む必要があります。
リポジトリを基準に元に戻す
リポジトリを基準にもう一度適用して、ドリフトをなくしてください。そのあとkubectl diff -n gitops-lab -f /root/gitops/repo/apps/web/をもう一度実行し、そのときの終了コードを/root/gitops/out/clean-exit.txtに保存してください。手で作った5をリポジトリに反映してはいけません。リポジトリのreplicasは3で、作業ツリーはきれいである必要があります。
手で作った値をこっそりリポジトリに反映すれば、事故をコードに昇格させることになります。リポジトリが正しいと仮定してクラスターを合わせたあと、差がないときの終了コードを確認してください。
同期スクリプトとレポートを作る
/root/gitops/sync.shを作って、実行権限を与えてください。このスクリプトは、(a)kubectl diffで適用前の差を確認し、(b)kubectl applyで適用し、(c)git rev-parse HEADでどのコミットを適用したかを記録する必要があります。実行結果として、/root/gitops/out/sync-report.jsonにrepo_commit(現在のHEADの完全なハッシュ)、drift(ブール値のfalse)、applied(適用したオブジェクトの数、2以上)の3つのキーを入れてください。
コントローラーがやっていることを、シェルで真似ます。適用前に何が変わるかを見て、適用し、どのコミットを適用したかを残します。スクリプトをリポジトリの中に置くと、レポートに書いたハッシュがずれます。