CRD とは何か
v1.37 時点(2026年10月26日 確認)
目次
この記事では Kubernetes の API に自分のリソースの種類を足す仕組みである CRD(CustomResourceDefinition) を、図で説明します。CRD のマニフェストに何を書くのか、登録すると kube-apiserver が何をするのかがわかります。CRD で足したリソースにコントローラーを組み合わせる Operator の考え方もわかります。後半では kind のクラスタで、公式ドキュメントの例の CRD を登録し、kubectl get で扱えるようになることを確かめる手順を示します。
第5回で見たマニフェストと宣言的 API の内容を前提にしています。Operator の考え方では第6回の調整ループも使います。まだの方は先にこちらをどうぞ。
マニフェストと宣言的 API の仕組みKubernetes に渡すマニフェストの構造と、望ましい状態を書いて任せる宣言的 API の仕組みを図で説明します。apiVersion・kind・metadata・spec・status の役割と、spec と status を誰が書くのかを押さえ、kind のクラスタで kubectl explain を使い、Pod の spec と status を作成直後と数秒後で比べる手順を紹介します。以下の図は CRD、CRD で足したリソース、それを扱うコントローラー、kube-apiserver の関係を表しています。後半の手順で使う公式ドキュメントの例の名前で描いています。
利用者は CRD crontabs.stable.example.com を kube-apiserver に登録して、CronTab という新しい種類を Kubernetes の API に足します。CronTab は公式ドキュメントが説明に使う例の種類で、第16回の CronJob(batch の API グループの組み込みのリソース)とは別の種類です。CRD を登録するまで、クラスタに CronTab という種類はありません。登録したあとは、CronTab のオブジェクトを Pod などと同じように kubectl で作ったり取り出したりできます。
公式ドキュメントによると、カスタムリソースだけでは、構造を持ったデータを保存して取り出せるだけ です。そのため、CronTab を作っても cronSpec の時刻に何かが動くことはないはずです。カスタムリソースにカスタムコントローラーを組み合わせると、コントローラーが今の状態を宣言された望ましい状態に合わせ続ける、本当の意味の宣言的 API になります。第5回で見た分担です。カスタムリソースとコントローラーを組み合わせて、特定のアプリケーションの運用を自動化するものを Operator と呼びます(次の表と「カスタムコントローラーと Operator」で説明します)。後半の手順では、コントローラーは動かさずに CRD とカスタムリソースだけを扱います。
参考:Custom Resources | Kubernetes、Custom Resources | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、Operator pattern | Kubernetes、Command line tool (kubectl) | Kubernetes
CRD とは
この回に出てくる 4 つの言葉を、先に次の表にまとめます。どれもこのあと詳しく説明します。
| 言葉 | この回の例 | 何を指すか | 誰が作る・動かすか |
|---|---|---|---|
| CRD | crontabs.stable.example.com | 新しい種類の定義(名前とスキーマ) | 利用者が kube-apiserver に登録する |
| カスタムリソース | 種類の CronTab と、そのオブジェクトの my-new-cron-object | CRD などで足した種類と、そのオブジェクト。データとして保存される | オブジェクトは利用者が作り、kube-apiserver が受け付けて保存する |
| カスタムコントローラー | この回では動かさない | カスタムリソースを見張り、望ましい状態に合わせるプログラム | 利用者などがクラスタに足して動かす |
| Operator | 公式の例の SampleDB の Operator | カスタムリソースを使ってアプリケーションを管理するソフトウェアの拡張 | いちばん一般的には、CRD とコントローラーをクラスタに足して動かす |
参考:Custom Resources | Kubernetes、Operator pattern | Kubernetes
公式ドキュメントでは、「カスタムリソース」が足した種類(API の拡張)を指すことも、その種類のオブジェクトを指すこともあります。Custom Resources のページの冒頭はカスタムリソースを「ある種類の 1 つ 1 つのオブジェクト(原文は instances)」と説明し、続けて「API の拡張」とも説明しています。タスクのページは個々のオブジェクトを custom objects と呼んでいます。この回でも、どちらを指すかは文脈で読み分けてください。
参考:Custom Resources | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
まず、リソースという言葉から押さえます。公式ドキュメントによると、リソースは Kubernetes の API の中で、ある種類のオブジェクトの集まりを保存するエンドポイント(API のパス)です。たとえば組み込みの pods のリソースには、Pod のオブジェクトが集まっています。第5回で見た、URL で使う名前(pods)のほうの呼び方です。
カスタムリソース は Kubernetes の API の拡張 で、Kubernetes を入れただけの状態で使えるとは限りません。公式ドキュメントは、カスタムリソースが特定の Kubernetes の環境に合わせたカスタマイズを表すとしています。一方で、Kubernetes の中心的な機能の多くも今ではカスタムリソースで作られ、Kubernetes がよりモジュール化された(部品に分かれた)とも書いています。環境ごとのカスタマイズにも、Kubernetes 自身の機能にも使われている、ということです。カスタムリソースは動いているクラスタに登録したり消したりでき、クラスタの管理者はカスタムリソースをクラスタそのものとは別に更新できます。入れたあとは、Pod のような組み込みのリソースと同じく、kubectl でオブジェクトを作ったり取り出したりできます。
これまでの回にもカスタムリソースは出てきました。第24回の Gateway API のリソースはカスタムリソースとして定義されていて、Cloud Provider KIND が CRD を入れてから使いました。
公式ドキュメントによると、カスタムリソースをクラスタに足す方法は CRD と API Aggregation(以下、アグリゲーション)の 2 つです。CRD はプログラミングなしで作れ、追加のサービスは要らず、kube-apiserver が扱います(原文は “CRDs are handled by API server”)。アグリゲーションは第35回で見たアグリゲーション層の仕組みで、kube-apiserver の後ろで動く別の API サーバー(metrics-server など)にリクエストを取り次ぎます。公式ドキュメントは CRD のほうが使いやすく、アグリゲーションのほうが柔軟だとしています。この回では CRD だけを扱います。
参考:Custom Resources | Kubernetes、Custom Resources | Kubernetes、Custom Resources | Kubernetes、Kubernetes API Aggregation Layer | Kubernetes
CRD は カスタムリソースを定義するための API のリソース です。CRD のオブジェクトを作ると、指定した名前とスキーマ(データの形の決まり)を持つ新しいカスタムリソースができます。カスタムリソースのリクエストに応え、データを保存するのは Kubernetes の API です。そのため、カスタムリソースのために自分で API サーバーを書く必要はありません。CustomResourceDefinition そのものは、第37回で見たとおり Namespace に属さないリソースです。一方、CRD で作る CronTab のほうが Namespace に属するかどうかは、CRD の scope で決めます(この回の例では Namespace に属します)。
参考:Custom Resources | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
なぜ CRD が必要なのか
たとえば、データベースを kubectl apply で「SampleDB を 1 つ」と宣言するだけで、用意やバックアップまで任せたい場合を考えます。公式ドキュメントの Operator の例は、これに近い形です(詳しくは「カスタムコントローラーと Operator」で説明します)。Pod や Deployment は、Kubernetes が最初から持っている宣言的 API の種類でした。Extending Kubernetes のページは、新しいコントローラー、アプリケーションの設定のオブジェクト、そのほかの宣言的 API を定義して kubectl などのツールで管理したいなら、カスタムリソースを足すことを検討するよう書いています。
参考:Operator pattern | Kubernetes、Extending Kubernetes | Kubernetes
設定を置くだけなら、第17回の ConfigMap もあります。公式ドキュメントは ConfigMap とカスタムリソースを使い分ける条件を挙げています。ConfigMap を使うのは、挙げられた条件のどれかに当てはまる場合です。たとえば mysql.cnf のような決まった形式の設定ファイルがある場合や、設定ファイルの主な使い道が、Pod の中のプログラムが読んで自分を設定することである場合です。一方、カスタムリソースを使うのは、6 つ挙げられた条件の多く(原文は most of the following)に当てはまる場合です。主なものは次の 4 つです。
- Kubernetes のクライアントライブラリや CLI で、新しいリソースを作ったり更新したりしたい
kubectl get my-object object-nameのように、kubectl からそのまま扱いたい- 新しいオブジェクトの変化を見張って、ほかのオブジェクトを作ったり変えたりする自動化を作りたい
.spec、.status、.metadataのような Kubernetes の API の決まりを使いたい
3 つ目は第6回のコントローラーのような仕組みを、自分の種類のために作りたい場合と読めます。組み込みのコントローラーが Deployment を見て ReplicaSet を作るのと同じ形です。カスタムリソースとコントローラーの組み合わせは「カスタムコントローラーと Operator」で説明します。
ただし、何でもカスタムリソースにすればよいわけではありません。公式ドキュメントはアプリケーション、利用者、監視のデータの置き場にカスタムリソースを使うのを避ける(Avoid)よう書いています。
CRD の仕組み
CRD のマニフェスト
以下は公式ドキュメントのタスクに載っている CRD の例です。CronTab という種類のカスタムリソースを定義しています。コメントも原文のまま引用します。
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: crontabs.stable.example.com
spec:
# group name to use for REST API: /apis/<group>/<version>
group: stable.example.com
# list of versions supported by this CustomResourceDefinition
versions:
- name: v1
# Each version can be enabled/disabled by Served flag.
served: true
# One and only one version must be marked as the storage version.
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
image:
type: string
replicas:
type: integer
# either Namespaced or Cluster
scope: Namespaced
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: crontabs
# singular name to be used as an alias on the CLI and for display
singular: crontab
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: CronTab
# shortNames allow shorter string to match your resource on the CLI
shortNames:
- ct参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
group の stable.example.com に含まれる example.com は、一般に説明の例のために取っておかれたドメイン名です。実際に使うときは、自分たちが持つドメイン名などをもとにグループの名前を付けることになると考えられます。CRD の apiVersion は apiextensions.k8s.io/v1 で、CRD そのものも apiextensions.k8s.io という API グループのリソースです。spec の主なフィールドを v1.37 時点の API リファレンスに沿って次の表にまとめます。
| フィールド | 意味 |
|---|---|
group | カスタムリソースの API グループで、カスタムリソースは /apis/<group>/... で提供される。必須 |
versions | カスタムリソースの API のバージョンの一覧。必須 |
scope | カスタムリソースが Namespace に属するか(Namespaced)、クラスタ全体のものか(Cluster)。必須 |
names | カスタムリソースの名前(URL での名前、kind など)。必須 |
versions の 1 つ 1 つには次のフィールドを書きます。name、served、storage は必須です。
| フィールド | 意味 |
|---|---|
name | バージョンの名前(v1、v2beta1 など)。served が true なら、/apis/<group>/<name>/... で提供される |
served | このバージョンを REST API で提供するかどうか |
storage | カスタムリソースを保存するときに、このバージョンを使うかどうか。true のバージョンはちょうど 1 つでなければならない |
schema | このバージョンの検証などに使うスキーマ。openAPIV3Schema に書く |
公式ドキュメントによると、複数のバージョンがあれば、同じオブジェクトを 2 つの API のバージョンで提供できます。これは、フィールドの名前を変えるような API の変更をしやすくするのに役立つこともある(Can help)とされています。カスタムリソースは CRD の spec で決めた、今の保存用のバージョン(storage: true のバージョン)で保存されます。複数のバージョンを提供する方法は公式ドキュメントの別のページにあり、この回では扱いません。
参考:Custom Resources | Kubernetes、Custom Resources | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
names には kubectl や API で使う名前を書きます。第3回で、kubectl の TYPE には単数形・複数形・省略形(CRD では shortNames。この回では短い名前と呼びます)のどれでも書けると見ました。CRD ではその名前を自分で決めます。
| フィールド | 意味 | 例の値 |
|---|---|---|
plural | URL で使う複数形の名前で、すべて小文字。必須 | crontabs |
singular | 単数形の名前で、すべて小文字。省くと kind を小文字にしたもの | crontab |
kind | マニフェストや API の呼び出しの kind に使う名前で、ふつうは CamelCase の単数形。必須 | CronTab |
shortNames | 短い名前で、すべて小文字。kubectl get ct のように使える | ct |
CRD の metadata.name は <plural>.<group> の形でなければなりません(例の crontabs.stable.example.com)。API リファレンスは group と plural の説明でも、CRD の名前と合っていなければならない(Must)としています。公式ドキュメントによると、CRD の名前は正しい DNS サブドメイン名(第5回)でなければならず、CRD で定義した種類のオブジェクトの名前も同じです。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、CustomResourceDefinition | Kubernetes API Reference、Custom Resources | Kubernetes
CRD で足したリソースは、必ず新しい API グループに入ります。公式ドキュメントによると、今ある API グループを置き換えたり変えたりはできません。API を足しても、Pod のような今ある API の振る舞いを直接変えられるわけではありません。
登録すると何が起きるか
公式ドキュメントによると、CRD を作ると、kube-apiserver は指定したバージョンごとに、新しいリソースのパス(原文は RESTful resource path)を作ります。例の CRD なら、Namespace に属する次のエンドポイントができます。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
group の stable.example.com、バージョンの v1、plural の crontabs が、第5回で見たパスの形(/apis/グループ/バージョン/namespaces/Namespace/リソースの種類)に入っています。* には Namespace の名前が、... にはオブジェクトの名前などが入ると読めます。このエンドポイントで CronTab のオブジェクトを作ったり管理したりします。エンドポイントができるまでには数秒かかることがあります。公式ドキュメントによると、CRD の Established の状態(condition)が true になるのを見張るか、kube-apiserver の Discovery の情報(第5回)にリソースが現れるのを見張れば、できたことがわかります。Established は英語で「確立された」という意味で、登録が済んで使えるようになったことを表すと読めます。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、CustomResourceDefinition | Kubernetes API Reference
以下の図は CRD を登録してから、CronTab のオブジェクトを作って kubectl get で取り出すまでの流れを表しています。
1 と 2 が CRD の登録、3 と 4 がカスタムリソースの作成と取り出しで、1・2 と 3・4 は別々のリクエストです。利用者からの 1 本の線は CRD の apply と CronTab の apply・get をまとめて描いたものです。2 から 3 への線は、2 で用意したパスで、あとから届く CronTab のリクエストを受け付けることを表します。CronTab を作るときは 3 と 4 を通り、kubectl get で取り出すときは 4 だけを通ると考えられます。
赤の実線は、3 の検証でスキーマに合わないカスタムリソースが拒否されたときに返る線です(スキーマは次の「スキーマによる検証」で説明します)。灰色の破線はこのシリーズの図で応答に使う線で、4 で保存したり取り出したりした結果を返します。etcd への線の「1 と 4」は CRD も CronTab も etcd に保存されることを表します。
公式ドキュメントによると、CRD で足したカスタムリソースのデータは、検証を通ったあとにクラスタの保存先の etcd に保存されます。「CRD とは」で見たとおり CRD は kube-apiserver が扱うので、図の 1 から 4 はどれも kube-apiserver の中で行われると考えられます。kube-apiserver の中の細かい処理の順番は、このメディアで確かめた範囲の公式ドキュメントには書かれていません。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、Custom Resources | Kubernetes
登録したカスタムリソースには、組み込みのリソースと同じ API の機能が付きます。公式ドキュメントが挙げる共通の機能のうち、主なものを次の表にまとめます。
| 機能 | 内容 |
|---|---|
| CRUD | 新しいエンドポイントで、HTTP と kubectl による作成・読み取り・更新・削除ができる |
| Watch | 新しいエンドポイントで、Kubernetes の watch(第4回)ができる |
| Discovery | kubectl などのクライアントが新しい種類を自動で知り、一覧・表示・フィールドの編集の操作を提供する |
| 組み込みの認証・認可 | 認証に kube-apiserver を使い、kube-apiserver の認可(RBAC など)も再利用できる |
| ラベルとアノテーション | 組み込みのリソースと同じ metadata(第42回)を持てる |
Discovery に載るので、kubectl api-resources(第3回)の一覧にも現れると考えられます。kubectl のリファレンスにある kubectl api-resources の表の列は NAME、SHORTNAMES、APIVERSION、NAMESPACED、KIND です。CRD の plural、shortNames、group とバージョン、scope、kind が、それぞれの列に対応すると読めます。kubectl では、リソースの種類の名前(第3回の TYPE)の大文字小文字は区別されません。CRD で決めた単数形、複数形、短い名前のどれでも使えます。一方、my-new-cron-object のようなオブジェクトの名前は、第3回で見たとおり大文字小文字を区別します。
参考:Command line tool (kubectl) | Kubernetes、Command line tool (kubectl) | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
消すときの動きも決まっています。公式ドキュメントによると、CRD を消すと、サーバーはそのエンドポイントを外し、保存されていたカスタムリソースもすべて消します。あとで同じ CRD を作り直しても、中身は空から始まります。また、組み込みのオブジェクトと同じく、Namespace を消すと、その Namespace のカスタムリソースもすべて消えます。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
スキーマによる検証
カスタムリソースは組み込みのフィールド(apiVersion、kind、metadata)のほかに、自分で決めたフィールドに構造を持ったデータを入れます。そのデータの形を決めるのが、versions の schema.openAPIV3Schema に書く OpenAPI v3.0 のスキーマです。OpenAPI は第3回で触れた、API の仕様を書き表す形式です。公式ドキュメントによると、スキーマは作成と更新のときに検証に使われます。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
一般に OpenAPI のスキーマでは、type: object は中にフィールドを持つ入れ物、properties はその中のフィールドの一覧と読めます。例の CRD のスキーマと、後半の手順で作る CronTab のマニフェストの対応を、次の表にまとめます。
| スキーマの場所 | 型 | マニフェストで対応する場所 |
|---|---|---|
| いちばん上(root) | type: object | CronTab のオブジェクト全体 |
いちばん上の properties の spec | type: object | spec: |
spec の properties の cronSpec、image | type: string | cronSpec: "* * * * */5"、image: my-awesome-cron-image |
spec の properties の replicas | type: integer | replicas: 5 |
apiextensions.k8s.io/v1 の CRD では structural schema(構造的なスキーマ)を書く必要があります。その条件の 1 つ目はいちばん上(root)、オブジェクトの各フィールド、配列の各要素に、空でない type を書くことです。表のとおり、例の CRD ではどの場所にも type があります。残りの条件は allOf などの論理の組み合わせの中での書き方(2 つ)と、metadata に書ける制約(metadata.name と metadata.generateName の制約だけ)です。ほかにも細かい条件や例外がありますが、この回では扱いません。条件に合わないことは、CRD の NonStructural の状態に報告されます。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
スキーマには型のほかに、値の制約も書けます。公式ドキュメントの Validation の節の例では、例の CRD の cronSpec と replicas に次の制約を足しています。後半の手順でもこの例を使います。
cronSpec:
type: string
pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
image:
type: string
replicas:
type: integer
minimum: 1
maximum: 10参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
cronSpec は文字列で、pattern の正規表現(文字列の形を表す書き方)に合わなければなりません。この正規表現は、数字か *(後ろに /数字 を付けてもよい)が空白で区切られて 5 つ並ぶ形を求めていると読めます。replicas は整数で、1 以上 10 以下でなければなりません。公式ドキュメントによると、フィールドに正しくない値があると、CronTab を作るリクエストは拒否されます。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
スキーマに無いフィールド(知らないフィールド)の扱いは、kubectl の --validate の設定で変わります。v1.37 時点の kubectl の既定の --validate=true は、サーバー側の Strict の検査にあたります(第5回)。この設定では、知らないフィールドがあると kube-apiserver がリクエストを拒否します。一方、--validate=false で kubectl の検査を切ると、kube-apiserver は ConfigMap などの組み込みのリソースと同じく、知らないフィールドを保存の前に取り除きます(pruning)。タスクのページの pruning の例は、この形で確かめています。スキーマは kubectl にも公開され、kubectl explain でも使われます。
参考:Kubernetes API Concepts | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
公式ドキュメントによると、ほかにも CEL という式の言語で決まりを書く方法(Validation rules の機能が有効で、スキーマが structural schema の場合)や、アドミッションの Webhook(第40回)で検証を足す方法があります。この回では扱いません。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
kubectl get で表示する列も CRD で決められます。公式ドキュメントによると、kubectl はサーバー側の出力の整形に頼っていて、kubectl get でどの列を表示するかはクラスタの kube-apiserver が決めます。CRD の versions に additionalPrinterColumns を書くと、その列を変えられます。公式の例では Spec、Replicas、Age の 3 つの列を足し、それぞれの値を jsonPath で .spec.cronSpec、.spec.replicas、.metadata.creationTimestamp から取ります。NAME の列は暗黙に付くので、書く必要はありません。API リファレンスによると、列を書かなければ、暗黙に付く NAME の列のほかには、カスタムリソースの経過時間を表す列が 1 つだけ使われます。後半の手順の CRD には additionalPrinterColumns を書かないので、この場合にあたります。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、CustomResourceDefinition | Kubernetes API Reference
カスタムリソースと RBAC
公式ドキュメントによると、CRD はいつも組み込みのリソースと同じ認証・認可・監査のログを使います。ただし RBAC(第38回)を使う場合、ほとんどのロールは新しいリソースへのアクセスを許しません。例外は cluster-admin のロールと、ワイルドカード("*")の規則で作ったロールです。新しいリソースへのアクセスは明示的に許す必要があります。CRD やアグリゲーションの API には足した種類のためのロールの定義が一緒に付いてくることがよくあります。
RBAC のページには既定の admin と edit の ClusterRole に CronTab を管理する権限を、view に読むだけの権限を足す例があります。そのうち admin と edit 用の ClusterRole の rules を引用します。第38回で見た形のまま、apiGroups に CRD の group、resources に URL での名前の crontabs を書いています。例の ClusterRole には既定のロールに規則を足すためのラベル(rbac.authorization.k8s.io/aggregate-to-edit: "true" など)も付いていますが、その仕組みはこの回では扱いません。
rules:
- apiGroups: ["stable.example.com"]
resources: ["crontabs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]カスタムコントローラーと Operator
カスタムリソースに動きを与えるのは、カスタムコントローラーです。公式ドキュメントによると、カスタムコントローラーはクラスタのライフサイクルとは別に、動いているクラスタにデプロイしたり更新したりできます。どんな種類のリソースとも組み合わせられますが、カスタムリソースと組み合わせると特に役に立ちます。カスタムコントローラーを使えば、特定のアプリケーションについての知識を、Kubernetes の API の拡張として書き表せます。
Operator は カスタムリソースを使ってアプリケーションとその部品を管理する、Kubernetes のソフトウェアの拡張 です。公式ドキュメントによると、Operator は Kubernetes の原則、特に制御ループ(第6回の調整ループ)に従います。Operator パターンはコントローラーを 1 つ以上のカスタムリソースと結び付けることで、Kubernetes 自身のコードを変えずにクラスタの振る舞いを拡張します。Operator は Kubernetes の API のクライアントで、カスタムリソースのコントローラーとして働きます。
参考:Operator pattern | Kubernetes、Operator pattern | Kubernetes
公式ドキュメントは Operator パターンの動機を人間の運用者(human operator)にたとえて説明しています。特定のアプリケーションやサービスを世話する運用者は、システムがどう振る舞うべきか、どうデプロイするか、問題が起きたらどう対応するかをよく知っています。Operator パターンはこの運用者の目的をとらえ、Kubernetes 自身が提供する範囲を超えた作業を、コードで自動化する方法だと説明されています。Extending Kubernetes のページは、望ましい状態をもとにインフラをデプロイする作業を、人間の運用者に代わってコントローラーが担うなら、そのコントローラーは Operator パターンにも従っているかもしれない(may)としています。Operator パターンは特定のアプリケーションの管理に使われます。対象はふつう、状態を持ち、管理に注意が要るアプリケーションです。
参考:Operator pattern | Kubernetes、Extending Kubernetes | Kubernetes
第6回の調整ループに当てはめると、Operator のコントローラーも「見る・比べる・変える」を繰り返すと考えられます。Extending Kubernetes のページによると、コントローラーはふつう、オブジェクトの .spec を読み、必要なら何かをして、オブジェクトの .status を更新します。Controllers のページによると、コントローラーの多くは 1 つの種類のリソースを望ましい状態として読み、別の種類のリソースを管理します。deployment-controller が Deployment を読んで ReplicaSet を作ったのと同じ形です。
参考:Extending Kubernetes | Kubernetes、Controllers | Kubernetes
公式ドキュメントは Operator の例として SampleDB というデータベースの Operator を挙げています。部品と動きを次の表にまとめます。
| 部品・動き | 公式の例での中身 |
|---|---|
| カスタムリソース | クラスタに設定できる SampleDB |
| コントローラーを動かすもの | Operator のコントローラーの部分を含む Pod を動かし続ける Deployment と、Operator のコードのイメージ |
| SampleDB を足したとき | 保存先の PersistentVolumeClaim、SampleDB を動かす StatefulSet、最初の設定をする Job を用意する |
| SampleDB を消したとき | スナップショットを取り、StatefulSet とボリュームも消えるようにする |
| ふだんの運用 | 定期的なバックアップ。データベースが古いバージョンなら、更新する Job を作る |
つまり SampleDB の Operator は、SampleDB を望ましい状態として読み、StatefulSet や PersistentVolumeClaim(第27回、第28回)などを作って管理します。公式ドキュメントによると、Operator の中核は、設定されたリソースに現実を合わせる方法を kube-apiserver に伝えるコードです。これは、kube-apiserver とやり取りしながら(SampleDB を読み、StatefulSet などを作るよう求めながら)現実を合わせるコード、という意味だと読めます。
Operator をデプロイするいちばん一般的な方法は、CRD と、それに対応するコントローラーをクラスタに足すことです。コントローラーはふつうコントロールプレーンの外で、ほかのコンテナのアプリケーションと同じように動きます。たとえばクラスタの中で Deployment として動かせます。デプロイしたあとは、Operator が使う種類のリソースを足したり、変えたり、消したりして使います。公式の例では kubectl get SampleDB で設定したデータベースを一覧し、kubectl edit SampleDB/example-database で設定を変えます。変更を適用し、今のサービスをよい状態に保つのは Operator です。
参考:Operator pattern | Kubernetes、Operator pattern | Kubernetes、Controllers | Kubernetes
第43回の Helm のチャートとのつながりも、公式ドキュメントに書かれています。チャートなどのパッケージには CRD と一緒に、新しいカスタムリソースの処理を実装した第三者のコードの Deployment が含まれていることがよくあります。一方、CRD を作るだけなら、新しい故障の原因が自動で増えることはありません(たとえば kube-apiserver で第三者のコードが動き出すことはありません)。
求める動きを実装した Operator が見つからなければ、自分で書けます。Operator(つまりコントローラー)は、Kubernetes の API のクライアントとして働ける言語なら、どれでも実装できます。公式ドキュメントは Operator を書くためのライブラリやツールとして、kubebuilder や Operator Framework などを、Kubernetes のプロジェクトの外のものとして挙げています。既製の Operator を探す場所として OperatorHub.io も紹介しています。
参考:Operator pattern | Kubernetes、Operator pattern | Kubernetes
コントローラーが .status を書く形に合わせて、CRD には status のサブリソース(第5回の /status)を有効にする機能があります。公式ドキュメントによると、利用者が spec を、コントローラーが status を書く、という細かいアクセスの制御ができます。この回の手順では使いません。
参考:Custom Resources | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
手元で試す
ここまでの内容を手元の kind のクラスタで試す手順を示します。公式ドキュメントのタスクにある CronTab の CRD を登録し、kubectl api-resources に現れることを確かめます。続けて、スキーマに合わない CronTab が拒否されること、正しい CronTab を kubectl get で扱えることを確かめます。最後に CRD を消して、CronTab も一緒に消えることを確かめます。
この節で引用する出力の例は、公式ドキュメントに載っている、ほかの環境で取ったものです。名前や時間などの値は手元とは違います。各段に書く成功の目安は公式ドキュメントから予想される結果で、手元で実行して確かめたものではありません。結果が違ったときは、まず --context kind-kind の付け忘れ、ファイル名、YAML のインデント(行頭の空白)を見直してください。
コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。
試す環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| kind | v0.33.0 |
| Kubernetes | v1.37.0(kind v0.33.0 の既定のノードイメージ) |
| ノード構成 | シングルノード(クラスタ名 kind):コントロールプレーン 1 |
| 必要なもの | Docker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)2 つ |
参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes
この回では Pod を動かさないので、第43回と同じシングルノードのクラスタを使います。公式のタスクはノードが 2 つ以上のクラスタを勧めて(recommended)いますが、手順は複数のノードを前提にしません。公式のタスクの前提にある Kubernetes 1.16 以降という条件も満たしています。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
kind でクラスタを作る手順は、以下の記事で解説しています。
kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。この回では端末を 2 つ使います。これまでの回と同じく、1 つ目の端末は変化の監視(この回では CRD、途中から CronTab)に取っておきます。クラスタの準備も含めて、操作は 2 つ目の端末で行います。2 つ目の端末では最初にこの回のファイルを置く作業用のディレクトリを作って移ります。ディレクトリの名前は一例です。
$ mkdir crd && cd crd
この回のファイルとコマンドはすべてこのディレクトリで扱います。
シングルノードのクラスタを用意する
2 つ目の端末で、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
kind が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。
$ kind create cluster
$ kind get clusters
kind create cluster は終わるまでに時間がかかることがあります。かかる時間は環境によって違うと考えられるので、コマンドが終わって次の入力ができるようになるまで待ちます。kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからの kubectl のコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。続けて、ノードを一覧します。
$ kubectl get nodes --context kind-kind
ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。
前の回のクラスタを使い続けている場合は、この回で登録する CRD がまだ無いことも確かめます。crd は CustomResourceDefinition の短い名前で、kubectl のリファレンスの表に載っています。
$ kubectl get crd crontabs.stable.example.com --context kind-kind
名前を指定したので、無ければエラーの形で、見つからないことを知らせる 1 行が出ると考えられます。この回ではそれが期待どおりの結果で、そのまま進められます。crontabs.stable.example.com の行が表示された(すでに登録されている)場合は、自分で作ったものかを確かめます。「登録すると何が起きるか」のとおり CRD を消すと CronTab のオブジェクトもすべて消えるので、残したいものが無いかも先に確かめてください。そのうえで kubectl delete crd crontabs.stable.example.com --context kind-kind で消します。
第38回では、kind の利用者が cluster-admin の権限を持つと推測しました。第38回の手順で kubectl auth can-i '*' '*' が yes になったことは、この推測に合います。推測のとおりなら、CRD もカスタムリソースも、このあとの手順のとおりに扱えるはずです。
CRD を監視する
1 つ目の端末で、CRD の一覧を監視します。監視だけなので、作業用のディレクトリに移る必要はありません。
$ kubectl get crd --watch --context kind-kind
CRD が何も無ければ、何も表示されないか、見つからないことを知らせる 1 行が出て、止まったように見えると考えられます。ほかの CRD の行が表示されても、そのまま進めてかまいません。CRD を登録すると、この端末に行が書き足されるはずです。
CRD を登録する
2 つ目の端末で、登録する前の stable.example.com の API グループを確かめます。--api-group で API グループを絞れます(第24回)。
$ kubectl api-resources --api-group=stable.example.com --context kind-kind
リソースの行は 1 つも出ないはずです。まだ CRD を登録していないので、stable.example.com の API グループはこのクラスタにありません。
以下は公式ドキュメントの Validation の節にある CRD の例で、そのまま使います。「CRD のマニフェスト」で引用した例と比べると、原文のコメントが openAPIV3Schema の上の 1 行だけになり、cronSpec に pattern、replicas に minimum と maximum が足されています。resourcedefinition.yaml という名前で保存します。
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: crontabs.stable.example.com
spec:
group: stable.example.com
versions:
- name: v1
served: true
storage: true
schema:
# openAPIV3Schema is the schema for validating custom objects.
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
image:
type: string
replicas:
type: integer
minimum: 1
maximum: 10
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、CustomResourceDefinition | Kubernetes API Reference
2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f resourcedefinition.yaml --context kind-kind
customresourcedefinition の crontabs.stable.example.com が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には crontabs.stable.example.com の行が書き足されるはずです。--watch は変化のたびに行を出すので、CRD の状態が変わると、同じ名前の行が何回か出ることもあると考えられます。
エンドポイントができるまで数秒かかることがあるので、公式ドキュメントのとおり Established の状態を待ちます。kubectl wait は、指定した状態がリソースの status に現れるまで待つコマンドです。状態の値の既定は true です。
$ kubectl wait --for=condition=Established crd/crontabs.stable.example.com --context kind-kind
参考:kubectl wait | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
状態が満たされると、そのことを知らせる 1 行が出て終わるはずです。kubectl wait は既定では 30 秒で待つのをやめます。時間切れのエラーが出たら、もう一度実行します。
もう一度、stable.example.com の API グループを確かめます。
$ kubectl api-resources --api-group=stable.example.com --context kind-kind
今度は 1 行が出るはずです。「登録すると何が起きるか」の対応のとおり、NAME 列が crontabs、SHORTNAMES 列が ct、APIVERSION 列が stable.example.com/v1、NAMESPACED 列が true、KIND 列が CronTab になると考えられます。
続けて、公開されたスキーマを kubectl explain で見ます(第5回)。
$ kubectl explain crontabs.spec --context kind-kind
参考:kubectl explain | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
フィールドの一覧に cronSpec、image、replicas が並ぶはずです。例の CRD のスキーマには description を書いていないので、説明の文は表示されないと考えられます。
この段では 1 つ目の端末に crontabs.stable.example.com の行が現れ、kubectl api-resources に crontabs の行が出れば成功です。
CronTab を監視する
1 つ目の端末で Ctrl+C を押して CRD の監視を止め、代わりに CronTab の一覧を監視します。CRD を登録したので、crontabs も一覧できるようになっているはずです。この監視は片付けの前まで止めずに続けます。
$ kubectl get crontabs --watch --context kind-kind
まだ CronTab のオブジェクトは無いので、何も表示されないか、見つからないことを知らせる 1 行が出て、止まったように見えると考えられます。マニフェストに Namespace を書かないので、オブジェクトは第5回と同じく default の Namespace に作られると考えられます。-n を付けていないこの監視も default の Namespace を見ていると考えられます。
スキーマに合わない CronTab を渡す
以下は公式ドキュメントの Validation の節にある、正しくない値を持つ CronTab の例です。公式ドキュメントによると、cronSpec は正規表現に合わず、replicas は 10 を超えています。cronSpec の値は 4 つしか並んでいません。この正規表現は第16回の CronJob の schedule と同じく 5 つの欄を並べる形に似せていると読めます。公式の例ではファイル名が my-crontab.yaml ですが、このあとの正しい例と分けるため、my-crontab-invalid.yaml という名前で保存します。
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
cronSpec: "* * * *"
image: my-awesome-cron-image
replicas: 152 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f my-crontab-invalid.yaml --context kind-kind
公式ドキュメントの例では次のエラーになります。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
例の creationTimestamp は 2017 年なので、古いバージョンで取った出力だと考えられます。手元では文面が例と違うことがあると考えられますが、見るのは最後の 2 行にあたる内容です。spec.cronSpec が正規表現に合わないことと、spec.replicas が 10 以下でなければならないことの 2 つが知らされるはずです。オブジェクトは作られないので、1 つ目の端末には変化が無いはずです。
この段ではエラーで断られ、1 つ目の端末に行が書き足されなければ成功です。
CronTab を作って kubectl get で扱う
以下は公式ドキュメントの Validation の節にある、正しい値を持つ CronTab の例です。replicas を 5 にし、cronSpec を 5 つ並べています。my-crontab.yaml という名前で保存します。
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
cronSpec: "* * * * */5"
image: my-awesome-cron-image
replicas: 5参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f my-crontab.yaml --context kind-kind
公式ドキュメントの例では、出力は次のとおりです。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
手元でも my-new-cron-object が作られたことを知らせる 1 行が出るはずです。例の 1 行は古い形式だと考えられ、手元では形が違うことがあります。1 つ目の端末には my-new-cron-object の行が書き足されるはずです。
CronTab を一覧します。
$ kubectl get crontab --context kind-kind
公式ドキュメントの例では次のように表示されます。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
この CRD には additionalPrinterColumns を書いていないので、手元でも例と同じく NAME と AGE の 2 つの列になるはずです。AGE の値は作ってからの時間なので、例とは違います。単数形の crontab で一覧できることも確かめられます。
続けて、短い名前の ct で、オブジェクトを YAML の形式で表示します。
$ kubectl get ct -o yaml --context kind-kind
公式ドキュメントの例では次のように表示されます。
apiVersion: v1
items:
- apiVersion: stable.example.com/v1
kind: CronTab
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"stable.example.com/v1","kind":"CronTab","metadata":{"annotations":{},"name":"my-new-cron-object","namespace":"default"},"spec":{"cronSpec":"* * * * */5","image":"my-awesome-cron-image"}}
creationTimestamp: "2021-06-20T07:35:27Z"
generation: 1
name: my-new-cron-object
namespace: default
resourceVersion: "1326"
uid: 9aab1d66-628e-41bb-a422-57b8b3b1f5a9
spec:
cronSpec: '* * * * */5'
image: my-awesome-cron-image
kind: List
metadata:
resourceVersion: ""
selfLink: ""参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
一覧の結果なので、出力は kind: List に包まれ、items の中に CronTab が入っています。公式ドキュメントによると、kind: List は kubectl が一覧を表すための、クライアント側の形です。
例は replicas を書いていない CronTab を作ったときのものです。手元の表示は、例と次の点が違うと考えられます。
| 違い | 手元の表示 |
|---|---|
spec | cronSpec と image に加えて、replicas: 5 がある |
| last-applied のアノテーション | 中の JSON にも "replicas":5 がある |
creationTimestamp、resourceVersion、uid | 手元で作ったときの値 |
metadata には第5回で Pod に見たのと同じく、uid、creationTimestamp、resourceVersion が加わっているはずです。カスタムリソースも組み込みのリソースと同じ metadata を持つことがわかります。
最後に、「登録すると何が起きるか」のエンドポイントで、同じ CronTab を取り出します。第5回と同じく --raw を使います。
$ kubectl get --raw /apis/stable.example.com/v1/namespaces/default/crontabs/my-new-cron-object --context kind-kind
参考:kubectl get | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
JSON の形式で、"kind":"CronTab" を含む CronTab が返るはずです。CRD の group(stable.example.com)、バージョン(v1)、plural(crontabs)が、kube-apiserver の新しいパスになっていることを確かめてください。
この段では kubectl get crontab と kubectl get ct の両方で my-new-cron-object が表示され、--raw で同じオブジェクトが返れば成功です。
片付け
1 つ目の端末で Ctrl+C を押して、CronTab の監視を止めます。これから消す CRD が、監視している種類(crontabs)そのものを定義しているので、監視を先に止めておきます。監視したまま CRD を消したときに何が表示されるかは、確かめた範囲の公式ドキュメントには書かれていません。
2 つ目の端末で、CronTab は消さずに、CRD だけを消します。そのあと CronTab を一覧します。
$ kubectl delete -f resourcedefinition.yaml --context kind-kind
$ kubectl get crontabs --context kind-kind
公式ドキュメントの例では、2 つ目のコマンドは次のエラーになります。
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、kubectl delete | Kubernetes
1 つ目のコマンドでは CRD を消したことを知らせる 1 行が出るはずです。保存されていた CronTab も一緒に消すので、コマンドがすぐには終わらないこともあると考えられます。そのときはコマンドが終わるまで待ちます。2 つ目のコマンドの文面は手元の kubectl では例と違うことも考えられますが、crontabs が見つからないことを知らせるエラーになるはずです。エンドポイントが外されたからです。kubectl api-resources --api-group=stable.example.com --context kind-kind を実行すると、登録する前と同じく、行が 1 つも出ないはずです。
CronTab のオブジェクトも消えたかを確かめるため、同じ CRD をもう一度登録して、CronTab を一覧します。
$ kubectl apply -f resourcedefinition.yaml --context kind-kind
$ kubectl wait --for=condition=Established crd/crontabs.stable.example.com --context kind-kind
$ kubectl get crontabs --context kind-kind
公式ドキュメントによると、作り直した CRD は空から始まります。そのため最後のコマンドでは、my-new-cron-object は表示されず、見つからないことを知らせる 1 行が出るはずです。確かめたら、CRD をもう一度消します。
$ kubectl delete -f resourcedefinition.yaml --context kind-kind
参考:Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
最後に、クラスタを削除し、消えたことを確かめます。続けて次の回を試す場合は、クラスタを残しておいてもかまいません。クラスタを削除しても、作業用のディレクトリのファイルは残ります。要らなければ、作業用のディレクトリごと消してかまいません。
$ kind delete cluster
$ kind get clusters
kind delete cluster は存在しないクラスタを指定してもエラーを返しません。kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。
注意点
CRD とカスタムリソースでつまずきやすい点を次の表にまとめます。
| 項目 | 内容 |
|---|---|
| カスタムリソースだけでは何も動かない | データを保存して取り出せるだけ。動きはカスタムコントローラーが担う |
| CRD を消すとカスタムリソースも消える | サーバーはエンドポイントを外し、保存されていたカスタムリソースをすべて消す。作り直した CRD は空から始まる |
CRD の名前は <plural>.<group> | group と plural に合っていなければならない。CRD の名前とオブジェクトの名前は、正しい DNS サブドメイン名でなければならない |
storage: true はちょうど 1 つ | versions の中で、保存に使うバージョンは 1 つだけにしなければならない |
apiextensions.k8s.io/v1 では structural schema が要る | いちばん上と各フィールドに type を書く。条件に合わないことは NonStructural の状態に報告される |
| 知らないフィールドは拒否されるか、取り除かれる | kubectl の既定(--validate=true)では、kube-apiserver が知らないフィールドを含むリクエストを拒否する。--validate=false で検査を切ると、保存の前に取り除かれる |
| ほとんどのロールは新しいリソースを許さない | 例外は cluster-admin とワイルドカードのロール。新しいリソースへのアクセスは明示的に許す必要がある |
| アプリケーション、利用者、監視のデータの置き場にしない | 公式ドキュメントは避ける(Avoid)よう書いている |
参考:Custom Resources | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes、CustomResourceDefinition | Kubernetes API Reference、Kubernetes API Concepts | Kubernetes
関連記事
次の回ではこれまでの回の要素を組み合わせて、Web アプリケーションとデータベースを一式デプロイします。
Go のアプリと MySQL を一式デプロイする(総まとめ)これまでの回の要素を組み合わせ、Go で書いた Web アプリケーションと MySQL を一式でデプロイする仕組みを図で説明します。StatefulSet と PVC、Secret と Kustomize、Service と DNS、readiness Probe のつながりを押さえ、kind のクラスタで DB の Pod を消してもメッセージが残ることを確かめる手順を示します。CRD で足す API の例として、Gateway API の CRD を入れてから使う手順については、以下の記事で解説しています。
Ingress と Gateway API の違いクラスタの外からの HTTP の通信を Service に振り分ける Ingress と Gateway API の違いを図で説明します。Ingress コントローラーが要ること、CRD で足す Gateway API の GatewayClass・Gateway・HTTPRoute の役割の分担を押さえ、kind のクラスタで同じ振り分けを両方で書いて比べる手順を紹介します。カスタムリソースへの権限を決める RBAC のロールと結び付けについては、以下の記事で解説しています。
ServiceAccount と RBAC の仕組みPod の中のプログラムが API を使うときの身元になる ServiceAccount と、誰にどの操作を許すかを決める RBAC の仕組みを図で説明します。トークンが Pod に届く流れ、Role と RoleBinding の書き方、権限が足し算だけで決まることを押さえ、権限の無い ServiceAccount が断られる様子を kind のクラスタで確かめる手順を紹介します。