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

CRDとオペレータ

CRはデプロイの入力であり状態報告書でもある

TT Labで続きを見る

一言でいうと

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の仕様から導かれる「望ましい状態」のオブジェクトを作り、次のモジュールの調整ループに進む準備をします。