CRはデプロイの入力であり状態報告書でもある
一言でいうと
CRは、ユーザーがほしいものを書く場所(spec)と、システムが観察したものを返す場所(status)を、1つのオブジェクトの中に持っています。この2つを混ぜた瞬間、デプロイは追跡できなくなります。
なぜ必要なのか
「このサービスは今ちゃんと動いていますか」という質問に答える方法を考えてみましょう。Helmだけを使う場合は、releaseの一覧を見て、そのreleaseが作ったDeploymentを探し、そのDeploymentのReplicaSetを探して、Podを数えなければなりません。ツールが教えてくれるのは「インストールコマンドが成功した」ところまでで、その後の状態は人が組み立てる必要があります。
CRは、その組み立てをオブジェクトの中に取り込みます。ユーザーはspecに意図を書き、コントローラーはstatusに観察の結果を書きます。すると、kubectl get webserviceの1行がそのままデプロイ状態のダッシュボードになります。ただし、この構造が成り立つには規律が必要です。
どう動くのか
規律1 — specはユーザーが、statusはコントローラーが書く。コントローラーがspecに値を書き込んだ瞬間から、GitOpsのリポジトリとクラスターがずれ始めます。ユーザーがコミットしたマニフェストにはない値がクラスターにだけ生まれ、次の同期がそれを消します。そのため、statusは必ず別のパスで書きます。
잘못됨: spec 과 status 를 한 번에 update -> status 서브리소스가 켜져 있으면 status 는 무시됨
올바름: status 만 서브리소스 경로로 갱신
このコードブロックの韓国語の2行は、順に、誤りはspecとstatusを一度にupdateすること(statusサブリソースが有効だとstatusは無視される)、正しいのはstatusだけをサブリソースのパスで更新すること、という意味です。
規律2 — 命令ではなく状態を書く。spec.restartNow: trueのようなフィールドはアンチパターンです。一度実行されたら、その値は意味を失い、誰がいつ消したのかも追跡できません。代わりに、spec.versionやspec.pausedのように、持続的な意味を持つ値で表現します。そうすれば、調整ループがいつ再び回っても同じ判断をします。
規律3 — 下位リソースには所有者参照を付ける。CRが作ったConfigMap・Deployment・ServiceにownerReferencesを付けると、2つのことが付いてきます。1つ目は、親が削除されたときに、ガベージコレクターが子を自動的に片付けることです。2つ目は、コントローラーが「自分が作ったもの」だけを管理するようになり、調整の範囲が明確になることです。ここで重要な落とし穴が1つあります。所有者参照は名前ではなくuidでつながります。同じ名前の親を削除して作り直すとuidが変わり、古いuidを指している子は孤児になって、すぐに回収されます。
規律4 — statusはconditionsの標準に従う。単純なphase: Runningという文字列よりも、conditions配列が推奨されます。
| フィールド | 意味 |
|---|---|
type |
条件の名前(Ready、Progressing、Degraded) |
status |
True / False / Unknown |
reason |
機械が読む短い理由コード |
message |
人が読む説明 |
lastTransitionTime |
状態が最後に変わった時刻 |
reasonとmessageを両方置く理由が核心です。アラートルールはreasonで分岐し、オンコール担当者はmessageを読みます。片方だけにすると、どちらか一方が不便になります。
規律5 — observedGenerationで時間差を可視化する。metadata.generationはspecが変更されるたびに上がり、status.observedGenerationはコントローラーが最後に処理した世代です。両者が違うなら、「まだ最新の仕様を反映していない」という意味です。この1組がないと、ユーザーはstatusが新しいspecを反映したものなのか、古いspecの名残なのかを区別できません。
現場での姿
1つ目は、statusだけを見て判断する事故です。statusは、コントローラーが観察した結果をキャッシュしたものであり、真実の源ではありません。本当の状態は実際のリソースにあります。statusだけを見て分岐するコントローラーは、statusが古くなったときに、誤った判断を下します。
2つ目は、statusに配列を際限なく積む事故です。イベントやログをstatusの配列に蓄積すると、更新のたびにオブジェクト全体が書き直され、informerキャッシュのメモリも一緒に膨らみます。conditionsのように、サイズが固定された構造を使う必要があります。
3つ目は、カスタムカラムとラベルの組み合わせです。CRが正式なオブジェクトであることの最も実用的な結果は、ラベルセレクターです。spec.tierに値を入れることと、metadata.labels.tierを付けることは別のことです。前者はコントローラーが読む意図であり、後者は人とツールが選ぶ索引です。両方が必要です。
次のラボですること
crd-labネームスペースに、最小の仕様のCRと全体の仕様のCRをそれぞれ作成してデフォルト値の注入を確認し、所有者参照で子のConfigMapを親に結び付け、statusサブリソースに観測値とReady条件を直接書きます。そのあと、ラベルセレクターとカスタムカラムで必要なものだけを取り出してみて、最後に、CRの仕様から導かれる「望ましい状態」のオブジェクトを作り、次のモジュールの調整ループに進む準備をします。