CRD 와 오퍼레이터 · CRD 작성과 배포 · 실습
API 서버에 검증을 떠넘기기
목표
일부러 규칙을 어긴 리소스를 던져 API 서버가 무엇을 어떻게 거부하는지 직접 수집하고, 기본값 주입·pruning·CEL 검증까지 스키마 하나로 처리되는 것을 확인합니다.
왜 중요한가
검증을 스키마로 옮기는 진짜 이득은 "거부한다"가 아니라 "거부하면서 무엇이 왜 안 되는지 사용자에게 알려 준다" 입니다. enum 을 넣으면 거부 메시지에 허용 목록이 함께 나오고, maximum 을 넣으면 상한이 그대로 문구에 들어갑니다. 사용자는 위키를 뒤지지 않고 오류만 읽어도 고칠 수 있습니다. pruning 은 처음에는 당황스럽습니다 — 오타 필드가 에러도 없이 조용히 사라지기 때문입니다. 하지만 이것이 "스키마가 곧 계약"을 강제하는 장치이고, 그래서 임의의 키를 받아야 하는 자리는 예외적으로만, 그 자리만 열어야 합니다. 마지막으로 CEL 은 판을 바꿨습니다. 예전에는 "prod 면 복제본 2개 이상" 같은 필드 간 제약을 위해 검증 웹훅 서버를 띄우고, 인증서를 관리하고, 그 웹훅이 죽으면 클러스터가 마비되는 위험까지 감수해야 했습니다. 이제는 스키마의 한 줄입니다.
단계
시작 전 준비: 실습 파드는 실습마다 새로 뜨므로 앞 실습에서 만든 클러스터 상태는 남아 있지 않습니다. kubectl get crd webservices.apps.labhub.io 가 비어 있으면 앞 실습에서 쓴 CRD 를 /root/crd/crd.yaml 로 다시 작성해 적용하고 kubectl create ns crd-lab 도 하세요. 이 실습이 요구하는 스키마는 spec.required: [image], replicas(integer, minimum 1, maximum 10, default 1), tier(string, enum [dev, stage, prod], default dev) 이며, 6번과 7번에서 spec.extra 와 CEL 규칙을 여기에 더하게 됩니다.
1. /root/crd/validate/no-image.yaml 에 metadata.name: no-image(네임스페이스 crd-lab)이고 spec 에 image 가 없는 WebService 를 쓰고 적용해 보세요. 실패 출력을 표준 에러까지 포함해 /root/crd/validate/out/err-required.txt 로 저장하세요. 출력에 spec.image 와 필수 값 관련 문구가 있어야 하고, no-image 는 클러스터에 남으면 안 됩니다.
2. /root/crd/validate/bad-type.yaml 에 metadata.name: bad-type, spec.image: nginx:1.27, spec.replicas: "three" 를 쓰고 적용해 실패 출력을 /root/crd/validate/out/err-type.txt 로 저장하세요.
3. /root/crd/validate/bad-tier.yaml 에 metadata.name: bad-tier, spec.image: nginx:1.27, spec.tier: qa 를 쓰고 적용해 실패 출력을 /root/crd/validate/out/err-enum.txt 로 저장하세요. 출력에 허용되는 값들이 함께 보여야 합니다.
4. /root/crd/validate/too-many.yaml 에 metadata.name: too-many, spec.image: nginx:1.27, spec.replicas: 50 을 쓰고 적용해 실패 출력을 /root/crd/validate/out/err-range.txt 로 저장하세요.
5. /root/crd/validate/defaulted.yaml 에 metadata.name: defaulted 와 spec.image 만 쓰세요. spec.replicas 와 spec.tier 는 절대 적지 마세요. 적용한 뒤 저장된 오브젝트에 replicas: 1, tier: dev 가 채워졌는지 확인하세요.
6. /root/crd/crd.yaml 의 v1 스키마에 properties.spec.properties.extra 를 type: object 와 x-kubernetes-preserve-unknown-fields: true 로 추가하고 CRD 를 재적용하세요. 그다음 /root/crd/validate/pruned.yaml 에 metadata.name: pruned, spec.image, 그리고 스키마에 없는 spec.bogus: anything 을 넣어 적용하고(저장된 오브젝트에서 bogus 는 사라져야 합니다), /root/crd/validate/preserved.yaml 에 metadata.name: preserved, spec.image, spec.extra.custom: kept 를 넣어 적용하세요(이 값은 남아 있어야 합니다).
7. v1 스키마의 properties.spec 바로 아래에 x-kubernetes-validations 배열을 추가하세요. 첫 규칙의 rule 은 self.tier != 'prod' || self.replicas >= 2 이고 message 는 prod 계층은 복제본이 2개 이상이어야 합니다 입니다. 재적용한 뒤 /root/crd/validate/cel-violation.yaml 에 metadata.name: cel-violation, spec.image, spec.tier: prod, spec.replicas: 1 을 넣어 적용하고 실패 출력을 /root/crd/validate/out/err-cel.txt 로 저장하세요. 그 출력에 위 message 문구가 그대로 들어 있어야 합니다.
8. /root/crd/validate/out/matrix.json 을 만드세요. 최상위 키는 cases 이고 배열의 각 원소는 name, expected(rejected 또는 accepted), actual, rule 네 키를 갖습니다. 거부 5건(no-image/required, bad-type/type, bad-tier/enum, too-many/maximum, cel-violation/cel)과 통과 3건(defaulted/default, pruned/pruning, preserved/preserve-unknown-fields)을 모두 넣고, 모든 케이스에서 expected 와 actual 이 같아야 합니다.
참고
- 실습 파드는 실습마다 새로 뜨므로 앞 실습의 클러스터 상태는 남아 있지 않습니다. 그래도 선언을 파일로 남겨 두면 어느 파드에서든 같은 상태를 다시 세울 수 있습니다 — 이것이 선언형의 실질적 이점입니다.
- 적용 실패 출력을 파일로 남기려면
kubectl apply -f 파일 > 출력파일 2>&1처럼 표준 에러를 함께 받아야 합니다. 거부 메시지는 표준 출력이 아닙니다. /opt/lab/fixtures/crd/sample-cr.yaml을 복사해 값만 바꾸면 케이스를 빠르게 만들 수 있습니다.- CEL 규칙은 기본값이 채워진 뒤에 평가되므로
self.tier와self.replicas는 항상 존재합니다. - 흔한 실수 1: 5번에서 매니페스트에
replicas를 적어 두는 것. 그러면 기본값이 채워진 것인지 내가 적은 것인지 구분할 수 없어 채점이 실패합니다. - 흔한 실수 2: 6번에서
x-kubernetes-preserve-unknown-fields를 spec 전체에 거는 것. 그러면 pruning 이 spec 전역에서 꺼져bogus도 살아남습니다.extra아래에만 거세요. - 흔한 실수 3: 8번에서
actual을 예상값으로 복사해 적는 것. 실제로 클러스터를 조회해 확인한 결과를 적어야 하며, 채점기가 클러스터와 대조합니다.
단계 8개
- 필수 필드 누락이 거부되는 것 보기
- 타입 불일치가 거부되는 것 보기
- 열거값 위반과 허용 목록 안내 보기
- 범위 초과가 거부되는 것 보기
- 적지 않은 필드에 기본값이 채워지는 것 확인하기
- 모르는 필드의 잘림과 예외적 보존
- 필드 간 제약을 CEL 로 표현하기
- 검증 매트릭스 만들고 실제와 대조하기