CustomResourceDefinition의 버전

CustomResourceDefinition의 버전 (Versions in CustomResourceDefinitions)

이 페이지는 CustomResourceDefinitions에 버전 정보를 추가하는 방법을 설명해요. 이것은 CustomResourceDefinitions의 안정성 수준을 나타내거나 API 표현 사이의 변환과 함께 API를 새 버전으로 전진시키기 위한 것이에요. 또한 객체를 한 버전에서 다른 버전으로 업그레이드하는 방법도 설명해요.

출처: 문서

본문

시작하기 전에 (Before you begin)

Kubernetes 클러스터가 있어야 하고 kubectl 명령줄 도구가 클러스터와 통신하도록 구성되어 있어야 해요. 이 튜토리얼은 컨트롤 플레인 호스트로 작동하지 않는 노드가 최소 두 개 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube를 이용해 만들거나, 아래 Kubernetes 플레이그라운드 중 하나를 사용할 수 있어요.

커스텀 리소스에 대한 초기 이해가 있어야 해요. 버전을 확인하려면 kubectl version을 입력해요.

개요 (Overview)

CustomResourceDefinition API는 CustomResourceDefinition의 새 버전을 도입하고 업그레이드하기 위한 워크플로를 제공해요.

CustomResourceDefinition이 생성되면 첫 버전이 CustomResourceDefinition spec.versions 목록에 적절한 안정성 수준과 버전 번호로 설정돼요. 예를 들어 v1beta1은 첫 버전이 아직 안정적이지 않음을 나타내요. 모든 커스텀 리소스 객체는 처음에 이 버전에 저장될 거예요.

CustomResourceDefinition이 생성되면 클라이언트가 v1beta1 API를 사용하기 시작할 수 있어요.

나중에 v1 같은 새 버전을 추가해야 할 수도 있어요.

새 버전 추가:

  • 변환 전략(conversion strategy)을 선택해요. 커스텀 리소스 객체가 두 버전 모두에서 서빙될 수 있어야 하므로 때로는 저장된 버전과 다른 버전으로 서빙될 거예요. 이것을 가능하게 하려면 커스텀 리소스 객체가 저장되는 버전과 서빙되는 버전 사이에서 때때로 변환되어야 해요. 변환이 스키마 변경을 포함하고 커스텀 로직이 필요하면 변환 webhook을 사용해야 해요. 스키마 변경이 없다면 기본 None 변환 전략을 사용할 수 있고 다른 버전을 서빙할 때 apiVersion 필드만 수정될 거예요.
  • 변환 webhook을 사용한다면 변환 webhook을 만들고 배포해요. 자세한 내용은 Webhook 변환을 참고해요.
  • CustomResourceDefinition을 업데이트해 spec.versions 목록에 새 버전을 served:true로 포함해요. 또한 spec.conversion 필드를 선택한 변환 전략으로 설정해요. 변환 webhook을 사용한다면 webhook을 호출하도록 spec.conversion.webhookClientConfig 필드를 구성해요.

새 버전이 추가되면 클라이언트가 점진적으로 새 버전으로 마이그레이션할 수 있어요. 일부 클라이언트가 이전 버전을 사용하고 다른 클라이언트가 새 버전을 사용하는 것은 완전히 안전해요.

저장된 객체를 새 버전으로 마이그레이션:

클라이언트가 이전 버전과 새 버전을 모두 사용하는 것은 객체를 새 저장 버전으로 업그레이드하는 전, 중, 후 모두 안전해요.

이전 버전 제거:

  • 모든 클라이언트가 완전히 새 버전으로 마이그레이션되었는지 확인해요. 이전 버전을 통해 여전히 접근하는 클라이언트를 식별하는 데 도움이 되도록 kube-apiserver 로그를 검토할 수 있어요.
  • spec.versions 목록에서 이전 버전에 대해 served를 false로 설정해요. 어떤 클라이언트가 여전히 예기치 않게 이전 버전을 사용하고 있다면 이전 버전의 커스텀 리소스 객체에 접근하려고 시도할 때 오류를 보고하기 시작할 수 있어요. 이것이 발생하면 이전 버전에서 served:true로 다시 전환하고 남은 클라이언트를 새 버전으로 마이그레이션한 다음 이 단계를 반복해요.
  • 기존 객체를 새 저장 버전으로 업그레이드 단계가 완료되었는지 확인해요. CustomResourceDefinition의 spec.versions 목록에서 새 버전에 storage가 true로 설정되었는지 확인해요. 이전 버전이 더 이상 CustomResourceDefinition status.storedVersions에 나열되지 않는지 확인해요.
  • CustomResourceDefinition spec.versions 목록에서 이전 버전을 제거해요.
  • 변환 webhook에서 이전 버전에 대한 변환 지원을 제거해요.

여러 버전 지정 (Specify multiple versions)

CustomResourceDefinition API versions 필드는 여러분이 개발한 커스텀 리소스의 여러 버전을 지원하는 데 사용될 수 있어요. 버전은 다른 스키마를 가질 수 있고, 변환 webhook가 커스텀 리소스를 버전 사이에서 변환할 수 있어요.

Webhook 변환은 적용 가능한 곳에서 Kubernetes API 규칙(https://github.com/kubernetes/community/blob/main/contributors/devel/sig-architecture/api-conventions.md)을 따라야 해요. 특히 유용한 함정과 제안의 집합에 대해 API 변경 문서를 참고해요.

이 예시는 두 버전을 가진 CustomResourceDefinition을 보여줘요. 첫 예시에서는 모든 버전이 그들 사이에 변환 없이 같은 스키마를 공유한다고 가정해요. YAML의 주석이 더 많은 컨텍스트를 제공해요.

  • apiextensions.k8s.io/v1
  • apiextensions.k8s.io/v1beta1
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: example.com
  # list of versions supported by this CustomResourceDefinition
  versions:
  - name: v1beta1
    # 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
    # A schema is required
    schema:
      openAPIV3Schema:
        type: object
        properties:
          host:
            type: string
          port:
            type: string
  - name: v1
    served: true
    storage: false
    schema:
      openAPIV3Schema:
        type: object
        properties:
          host:
            type: string
          port:
            type: string
  # The conversion section is introduced in Kubernetes 1.13+ with a default value of
  # None conversion (strategy sub-field set to None).
  conversion:
    # None conversion assumes the same schema for all versions and only sets the apiVersion
    # field of custom resources to the proper value
    strategy: None
  # 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
# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: example.com
  # list of versions supported by this CustomResourceDefinition
  versions:
  - name: v1beta1
    # 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
  - name: v1
    served: true
    storage: false
  validation:
    openAPIV3Schema:
      type: object
      properties:
        host:
          type: string
        port:
          type: string
  # The conversion section is introduced in Kubernetes 1.13+ with a default value of
  # None conversion (strategy sub-field set to None).
  conversion:
    # None conversion assumes the same schema for all versions and only sets the apiVersion
    # field of custom resources to the proper value
    strategy: None
  # 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 PascalCased singular type. Your resource manifests use this.
    kind: CronTab
    # shortNames allow shorter string to match your resource on the CLI
    shortNames:
    - ct

CustomResourceDefinition을 YAML 파일에 저장한 다음 kubectl apply를 사용해 만들 수 있어요.

kubectl apply -f my-versioned-crontab.yaml

생성 후 API 서버는 각 활성 버전을 HTTP REST 엔드포인트에서 서빙하기 시작해요. 위 예시에서 API 버전은 /apis/example.com/v1beta1/apis/example.com/v1에서 사용 가능해요.

버전 우선순위 (#version-priority)

CustomResourceDefinition에서 버전이 정의된 순서와 관계없이 우선순위가 가장 높은 버전이 kubectl이 객체에 접근하는 기본 버전으로 사용돼요. 우선순위는 name 필드를 파싱해 버전 번호, 안정성(GA, Beta 또는 Alpha), 그 안정성 수준 내에서의 순서를 결정함으로써 정해져요.

버전 정렬에 사용되는 알고리즘은 Kubernetes 프로젝트가 Kubernetes 버전을 정렬하는 것과 같은 방식으로 버전을 정렬하도록 설계됐어요. 버전은 v 다음에 숫자, 선택적 beta 또는 alpha 지정, 선택적 추가 숫자 버전 정보로 시작해요. 대체로 버전 문자열은 v2 또는 v2beta1처럼 보일 수 있어요. 버전은 다음 알고리즘으로 정렬돼요:

  • Kubernetes 버전 패턴을 따르는 항목이 그렇지 않은 항목보다 먼저 정렬됨.
  • Kubernetes 버전 패턴을 따르는 항목의 경우 버전 문자열의 숫자 부분이 가장 큰 것부터 가장 작은 것까지 정렬됨.
  • beta 또는 alpha 문자열이 첫 숫자 부분을 따르면 그 순서로 정렬되며, betaalpha 접미사가 없는 동등한 문자열(GA 버전으로 간주됨) 다음에 온다.
  • beta 또는 alpha 다음에 다른 숫자가 오면 그 숫자도 가장 큰 것부터 가장 작은 것까지 정렬됨.
  • 위 형식에 맞지 않는 문자열은 알파벳순으로 정렬되고 숫자 부분은 특별히 취급되지 않음. 아래 예시에서 foo1foo10 위에 정렬되는 것을 주목해요. 이것은 Kubernetes 버전 패턴을 따르는 항목의 숫자 부분 정렬과 다르다.

다음 정렬된 버전 목록을 보면 이해가 될 거예요:

- v10
- v2
- v1
- v11beta2
- v10beta3
- v3beta1
- v12alpha1
- v11alpha2
- foo1
- foo10

여러 버전 지정의 예시의 경우 버전 정렬 순서는 v1, 그다음 v1beta1이에요. 이것은 제공된 객체가 버전을 지정하지 않으면 kubectl 명령이 기본 버전으로 v1을 사용하게 해요.

버전 폐기 (#version-deprecation)

v1.19부터 CustomResourceDefinition은 정의하는 리소스의 특정 버전이 deprecated임을 나타낼 수 있어요. 그 리소스의 deprecated 버전에 대한 API 요청이 이루어지면 API 응답에 헤더로 경고 메시지가 반환돼요. 원한다면 리소스의 각 deprecated 버전에 대한 경고 메시지를 커스터마이즈할 수 있어요.

커스터마이즈된 경고 메시지는 deprecated API 그룹, 버전, 종류를 나타내야 하고, 적용 가능하면 대신 사용해야 할 API 그룹, 버전, 종류를 나타내야 해요.

  • apiextensions.k8s.io/v1
  • apiextensions.k8s.io/v1beta1
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.example.com
spec:
  group: example.com
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
  scope: Namespaced
  versions:
  - name: v1alpha1
    served: true
    storage: false
    # This indicates the v1alpha1 version of the custom resource is deprecated.
    # API requests to this version receive a warning header in the server response.
    deprecated: true
    # This overrides the default warning returned to API clients making v1alpha1 API requests.
    deprecationWarning: "example.com/v1alpha1 CronTab is deprecated; see http://example.com/v1alpha1-v1 for instructions to migrate to example.com/v1 CronTab"

    schema: ...
  - name: v1beta1
    served: true
    # This indicates the v1beta1 version of the custom resource is deprecated.
    # API requests to this version receive a warning header in the server response.
    # A default warning message is returned for this version.
    deprecated: true
    schema: ...
  - name: v1
    served: true
    storage: true
    schema: ...
# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: crontabs.example.com
spec:
  group: example.com
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
  scope: Namespaced
  validation: ...
  versions:
  - name: v1alpha1
    served: true
    storage: false
    # This indicates the v1alpha1 version of the custom resource is deprecated.
    # API requests to this version receive a warning header in the server response.
    deprecated: true
    # This overrides the default warning returned to API clients making v1alpha1 API requests.
    deprecationWarning: "example.com/v1alpha1 CronTab is deprecated; see http://example.com/v1alpha1-v1 for instructions to migrate to example.com/v1 CronTab"
  - name: v1beta1
    served: true
    # This indicates the v1beta1 version of the custom resource is deprecated.
    # API requests to this version receive a warning header in the server response.
    # A default warning message is returned for this version.
    deprecated: true
  - name: v1
    served: true
    storage: true

버전 제거 (#version-removal)

이전 API 버전은 커스텀 리소스의 이전 버전을 서빙한 모든 클러스터에 대해 기존 저장 데이터가 더 새로운 API 버전으로 마이그레이션되고, 이전 버전이 CustomResourceDefinition의 status.storedVersions에서 제거된 후에만 CustomResourceDefinition 매니페스트에서 제거될 수 있어요.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.example.com
spec:
  group: example.com
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
  scope: Namespaced
  versions:
  - name: v1beta1
    # This indicates the v1beta1 version of the custom resource is no longer served.
    # API requests to this version receive a not found error in the server response.
    served: false
    schema: ...
  - name: v1
    served: true
    # The new served version should be set as the storage version
    storage: true
    schema: ...

Webhook 변환 (#webhook-conversion)

위 예시는 변환 시 apiVersion 필드만 설정하고 객체의 나머지를 변경하지 않는 버전 사이의 None 변환을 가져요. API 서버는 변환이 필요할 때 외부 서비스를 호출하는 webhook 변환도 지원해요. 예를 들어:

  • 커스텀 리소스가 저장된 버전과 다른 버전으로 요청됨.
  • 한 버전에서 Watch가 생성되었지만 변경된 객체가 다른 버전에 저장됨.
  • 커스텀 리소스 PUT 요청이 스토리지 버전과 다른 버전에 있음.

이러한 모든 경우를 다루고 API 서버의 변환을 최적화하기 위해 변환 요청은 외부 호출을 최소화하도록 여러 객체를 포함할 수 있어요. webhook는 이러한 변환을 독립적으로 수행해야 해요.

변환 webhook 서버 작성 (#write-a-conversion-webhook-server)

Kubernetes e2e 테스트에서 검증된 커스텀 리소스 변환 webhook 서버(https://github.com/kubernetes/kubernetes/tree/v1.25.3/test/images/agnhost/crd-conversion-webhook/main.go)의 구현을 참고해주세요. webhook는 API 서버가 보낸 ConversionReview 요청을 처리하고 ConversionResponse에 싸인 변환 결과를 다시 보내요. 요청이 객체의 순서를 변경하지 않고 독립적으로 변환되어야 하는 커스텀 리소스 목록을 포함한다는 점을 유의해요.

예시 서버는 다른 변환에 재사용할 수 있도록 구성돼 있어요. 대부분의 공통 코드는 프레임워크 파일(https://github.com/kubernetes/kubernetes/tree/v1.25.3/test/images/agnhost/crd-conversion-webhook/converter/framework.go)에 있으며 다른 변환에 대해 하나의 함수만 구현하면 되게 남겨둬요.

허용되는 변형 (#permissible-mutations)

변환 webhook는 변환된 객체의 metadata 내부에서 labels와 annotations 외에는 아무것도 변형해서는 안 돼요. name, UID, namespace에 대한 시도된 변경은 거부되고 변환을 일으킨 요청을 실패시켜요. 다른 모든 변경은 무시돼요.

변환 webhook 서비스 배포 (#deploy-the-conversion-webhook-service)

변환 webhook 배포 문서는 admission webhook 예시 서비스와 같아요. 다음 섹션의 가정은 변환 webhook 서버가 default 네임스페이스의 example-conversion-webhook-server라는 서비스에 배포되고 /crdconvert 경로에서 트래픽을 서빙한다는 것이에요.

변환 webhook를 사용하도록 CustomResourceDefinition 구성 (#configure-customresourcedefinition-to-use-conversion-webhooks)

spec의 conversion 섹션을 수정해 None 변환 예시를 변환 webhook를 사용하도록 확장할 수 있어요:

  • apiextensions.k8s.io/v1
  • apiextensions.k8s.io/v1beta1
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: example.com
  # list of versions supported by this CustomResourceDefinition
  versions:
  - name: v1beta1
    # 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
    # Each version can define its own schema when there is no top-level
    # schema is defined.
    schema:
      openAPIV3Schema:
        type: object
        properties:
          hostPort:
            type: string
  - name: v1
    served: true
    storage: false
    schema:
      openAPIV3Schema:
        type: object
        properties:
          host:
            type: string
          port:
            type: string
  conversion:
    # the Webhook strategy instructs the API server to call an external webhook for any conversion between custom resources.
    strategy: Webhook
    # webhook is required when strategy is `Webhook` and it configures the webhook endpoint to be called by API server.
    webhook:
      # conversionReviewVersions indicates what ConversionReview versions are understood/preferred by the webhook.
      # The first version in the list understood by the API server is sent to the webhook.
      # The webhook must respond with a ConversionReview object in the same version it received.
      conversionReviewVersions: ["v1","v1beta1"]
      clientConfig:
        service:
          namespace: default
          name: example-conversion-webhook-server
          path: /crdconvert
        caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
  # 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
# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: example.com
  # prunes object fields that are not specified in OpenAPI schemas below.
  preserveUnknownFields: false
  # list of versions supported by this CustomResourceDefinition
  versions:
  - name: v1beta1
    # 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
    # Each version can define its own schema when there is no top-level
    # schema is defined.
    schema:
      openAPIV3Schema:
        type: object
        properties:
          hostPort:
            type: string
  - name: v1
    served: true
    storage: false
    schema:
      openAPIV3Schema:
        type: object
        properties:
          host:
            type: string
          port:
            type: string
  conversion:
    # the Webhook strategy instructs the API server to call an external webhook for any conversion between custom resources.
    strategy: Webhook
    # webhookClientConfig is required when strategy is `Webhook` and it configures the webhook endpoint to be called by API server.
    webhookClientConfig:
      service:
        namespace: default
        name: example-conversion-webhook-server
        path: /crdconvert
      caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
  # 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

CustomResourceDefinition을 YAML 파일에 저장한 다음 kubectl apply를 사용해 적용할 수 있어요.

kubectl apply -f my-versioned-crontab-with-conversion.yaml

새 변경 사항을 적용하기 전에 변환 서비스가 실행 중인지 확인해요.

webhook에 접촉 (#contacting-the-webhook)

API 서버가 요청을 변환 webhook에 보내야 한다고 결정하면 webhook에 어떻게 접촉할지 알아야 해요. 이것은 webhook 구성의 webhookClientConfig stanza에서 지정돼요.

변환 webhook는 URL 또는 서비스 참조를 통해 호출될 수 있고, 선택적으로 TLS 연결을 검증하는 데 사용할 커스텀 CA 번들을 포함할 수 있어요.

URL (#url)

url은 webhook의 위치를 표준 URL 형식(scheme://host:port/path)으로 제공해요.

host는 클러스터에서 실행되는 서비스를 참조해서는 안 돼요. 대신 service 필드를 지정해 서비스 참조를 사용해요. host는 일부 apiserver에서 외부 DNS를 통해 해석될 수 있어요(즉, kube-apiserver는 계층 위반이 되므로 in-cluster DNS를 해석할 수 없음). host는 IP 주소일 수도 있어요.

호스트로 localhost나 127.0.0.1을 사용하는 것은 이 webhook에 호출을 해야 할 수도 있는 apiserver를 실행하는 모든 호스트에서 이 webhook를 실행하는 데 매우 주의를 기울이지 않는 한 위험하다는 점을 유의해주세요. 그러한 설치는 이식 불가하거나 새 클러스터에서 쉽게 실행되지 않을 가능성이 높아요.

scheme은 "https"여야 해요. URL은 "https://"로 시작해야 해요.

사용자나 basic auth(예: "user:password@")를 사용하려고 시도하는 것은 허용되지 않아요. Fragments("#...")와 query parameters("?...")도 허용되지 않아요.

다음은 URL을 호출하도록 구성된 변환 webhook의 예시예요(그리고 시스템 신뢰 루트를 사용해 TLS 인증서를 검증할 것으로 기대하므로 caBundle을 지정하지 않음):

  • apiextensions.k8s.io/v1
  • apiextensions.k8s.io/v1beta1
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhook:
      clientConfig:
        url: "https://my-webhook.example.com:9443/my-webhook-path"
...
# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhookClientConfig:
      url: "https://my-webhook.example.com:9443/my-webhook-path"
...

서비스 참조 (#service-reference)

webhookClientConfig 내부의 service stanza는 변환 webhook의 서비스에 대한 참조예요. webhook가 클러스터 내에서 실행된다면 url 대신 service를 사용해야 해요. service namespace와 name은 필수예요. port는 선택 사항이며 기본값은 443이에요. path는 선택 사항이며 기본값은 "/"이에요.

다음은 포트 "1234"에서 하위 경로 "/my-path"의 서비스를 호출하고 커스텀 CA 번들로 ServerName my-service-name.my-service-namespace.svc에 대해 TLS 연결을 검증하도록 구성된 webhook의 예시예요.

  • apiextensions.k8s.io/v1
  • apiextensions.k8s.io/v1beta1
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhook:
      clientConfig:
        service:
          namespace: my-service-namespace
          name: my-service-name
          path: /my-path
          port: 1234
        caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
...
# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhookClientConfig:
      service:
        namespace: my-service-namespace
        name: my-service-name
        path: /my-path
        port: 1234
      caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
...

Webhook 요청과 응답 (#webhook-request-and-response)

요청 (#request)

Webhook에는 Content-Type: application/json과 함께 POST 요청이 전송되며, body는 apiextensions.k8s.io API 그룹의 ConversionReview API 객체를 JSON으로 직렬화한 것으로 전송돼요.

Webhook는 CustomResourceDefinition의 conversionReviewVersions 필드로 어떤 버전의 ConversionReview 객체를 수락하는지 지정할 수 있어요:

  • apiextensions.k8s.io/v1
  • apiextensions.k8s.io/v1beta1
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhook:
      conversionReviewVersions: ["v1", "v1beta1"]
      ...

conversionReviewVersions는 apiextensions.k8s.io/v1 커스텀 리소스 정의를 만들 때 필수 필드예요. Webhook는 현재와 이전 API 서버가 이해하는 최소 하나의 ConversionReview 버전을 지원해야 해요.

# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    conversionReviewVersions: ["v1", "v1beta1"]
    ...

conversionReviewVersions가 지정되지 않으면 apiextensions.k8s.io/v1beta1 커스텀 리소스 정의를 만들 때 기본값은 v1beta1이에요.

API 서버는 자신이 지원하는 conversionReviewVersions 목록의 첫 ConversionReview 버전을 보내요. 목록의 어떤 버전도 API 서버가 지원하지 않으면 커스텀 리소스 정의가 생성되는 것이 허용되지 않을 거예요. API 서버가 이전에 생성된 변환 webhook 구성을 만나고 API 서버가 보내는 방법을 아는 어떤 ConversionReview 버전도 지원하지 않으면 webhook 호출 시도가 실패할 거예요.

이 예시는 CronTab 객체를 example.com/v1으로 변환하는 요청을 위한 ConversionReview 객체에 포함된 데이터를 보여줘요:

  • apiextensions.k8s.io/v1
  • apiextensions.k8s.io/v1beta1
{
  "apiVersion": "apiextensions.k8s.io/v1",
  "kind": "ConversionReview",
  "request": {
    # Random uid uniquely identifying this conversion call
    "uid": "705ab4f5-6393-11e8-b7cc-42010a800002",

    # The API group and version the objects should be converted to
    "desiredAPIVersion": "example.com/v1",

    # The list of objects to convert.
    # May contain one or more objects, in one or more versions.
    "objects": [
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1beta1",
        "metadata": {
          "creationTimestamp": "2019-09-04T14:03:02Z",
          "name": "local-crontab",
          "namespace": "default",
          "resourceVersion": "143",
          "uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
        },
        "hostPort": "localhost:1234"
      },
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1beta1",
        "metadata": {
          "creationTimestamp": "2019-09-03T13:02:01Z",
          "name": "remote-crontab",
          "resourceVersion": "12893",
          "uid": "359a83ec-b575-460d-b553-d859cedde8a0"
        },
        "hostPort": "example.com:2345"
      }
    ]
  }
}
{
  # Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
  "apiVersion": "apiextensions.k8s.io/v1beta1",
  "kind": "ConversionReview",
  "request": {
    # Random uid uniquely identifying this conversion call
    "uid": "705ab4f5-6393-11e8-b7cc-42010a800002",

    # The API group and version the objects should be converted to
    "desiredAPIVersion": "example.com/v1",

    # The list of objects to convert.
    # May contain one or more objects, in one or more versions.
    "objects": [
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1beta1",
        "metadata": {
          "creationTimestamp": "2019-09-04T14:03:02Z",
          "name": "local-crontab",
          "namespace": "default",
          "resourceVersion": "143",
          "uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
        },
        "hostPort": "localhost:1234"
      },
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1beta1",
        "metadata": {
          "creationTimestamp": "2019-09-03T13:02:01Z",
          "name": "remote-crontab",
          "resourceVersion": "12893",
          "uid": "359a83ec-b575-460d-b553-d859cedde8a0"
        },
        "hostPort": "example.com:2345"
      }
    ]
  }
}

응답 (#response)

Webhook는 200 HTTP 상태 코드, Content-Type: application/json, 그리고 (받은 것과 같은 버전의) response stanza가 채워진 ConversionReview 객체를 JSON으로 직렬화한 body로 응답해요.

변환이 성공하면 webhook는 다음 필드를 포함하는 response stanza를 반환해야 해요:

  • uid: webhook에 보낸 request.uid에서 복사됨
  • result: {"status":"Success"}로 설정됨
  • convertedObjects: request.objects의 모든 객체를 request.desiredAPIVersion으로 변환해 포함함

webhook의 최소 성공 응답의 예시:

  • apiextensions.k8s.io/v1
  • apiextensions.k8s.io/v1beta1
{
  "apiVersion": "apiextensions.k8s.io/v1",
  "kind": "ConversionReview",
  "response": {
    # must match <request.uid>
    "uid": "705ab4f5-6393-11e8-b7cc-42010a800002",
    "result": {
      "status": "Success"
    },
    # Objects must match the order of request.objects, and have apiVersion set to <request.desiredAPIVersion>.
    # kind, metadata.uid, metadata.name, and metadata.namespace fields must not be changed by the webhook.
    # metadata.labels and metadata.annotations fields may be changed by the webhook.
    # All other changes to metadata fields by the webhook are ignored.
    "convertedObjects": [
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1",
        "metadata": {
          "creationTimestamp": "2019-09-04T14:03:02Z",
          "name": "local-crontab",
          "namespace": "default",
          "resourceVersion": "143",
          "uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
        },
        "host": "localhost",
        "port": "1234"
      },
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1",
        "metadata": {
          "creationTimestamp": "2019-09-03T13:02:01Z",
          "name": "remote-crontab",
          "resourceVersion": "12893",
          "uid": "359a83ec-b575-460d-b553-d859cedde8a0"
        },
        "host": "example.com",
        "port": "2345"
      }
    ]
  }
}
{
  # Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
  "apiVersion": "apiextensions.k8s.io/v1beta1",
  "kind": "ConversionReview",
  "response": {
    # must match <request.uid>
    "uid": "705ab4f5-6393-11e8-b7cc-42010a800002",
    "result": {
      "status": "Failed"
    },
    # Objects must match the order of request.objects, and have apiVersion set to <request.desiredAPIVersion>.
    # kind, metadata.uid, metadata.name, and metadata.namespace fields must not be changed by the webhook.
    # metadata.labels and metadata.annotations fields may be changed by the webhook.
    # All other changes to metadata fields by the webhook are ignored.
    "convertedObjects": [
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1",
        "metadata": {
          "creationTimestamp": "2019-09-04T14:03:02Z",
          "name": "local-crontab",
          "namespace": "default",
          "resourceVersion": "143",
          "uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
        },
        "host": "localhost",
        "port": "1234"
      },
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1",
        "metadata": {
          "creationTimestamp": "2019-09-03T13:02:01Z",
          "name": "remote-crontab",
          "resourceVersion": "12893",
          "uid": "359a83ec-b575-460d-b553-d859cedde8a0"
        },
        "host": "example.com",
        "port": "2345"
      }
    ]
  }
}

변환이 실패하면 webhook는 다음 필드를 포함하는 response stanza를 반환해야 해요:

  • uid: webhook에 보낸 request.uid에서 복사됨
  • result: {"status":"Failed"}로 설정됨

실패한 경우 webhook는 성공했을 때 무시될 convertedObjects를 설정해서는 안 된다는 점을 유의해요. 그리고 실패한 변환은 API 서버가 변경하는 요청을 중단시키고 응답 받습니다.

변환 요청이 실패했음을 나타내는 webhook의 응답 예시(선택적 메시지 포함):

  • apiextensions.k8s.io/v1
  • apiextensions.k8s.io/v1beta1
{
  "apiVersion": "apiextensions.k8s.io/v1",
  "kind": "ConversionReview",
  "response": {
    "uid": "<value from request.uid>",
    "result": {
      "status": "Failed",
      "message": "hostPort could not be parsed into a separate host and port"
    }
  }
}
{
  # Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
  "apiVersion": "apiextensions.k8s.io/v1beta1",
  "kind": "ConversionReview",
  "response": {
    "uid": "<value from request.uid>",
    "result": {
      "status": "Failed",
      "message": "hostPort could not be parsed into a separate host and port"
    }
  }
}

버전이 있는 CustomResourceDefinition 객체 작성, 읽기, 업데이트 (#writing-reading-and-updating-versioned-customresourcedefinition-objects)

객체가 작성될 때 쓰기 시점에 스토리지 버전으로 지정된 버전에 저장돼요. 스토리지 버전이 변경되면 기존 객체는 절대 자동으로 변환되지 않아요. 하지만 새로 생성되거나 업데이트된 객체는 새 스토리지 버전으로 작성돼요. 더 이상 서빙되지 않는 버전으로 작성된 객체가 있을 수 있어요.

객체를 읽을 때 경로의 일부로 버전을 지정해요. 현재 서빙되는 어떤 버전으로든 객체를 요청할 수 있어요. 객체의 저장 버전과 다른 버전을 지정하면 Kubernetes는 요청한 버전으로 객체를 반환하지만 저장된 객체는 디스크에서 변경되지 않아요.

읽기 요청을 서빙하는 동안 반환되는 객체에 무슨 일이 일어나는지는 CRD의 spec.conversion에 지정된 것에 달려 있어요:

  • 기본 전략 값 None이 지정되면 객체에 대한 유일한 수정은 apiVersion 문자열을 변경하고 (구성에 따라) 알 수 없는 필드를 가지치기(/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-pruning)하는 것이다. 스토리지와 요청된 버전 사이에 스키마가 다르면 좋은 결과로 이어지지 않을 가능성이 높다는 점을 유의해요. 특히 같은 데이터가 버전 사이에 다른 필드로 표현된다면 이 전략을 사용해서는 안 돼요.
  • webhook 변환(#webhook-변환)이 지정되면 이 메커니즘이 변환을 제어해요.

기존 객체를 업데이트하면 현재 스토리지 버전인 버전으로 다시 작성돼요. 이것은 객체가 한 버전에서 다른 버전으로 변경될 수 있는 유일한 방법이에요.

이것을 설명하기 위해 다음 가상의 일련의 사건을 고려해요:

  • 스토리지 버전이 v1beta1임. 객체를 만들면 v1beta1 버전으로 저장됨.
  • CustomResourceDefinition에 v1 버전을 추가하고 그것을 스토리지 버전으로 지정함. 여기서 v1과 v1beta1의 스키마는 동일하며, 이것은 Kubernetes 생태계에서 API를 stable로 승격할 때 일반적인 경우임.
  • v1beta1 버전으로 객체를 읽은 다음 v1 버전으로 다시 읽음. 두 반환된 객체는 apiVersion 필드를 제외하고 동일함.
  • 새 객체를 만들면 v1 버전으로 저장됨. 이제 두 객체가 있고, 하나는 v1beta1에 또 하나는 v1에 있음.
  • 첫 객체를 업데이트하면 현재 스토리지 버전이므로 이제 v1 버전으로 저장됨.

이전 스토리지 버전 (#previous-storage-versions)

API 서버는 한 번이라도 스토리지 버전으로 표시된 각 버전을 status 필드 storedVersions에 기록해요. 객체는 한 번이라도 스토리지 버전으로 지정된 어떤 버전에 저장되었을 수 있어요. 스토리지 버전이었던 적이 없는 버전의 스토리지에는 어떤 객체도 존재할 수 없어요.

기존 객체를 새 저장 버전으로 업그레이드 (#upgrade-existing-objects-to-a-new-stored-version)

버전을 폐기하고 지원을 제거할 때 스토리지 업그레이드 절차를 선택해요.

옵션 1: Storage Version Migration 사용:

  • 커스텀 리소스에 대해 Storage Version Migration을 실행해요. 예를 들어 업데이트된 CustomResourceDefinition과 같은 매니페스트에 StorageVersionMigration 리소스를 포함할 수 있어요.
  • 마이그레이션이 성공하면 이전 버전이 CustomResourceDefinition status.storedVersions 필드에서 제거될 거예요.

옵션 2: 기존 객체를 새 저장 버전으로 수동 업그레이드:

다음은 v1beta1에서 v1로 업그레이드하는 예시 절차예요.

  • CustomResourceDefinition 파일에서 v1을 storage로 설정하고 kubectl로 적용해요. storedVersions는 이제 v1beta1, v1임.
  • 업그레이드 절차를 작성해 모든 기존 객체를 나열하고 같은 내용으로 다시 작성해요. 이것은 백엔드가 객체를 현재 스토리지 버전인 v1으로 쓰도록 강제해요.
  • CustomResourceDefinition status.storedVersions 필드에서 v1beta1을 제거해요.

CRD 객체의 status 하위 리소스를 kubectl로 패치하는 방법의 예시는 다음과 같아요:

kubectl patch customresourcedefinitions <CRD_Name> --subresource='status' --type='merge' -p '{"status":{"storedVersions":["v1"]}}'

더 알아보기 (Learn more)