desired / actual / planの三分割を作る
このラボは本物のVM上で動きます
このボックスはPodではなく、KubeVirtが起動した仮想マシンです。Linuxカーネルが別に動き、systemdが実際にサービスを管理し、dockerは模倣ではなく本物のDockerエンジンです。docker runで起動したコンテナは実際にプロセスになり、docker execもdocker logsもそのまま動作します。
以前は、このラボはPodの中で動いていました。カーネルの権限をすべて手放したボックスだったため、コンテナを起動するステップがふさがれていて、そのためイメージのアーカイブを直接展開してみるという回り道で学んでいました。今は回り道は不要です。
知っておくことが2つあります。
- 初回の起動に1分ほどかかります。 VMが起動して、Dockerをインストールするからです。Podのラボ(通常40秒)より遅いです。
- ブラウザーのプレビューはありません。 VMに入ってくる接続は、採点用のポート1つだけが開いています。Webサーバーを起動したなら、VMの中で
curlで確認してください。
目標
望ましい状態(desired)、現在の状態(actual)、両者の差(plan)という3分割の構造を自分で作り、ドリフトがどう検知され、どう収束するかを確認します。
なぜ重要なのか
IaCツールが画面に出力する+ create、- destroy、~ updateは魔法ではなく、2つの状態を比較した結果です。この構造を手で1回作ってみると、planを読む目が変わります。特に、自動化では結果を終了コードで受け取る必要があるので、0は変更なし、1はエラー、2は変更ありという慣例に、そのまま従います。3軸モデルでいえば、コードはDesired、状態ファイルはLast Known、実際のインフラはActualで、3つのうちどれか2つでも食い違えばドリフトです。GitOpsの自己修復も、結局はこの比較を繰り返し回すことです。
ステップ
/root/iac1/desired.yamlに、望ましい状態を宣言してください。最上位にcontainers:の行を1つ置き、その下に項目を2つ置きます。名前はそれぞれiac-webとiac-cacheで、項目ごとにimage:を1行ずつ書きます(ファイル全体でimage:はちょうど2行)。イメージは、Podに事前にあるalpine:3.20、busybox:1.36、python:3.12-alpine、nginx:1.27-alpineの中からだけ選んでください。/root/iac1/parse.shを作成して、そのYAMLを/root/iac1/desired.jsonに変換してください。JSONの形は{"containers":[{"name":"...","image":"..."}, ...]}で、要素は2個、imageの値が空であってはいけません。/root/iac1/actual.shを作成してください。現在実際に起動している管理対象のコンテナを、同じ形のJSONで標準出力に出力します。各要素にはnameとimageの両方が必要です。管理対象は、名前がiac-で始まるコンテナだけで、イメージの文字列は、localhost/やdocker.io/library/の接頭辞を取り除いて、desiredと同じ表記に揃えてください。/root/iac1/plan.sh <desired.json> <actual.json>を作成してください。desiredにだけあれば+ create <이름>、actualにだけあれば- destroy <이름>、両方にあってimageが違えば~ update <이름>を、1行ずつ出力します(プレースホルダーはコンテナ名です)。差が1つもなければno changesを出力して、終了コード0を返します。差があれば、終了コード2を返します(0=変更なし、1=エラー、2=変更あり)。/root/iac1/apply.shを作成して、実際の状態を宣言に合わせてください。終わったら、desiredに書かれた2つのコンテナが、どちらもrunningでなければなりません。- 何も変えないまま、planをもう一度実行して、
/root/iac1/plan2.txtに保存してください。このファイルにはno changesがあり、+、-、~で始まる行が1つもあってはいけません。 - コードの外から状態を揺さぶってください。
docker rm -f iac-cacheでコンテナを1つ削除してから、planを実行して/root/iac1/drift.txtに保存します。変更の行はちょうど1つで、その行は+ create iac-cacheでなければなりません。 - もう一度
apply.shを実行して収束させ、そのあとのplanを/root/iac1/plan3.txtに保存してください。no changesが出て、2つのコンテナがどちらもrunningでなければなりません。
参考
- このラボの核心は、パーサーの品質ではなく、desired/actual/planの3分割です。YAMLのパースは、
grep/sed程度で十分です。 nginx:1.27-alpineはそのままにしておくと起動し続けますが、alpine:3.20はすぐに終了します。docker run -d --name iac-cache alpine:3.20 tail -f /dev/nullのようにして、つなぎとめておきます。- 採点ツールの6つ目のチェックは、desired.jsonとactual.shの出力を、
{name,image}だけ残して並べ替えたうえで、そのまま比較します。イメージの表記が1文字でも違えば失敗します。 - よくある間違い: actual.shが、
iac-ではない他のラボのコンテナまで拾ってくること、planの出力で記号と語の間の空白を抜くこと、ステップ7で2つ削除して、変更の行が2つになること。
望ましい状態を宣言する
/root/iac1/desired.yamlに、望ましい状態を宣言してください。最上位にcontainers:の行を1つ置き、その下に項目を2つ置きます。名前はそれぞれiac-webとiac-cacheで、項目ごとにimage:を1行ずつ書きます(ファイル全体でimage:はちょうど2行)。イメージは、Podに事前にあるalpine:3.20、busybox:1.36、python:3.12-alpine、nginx:1.27-alpineの中からだけ選んでください。
/root/iac1/desired.yamlの最上位にcontainers:の行を1つ置き、その下に項目を2つ置きます。名前は正確にiac-webとiac-cache、項目ごとにimage:を1行ずつ(合計2行)です。オフラインなので、イメージは事前にあるものだけを使います。
宣言をマシンが読める形式にする
/root/iac1/parse.shを作成して、そのYAMLを/root/iac1/desired.jsonに変換してください。JSONの形は{"containers":[{"name":"...","image":"..."}, ...]}で、要素は2個、imageの値が空であってはいけません。
YAMLパーサーがなくてもかまいません。grep/sed/awkでnameとimageの値を取り出して、jq -nで{containers:[...]}を組み立ててください。/root/iac1/desired.jsonの要素は2個で、imageが空であってはいけません。
現在の状態を読み取る
/root/iac1/actual.shを作成してください。現在実際に起動している管理対象のコンテナを、同じ形のJSONで標準出力に出力します。各要素にはnameとimageの両方が必要です。管理対象は、名前がiac-で始まるコンテナだけで、イメージの文字列は、localhost/やdocker.io/library/の接頭辞を取り除いて、desiredと同じ表記に揃えてください。
actual.shは、標準出力でdesiredと同じ形のJSONを出力します。管理対象は、名前がiac-で始まるコンテナだけです。podmanが付けるlocalhost/、docker.io/library/の接頭辞は取り除いて、desiredと表記を揃えてください。
差を計算する
/root/iac1/plan.sh <desired.json> <actual.json>を作成してください。desiredにだけあれば+ create <이름>、actualにだけあれば- destroy <이름>、両方にあってimageが違えば~ update <이름>を、1行ずつ出力します(プレースホルダーはコンテナ名です)。差が1つもなければno changesを出力して、終了コード0を返します。差があれば、終了コード2を返します(0=変更なし、1=エラー、2=変更あり)。
plan.sh <desired.json> <actual.json>は、desiredにだけあれば+ create <이름>、actualにだけあれば- destroy <이름>、イメージが違えば~ update <이름>を出力します(プレースホルダーはコンテナ名です)。記号と語の間は空白です。差がなければ、no changesと終了コード0です。
宣言した状態を作る
/root/iac1/apply.shを作成して、実際の状態を宣言に合わせてください。終わったら、desiredに書かれた2つのコンテナが、どちらもrunningでなければなりません。
apply.shは、planが言ったことだけを実行すればよいです。alpine:3.20のようにすぐ終了するイメージは、tail -f /dev/nullのようなコマンドを与えないと、runningのまま残りません。
再適用しても変化がないことを確認する
何も変えないまま、planをもう一度実行して、/root/iac1/plan2.txtに保存してください。このファイルにはno changesがあり、+、-、~で始まる行が1つもあってはいけません。
何も変えていない状態で、planをもう一度実行して、/root/iac1/plan2.txtに保存してください。no changesだけがあり、+、-、~で始まる行が1つもあってはいけません。採点ツールは、desired.jsonとactual.shの結果を再び比較するので、イメージの表記が1文字違うだけでも失敗します。
外で起きた変更を検出する
コードの外から状態を揺さぶってください。docker rm -f iac-cacheでコンテナを1つ削除してから、planを実行して/root/iac1/drift.txtに保存します。変更の行はちょうど1つで、その行は+ create iac-cacheでなければなりません。
docker rm -f iac-cacheでコードの外から状態を揺さぶったあと、planを/root/iac1/drift.txtに保存します。変更の行はちょうど1つ、+ create iac-cacheでなければなりません。1つだけ削除したのに複数行が出るなら、actual.shが管理対象を誤って選んでいるという意味です。
自動で収束させる
もう一度apply.shを実行して収束させ、そのあとのplanを/root/iac1/plan3.txtに保存してください。no changesが出て、2つのコンテナがどちらもrunningでなければなりません。
もう一度applyしたあとのplanを、/root/iac1/plan3.txtに保存します。no changesが出て、2つのコンテナがどちらもrunningなら、GitOpsコントローラーが行う自己修復を、手で再現したことになります。