LabHub
배우기 러닝패스 코스

CNPA — Cloud Native Platform Engineering Associate

You Learn the Contract by Being Rejected

LabHub 에서 이어서 보기

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

한 줄 요약

CRD 의 값어치는 API 서버의 검증·기본값·RBAC·감사를 빌려 오는 데 있습니다. 무엇이든 받아 주는 클러스터에서는 그중 무엇도 확인할 수 없습니다.

Concept map: API 서버의 능력이 없어서 · 기본이 잘라내기 · 함께 쓸 수 없습니다 · 잘라내기도 검증도 통째로 꺼집니다.

왜 거절당해 봐야 하나

앞 모듈에서 CRD 로 플랫폼 API 를 만들었습니다. 그런데 그 실습이 도는 곳에는 API 서버의 능력이 없어서, 무엇을 넣어도 받아 주었습니다.

CRD 는 "API 서버가 이미 가진 능력을 내 타입에 빌려 준다" 는 물건입니다. 그 능력이란 이것입니다.

검증      스키마에 안 맞으면 거절한다
기본값    빠진 값을 저장 시점에 채운다
RBAC      다른 자원과 똑같이 권한을 건다
감사      누가 언제 무엇을 바꿨는지 남는다
watch     컨트롤러가 변화를 구독한다
문서      kubectl explain 이 스키마를 읽어 준다

직접 API 를 만들면 이 여섯을 전부 다시 만들어야 합니다. 그런데 받아 주기만 하는 클러스터에서는 그중 무엇도 확인할 수 없었습니다.

조용히 지워지는 필드

구조적 스키마는 기본이 잘라내기입니다. properties 에 없는 필드는 저절로 지워집니다 — 막는 설정을 켠 것이 아닙니다. replicasreplica 로 잘못 쓰면 그 값이 사라지고 아무도 알려 주지 않습니다.

additionalProperties: false 를 쓰려다 물리는 자리이기도 합니다. properties함께 쓸 수 없습니다(Forbidden: mutual exclusive). 쓸 필요가 없어서 막아 둔 것입니다.

반대로 x-kubernetes-preserve-unknown-fields: true 를 넣으면 잘라내기도 검증도 통째로 꺼집니다. 편하다고 넣는 순간 모든 담장이 사라집니다.

기본값은 누가 채우느냐가 다르다

스키마의 default    저장 시점에 채워져 kubectl get -o yaml 에 바로 보인다
컨트롤러가 채움     한참 뒤에 나타난다. 그 사이에는 비어 있다

개발자가 확인했을 때 값이 있어야 무슨 일이 일어날지 압니다. 골든 패스는 여기서 만들어집니다.

스키마를 바꿀 때 무엇이 깨지나

CRD 는 배포하고 나면 이미 저장된 오브젝트가 있습니다. 스키마를 바꾸는 것은 저장된 데이터를 다시 해석하는 일 이라 규칙이 있습니다.

변경 안전한가 이유
선택 필드 추가 옛 오브젝트는 그 필드가 비어 있을 뿐
필수 필드 추가 옛 오브젝트가 검증에 걸려 수정도 못 하게 된다
필드 삭제 ⚠️ 값이 잘려 나간다. 되돌릴 수 없다
타입 변경(string→int) 저장된 값을 못 읽는다
enum 값 추가
enum 값 삭제 그 값을 쓰던 오브젝트가 무효가 된다

필수 필드를 더해야 한다면 새 버전 을 만듭니다(v1alpha1v1beta1). storage: true 인 버전은 하나뿐이고, 나머지는 변환(conversion)을 거쳐 제공됩니다. 변환 웹훅이 없으면 None 전략이라 필드가 그대로 통과하므로, 구조가 다르면 웹훅을 세워야 합니다.

검증을 어디까지 스키마로 할 수 있나

OpenAPI 스키마로 되는 것과 안 되는 것이 갈립니다.

properties:
  replicas:
    type: integer
    minimum: 1
    maximum: 100
    default: 3
  tier:
    type: string
    enum: [bronze, silver, gold]
  name:
    type: string
    pattern: '^[a-z][a-z0-9-]{2,30}$'

여기까지는 스키마로 됩니다. 필드 사이의 관계 는 안 됩니다 — "tier 가 gold 면 replicas 가 5 이상" 같은 것입니다. 예전에는 웹훅이 필요했지만, 지금은 CEL 검증 규칙으로 CRD 안에서 표현할 수 있습니다.

x-kubernetes-validations:
  - rule: "self.tier != 'gold' || self.replicas >= 5"
    message: "gold 등급은 복제본이 5개 이상이어야 합니다"
  - rule: "self.name == oldSelf.name"      # 불변 필드
    message: "name 은 만든 뒤에 바꿀 수 없습니다"

웹훅보다 나은 점이 분명합니다. 별도 배포가 없고, 웹훅이 죽어서 API 가 멈추는 일이 없으며, 오류 메시지를 스키마와 같은 자리에 둡니다. 웹훅은 CEL 로 표현할 수 없을 때만 씁니다.

상태를 어디에 두나

statusspec 과 분리해 서브리소스로 둡니다.

subresources:
  status: {}
  scale:
    specReplicasPath: .spec.replicas
    statusReplicasPath: .status.replicas

이렇게 하면 사용자가 status 를 못 고치고 컨트롤러가 spec 을 못 고칩니다. 권한이 자연스럽게 갈립니다. scale 서브리소스를 두면 kubectl scale 과 HPA 가 그대로 동작합니다 — 커스텀 리소스에 HPA 를 붙이는 방법이 이것입니다.

실무에서 진짜 중요한 것

오타는 오류가 아니라 침묵으로 돌아옵니다. 구조적 스키마는 기본이 잘라내기라, replicasreplica 로 쓰면 값이 조용히 사라집니다. 플랫폼 API 를 낼 때는 kubectl get -o yaml 로 저장된 결과를 되읽어 보라고 안내문에 적어 두는 편이 낫습니다.

x-kubernetes-preserve-unknown-fields: true 는 마지막 수단입니다. 편하다고 넣는 순간 잘라내기도 검증도 통째로 꺼져서, 그 타입에 세워 둔 담장이 전부 사라집니다.

기본값은 컨트롤러가 아니라 스키마에 둡니다. 스키마의 default 는 저장 시점에 채워져 개발자가 바로 확인할 수 있지만, 컨트롤러가 채우면 그 사이에는 비어 있습니다. 골든 패스는 이 차이에서 만들어집니다.

다음 실습에서 이것들을 진짜 API 서버 위에서 직접 거절당해 가며 확인합니다.