CNPA — Cloud Native Platform Engineering Associate
Building a Platform API With CRDs
한국어 원문으로 표시합니다.
목표
쿠버네티스 API 를 확장해 WebService 라는 플랫폼 API 를 직접 만들고, 스키마 검증·서버 사이드 기본값·테넌트 경계·셀프서비스 RBAC 까지 한 세트를 실제 클러스터에서 완성합니다.
왜 중요한가
플랫폼 API 를 사내 웹 앱으로 만들면 상태 저장, 동시성 제어, 인증·인가, 감사, watch 를 전부 다시 구현해야 합니다. CRD 를 등록하면 그것들이 전부 따라옵니다 — 그래서 쿠버네티스 API 가 플랫폼의 공용어가 됐습니다. 특히 OpenAPI 스키마는 셀프서비스의 세 조건 중 '빠른 피드백'을 가장 싸게 구현하는 장치입니다. maximum: 10 한 줄이면 잘못된 요청이 30 분 뒤 파이프라인 로그가 아니라 즉시, 사람이 읽을 수 있는 문장으로 거절됩니다. default 도 마찬가지로 '안전한 기본값'을 서버가 강제해 줍니다. 그리고 CRD 만으로는 플랫폼이 완성되지 않습니다 — 테넌트가 서로를 침범할 수 없다는 경계(네임스페이스·쿼터·RBAC)가 함께 있어야 비로소 티켓 없이 권한을 줄 수 있습니다. 이 실습의 모든 리소스는 쿠버네티스 내장 리소스이므로 실제로 적용하고 kubectl 로 채점합니다.
단계
/root/cnpa-platform/crd.yaml에 CustomResourceDefinition 을 작성하고 적용하세요 —metadata.name: webservices.platform.labhub.io,spec.group: platform.labhub.io,spec.scope: Namespaced,spec.names는 kindWebService, pluralwebservices, singularwebservice, shortNames 첫 항목ws, 버전v1alpha1은served: true,storage: true.- 같은 CRD 의
v1alpha1스키마를 완성하세요 —spec객체에image(string, 필수),replicas(integer,default: 2,minimum: 1,maximum: 10),public(boolean,default: false). 그리고additionalPrinterColumns에 이름Image(jsonPath.spec.image, type string)와Replicas(jsonPath.spec.replicas, type integer) 두 열을 추가하세요. - 네임스페이스
tenant-blue를 만드세요 — 라벨platform.labhub.io/tenant: blue와pod-security.kubernetes.io/enforce: baseline. tenant-blue에 ResourceQuotatenant-blue-quota를 만드세요 —requests.cpu: "2",requests.memory: 4Gi,limits.cpu: "4",limits.memory: 8Gi,pods: "10". 같은 네임스페이스에 LimitRangetenant-blue-limits를 만드세요 — typeContainer,default는 cpu200m/ memory256Mi,defaultRequest는 cpu100m/ memory128Mi.tenant-blue에 WebServiceshop을 만드세요 —spec.image: ghcr.io/labhub/shop:1.0.0만 쓰고replicas와public은 쓰지 마세요. 만든 뒤 다시 읽어 두 값이 채워졌는지 확인하세요.- 스키마 위반을 확인하세요.
spec.replicas: 20인 WebServicebad를tenant-blue에 만들어 보고, 그 실패 출력(표준 에러 포함)을/root/cnpa-platform/reject.txt에 저장하세요.bad는 클러스터에 남아 있으면 안 됩니다. - ServiceAccount
blue-dev를tenant-blue에 만들고, 같은 네임스페이스에 Rolewebservice-editor(apiGroupsplatform.labhub.io, resourceswebservices, verbsget,list,watch,create,update,patch,delete)와 RoleBindingblue-devs(그 Role 을blue-devServiceAccount 에 연결)를 만드세요. 쿼터를 고칠 권한은 주지 마세요. - 같은 패턴으로 두 번째 테넌트를 만드세요 — 네임스페이스
tenant-green(라벨platform.labhub.io/tenant: green), ResourceQuotatenant-green-quota(pods: "10"포함), 그리고 WebServiceapi(spec.image: ghcr.io/labhub/api:1.0.0, replicas 는 지정하지 않음).tenant-blue의blue-dev가tenant-green에는 WebService 를 만들 수 없어야 합니다.
참고
- CRD 를 적용한 직후에는
kubectl get ws -n tenant-blue처럼 축약형도 바로 동작해야 합니다. - 권한 확인은
kubectl auth can-i <verb> <resource> --as=system:serviceaccount:tenant-blue:blue-dev -n <네임스페이스>로 합니다. - 실패 출력을 파일에 담으려면 표준 에러도 함께 리다이렉트해야 합니다.
- 흔한 실수 1: CRD 의
metadata.name을webservice.platform.labhub.io처럼 단수로 쓰는 것. 반드시 복수형이어야 합니다. - 흔한 실수 2:
default를spec객체 자체가 아니라 각 속성 안에 넣어야 한다는 점. 그리고required는 속성 이름들의 배열입니다.
CRD 등록 — 그룹·스코프·이름
/root/cnpa-platform/crd.yaml 에 CustomResourceDefinition 을 작성하고 적용하세요 — metadata.name: webservices.platform.labhub.io, spec.group: platform.labhub.io, spec.scope: Namespaced, spec.names 는 kind WebService, plural webservices, singular webservice, shortNames 첫 항목 ws, 버전 v1alpha1 은 served: true, storage: true.
CRD 이름은 <복수형>.<그룹> 형식이어야 합니다. 테넌트가 만드는 리소스이므로 스코프 선택에 주의하세요.
스키마와 프린터 컬럼
같은 CRD 의 v1alpha1 스키마를 완성하세요 — spec 객체에 image(string, 필수), replicas(integer, default: 2, minimum: 1, maximum: 10), public(boolean, default: false). 그리고 additionalPrinterColumns 에 이름 Image(jsonPath .spec.image, type string)와 Replicas(jsonPath .spec.replicas, type integer) 두 열을 추가하세요.
OpenAPI v3 스키마는 versions 배열 안의 각 버전에 붙습니다. required, minimum/maximum, default 를 각각 어디에 쓰는지 구분하세요. 프린터 컬럼도 버전마다 정의합니다.
테넌트 네임스페이스
네임스페이스 tenant-blue 를 만드세요 — 라벨 platform.labhub.io/tenant: blue 와 pod-security.kubernetes.io/enforce: baseline.
네임스페이스 자체가 테넌트 경계입니다. 소속을 나타내는 라벨과 파드 보안 표준 라벨을 함께 붙이세요.
ResourceQuota 와 LimitRange
tenant-blue 에 ResourceQuota tenant-blue-quota 를 만드세요 — requests.cpu: "2", requests.memory: 4Gi, limits.cpu: "4", limits.memory: 8Gi, pods: "10". 같은 네임스페이스에 LimitRange tenant-blue-limits 를 만드세요 — type Container, default 는 cpu 200m / memory 256Mi, defaultRequest 는 cpu 100m / memory 128Mi.
둘은 역할이 다릅니다. 하나는 네임스페이스 총량의 상한이고, 다른 하나는 개별 컨테이너의 기본값과 범위입니다. 둘 다 있어야 '요청을 안 쓴 파드'가 쿼터를 통과합니다.
CR 생성과 서버 사이드 기본값
tenant-blue 에 WebService shop 을 만드세요 — spec.image: ghcr.io/labhub/shop:1.0.0 만 쓰고 replicas 와 public 은 쓰지 마세요. 만든 뒤 다시 읽어 두 값이 채워졌는지 확인하세요.
replicas 를 아예 쓰지 말고 만들어 보세요. 저장된 오브젝트를 다시 읽으면 값이 들어가 있어야 합니다.
스키마 위반이 거절되는지
스키마 위반을 확인하세요. spec.replicas: 20 인 WebService bad 를 tenant-blue 에 만들어 보고, 그 실패 출력(표준 에러 포함)을 /root/cnpa-platform/reject.txt 에 저장하세요. bad 는 클러스터에 남아 있으면 안 됩니다.
범위를 벗어난 값으로 만들어 보고, 그때 나오는 에러 메시지를 파일로 남기세요. 표준 에러도 함께 저장해야 합니다.
셀프서비스 권한과 가드레일
ServiceAccount blue-dev 를 tenant-blue 에 만들고, 같은 네임스페이스에 Role webservice-editor(apiGroups platform.labhub.io, resources webservices, verbs get,list,watch,create,update,patch,delete)와 RoleBinding blue-devs(그 Role 을 blue-dev ServiceAccount 에 연결)를 만드세요. 쿼터를 고칠 권한은 주지 마세요.
새 리소스에도 기존 RBAC 이 그대로 적용됩니다. 규칙의 apiGroups 와 resources 에 무엇을 쓰는지 확인하고, 쿼터는 손대지 못하게 남겨 두세요.
두 번째 테넌트와 격리 증명
같은 패턴으로 두 번째 테넌트를 만드세요 — 네임스페이스 tenant-green(라벨 platform.labhub.io/tenant: green), ResourceQuota tenant-green-quota(pods: "10" 포함), 그리고 WebService api(spec.image: ghcr.io/labhub/api:1.0.0, replicas 는 지정하지 않음). tenant-blue 의 blue-dev 가 tenant-green 에는 WebService 를 만들 수 없어야 합니다.
같은 패턴을 한 번 더 적용하는 것이 플랫폼입니다. 그리고 첫 테넌트의 권한이 두 번째 테넌트에는 닿지 않아야 합니다.