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

CNPA — クラウドネイティブプラットフォームエンジニアリングアソシエイト

拒否されて初めて契約が分かる

TT Labで続きを見る

一言でいうと

CRDの価値は、APIサーバーの検証・デフォルト値・RBAC・監査を借りてくる点にあります。何でも受け入れるクラスターでは、そのどれも確認できません。

なぜ拒否されてみる必要があるのか

前のモジュールでは、CRDでプラットフォームAPIを作りました。ところが、そのラボが動く場所にはAPIサーバーの能力がなかったため、何を入れても受け入れられていました。

CRDは、「APIサーバーがすでに持っている能力を、自分の型に貸してくれる」ものです。その能力とは、次のものです。

검증      스키마에 안 맞으면 거절한다
기본값    빠진 값을 저장 시점에 채운다
RBAC      다른 자원과 똑같이 권한을 건다
감사      누가 언제 무엇을 바꿨는지 남는다
watch     컨트롤러가 변화를 구독한다
문서      kubectl explain 이 스키마를 읽어 준다

APIを自前で作ると、この6つをすべて作り直す必要があります。ところが、受け入れるだけのクラスターでは、そのどれも確認できませんでした。

黙って消されるフィールド

構造化スキーマは、デフォルトで切り捨てです。propertiesにないフィールドは、自然に消えます。拒否する設定を有効にしたわけではありません。replicasをreplicaと誤って書くと、その値が消え、誰も教えてくれません。

additionalProperties: falseを使おうとして引っかかる箇所でもあります。propertiesと一緒には使えません(Forbidden: mutual exclusive)。使う必要がないため、塞いであるのです。

逆に、x-kubernetes-preserve-unknown-fields: trueを入れると、切り捨ても検証も丸ごとオフになります。楽だからと入れた瞬間に、すべての柵がなくなります。

デフォルト値は、誰が補うかが違います

스키마의 default    저장 시점에 채워져 kubectl get -o yaml 에 바로 보인다
컨트롤러가 채움     한참 뒤에 나타난다. 그 사이에는 비어 있다

開発者が確認したときに値が入っていてこそ、何が起きるのかがわかります。ゴールデンパスは、ここで作られます。

スキーマを変更すると何が壊れるか

CRDは、デプロイしたあとには、すでに保存されたオブジェクトがあります。スキーマを変更することは、 保存されたデータを再解釈する作業なので、ルールがあります。

変更 安全か 理由
オプションフィールドの追加 ✅ 古いオブジェクトは、そのフィールドが空なだけです
必須フィールドの追加 ❌ 古いオブジェクトが検証に引っかかり、更新もできなくなります
フィールドの削除 ⚠️ 値が切り捨てられます。元に戻せません
型の変更(string→int) ❌ 保存された値を読めません
enum値の追加 ✅
enum値の削除 ❌ その値を使っていたオブジェクトが無効になります

必須フィールドを追加する必要があるなら、新しいバージョンを作ります(v1alpha1 → v1beta1)。 storage: trueのバージョンは1つだけで、残りは変換(conversion)を経て提供されます。 変換WebhookがなければNoneストラテジーなのでフィールドがそのまま通過するため、構造が違う場合は Webhookを立てる必要があります。

検証をどこまでスキーマでできるか

OpenAPIスキーマでできることと、できないことが分かれます。

properties:
  replicas:
    type: integer
    minimum: 1
    maximum: 100
    default: 3
  tier:
    type: string
    enum: [bronze, silver, gold]
  name:
    type: string
    pattern: '^[a-z][a-z0-9-]{2,30}$'

ここまではスキーマでできます。フィールド間の関係はできません。「tierがgoldなら replicasが5以上」のようなものです。以前はWebhookが必要でしたが、今はCEL検証 ルールでCRDの中で表現できます。

x-kubernetes-validations:
  - rule: "self.tier != 'gold' || self.replicas >= 5"
    message: "gold 등급은 복제본이 5개 이상이어야 합니다"
  - rule: "self.name == oldSelf.name"      # 불변 필드
    message: "name 은 만든 뒤에 바꿀 수 없습니다"

Webhookより優れている点は明確です。別途デプロイが不要で、Webhookが落ちてAPIが止まる ことがなく、エラーメッセージをスキーマと同じ場所に置けます。Webhookは、CELで表現できない ときだけ使います。

ステータスをどこに置くか

statusは、specと分離して、サブリソースとして置きます。

subresources:
  status: {}
  scale:
    specReplicasPath: .spec.replicas
    statusReplicasPath: .status.replicas

こうすると、ユーザーはstatusを変更できず、コントローラーはspecを変更できません。権限が 自然に分かれます。scaleサブリソースを置くと、kubectl scaleとHPAがそのまま 動作します。カスタムリソースにHPAを付ける方法が、これです。

実務で本当に大切なこと

タイプミスは、エラーではなく沈黙として返ってきます。構造化スキーマはデフォルトで切り捨てなので、replicasをreplicaと書くと、値が黙って消えます。プラットフォームAPIを公開するときは、kubectl get -o yamlで保存された結果を読み直すよう、案内文に書いておくほうがよいです。

x-kubernetes-preserve-unknown-fields: trueは最後の手段です。楽だからと入れた瞬間に、切り捨ても検証も丸ごとオフになり、その型に立てておいた柵がすべてなくなります。

デフォルト値は、コントローラーではなくスキーマに置きます。スキーマのdefaultは保存時点で補われるため、開発者がすぐに確認できますが、コントローラーが補うと、その間は空のままです。ゴールデンパスは、この違いから生まれます。

次のラボでは、これらを本物のAPIサーバー上で、実際に拒否されながら確認します。