CustomResourceDefinition으로 Kubernetes API 확장하기
CustomResourceDefinition으로 Kubernetes API 확장하기 (Extend the Kubernetes API with CustomResourceDefinitions)
이 페이지는 CustomResourceDefinition을 만들어 커스텀 리소스를 Kubernetes API에 설치하는 방법을 보여줘요.
출처: 문서
본문
시작하기 전에 (Before you begin)
Kubernetes 클러스터가 있어야 하고 kubectl 명령줄 도구가 클러스터와 통신하도록 구성되어 있어야 해요. 이 튜토리얼은 컨트롤 플레인 호스트로 작동하지 않는 노드가 최소 두 개 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube를 이용해 만들거나, 아래 Kubernetes 플레이그라운드 중 하나를 사용할 수 있어요.
- iximiuz Labs (https://labs.iximiuz.com/playgrounds?category=kubernetes&filter=all)
- Killercoda (https://killercoda.com/playgrounds/scenario/kubernetes)
- KodeKloud (https://kodekloud.com/public-playgrounds)
버전을 확인하려면 kubectl version을 입력해요.
CustomResourceDefinition 만들기 (#create-a-customresourcedefinition)
새 CustomResourceDefinition(CRD)을 만들면 Kubernetes API 서버가 지정한 각 버전에 대해 새로운 RESTful 리소스 경로를 만들어요. CRD 객체에서 만들어진 커스텀 리소스는 CRD의 spec.scope 필드에 지정된 대로 네임스페이스 범위 또는 클러스터 범위가 될 수 있어요. 기존 내장 객체와 마찬가지로 네임스페이스를 삭제하면 그 네임스페이스의 모든 커스텀 객체를 삭제해요. CustomResourceDefinition 자체는 네임스페이스가 없고 모든 네임스페이스에서 사용 가능해요.
예를 들어 다음 CustomResourceDefinition을 resourcedefinition.yaml에 저장하면:
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
만들어요:
kubectl apply -f resourcedefinition.yaml
그런 다음 새로운 네임스페이스 범위 RESTful API 엔드포인트가 다음에 생성돼요:
/apis/stable.example.com/v1/namespaces/*/crontabs/...
이 엔드포인트 URL을 사용해 커스텀 객체를 만들고 관리할 수 있어요. 이 객체들의 kind는 위에서 만든 CustomResourceDefinition 객체의 spec에서 나온 CronTab이 될 거예요.
엔드포인트가 생성되는 데 몇 초가 걸릴 수 있어요. CustomResourceDefinition의 Established 조건이 true가 되는 것을 보거나 API 서버의 발견 정보에서 리소스가 나타나는 것을 볼 수 있어요.
커스텀 객체 만들기 (#create-custom-objects)
CustomResourceDefinition 객체가 생성된 후 커스텀 객체를 만들 수 있어요. 커스텀 객체는 커스텀 필드를 포함할 수 있어요. 이 필드는 임의의 JSON을 포함할 수 있어요.
다음 예시에서 cronSpec과 image 커스텀 필드는 kind CronTab의 커스텀 객체에 설정돼요. kind CronTab은 위에서 만든 CustomResourceDefinition 객체의 spec에서 나와요.
다음 YAML을 my-crontab.yaml에 저장하면:
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
cronSpec: "* * * * */5"
image: my-awesome-cron-image
만들어요:
kubectl apply -f my-crontab.yaml
그런 다음 kubectl을 사용해 CronTab 객체를 관리할 수 있어요. 예:
kubectl get crontab
다음과 같은 목록을 출력해야 해요:
NAME AGE
my-new-cron-object 6s
리소스 이름은 kubectl을 사용할 때 대소문자를 구분하지 않으며, CRD에 정의된 단수 또는 복수 형태와 어떤 짧은 이름도 사용할 수 있어요.
또한 원시 YAML 데이터를 볼 수 있어요:
kubectl get ct -o yaml
그것이 생성에 사용한 YAML의 커스텀 cronSpec과 image 필드를 포함하는 것을 볼 수 있어야 해요:
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: ""
CustomResourceDefinition 삭제 (#delete-a-customresourcedefinition)
CustomResourceDefinition을 삭제하면 서버가 RESTful API 엔드포인트를 제거하고 그 안에 저장된 모든 커스텀 객체를 삭제해요.
kubectl delete -f resourcedefinition.yaml
kubectl get crontabs
Error from server (NotFound): Unable to list {"stable.example.com" "v1" "crontabs"}: the server could not
find the requested resource (get crontabs.stable.example.com)
나중에 같은 CustomResourceDefinition을 다시 만들면 빈 상태로 시작돼요.
구조적 스키마 지정 (#specifying-a-structural-schema)
CustomResources는 커스텀 필드에 구조화된 데이터를 저장해요. (API 서버가 암시적으로 검증하는 내장 필드 apiVersion, kind, metadata와 함께). OpenAPI v3.0 검증으로 생성과 업데이트 중에 검증되는 스키마를 지정할 수 있어요. 자세한 내용과 그러한 스키마의 한계는 아래를 비교해보세요.
apiextensions.k8s.io/v1에서 구조적 스키마(structural schema)의 정의는 CustomResourceDefinitions에 필수예요. CustomResourceDefinition의 베타 버전에서는 구조적 스키마가 선택 사항이었어요.
구조적 스키마는 다음을 충족하는 OpenAPI v3.0 검증 스키마(#검증)예요:
- 루트, 객체 노드의 각 지정된 필드(OpenAPI에서 properties 또는 additionalProperties 통해), 배열 노드의 각 항목(OpenAPI에서 items 통해)에 대해 비어 있지 않은 type(OpenAPI의 type 통해)을 지정함. 예외:
x-kubernetes-int-or-string: true인 노드,x-kubernetes-preserve-unknown-fields: true인 노드. - allOf, anyOf, oneOf, not 중 어느 하나 안에서 지정된 객체의 각 필드와 배열의 각 항목에 대해 스키마도 그러한 논리 접속사 밖에서 필드/항목을 지정함(예시 1과 2 비교).
- allOf, anyOf, oneOf, not 안에서 description, type, default, additionalProperties, nullable을 설정하지 않음 (
x-kubernetes-int-or-string: true의 두 패턴 제외, 아래 참고). - metadata가 지정되면 metadata.name과 metadata.generateName에 대한 제한만 허용됨.
비구조적 예시 1:
allOf:
- properties:
foo:
# ...
규칙 2와 충돌한다. 다음이 올바른 것:
properties:
foo:
# ...
allOf:
- properties:
foo:
# ...
비구조적 예시 2:
allOf:
- items:
properties:
foo:
# ...
규칙 2와 충돌한다. 다음이 올바른 것:
items:
properties:
foo:
# ...
allOf:
- items:
properties:
foo:
# ...
비구조적 예시 3:
properties:
foo:
pattern: "abc"
metadata:
type: object
properties:
name:
type: string
pattern: "^a"
finalizers:
type: array
items:
type: string
pattern: "my-finalizer"
anyOf:
- properties:
bar:
type: integer
minimum: 42
required: ["bar"]
description: "foo bar object"
다음 위반 때문에 구조적 스키마가 아니에요:
- 루트의 type이 누락됨(규칙 1).
- foo의 type이 누락됨(규칙 1).
- anyOf 안의 bar가 밖에서 지정되지 않음(규칙 2).
- bar의 type이 anyOf 안에 있음(규칙 3).
- description이 anyOf 안에 설정됨(규칙 3).
- metadata.finalizers가 제한될 수 없음(규칙 4).
대조적으로 다음 대응 스키마는 구조적이에요:
type: object
description: "foo bar object"
properties:
foo:
type: string
pattern: "abc"
bar:
type: integer
metadata:
type: object
properties:
name:
type: string
pattern: "^a"
anyOf:
- properties:
bar:
minimum: 42
required: ["bar"]
구조적 스키마 규칙의 위반은 CustomResourceDefinition의 NonStructural 조건에 보고돼요.
필드 가지치기 (#field-pruning)
CustomResourceDefinitions는 검증된 리소스 데이터를 클러스터의 영속 저장소인 etcd(/docs/tasks/administer-cluster/configure-upgrade-etcd/)에 저장해요. ConfigMap 같은 네이티브 Kubernetes 리소스와 마찬가지로 API 서버가 인식하지 못하는 필드를 지정하면 알 수 없는 필드가 영속화되기 전에 가지치기(제거)돼요.
apiextensions.k8s.io/v1beta1에서 apiextensions.k8s.io/v1로 변환된 CRD는 구조적 스키마가 없을 수 있고, spec.preserveUnknownFields가 true일 수 있어요.
다음 YAML을 my-crontab.yaml에 저장하면:
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
cronSpec: "* * * * */5"
image: my-awesome-cron-image
someRandomField: 42
만들어요:
kubectl create --validate=false -f my-crontab.yaml -o yaml
출력은 다음과 유사해요:
apiVersion: stable.example.com/v1
kind: CronTab
metadata:
creationTimestamp: 2017-05-31T12:56:35Z
generation: 1
name: my-new-cron-object
namespace: default
resourceVersion: "285"
uid: 9423255b-4600-11e7-af6a-28d2447dc82b
spec:
cronSpec: '* * * * */5'
image: my-awesome-cron-image
someRandomField 필드가 가지치기된 것을 주목해요.
이 예시는 --validate=false 명령줄 옵션을 추가해 클라이언트 측 검증을 꺼서 API 서버의 동작을 보여줬어요. OpenAPI 검증 스키마가 (OpenAPI에 검증 스키마 게시(#openapi에-검증-스키마-게시) 참고) 클라이언트에도 게시되므로 kubectl도 알 수 없는 필드를 확인하고 그러한 객체를 API 서버에 보내기 훨씬 전에 거부해요.
가지치기 제어 (#controlling-pruning)
기본적으로 커스텀 리소스의 모든 버전에 걸쳐 지정되지 않은 모든 필드가 가지치기돼요. 하지만 구조적 OpenAPI v3 검증 스키마(#구조적-스키마-지정)에 x-kubernetes-preserve-unknown-fields: true를 추가해 특정 필드 하위 트리에 대해 그렇게 하지 않도록 선택할 수 있어요.
예:
type: object
properties:
json:
x-kubernetes-preserve-unknown-fields: true
json 필드는 아무것도 가지치기되지 않고 어떤 JSON 값을 저장할 수 있어요.
또한 허용되는 JSON을 부분적으로 지정할 수 있어요. 예:
type: object
properties:
json:
x-kubernetes-preserve-unknown-fields: true
type: object
description: this is arbitrary JSON
이것으로 객체 유형 값만 허용돼요.
가지치기는 각 지정된 property(또는 additionalProperties)에 대해 다시 활성화돼요:
type: object
properties:
json:
x-kubernetes-preserve-unknown-fields: true
type: object
properties:
spec:
type: object
properties:
foo:
type: string
bar:
type: string
이것으로 값:
json:
spec:
foo: abc
bar: def
something: x
status:
something: x
다음으로 가지치기돼요:
json:
spec:
foo: abc
bar: def
status:
something: x
이것은 지정된 spec 객체의 something 필드는 가지치기되지만 밖의 모든 것은 가지치기되지 않음을 의미해요.
IntOrString (#intorstring)
schema의 x-kubernetes-int-or-string: true인 노드는 규칙 1에서 제외되므로 다음은 구조적이에요:
type: object
properties:
foo:
x-kubernetes-int-or-string: true
또한 그러한 노드는 규칙 3에서 x-kubernetes-int-or-string: true 다음의 두 패턴이 (정확히 그것들, 추가 필드의 변형 없이) 허용된다는 의미에서 부분적으로 제외돼요:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
...
그리고:
x-kubernetes-int-or-string: true
allOf:
- anyOf:
- type: integer
- type: string
- # ... zero or more
...
그 사양 중 하나로 정수와 문자열 모두 검증돼요.
검증 스키마 게시에서 x-kubernetes-int-or-string: true는 위에 표시된 두 패턴 중 하나로 펼쳐져요.
RawExtension (#rawextension)
RawExtensions(runtime.RawExtension(/docs/reference//kubernetes-api/workload-resources/controller-revision-v1#RawExtension)에서처럼)는 apiVersion과 kind 필드를 가진 완전한 Kubernetes 객체를 보유해요.
x-kubernetes-embedded-resource: true를 설정해 그러한 내장 객체를 지정할 수 있어요 (완전히 제약 없이 또는 부분적으로 지정). 예:
type: object
properties:
foo:
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
여기서 foo 필드는 완전한 객체를 보유해요. 예:
foo:
apiVersion: v1
kind: Pod
spec:
# ...
x-kubernetes-preserve-unknown-fields: true가 함께 지정되었으므로 아무것도 가지치기되지 않아요. x-kubernetes-preserve-unknown-fields: true의 사용은 선택 사항이에요.
x-kubernetes-embedded-resource: true로 apiVersion, kind, metadata가 암시적으로 지정되고 검증돼요.
CRD의 여러 버전 서빙 (#serving-multiple-versions-of-a-crd)
CustomResourceDefinition의 여러 버전을 서빙하고 객체를 한 버전에서 다른 버전으로 마이그레이션하는 것에 대한 더 많은 정보는 CustomResourceDefinition 버전을 참고해요.
고급 주제 (#advanced-topics)
Finalizers (#finalizers)
Finalizers는 컨트롤러가 비동기 사전 삭제 훅을 구현하게 해줘요. 커스텀 객체는 내장 객체와 유사하게 finalizers를 지원해요.
커스텀 객체에 finalizer를 이렇게 추가할 수 있어요:
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
finalizers:
- stable.example.com/finalizer
커스텀 finalizer의 식별자는 도메인 이름, 슬래시, finalizer의 이름으로 구성돼요. 어떤 컨트롤러든 finalizer를 어떤 객체의 finalizer 목록에 추가할 수 있어요.
finalizers가 있는 객체에 대한 첫 삭제 요청은 metadata.deletionTimestamp 필드의 값을 설정하지만 객체를 삭제하지 않아요. 이 값이 설정되면 finalizers 목록의 항목은 제거만 될 수 있어요. finalizers가 남아 있는 동안 객체의 강제 삭제도 불가능해요.
metadata.deletionTimestamp 필드가 설정되면 객체를 감시하는 컨트롤러가 처리하는 finalizers를 실행하고 완료된 후 목록에서 finalizer를 제거해요. 각 컨트롤러가 목록에서 자신의 finalizer를 제거하는 것은 그 컨트롤러의 책임이에요.
metadata.deletionGracePeriodSeconds의 값은 폴링 업데이트 사이의 간격을 제어해요.
finalizers 목록이 비면, 즉 모든 finalizer가 실행되었으면 리소스가 Kubernetes에 의해 삭제돼요.
검증 (#validation)
커스텀 리소스는 OpenAPI v3.0 스키마를 통해, Validation Rules 기능이 활성화되면 x-kubernetes-validations로 검증되며, admission webhooks로 추가 검증을 추가할 수 있어요.
추가로 다음 제한이 스키마에 적용돼요:
- 다음 필드는 설정할 수 없음: definitions, dependencies, deprecated, discriminator, id, patternProperties, readOnly, writeOnly, xml, $ref.
- uniqueItems 필드는 true로 설정할 수 없음.
- additionalProperties 필드는 false로 설정할 수 없음.
- additionalProperties 필드는 properties와 상호 배타적임.
Validation rules 기능이 활성화되고 CustomResourceDefinition 스키마가 구조적 스키마(#구조적-스키마-지정)이면 x-kubernetes-validations 확장을 사용해 CEL(Common Expression Language)(https://github.com/google/cel-spec) 표현식으로 커스텀 리소스를 검증할 수 있어요.
다른 제한과 CustomResourceDefinition 기능은 구조적 스키마 섹션을 참고해요.
스키마는 CustomResourceDefinition에 정의돼요. 다음 예시에서 CustomResourceDefinition은 커스텀 객체에 다음 검증을 적용해요:
- spec.cronSpec은 문자열이어야 하고 정규식이 설명하는 형식이어야 함.
- spec.replicas는 정수여야 하고 최소값 1과 최대값 10을 가져야 함.
CustomResourceDefinition을 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
만들어요:
kubectl apply -f resourcedefinition.yaml
kind CronTab의 커스텀 객체를 생성하는 요청은 그 필드에 유효하지 않은 값이 있으면 거부돼요. 다음 예시에서 커스텀 객체는 유효하지 않은 값을 가진 필드를 포함해요:
- spec.cronSpec이 정규식과 일치하지 않음.
- spec.replicas가 10보다 큼.
다음 YAML을 my-crontab.yaml에 저장하면:
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
cronSpec: "* * * *"
image: my-awesome-cron-image
replicas: 15
생성하려고 시도하면:
kubectl apply -f my-crontab.yaml
그러면 오류를 받아요:
The CronTab "my-new-cron-object" is invalid: []: Invalid value: map[string]interface {}{"apiVersion":"stable.example.com/v1", "kind":"CronTab", "metadata":map[string]interface {}{"name":"my-new-cron-object", "namespace":"default", "deletionTimestamp":interface {}(nil), "deletionGracePeriodSeconds":(*int64)(nil), "creationTimestamp":"2017-09-05T05:20:07Z", "uid":"e14d79e7-91f9-11e7-a598-f0761cb232d1", "clusterName":""}, "spec":map[string]interface {}{"cronSpec":"* * * *", "image":"my-awesome-cron-image", "replicas":15}}:
validation failure list:
spec.cronSpec in body should match '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
spec.replicas in body should be less than or equal to 10
필드가 유효한 값을 포함하면 객체 생성 요청이 수락돼요.
다음 YAML을 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
만들어요:
kubectl apply -f my-crontab.yaml
crontab "my-new-cron-object" created
검증 레칫팅 (#validation-ratcheting)
v1.30보다 오래된 Kubernetes 버전을 사용한다면 이 동작을 사용하려면 CRDValidationRatcheting 기능 게이트(/docs/reference/command-line-tools-reference/feature-gates/)를 명시적으로 활성화해야 하며, 그러면 클러스터의 모든 CustomResourceDefinition에 적용돼요.
기능 게이트를 활성화했다면 Kubernetes가 CustomResourceDefinition에 대해 검증 레칫팅(validation ratcheting)을 구현해요. API 서버는 검증에 실패한 리소스의 각 부분이 업데이트 작업에 의해 변경되지 않았다면 업데이트 후 유효하지 않은 리소스에 대한 업데이트를 수락할 의향이 있어요. 즉 남아 있는 유효하지 않은 리소스의 어떤 부분도 이미 잘못되어 있었어야 해요. 이 메커니즘을 사용해 유효한 리소스를 유효하지 않게 되도록 업데이트할 수는 없어요.
이 기능은 CRD 작성자가 특정 조건에서 OpenAPIV3 스키마에 새 검증을 자신 있게 추가할 수 있게 해줘요. 사용자는 객체의 버전을 높이거나 워크플로를 깨지 않고 새 스키마로 안전하게 업데이트할 수 있어요.
CRD의 OpenAPIV3 스키마에 배치된 대부분의 검증은 레칫팅을 지원하지만 몇 가지 예외가 있어요. 다음 OpenAPIV3 스키마 검증은 Kubernetes 1.37의 구현에서 레칫팅이 지원되지 않으며 위반되면 평소처럼 오류를 계속 던질 거예요:
- Quantors allOf, oneOf, anyOf, not: 이러한 필드 중 하나의 하위 값의 어떤 검증도 레칫팅되지 않음.
- x-kubernetes-validations: Kubernetes 1.28의 경우 CRD 검증 규칙(#검증-규칙)은 레칫팅에 무시됨. Kubernetes 1.29의 Alpha 2부터 x-kubernetes-validations는 oldSelf를 참조하지 않는 경우에만 레칫팅됨. Transition Rules는 절대 레칫팅되지 않음: oldSelf를 사용하지 않는 규칙에 의해 발생한 오류만 그 값이 변경되지 않으면 자동으로 레칫팅될 것. CEL 표현식에 대한 커스텀 레칫팅 로직을 작성하려면 optionalOldSelf(#field-optional-oldself)를 확인해요.
- x-kubernetes-list-type: 하위 스키마의 목록 유형을 변경하여 발생하는 오류는 레칫팅되지 않음. 예를 들어 중복이 있는 목록에 set을 추가하면 항상 오류가 발생함.
- x-kubernetes-list-map-keys: 목록 스키마의 맵 키 변경으로 발생하는 오류는 레칫팅되지 않음.
- required: 필수 필드 목록 변경으로 발생하는 오류는 레칫팅되지 않음.
- properties: 속성 이름의 추가/제거/수정은 레칫팅되지 않지만, 속성의 이름이 같게 유지되면 각 속성의 스키마와 하위 스키마의 검증 변경은 레칫팅될 수 있음.
- additionalProperties: 이전에 지정된 additionalProperties 검증을 제거하는 것은 레칫팅되지 않음.
- metadata: 객체의 metadata에 대한 Kubernetes 내장 검증에서 오는 오류(객체 이름이나 레이블 값의 문자 같은 것)는 레칫팅되지 않음. 커스텀 리소스의 metadata에 대해 자체 추가 규칙을 지정하면 그 추가 검증은 레칫팅될 것.
검증 규칙 (#validation-rules)
검증 규칙은 CEL(Common Expression Language)(https://github.com/google/cel-spec)을 사용해 커스텀 리소스 값을 검증해요. 검증 규칙은 x-kubernetes-validations 확장을 사용해 CustomResourceDefinition 스키마에 포함돼요.
Rule은 스키마에서 x-kubernetes-validations 확장의 위치로 범위가 정해져요. 그리고 CEL 표현식의 self 변수는 범위가 정해진 값에 바인딩돼요.
모든 검증 규칙은 현재 객체로 범위가 정해져요: 교차 객체 또는 유상태 검증 규칙은 지원되지 않아요.
예:
# ...
openAPIV3Schema:
type: object
properties:
spec:
type: object
x-kubernetes-validations:
- rule: "self.minReplicas <= self.replicas"
message: "replicas should be greater than or equal to minReplicas."
- rule: "self.replicas <= self.maxReplicas"
message: "replicas should be smaller than or equal to maxReplicas."
properties:
# ...
minReplicas:
type: integer
replicas:
type: integer
maxReplicas:
type: integer
required:
- minReplicas
- replicas
- maxReplicas
이 커스텀 리소스를 생성하는 요청을 거부할 거예요:
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
minReplicas: 0
replicas: 20
maxReplicas: 10
응답으로:
The CronTab "my-new-cron-object" is invalid:
* spec: Invalid value: map[string]interface {}{"maxReplicas":10, "minReplicas":0, "replicas":20}: replicas should be smaller than or equal to maxReplicas.
x-kubernetes-validations는 여러 규칙을 가질 수 있어요. x-kubernetes-validations 아래의 rule은 CEL이 평가할 표현식을 나타내요. message는 검증이 실패할 때 표시되는 메시지를 나타내요. message가 설정되지 않으면 위 응답은 다음과 같을 거예요:
The CronTab "my-new-cron-object" is invalid:
* spec: Invalid value: map[string]interface {}{"maxReplicas":10, "minReplicas":0, "replicas":20}: failed rule: self.replicas <= self.maxReplicas
검증 규칙은 CRD가 생성/업데이트될 때 컴파일돼요. 검증 규칙의 컴파일이 실패하면 CRD의 생성/업데이트 요청이 실패해요. 컴파일 과정은 유형 검사도 포함해요.
컴파일 실패:
- no_matching_overload: 이 함수는 인자의 유형에 대한 오버로드가 없음. 예를 들어 정수 유형 필드에 대해
self == true같은 규칙이 오류를 얻을 것. - no_such_field: 원하는 필드를 포함하지 않음. 예를 들어 존재하지 않는 필드에 대해
self.nonExistingField > 0같은 규칙이 다음 오류를 반환할 것. - invalid argument: 매크로에 대한 잘못된 인자. 예를 들어
has(self)같은 규칙이 오류를 반환할 것.
검증 규칙 예시:
| 규칙 | 목적 |
|---|---|
| self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas | replicas를 정의하는 세 필드가 적절히 순서가 있다고 검증 |
| 'Available' in self.stateCounts | 'Available' 키를 가진 항목이 맵에 존재한다고 검증 |
| (size(self.list1) == 0) != (size(self.list2) == 0) | 두 목록 중 하나는 비어 있지 않지만 둘 다는 아님을 검증 |
| !('MY_KEY' in self.map1) | |
| self.envars.filter(e, e.name == 'MY_ENV').all(e, e.value.matches('^[a-zA-Z]*$') | 키 필드 'name'이 'MY_ENV'인 listMap 항목의 'value' 필드 검증 |
| has(self.expired) && self.created + self.ttl < self.expired | 'expired' 날짜가 'create' 날짜 + 'ttl' 기간보다 이후임을 검증 |
| self.health.startsWith('ok') | 'health' 문자열 필드가 접두사 'ok'를 가짐을 검증 |
| self.widgets.exists(w, w.key == 'x' && w.foo < 10) | 키 'x'를 가진 listMap 항목의 'foo' 속성이 10보다 작음을 검증 |
| type(self) == string ? self == '100%' : self == 1000 | int-or-string 필드를 정수와 문자열 경우 모두 검증 |
| self.metadata.name.startsWith(self.prefix) | 객체의 이름이 다른 필드 값의 접두사를 가짐을 검증 |
| self.set1.all(e, !(e in self.set2)) | 두 listSet이 분리되어 있음을 검증 |
| size(self.names) == size(self.details) && self.names.all(n, n in self.details) | 'details' 맵이 'names' listSet의 항목으로 키가 지정됨을 검증 |
| size(self.clusters.filter(c, c.name == self.primary)) == 1 | 'primary' 속성이 'clusters' listMap에서 정확히 한 번만 나타남을 검증 |
Xref: CEL에서 지원되는 평가.
- Rule이 리소스의 루트로 범위가 정해지면 CRD의 OpenAPIv3 스키마에 선언된 어떤 필드로든 필드 선택을 할 수 있으며, apiVersion, kind, metadata.name, metadata.generateName도 할 수 있어요. 이것은 같은 표현식에서 spec과 status의 필드 선택을 모두 포함해요.
- Rule이 properties를 가진 객체로 범위가 정해지면 객체의 접근 가능한 속성은 self.field로 필드 선택 가능하고 필드 존재는 has(self.field)로 확인할 수 있어요. 널 값 필드는 CEL 표현식에서 없는 필드로 취급돼요.
- Rule이 additionalProperties(즉 맵)를 가진 객체로 범위가 정해지면 맵의 값은 self[mapKey]로 접근 가능하고, 맵 포함은 mapKey in self로 확인할 수 있고, 맵의 모든 항목은 self.all(...) 같은 CEL 매크로와 함수로 접근 가능해요.
- Rule이 배열로 범위가 정해지면 배열의 요소는 self[i]로, 그리고 매크로와 함수로도 접근 가능해요.
- Rule이 스칼라로 범위가 정해지면 self가 스칼라 값에 바인딩돼요.
예시:
| 규칙이 범위된 필드의 유형 | 규칙 예시 |
|---|---|
| 루트 객체 | self.status.actual <= self.spec.maxDesired |
| 객체의 맵 | self.components['Widget'].priority < 10 |
| 정수 목록 | self.values.all(value, value >= 0 && value < 100) |
| 문자열 | self.startsWith('kube') |
apiVersion, kind, metadata.name, metadata.generateName은 항상 객체의 루트와 x-kubernetes-embedded-resource로 어노테이션된 객체에서 접근 가능해요. 다른 metadata 속성은 접근할 수 없어요.
x-kubernetes-preserve-unknown-fields를 통해 커스텀 리소스에 보존된 알 수 없는 데이터는 CEL 표현식에서 접근할 수 없어요. 여기에는 다음이 포함돼요:
- x-kubernetes-preserve-unknown-fields가 있는 객체 스키마에 의해 보존된 알 수 없는 필드 값.
- 속성 스키마가 "알 수 없는 유형"인 객체 속성. "알 수 없는 유형"은 재귀적으로 정의됨: type이 없고 x-kubernetes-preserve-unknown-fields가 true로 설정된 스키마, items 스키마가 "알 수 없는 유형"인 배열, additionalProperties 스키마가 "알 수 없는 유형"인 객체.
[a-zA-Z_.-/][a-zA-Z0-9_.-/]* 형식의 속성 이름만 접근 가능해요. 접근 가능한 속성 이름은 표현식에서 접근할 때 다음 규칙에 따라 이스케이프돼요:
| 이스케이프 시퀀스 | 속성 이름에 해당 |
|---|---|
| underscores | __ |
| dot | . |
| dash | - |
| slash | / |
| {keyword} | CEL 예약 키워드(https://github.com/google/cel-spec/blob/v0.6.0/doc/langdef.md#syntax) |
참고: CEL 예약 키워드는 이스케이프되기 위해 정확한 속성 이름과 일치해야 해요(예: sprint라는 단어의 int는 이스케이프되지 않음).
이스케이프 예시:
| 속성 이름 | 이스케이프된 속성 이름이 있는 규칙 |
|---|---|
| namespace | self.namespace > 0 |
| x-prop | self.x__dash__prop > 0 |
| redact__d | self.redact__underscores__d > 0 |
| string | self.startsWith('kube') |
x-kubernetes-list-type이 set 또는 map인 배열의 동등성은 요소 순서를 무시해요. 즉 [1, 2] == [2, 1]이에요. x-kubernetes-list-type이 있는 배열의 결합은 목록 유형의 의미를 사용해요:
- set: X + Y는 X의 모든 요소의 배열 위치가 보존되고 Y의 교차하지 않는 요소가 그 부분 순서를 유지한 채 추가되는 합집합을 수행함.
- map: X + Y는 X의 모든 키의 배열 위치가 보존되지만 X와 Y의 키 집합이 교차할 때 값이 Y의 값으로 덮어써지는 병합을 수행함. 교차하지 않는 키를 가진 Y의 요소는 부분 순서를 유지한 채 추가됨.
OpenAPIv3에서 CEL 유형으로의 선언 유형 매핑:
| OpenAPIv3 유형 | CEL 유형 |
|---|---|
| Properties가 있는 'object' | object / "message type" |
| AdditionalProperties가 있는 'object' | map |
| x-kubernetes-embedded-type이 있는 'object' | object / "message type", 'apiVersion', 'kind', 'metadata.name', 'metadata.generateName'이 스키마에 암시적으로 포함됨 |
| x-kubernetes-preserve-unknown-fields가 있는 'object' | object / "message type", 알 수 없는 필드는 CEL 표현식에서 접근 불가 |
| x-kubernetes-int-or-string | int 또는 string인 동적 객체, type(value)로 유형을 확인할 수 있음 |
| 'array' | list |
| x-kubernetes-list-type=map인 'array' | 맵 기반 동등성 및 고유 키 보장이 있는 list |
| x-kubernetes-list-type=set인 'array' | set 기반 동등성 및 고유 항목 보장이 있는 list |
| 'boolean' | boolean |
| 'number' (모든 형식) | double |
| 'integer' (모든 형식) | int (64) |
| 'null' | null_type |
| 'string' | string |
| format=byte (base64 인코딩)인 'string' | bytes |
| format=date인 'string' | timestamp (google.protobuf.Timestamp) |
| format=datetime인 'string' | timestamp (google.protobuf.Timestamp) |
| format=duration인 'string' | duration (google.protobuf.Duration) |
Xref: CEL 유형, OpenAPI 유형, Kubernetes 구조적 스키마.
messageExpression 필드 (#the-messageexpression-field)
검증 규칙 실패에 대해 보고되는 문자열을 정의하는 message 필드와 유사하게, messageExpression은 메시지 문자열을 구성하는 데 CEL 표현식을 사용할 수 있게 해줘요. 이것은 검증 실패 메시지에 더 설명적인 정보를 삽입할 수 있게 해줘요. messageExpression은 문자열로 평가되어야 하고 rule 필드에 사용 가능한 것과 같은 변수를 사용할 수 있어요. 예:
x-kubernetes-validations:
- rule: "self.x <= self.maxLimit"
messageExpression: '"x exceeded max limit of " + string(self.maxLimit)'
CEL 문자열 결합(+ 연산자)은 문자열로 자동 캐스트하지 않는다는 점을 명심하세요. 문자열이 아닌 스칼라가 있으면 위 예시처럼 string(
messageExpression은 문자열로 평가되어야 하며, 이것은 CRD가 작성되는 동안 확인돼요. message와 messageExpression을 같은 규칙에 설정할 수 있고, 둘 다 있으면 messageExpression이 사용될 거예요. 하지만 messageExpression이 오류로 평가되면 message에 정의된 문자열이 대신 사용되고 messageExpression 오류가 기록될 거예요. 이 대체는 messageExpression에 정의된 CEL 표현식이 빈 문자열 또는 줄 바꿈을 포함하는 문자열을 생성해도 발생할 거예요.
위 조건 중 하나가 충족되고 message가 설정되지 않았다면 기본 검증 실패 메시지가 대신 사용될 거예요.
messageExpression은 CEL 표현식이므로 검증 함수의 리소스 사용에 나열된 제한이 적용돼요. messageExpression 실행 중 리소스 제약으로 인해 평가가 중단되면 더 이상의 검증 규칙은 실행되지 않을 거예요.
messageExpression 설정은 선택 사항이에요.
message 필드 (#field-message)
정적 메시지를 설정하려면 messageExpression 대신 message를 제공할 수 있어요. message의 값은 검증이 실패하면 불투명한 오류 문자열로 사용돼요.
message 설정은 선택 사항이에요.
reason 필드 (#field-reason)
요청이 이 검증 규칙에 실패할 때마다 반환되도록 검증 내부에 기계가 읽을 수 있는 검증 실패 이유를 추가할 수 있어요.
예:
x-kubernetes-validations:
- rule: "self.x <= self.maxLimit"
reason: "FieldValueInvalid"
호출자에게 반환되는 HTTP 상태 코드는 첫 번째 실패한 검증 규칙의 reason과 일치할 거예요. 현재 지원되는 이유는: "FieldValueInvalid", "FieldValueForbidden", "FieldValueRequired", "FieldValueDuplicate". 설정되지 않았거나 알 수 없는 이유는 기본적으로 "FieldValueInvalid"를 사용해요.
reason 설정은 선택 사항이에요.
fieldPath 필드 (#field-field-path)
검증이 실패할 때 반환되는 필드 경로를 지정할 수 있어요.
예:
x-kubernetes-validations:
- rule: "self.foo.test.x <= self.maxLimit"
fieldPath: ".foo.test.x"
위 예시에서 검증은 필드 x의 값이 maxLimit의 값보다 작아야 하는지 확인해요. fieldPath를 지정하지 않으면 검증 실패 시 fieldPath가 self가 범위가 정해진 곳으로 기본 설정될 거예요. fieldPath를 지정하면 반환된 오류가 필드 x의 위치를 적절히 참조할 거예요.
fieldPath 값은 스키마에서 이 x-kubernetes-validations 확장의 위치로 범위가 정해진 상대 JSON 경로여야 해요. 또한 스키마 내의 기존 필드를 참조해야 해요. 예를 들어 검증이 맵 testMap 아래의 특정 속성 foo를 확인하면 fieldPath를 ".testMap.foo" 또는 .testMap['foo']'로 설정할 수 있어요. 검증이 두 목록의 고유 속성을 확인해야 하면 fieldPath를 두 목록 중 하나로 설정할 수 있어요. 예를 들어 .testList1 또는 .testList2로 설정할 수 있어요. 현재 기존 필드를 참조하는 자식 연산을 지원해요. 더 많은 정보는 Kubernetes의 JSONPath 지원을 참고해요. fieldPath 필드는 배열의 숫자 인덱싱을 지원하지 않아요.
fieldPath 설정은 선택 사항이에요.
optionalOldSelf 필드 (#field-optional-oldself)
클러스터에 CRD 검증 레칫팅(#검증-레칫팅)이 활성화되어 있지 않으면 CustomResourceDefinition API는 이 필드를 포함하지 않고, 그것을 설정하려고 하면 오류가 발생할 수 있어요.
optionalOldSelf 필드는 아래에 설명된 Transition Rules의 동작을 바꾸는 불리언 필드예요. 일반적으로 transition rule은 oldSelf를 결정할 수 없을 때(객체 생성 중 또는 업데이트에서 새 값이 도입될 때) 평가하지 않아요.
optionalOldSelf가 true로 설정되면 transition rules가 항상 평가되고 oldSelf의 유형이 CEL Optional(https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes) 유형으로 변경돼요.
optionalOldSelf는 스키마 작성자가 (#검증-레칫팅)의 기본 동등성 기반 동작보다 더 많은 제어 도구를 원할 때 유용해요. 새 값에는 더 새롭고 보통 더 엄격한 제약을 도입하면서 이전 값은 이전 검증으로 "할아버지 조항" 또는 레칫팅되게 허용하는 경우요.
예시 사용:
| CEL | 설명 |
|---|---|
| self.foo == "foo" | |
| [oldSelf.orValue(""), self].all(x, ["OldCase1", "OldCase2"].exists(case, x == case)) | |
| oldSelf.optMap(o, o.size()).orValue(0) < 4 |
검증 함수 (#available-validation-functions)
가용 함수는 다음을 포함해요:
- 표준 정의 목록(https://github.com/google/cel-spec/blob/v0.7.0/doc/langdef.md#list-of-standard-definitions)에 정의된 CEL 표준 함수
- CEL 표준 매크로(https://github.com/google/cel-spec/blob/v0.7.0/doc/langdef.md#macros)
- CEL 확장 문자열 함수 라이브러리(https://pkg.go.dev/github.com/google/[email protected]/ext#Strings)
- Kubernetes CEL 확장 라이브러리(https://pkg.go.dev/k8s.io/[email protected]/pkg/apiserver/schema/cel/library#pkg-functions)
Transition rules (#transition-rules)
식별자 oldSelf를 참조하는 표현식을 포함하는 규칙은 암시적으로 transition rule로 간주돼요. Transition rules는 스키마 작성자가 그 밖에는 유효한 두 상태 사이의 특정 전환을 방지할 수 있게 해줘요. 예:
type: string
enum: ["low", "medium", "high"]
x-kubernetes-validations:
- rule: "!(self == 'high' && oldSelf == 'low') && !(self == 'low' && oldSelf == 'high')"
message: cannot transition directly between 'low' and 'high'
다른 규칙과 달리 transition rules는 다음 기준을 충족하는 작업에만 적용돼요:
- 작업이 기존 객체를 업데이트함. Transition rules는 생성 작업에는 절대 적용되지 않음.
- 이전 값과 새 값이 모두 존재함. 부모 노드에 transition rule을 배치해 값이 추가되거나 제거되었는지 확인하는 것은 여전히 가능함. Transition rules는 커스텀 리소스 생성에는 절대 적용되지 않음. 선택적 필드에 배치되면 transition rule은 필드를 설정하거나 해제하는 업데이트 작업에 적용되지 않음.
- transition rule이 검증하는 스키마 노드로의 경로가 이전 객체와 새 객체 사이에서 비교 가능한 노드로 해석되어야 함. 예를 들어 목록 항목과 그 하위 항목(
spec.foo[10].bar같은 것)은 기존 객체와 같은 객체에 대한 이후 업데이트 사이에서 반드시 상관될 수는 없음.
스키마 노드가 절대 적용될 수 없는 transition rule을 포함하면 CRD 작성 시 오류가 생성돼요. 예: "oldSelf cannot be used on the uncorrelatable portion of the schema within path ...".
Transition rules는 스키마의 상관 가능한 부분에서만 허용돼요. 모든 배열 부모 스키마가 x-kubernetes-list-type=map 유형이면 스키마의 일부가 상관 가능해요. 어떤 set 또는 atomic 배열 부모 스키마도 self를 oldSelf와 모호하지 않게 상관시키는 것을 불가능하게 해요.
다음은 transition rules에 대한 몇 가지 예시예요:
| 사용 사례 | 규칙 |
|---|---|
| 불변성 (Immutability) | self.foo == oldSelf.foo |
| 할당되면 수정/제거 방지 | oldSelf != 'bar' |
| 추가 전용 집합 (Append-only set) | self.all(element, element in oldSelf) |
| 이전 값이 X였으면 새 값은 A나 B만, Y나 Z는 아님 | oldSelf != 'X' |
| 단조(비감소) 카운터 | self >= oldSelf |
검증 함수의 리소스 사용 (#resource-use-by-validation-functions)
검증 규칙을 사용하는 CustomResourceDefinition을 만들거나 업데이트할 때 API 서버는 그 검증 규칙을 실행하는 것의 예상 영향을 확인해요. 규칙이 실행하기에 지나치게 비싼 것으로 추정되면 API 서버가 생성 또는 업데이트 작업을 거부하고 오류 메시지를 반환해요.
유사한 시스템이 인터프리터가 취하는 동작을 관찰하는 런타임에 사용돼요. 인터프리터가 너무 많은 명령을 실행하면 규칙의 실행이 중단되고 오류가 발생할 거예요. 각 CustomResourceDefinition은 또한 모든 검증 규칙 실행을 끝내기 위해 일정량의 리소스가 허용돼요. 생성 시 그 규칙의 총합이 그 한도를 초과할 것으로 추정되면 검증 오류도 발생할 거예요.
입력이 얼마나 크든 관계없이 항상 같은 시간이 걸리는 규칙만 지정한다면 검증의 리소스 예산에 문제가 발생할 가능성은 낮아요. 예를 들어 self.foo == 1을 주장하는 규칙은 그 자체로 검증 리소스 예산 그룹에서 거부될 위험이 없어요. 하지만 foo가 문자열이고 self.foo.contains("someString")이라는 검증 규칙을 정의하면 그 규칙은 foo가 길수록 더 오래 실행돼요. 또 다른 예는 foo가 배열이고 self.foo.all(x, x > 5)라는 검증 규칙을 지정한 경우예요. 비용 시스템은 foo의 길이에 대한 한도가 주어지지 않으면 항상 최악의 시나리오를 가정하며, 이것은 반복될 수 있는 것(목록, 맵 등)에 대해 발생할 거예요.
이 때문에 비용 추정 중 검증 오류를 방지하기 위해 검증 규칙에서 처리될 모든 것에 maxItems, maxProperties, maxLength로 한도를 두는 것이 모범 사례로 간주돼요. 예를 들어 하나의 규칙이 있는 이 스키마:
openAPIV3Schema:
type: object
properties:
foo:
type: array
items:
type: string
x-kubernetes-validations:
- rule: "self.all(x, x.contains('a string'))"
그러면 API 서버가 검증 예산 근거로 이 규칙을 거부해요:
spec.validation.openAPIV3Schema.properties[spec].properties[foo].x-kubernetes-validations[0].rule: Forbidden:
CEL rule exceeded budget by more than 100x (try simplifying the rule, or adding maxItems, maxProperties, and
maxLength where arrays, maps, and strings are used)
거부는 self.all이 foo의 모든 문자열에 contains()를 호출하는 것을 암시하고, 차례로 주어진 문자열이 'a string'을 포함하는지 확인하므로 발생해요. 한도 없이 이것은 매우 비싼 규칙이에요.
검증 한도를 지정하지 않으면 이 규칙의 추정 비용이 규칙별 비용 한도를 초과할 거예요. 하지만 적절한 곳에 한도를 추가하면 규칙이 허용될 거예요:
openAPIV3Schema:
type: object
properties:
foo:
type: array
maxItems: 25
items:
type: string
maxLength: 10
x-kubernetes-validations:
- rule: "self.all(x, x.contains('a string'))"
비용 추정 시스템은 규칙 자체의 추정 비용에 더해 규칙이 실행될 횟수를 고려해요. 예를 들어 다음 규칙은 규칙이 이제 개별 배열 항목에 정의되었음에도 불구하고 이전 예시와 같은 추정 비용을 가질 거예요:
openAPIV3Schema:
type: object
properties:
foo:
type: array
maxItems: 25
items:
type: string
x-kubernetes-validations:
- rule: "self.contains('a string'))"
maxLength: 10
목록 안의 목록이 self.all을 사용하는 검증 규칙을 가지면 중첩되지 않은 목록과 같은 규칙보다 훨씬 비싸요. 중첩되지 않은 목록에서 허용되었던 규칙이 허용되려면 두 중첩 목록에 더 낮은 한도를 설정해야 할 수도 있어요. 예를 들어 한도가 설정되지 않았더라도 다음 규칙은 허용돼요:
openAPIV3Schema:
type: object
properties:
foo:
type: array
items:
type: integer
x-kubernetes-validations:
- rule: "self.all(x, x == 5)"
하지만 (중첩 배열이 추가된) 다음 스키마의 같은 규칙은 검증 오류를 생성해요:
openAPIV3Schema:
type: object
properties:
foo:
type: array
items:
type: array
items:
type: integer
x-kubernetes-validations:
- rule: "self.all(x, x == 5)"
이것은 foo의 각 항목이 그 자체로 배열이고 각 하위 배열이 차례로 self.all을 호출하기 때문이에요. 검증 규칙이 사용되는 곳에서는 가능하면 중첩 목록과 맵을 피하세요.
기본값 (#defaulting)
OpenAPI v3 검증 스키마에서 기본값을 지정할 수 있어요:
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}$'
default: "5 0 * * *"
image:
type: string
replicas:
type: integer
minimum: 1
maximum: 10
default: 1
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
이것으로 cronSpec과 replicas 모두 기본값이 설정돼요:
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
image: my-awesome-cron-image
이어져:
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
cronSpec: "5 0 * * *"
image: my-awesome-cron-image
replicas: 1
기본값 설정은 객체에서 발생해요:
- 요청 버전 기본값을 사용해 API 서버에 대한 요청에서,
- 스토리지 버전 기본값을 사용해 etcd에서 읽을 때,
- 비어 있지 않은 패치가 있는 mutating admission 플러그인 후 admission webhook 객체 버전 기본값을 사용해.
etcd에서 데이터를 읽을 때 적용된 기본값은 etcd에 자동으로 다시 기록되지 않아요. 그 기본값을 etcd에 영속화하려면 API를 통한 업데이트 요청이 필요해요.
비리프(non-leaf) 필드의 기본값은 가지치기되어야 하고(metadata 필드의 기본값 제외) 제공된 스키마에 대해 검증되어야 해요. 예를 들어 위 예시에서 spec 필드에 대한 {"replicas": "foo", "badger": 1} 기본값은 유효하지 않을 거예요. badger은 알 수 없는 필드이고 replicas는 문자열이 아니기 때문이에요.
x-kubernetes-embedded-resources: true 노드의 metadata 필드(또는 metadata를 포함하는 기본값의 일부)에 대한 기본값은 CustomResourceDefinition 생성 중에는 가지치기되지 않지만 요청 처리 중 가지치기 단계를 통해 가지치기돼요.
기본값과 Nullable (#defaulting-and-nullable)
nullable 플래그를 지정하지 않거나 false 값을 주는 필드의 null 값은 기본값 설정이 일어나기 전에 가지치기돼요. 기본값이 있으면 적용돼요. nullable이 true이면 null 값이 보존되고 기본값이 설정되지 않아요.
예를 들어 아래 OpenAPI 스키마가:
type: object
properties:
spec:
type: object
properties:
foo:
type: string
nullable: false
default: "default"
bar:
type: string
nullable: true
baz:
type: string
foo, bar, baz에 null 값이 있는 객체 생성:
spec:
foo: null
bar: null
baz: null
다음으로 이어져:
spec:
foo: "default"
bar: null
여기서 foo는 필드가 non-nullable이므로 가지치기되고 기본값이 설정되며, bar는 nullable: true로 인해 null 값을 유지하고, baz는 필드가 non-nullable이고 기본값이 없으므로 가지치기돼요.
OpenAPI에 검증 스키마 게시 (#publish-validation-schema-in-openapi)
구조적(#구조적-스키마-지정)이고 가지치기(#필드-가지치기)를 활성화하는 CustomResourceDefinition OpenAPI v3 검증 스키마는 Kubernetes API 서버에서 OpenAPI v3(/docs/concepts/overview/kubernetes-api/#openapi-and-swagger-definitions)와 OpenAPI v2로 게시돼요. OpenAPI v2가 손실 변환(lossy conversion)을 나타내는 반면 OpenAPI v3 문서는 CustomResourceDefinition OpenAPI v3 검증 스키마의 무손실 표현이므로 OpenAPI v3 문서를 사용하는 것이 권장돼요.
kubectl 명령줄 도구는 게시된 스키마를 소비해 커스텀 리소스에 대해 클라이언트 측 검증(kubectl create와 kubectl apply), 스키마 설명(kubectl explain)을 수행해요. 게시된 스키마는 클라이언트 생성이나 문서화 같은 다른 목적으로도 소비될 수 있어요.
OpenAPI V2와의 호환성 (#compatibility-with-openapi-v2)
OpenAPI V2와의 호환성을 위해 OpenAPI v3 검증 스키마는 OpenAPI v2 스키마로 손실 변환을 수행해요. 스키마는 OpenAPI v2 spec(/docs/concepts/overview/kubernetes-api/#openapi-and-swagger-definitions)의 definitions와 paths 필드에 나타나요.
이전 1.13 버전의 kubectl과의 역호환성을 유지하기 위해 변환 중 다음 수정이 적용돼요. 이러한 수정은 kubectl이 너무 엄격해져서 이해하지 못하는 유효한 OpenAPI 스키마를 거부하는 것을 방지해요. 변환은 CRD에 정의된 검증 스키마를 수정하지 않으므로 API 서버의 검증(#검증)에 영향을 주지 않아요.
- 다음 필드는 OpenAPI v2가 지원하지 않으므로 제거됨: allOf, anyOf, oneOf, not 필드.
- nullable: true가 설정되면 OpenAPI v2가 nullable을 표현할 수 없으므로 type, nullable, items, properties를 버림. kubectl이 좋은 객체를 거부하지 않도록 이것이 필요함.
추가 프린터 열 (#additional-printer-columns)
kubectl 도구는 서버 측 출력 형식화에 의존해요. 클러스터의 API 서버가 kubectl get 명령에 표시할 열을 결정해요. CustomResourceDefinition에 대해 이 열을 커스터마이즈할 수 있어요. 다음 예시는 Spec, Replicas, Age 열을 추가해요.
CustomResourceDefinition을 resourcedefinition.yaml에 저장해요:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: crontabs.stable.example.com
spec:
group: stable.example.com
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
image:
type: string
replicas:
type: integer
additionalPrinterColumns:
- name: Spec
type: string
description: The cron spec defining the interval a CronJob is run
jsonPath: .spec.cronSpec
- name: Replicas
type: integer
description: The number of jobs launched by the CronJob
jsonPath: .spec.replicas
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
CustomResourceDefinition을 만들어요:
kubectl apply -f resourcedefinition.yaml
이전 섹션의 my-crontab.yaml을 사용해 인스턴스를 만들어요.
서버 측 인쇄를 호출해요:
kubectl get crontab my-new-cron-object
출력에서 NAME, SPEC, REPLICAS, AGE 열을 주목해요:
NAME SPEC REPLICAS AGE
my-new-cron-object * * * * * 1 7s
우선순위 (#priority)
각 열은 priority 필드를 포함해요. 현재 priority는 표준 보기와 와이드 보기(-o wide 플래그 사용)에서 표시되는 열을 구분해요.
- priority 0인 열은 표준 보기에 표시됨.
- priority가 0보다 큰 열은 와이드 보기에만 표시됨.
유형 (#type)
열의 type 필드는 다음 중 하나일 수 있어요(OpenAPI v3 데이터 유형 비교):
- integer – 비소수점 숫자
- number – 부동 소수점 숫자
- string – 문자열
- boolean – true 또는 false
- date – 이 타임스탬프 이후의 시간으로 다르게 렌더링됨
CustomResource 내부의 값이 열에 지정된 유형과 일치하지 않으면 값이 생략돼요. 값 유형이 올바른지 확인하려면 CustomResource 검증을 사용해요.
형식 (#format)
열의 format 필드는 다음 중 하나일 수 있어요:
- int32
- int64
- float
- double
- byte
- date
- date-time
- password
열의 format은 kubectl이 값을 인쇄할 때 사용하는 스타일을 제어해요.
필드 셀렉터 (#field-selectors)
필드 셀렉터는 클라이언트가 하나 이상의 리소스 필드의 값에 기반해 커스텀 리소스를 선택하게 해줘요.
모든 커스텀 리소스는 metadata.name과 metadata.namespace 필드 셀렉터를 지원해요.
CustomResourceDefinition(/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/)에 선언된 필드는 CustomResourceDefinition(/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/)의 spec.versions[*].selectableFields 필드에 포함될 때 필드 셀렉터와 함께 사용될 수도 있어요.
커스텀 리소스의 선택 가능한 필드 (#crd-selectable-fields)
CustomResourceDefinition(/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/)의 spec.versions[*].selectableFields 필드는 CustomResourceFieldSelectors 기능 게이트(/docs/reference/command-line-tools-reference/feature-gates/)의 기능으로 커스텀 리소스의 어떤 다른 필드가 필드 셀렉터에서 사용될 수 있는지 선언하는 데 사용될 수 있어요. (이 기능 게이트는 Kubernetes v1.31부터 기본적으로 활성화됨). 다음 예시는 .spec.color와 .spec.size 필드를 선택 가능한 필드로 추가해요.
CustomResourceDefinition을 shirt-resource-definition.yaml에 저장해요:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: shirts.stable.example.com
spec:
group: stable.example.com
scope: Namespaced
names:
plural: shirts
singular: shirt
kind: Shirt
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
color:
type: string
size:
type: string
selectableFields:
- jsonPath: .spec.color
- jsonPath: .spec.size
additionalPrinterColumns:
- jsonPath: .spec.color
name: Color
type: string
- jsonPath: .spec.size
name: Size
type: string
CustomResourceDefinition을 만들어요:
kubectl apply -f https://k8s.io/examples/customresourcedefinition/shirt-resource-definition.yaml
shirt-resources.yaml을 편집해 일부 Shirt를 정의해요. 예:
---
apiVersion: stable.example.com/v1
kind: Shirt
metadata:
name: example1
spec:
color: blue
size: S
---
apiVersion: stable.example.com/v1
kind: Shirt
metadata:
name: example2
spec:
color: blue
size: M
---
apiVersion: stable.example.com/v1
kind: Shirt
metadata:
name: example3
spec:
color: green
size: M
커스텀 리소스를 만들어요:
kubectl apply -f https://k8s.io/examples/customresourcedefinition/shirt-resources.yaml
모든 리소스를 가져와요:
kubectl get shirts.stable.example.com
출력은:
NAME COLOR SIZE
example1 blue S
example2 blue M
example3 green M
파란 셔츠를 가져와요(color가 blue인 Shirt 검색):
kubectl get shirts.stable.example.com --field-selector spec.color=blue
출력해야 해요:
NAME COLOR SIZE
example1 blue S
example2 blue M
color가 green이고 size가 M인 리소스만 가져와요:
kubectl get shirts.stable.example.com --field-selector spec.color=green,spec.size=M
출력해야 해요:
NAME COLOR SIZE
example3 green M
하위 리소스 (#subresources)
커스텀 리소스는 /status와 /scale 하위 리소스를 지원해요.
status와 scale 하위 리소스는 CustomResourceDefinition에서 정의함으로써 선택적으로 활성화할 수 있어요.
Status 하위 리소스 (#status-subresource)
status 하위 리소스가 활성화되면 커스텀 리소스의 /status 하위 리소스가 노출돼요.
- status와 spec stanza는 커스텀 리소스 내부에서 각각 .status와 .spec JSONPath로 표현됨.
- /status 하위 리소스에 대한 PUT 요청은 커스텀 리소스 객체를 받고 status stanza를 제외한 어떤 것의 변경도 무시함.
- /status 하위 리소스에 대한 PUT 요청은 커스텀 리소스의 status stanza만 검증함.
- 커스텀 리소스에 대한 PUT / POST / PATCH 요청은 status stanza의 변경을 무시함.
- .metadata.generation 값은 .metadata 또는 .status에 대한 변경을 제외한 모든 변경에 대해 증가됨.
- 다음 구조만 CRD OpenAPI 검증 스키마의 루트에 허용됨: description, example, exclusiveMaximum, exclusiveMinimum, externalDocs, format, items, maximum, maxItems, maxLength, minimum, minItems, minLength, multipleOf, pattern, properties, required, title, type, uniqueItems.
Scale 하위 리소스 (#scale-subresource)
scale 하위 리소스가 활성화되면 커스텀 리소스의 /scale 하위 리소스가 노출돼요. autoscaling/v1.Scale 객체가 /scale의 페이로드로 전송돼요.
scale 하위 리소스를 활성화하려면 CustomResourceDefinition에 다음 필드가 정의돼요.
- specReplicasPath는 scale.spec.replicas에 해당하는 커스텀 리소스 내부의 JSONPath를 정의해요. 필수 값이에요. .spec 아래의 점 표기법을 사용한 JSONPath만 허용돼요. 커스텀 리소스에서 specReplicasPath 아래에 값이 없으면 /scale 하위 리소스가 GET에서 오류를 반환해요.
- statusReplicasPath는 scale.status.replicas에 해당하는 커스텀 리소스 내부의 JSONPath를 정의해요. 필수 값이에요. .status 아래의 점 표기법을 사용한 JSONPath만 허용돼요. 커스텀 리소스에서 statusReplicasPath 아래에 값이 없으면 /scale 하위 리소스의 status replica 값이 0으로 기본 설정돼요.
- labelSelectorPath는 Scale.Status.Selector에 해당하는 커스텀 리소스 내부의 JSONPath를 정의해요. 선택 값이에요. HPA와 VPA와 함께 동작하려면 설정되어야 해요. .status 또는 .spec 아래의 점 표기법을 사용한 JSONPath만 허용돼요. 커스텀 리소스에서 labelSelectorPath 아래에 값이 없으면 /scale 하위 리소스의 status selector 값이 빈 문자열로 기본 설정돼요. 이 JSON 경로가 가리키는 필드는 문자열 형식으로 직렬화된 레이블 셀렉터를 포함하는 문자열 필드여야 해요(복잡한 셀렉터 구조가 아님).
다음 예시에서 status와 scale 하위 리소스 모두 활성화돼요.
CustomResourceDefinition을 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:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
image:
type: string
replicas:
type: integer
status:
type: object
properties:
replicas:
type: integer
labelSelector:
type: string
# subresources describes the subresources for custom resources.
subresources:
# status enables the status subresource.
status: {}
# scale enables the scale subresource.
scale:
# specReplicasPath defines the JSONPath inside of a custom resource that corresponds to Scale.Spec.Replicas.
specReplicasPath: .spec.replicas
# statusReplicasPath defines the JSONPath inside of a custom resource that corresponds to Scale.Status.Replicas.
statusReplicasPath: .status.replicas
# labelSelectorPath defines the JSONPath inside of a custom resource that corresponds to Scale.Status.Selector.
labelSelectorPath: .status.labelSelector
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
만들어요:
kubectl apply -f resourcedefinition.yaml
CustomResourceDefinition 객체가 생성된 후 커스텀 객체를 만들 수 있어요.
다음 YAML을 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: 3
만들어요:
kubectl apply -f my-crontab.yaml
그러면 새로운 네임스페이스 범위 RESTful API 엔드포인트가 다음에 생성돼요:
/apis/stable.example.com/v1/namespaces/*/crontabs/status
그리고:
/apis/stable.example.com/v1/namespaces/*/crontabs/scale
커스텀 리소스는 kubectl scale 명령으로 확장할 수 있어요. 예를 들어 다음 명령은 위에서 만든 커스텀 리소스의 .spec.replicas를 5로 설정해요:
kubectl scale --replicas=5 crontabs/my-new-cron-object
crontabs "my-new-cron-object" scaled
kubectl get crontabs my-new-cron-object -o jsonpath='{.spec.replicas}'
5
scale 하위 리소스가 활성화된 커스텀 리소스를 보호하려면 PodDisruptionBudget을 사용할 수 있어요.
범주 (#categories)
Categories는 커스텀 리소스가 속한 그룹화된 리소스의 목록이에요 (예: all). kubectl get <category-name>을 사용해 범주에 속한 리소스를 나열할 수 있어요.
다음 예시는 CustomResourceDefinition의 categories 목록에 all을 추가하고 kubectl get all을 사용해 커스텀 리소스를 출력하는 방법을 보여줘요.
다음 CustomResourceDefinition을 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:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
image:
type: string
replicas:
type: integer
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
# categories is a list of grouped resources the custom resource belongs to.
categories:
- all
만들어요:
kubectl apply -f resourcedefinition.yaml
CustomResourceDefinition 객체가 생성된 후 커스텀 객체를 만들 수 있어요.
다음 YAML을 my-crontab.yaml에 저장해요:
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
cronSpec: "* * * * */5"
image: my-awesome-cron-image
만들어요:
kubectl apply -f my-crontab.yaml
kubectl get을 사용할 때 범주를 지정할 수 있어요:
kubectl get all
그리고 kind CronTab의 커스텀 리소스를 포함할 거예요:
NAME AGE
crontabs/my-new-cron-object 3s
다음 단계 (What's next)
- 커스텀 리소스에 대해 읽기.
- CustomResourceDefinition 보기.
- CustomResourceDefinition의 여러 버전 서빙.