LabHub
배우기 러닝패스 코스

CNPE — クラウドネイティブプラットフォームエンジニア

CRDは機能ではなく契約です

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

CRD는 새로운 종류의 객체를 API에 등록합니다. 플랫폼 계약을 완성하려면 받을 값, 허용할 변경, 지원할 버전, 상태를 쓸 주체까지 정해야 합니다. 객체 생성 성공은 서비스 준비 완료와 다릅니다.

概念マップ: 한 줄 요약・왜 이게 필요했나・어떻게 동작하나・스키마는 모양을, CEL은 관계를 검사합니다

왜 이게 필요했나

다음은 연습 상황입니다. 팀이 데이터베이스를 신청하는 AppClaim을 만들었습니다. kubectl apply는 성공했지만 연결할 주소가 없습니다. API가 요청을 저장했을 뿐, 실제 데이터베이스를 만드는 컨트롤러는 아직 없습니다. 여기서 CRD를 다시 적용하는 것은 해결책이 아닙니다. 접수와 조정 중 어느 단계가 빠졌는지 먼저 나눠야 합니다.

CRD 자체가 워크로드를 생성하지는 않습니다. 컨트롤러를 결합해야 사용자의 원하는 상태를 실제 상태로 맞추는 동작이 생깁니다. 이 실습은 CRD·검증·RBAC·쿼터를 다루며 AppClaim 컨트롤러 구현은 포함하지 않습니다. 공식 Custom Resources 개념

어떻게 동작하나

스키마는 모양을, CEL은 관계를 검사합니다

타입이 integer라는 사실만으로 replicas <= maxReplicas가 보장되지는 않습니다. 예를 들어 9와 4는 각각 정수이지만 신청한 수가 상한보다 많습니다. 두 필드를 포함하는 spec 스키마 위치에 아래 규칙을 둡니다. 이는 전체 CRD가 아니라 해당 위치에 넣는 조각입니다.

type: object
required: [tier, replicas, maxReplicas]
properties:
  tier:
    type: string
    enum: [bronze, silver, gold]
  replicas: {type: integer, minimum: 1}
  maxReplicas: {type: integer, minimum: 1}
x-kubernetes-validations:
  - rule: "self.replicas <= self.maxReplicas"
    message: "replicas는 maxReplicas 이하여야 합니다"

required는 그 위치의 필드 누락을, enum은 허용한 값의 집합을, CEL은 두 값의 관계를 검사합니다. spec 자체도 반드시 있어야 하는 API라면 상위 객체 스키마에도 required: [spec]을 선언해야 합니다. 하위의 required가 상위 객체까지 필수로 만들지는 않습니다. 공식 CRD 스키마와 검증

불변 규칙의 위치가 비교 범위를 결정합니다

기본 전이 규칙에서 oldSelf는 대응하는 이전 값을 뜻합니다. 이번 실습처럼 optionalOldSelf를 사용하지 않는 규칙은 생성 시 이전 값이 없어 건너뜁니다. spec 위치의 self.tier == oldSelf.tier는 등급만 비교합니다. 같은 위치에서 self == oldSelf를 쓰면 spec 전체가 같아야 하므로 정상적인 replicas 변경까지 막습니다. 반대로 tier 필드 자체에 둔 self == oldSelf는 tier만 비교합니다. 문자열을 외우기보다 규칙이 붙은 위치를 확인하세요. 공식 CEL 전이 규칙 설명

불변성은 CEL로만 구현할 수 있는 개념이 아닙니다. 여기서는 CRD에 내장된 CEL을 선택했으며 별도 admission 검증을 사용하는 설계도 있습니다. 또 최신 Kubernetes의 optionalOldSelf: true는 이전 값이 없는 경우에도 규칙을 평가하고 oldSelf를 Optional 타입으로 바꿉니다. 따라서 “oldSelf가 있으면 언제나 생성 때 실행되지 않는다”라고 일반화하면 틀립니다. optionalOldSelf의 조건과 동작

지원 버전과 저장 버전은 다른 약속입니다

항목 이것만으로 보장하지 않는 것
served 그 버전의 API 경로 제공 다른 버전과 같은 검증 규칙
storage 새 쓰기에 사용할 저장 버전, 정확히 하나 기존 객체의 일괄 변환 완료
conversion 버전 사이 표현 변환 방식 외부 서비스 생성 또는 정책 검증 대체

두 버전을 읽었다고 객체가 두 개 생기는 것은 아닙니다. 같은 namespace/name의 객체를 다른 API 표현으로 읽는 것입니다. 저장 버전을 변경해도 기존 저장 객체가 자동으로 전부 다시 쓰이지는 않습니다. 구버전 제거는 클라이언트 이전, 저장 데이터 이전, status.storedVersions 정리까지 확인하는 작업입니다. 옛 버전을 무조건 영구 제공하는 것도, 새 버전 추가와 동시에 끄는 것도 정답이 아닙니다.

기본 conversion.strategy: None은 필드 이름 변경을 구현해 주지 않습니다. sizecapacity로 바꾸는 계약이라면 별도의 변환 설계가 필요합니다. 구버전 API가 열려 있는 동안은 그 버전으로도 정상 입력과 금지 입력을 확인하세요. 공식 버전 관리와 제거 절차

status는 별도 객체가 아니라 별도 쓰기 경로입니다

subresources.status: {}를 켜면 일반 객체에 대한 POST·PUT·PATCH는 status 변경을 무시합니다. /status로 보내는 변경은 status 이외의 변경을 무시합니다. 이 분리는 원하는 값과 관측한 값을 누가 쓸지 나누기 위한 장치입니다. status가 자동으로 채워지거나 “Ready”라는 문구가 진실임을 검증해 주지는 않습니다. 공식 status 서브리소스 계약

따라서 사용자가 spec을 수정할 권한과 컨트롤러가 status를 수정할 권한을 따로 설계합니다. additionalPrinterColumns는 사람이 빠르게 확인하도록 값을 표시할 뿐, 그 값을 계산하는 컨트롤러를 대신하지 않습니다.

현장에서 만나는 모습

LabHub의 기존 실습을 실제 k3s API에서 확인했을 때 v1은 replicas=9, maxReplicas=4tier=platinum을 거절했지만, v1alpha1은 같은 요청을 허용했습니다. 저장 버전이 v1이라는 이유로 v1의 규칙만 요구했던 것이 원인이었습니다. 두 served 버전의 스키마와 실제 요청을 함께 검사하도록 고쳤습니다.

이 사례의 핵심 질문은 “규칙이 한 곳에 있는가”가 아니라 “사용자가 이용할 수 있는 모든 API 경로가 같은 계약을 지키는가”입니다. 검증 목록에는 버전별 정상 생성, 잘못된 용량, 미정의 등급, 등급 변경 거절, replicas 변경 허용을 나란히 둡니다. 거절만 확인하면 모든 요청을 막는 잘못된 규칙을 놓칩니다.

다음 실습에서 할 것

두 버전의 AppClaim 계약을 정의하고 직접 서버 dry-run 요청을 보냅니다. 정상 값은 통과하고 잘못된 값은 해당 필드의 검증 오류로 거절되는지 확인하세요. 명령 실패만으로 검증 성공이라 하지 마세요. 다음 이론에서는 이와 별개인 주체의 권한과 발급 개수 상한을 다룹니다.