CRD: Custom Resource Definitions
Когда говорят, что Kubernetes — это «API-платформа, а не оркестратор контейнеров», имеют в виду именно CustomResourceDefinition. CRD позволяет добавить в кластер новый kind (Certificate, PostgresCluster, Application) и получить вокруг него весь API-инструментарий — kubectl get, RBAC, watch streams, validation — без единой строчки кода в kube-apiserver. Это extension механизм, на котором стоит весь cloud-native ecosystem: cert-manager, Prometheus Operator, ArgoCD, Istio, Crossplane. Без CRD не было бы 80% полезных вещей, которые ставят в Kubernetes.
KRaft контроллер: консенсус без ZooKeeper
Что такое CRD концептуально
CRD — это объект, который регистрирует новый resource type в API server. Сам CRD — это обычный Kubernetes ресурс (apiVersion: apiextensions.k8s.io/v1, kind: CustomResourceDefinition), но его создание имеет особый эффект: apiserver видит этот CRD и поднимает для него новый HTTP endpoint в своём API.
После применения CRD certificates.cert-manager.io:
- Появляется HTTP-маршрут
/apis/cert-manager.io/v1/namespaces/<ns>/certificates. - Можно сделать
kubectl create -f my-cert.yamlсkind: Certificate. - apiserver валидирует тело запроса по
schema.openAPIV3Schemaиз CRD — если полеdnsNamesне массив строк, придёт400 Invalid. kubectl explain certificate.spec.dnsNamesначинает работать — apiserver отдаёт OpenAPI документацию из самой CRD.- RBAC применяется автоматически:
verbs: [get, list, watch, create, update, patch, delete]наresources: [certificates]вapiGroups: [cert-manager.io]. kubectl api-resourcesпоказываетcertificatesрядом сpods,services, и т.д.
CRD без custom controller — это просто хранилище данных в etcd. Можно создать CRD PostgresCluster, сделать kubectl create -f my-pg.yaml, объект попадёт в etcd — но никакой Postgres-кластер не появится. Чтобы это работало, нужен controller, который watches PostgresCluster объекты и реконсилирует реальный мир (создаёт StatefulSet, PVC, Services) в соответствии со spec. CRD + controller вместе называются Operator.
Анатомия CRD
Минимальный полезный CRD выглядит так:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: certificates.cert-manager.io # ОБЯЗАТЕЛЬНО формат <plural>.<group>
spec:
group: cert-manager.io
names:
plural: certificates
singular: certificate
kind: Certificate
shortNames: [cert]
listKind: CertificateList
categories: [cert-manager]
scope: Namespaced # или Cluster (e.g. ClusterIssuer)
versions:
- name: v1
served: true # отвечать ли на этот API version
storage: true # ровно одна version имеет storage=true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
type: object
required: [dnsNames, issuerRef]
properties:
dnsNames:
type: array
items:
type: string
issuerRef:
type: object
required: [name, kind]
properties:
name: {type: string}
kind: {type: string}
group: {type: string}
duration:
type: string
pattern: '^(\d+(h|m|s))+$'
status:
type: object
properties:
conditions:
type: array
items:
type: object
subresources:
status: {} # отдельный endpoint /status
scale: # если CR должен поддерживать kubectl scale
specReplicasPath: .spec.replicas
statusReplicasPath: .status.readyReplicas
additionalPrinterColumns: # столбцы в kubectl get
- name: Ready
type: string
jsonPath: .status.conditions[?(@.type=="Ready")].status
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
Ключевые поля:
metadata.name— обязательно<plural>.<group>. Любое другое значение apiserver отвергает.group— DNS-style имя своей organization (cert-manager.io,argoproj.io,monitoring.coreos.com). Избегайте*.k8s.io— это зарезервировано.names.plural— нижний регистр, используется в URL:/apis/cert-manager.io/v1/certificates.names.kind— PascalCase, используется вkind:манифеста и в Go-структурах controllers.scope—NamespacedилиCluster. У одного CRD scope один навсегда. Менять — пересоздавать CRD.versions[]— массив version-specs. Можно иметьv1alpha1,v1beta1,v1одновременно. Apiserver конвертирует между ними через conversion webhook (или identity для simple cases).served: true— apiserver отвечает на этот version. Можно временноfalse, чтобы deprecate version без удаления.storage: true— ровно у одной version. Это canonical storage format в etcd; остальные версии конвертируются в неё при чтении/записи.
OpenAPI v3 schema: валидация на уровне apiserver
Без schema.openAPIV3Schema apiserver принимает любой YAML под spec и status. Это плохо: типичная ошибка пользователя (написал dnsName вместо dnsNames, забыл required-поле) пройдёт через apiserver молча, а controller потом упадёт с runtime ошибкой. Schema — это первая линия защиты.
Schema следует OpenAPI v3 (subset OpenAPI Specification), с некоторыми Kubernetes-specific расширениями:
type—object,array,string,integer,number,boolean.required: [...]— список обязательных полей.properties— структура полей для objects.items— описание элементов для arrays.pattern: '^...$'— regex для строк.enum: [...]— список допустимых значений.minimum,maximum— для чисел.x-kubernetes-preserve-unknown-fields: true— НЕ удалять unknown поля (по умолчанию pruning ON: всё, чего нет в schema, вырезается перед записью в etcd).x-kubernetes-int-or-string: true— поле может быть int или string (какportв Service).x-kubernetes-list-type: map / set / atomic— strategic merge patch behavior.x-kubernetes-validations: [...](v1.29+ GA): CEL expressions для cross-field validation (self.spec.minReplicas <= self.spec.maxReplicas).
Без schema CRD будет создан, но apiserver автоматически добавит x-kubernetes-preserve-unknown-fields: true и выключит structural schema features: subresources (status/scale), pruning, defaulting, server-side apply. По сути CRD будет работать как «свалка YAML» без validation. С v1.16 schema обязательна для apiextensions.k8s.io/v1 — без неё CRD откажет в создании. Schema — это не опция, а необходимость.
Subresources: /status и /scale
Subresources в CRD — это отдельные API endpoints на том же объекте, требующие отдельных RBAC permissions:
/status— endpoint/apis/<g>/<v>/.../<name>/status. Когда включён, поле.statusв обычном PUT/PATCH игнорируется apiserver-ом; обновлять status можно только через/statussubresource. Это разделение: пользователь пишет.spec, controller пишет.status, и они не пересекаются./scale— endpoint/apis/<g>/<v>/.../<name>/scale. Позволяетkubectl scale my-cr --replicas=5и интеграцию с HPA для custom resource. Поле.spec.replicasмапится черезspecReplicasPathв CRD.
RBAC для status и scale — отдельные subresources:
rules:
- apiGroups: [cert-manager.io]
resources: [certificates]
verbs: [get, list, watch, create, update, patch, delete]
- apiGroups: [cert-manager.io]
resources: [certificates/status] # для controller
verbs: [get, update, patch]
- apiGroups: [cert-manager.io]
resources: [certificates/scale]
verbs: [get, update, patch]
printer columns: красивый kubectl get
По умолчанию kubectl get certificates показывает только NAME и AGE. additionalPrinterColumns в CRD добавляет столбцы через jsonPath по объекту:
additionalPrinterColumns:
- name: Ready
type: string
jsonPath: .status.conditions[?(@.type=="Ready")].status
- name: Secret
type: string
jsonPath: .spec.secretName
- name: Issuer
type: string
jsonPath: .spec.issuerRef.name
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
Теперь kubectl get certificates сам по себе показывает status: Ready=True/False, имя secret, issuer, возраст. Это улучшает DX оператора в разы.
CKAD scope: что нужно знать про CRD
CKAD не требует уметь писать CRD вручную. На экзамене типичные задачи:
- Узнать, какие CRDs установлены в кластере:
kubectl get crd. - Посмотреть, какие версии и группы у конкретной CRD:
kubectl get crd certificates.cert-manager.io -o yaml. - Получить instances кастомного ресурса:
kubectl get certificates -n my-ns. - Понять, что данный ресурс — это не built-in, а от оператора:
kubectl api-resources --api-group=cert-manager.io. - Объяснить, почему создание CR ничего не делает — controller не установлен.
Команды для быстрого discovery:
kubectl api-resources— все resources, включая custom.kubectl api-resources --api-group=monitoring.coreos.com— фильтр по group.kubectl get crd— список всех CRDs.kubectl explain certificate— документация из schema.kubectl explain certificate.spec.dnsNames— деталь поля.
Killer-моменты
- CRD — это API server extension без extension code. Чисто декларативная регистрация нового resource type.
- CRD без controller — мусор в etcd. Нужен controller, реконсилирующий CR в реальный мир — это и есть Operator.
- Schema обязательна для apiextensions.k8s.io/v1. Без неё CRD не создаётся. Структурная schema включает pruning, defaulting, subresources.
- Все CRDs регистрируются apiextensions-controller внутри apiserver. Это in-process extension, не отдельный сервер (в отличие от aggregated API server, который будет в уроке 04).
kubectl explainработает на любой CRD автоматически, если schema толковая. Хорошие descriptions в schema = живая документация для users.