CNPA — クラウドネイティブプラットフォームエンジニアリングアソシエイト
拒否されて初めて契約が分かる
一言でいうと
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サーバー上で、実際に拒否されながら確認します。