LabHub
배우기 러닝패스 코스

CNPE — Cloud Native Platform Engineer

Designing a Platform API and Fencing It

LabHub 에서 이어서 보기

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

목표

AppClaim 이라는 셀프서비스 API 를 CRD 로 설계해 적용하고, 스키마와 CEL 로 잘못된 요청을 거절하게 만들고, RBAC 으로 테넌트가 할 수 있는 일과 할 수 없는 일을 나눈 뒤, 발급 개수에 상한을 겁니다.

왜 중요한가

플랫폼 API와 셀프서비스는 CNPE에서 다루는 핵심 영역입니다. 그리고 이 영역에서 묻는 것은 CRD 를 만들 줄 아는가가 아니라, 무엇을 API 서버가 거절하게 만들 것인가를 정할 줄 아는가 입니다.

이 판단이 중요한 이유는 거절하는 자리가 두 곳뿐이기 때문입니다. API 서버가 거절하면 사용자는 즉시 이유를 봅니다. 컨트롤러가 나중에 실패하면 사용자는 접수됐다고 믿고 기다립니다. 앞쪽에서 걸 수 있는 것을 뒤쪽으로 미루면 그만큼 사람이 헤맵니다.

이 실습에는 AppClaim을 실제 앱으로 바꾸는 컨트롤러가 없습니다. 그러나 kube-apiserver 는 진짜라서 CRD 등록, OpenAPI 검증, CEL 평가, RBAC 판정, ResourceQuota 어드미션이 전부 실제로 동작합니다. 그래서 여기서 확인하는 거절과 허용은 실제 클러스터에서 일어나는 것과 같습니다.

작업 디렉터리는 /root/cnpe-api 이고, 테넌트 네임스페이스는 tenant-blue 입니다.

단계

  1. /root/cnpe-api/appclaim-crd.yaml 에 CRD 를 씁니다. 그룹은 platform.labhub.io, 종류는 AppClaim, 복수형은 appclaims, 범위는 Namespaced 입니다. 버전은 v1alpha1v1 두 개이고 둘 다 served 이며 저장 버전은 v1 입니다. v1alpha1v1 모두에 status 서브리소스와 .spec.tier 를 보여 주는 출력 컬럼을 둡니다. 두 버전의 루트에서 spec을 필수로 하고, spec 안에서 tier·replicas·maxReplicas를 필수로 합니다. tier는 문자열, 두 수량은 1 이상의 정수입니다. 다 쓴 뒤 apply 합니다.
  2. v1alpha1v1 각각의 spec 에 검증을 더합니다. tierbronze, silver, gold 만 허용하고, CEL 규칙으로 replicasmaxReplicas 를 넘지 못하게 막습니다. 거절 메시지에는 maxReplicas 라는 필드 이름이 들어가야 합니다. 두 버전에서 bronze(1/1)·silver(2/6)·gold(3/3)의 정상 요청은 허용하고, spec 전체 누락·null·빈 객체, 필수 필드 누락, 잘못된 자료형, 0·음수는 거절되는지 서버 dry-run으로 확인합니다. 괄호는 replicas/maxReplicas입니다.
  3. 네임스페이스 tenant-blue 를 만들고, /root/cnpe-api/claim-checkout.yaml 에 AppClaim checkout 을 씁니다. tier 는 silver, replicas 는 2, maxReplicas 는 6 입니다. apply 한 뒤 두 버전으로 조회한 UID와 spec이 같은지 확인합니다.
  4. v1alpha1v1 각각의 spec 에 전이 규칙을 더해 tier 를 불변으로 만듭니다. 규칙은 oldSelf 를 참조해야 하고, 거절 메시지에는 tier 가 들어가야 합니다. tier 를 그대로 둔 채 replicas 만 바꾸는 변경은 계속 통과해야 합니다.
  5. /root/cnpe-api/tenant-rbac.yaml 에 ServiceAccount blue-dev, Role appclaim-author, RoleBinding appclaim-author 를 씁니다. 롤은 AppClaim 에 대해 get, list, watch, create, update, patch 를 허용합니다. 바인딩은 그 ServiceAccount 를 가리켜야 합니다.
  6. 롤에 delete 가 없고, resourcequotas 와 roles 와 rolebindings 를 만들 수 없고, secrets 를 읽을 수 없는지 kubectl auth can-i 로 확인합니다. AppClaim status의 patch·update와 kube-system의 AppClaim list도 거절되어야 합니다. 정상 get은 yes인지 대조하고, 통신·인증 오류를 no로 해석하지 마세요. 롤의 verbs, resources, apiGroups 어디에도 * 가 없어야 합니다.
  7. AppClaim search 를 하나 더 만들고, /root/cnpe-api/claim-quota.yaml 에 ResourceQuota blue-claims 를 씁니다. count/appclaims.platform.labhub.io 의 상한은 2 입니다. 쿼터의 status.used 가 2 로 채워지는지 확인하고, 비어 있으면 CRD Established·API discovery·쿼터 컨트롤러를 진단하고 재확인한 뒤, 세 번째 청구가 실제로 막히는지 확인합니다.
  8. /root/cnpe-api/api-report.txtstorage_version, claims, quota_hard, tenant_can_delete 네 줄을 키=값 형식으로 적습니다. 네 값 모두 클러스터에서 직접 조회한 것이어야 합니다.

참고

CRD 의 뼈대와 두 버전을 정하기

/root/cnpe-api/appclaim-crd.yaml 에 CRD 를 씁니다. 그룹은 platform.labhub.io, 종류는 AppClaim, 복수형은 appclaims, 범위는 Namespaced 입니다. 버전은 v1alpha1v1 두 개이고 둘 다 served 이며 저장 버전은 v1 입니다. v1alpha1v1 모두에 status 서브리소스와 .spec.tier 를 보여 주는 출력 컬럼을 둡니다. 두 버전의 루트에서 spec을 필수로 하고, spec 안에서 tier·replicas·maxReplicas를 필수로 합니다. tier는 문자열, 두 수량은 1 이상의 정수입니다. 다 쓴 뒤 apply 합니다.

저장 버전은 한 버전만 true 입니다. 옛 버전도 served로 두어야 그 버전의 요청을 받습니다. spec 안의 required는 spec 자체를 필수로 만들지 않으므로 루트의 required도 필요합니다. status 서브리소스는 상태 쓰기를 별도 경로로 분리합니다.

값의 집합과 필드 사이의 관계를 묶기

v1alpha1v1 각각의 spec 에 검증을 더합니다. tierbronze, silver, gold 만 허용하고, CEL 규칙으로 replicasmaxReplicas 를 넘지 못하게 막습니다. 거절 메시지에는 maxReplicas 라는 필드 이름이 들어가야 합니다. 두 버전에서 bronze(1/1)·silver(2/6)·gold(3/3)의 정상 요청은 허용하고, spec 전체 누락·null·빈 객체, 필수 필드 누락, 잘못된 자료형, 0·음수는 거절되는지 서버 dry-run으로 확인합니다. 괄호는 replicas/maxReplicas입니다.

값 하나의 모양은 enum 이 잡고, 두 필드의 관계는 CEL 이 잡습니다. 규칙은 spec 프로퍼티 아래에 x-kubernetes-validations 로 답니다. 거절 메시지는 사용자가 실제로 읽는 유일한 문장이므로 필드 이름을 넣으십시오.

첫 셀프서비스 요청을 접수하기

네임스페이스 tenant-blue 를 만들고, /root/cnpe-api/claim-checkout.yaml 에 AppClaim checkout 을 씁니다. tier 는 silver, replicas 는 2, maxReplicas 는 6 입니다. apply 한 뒤 두 버전으로 조회한 UID와 spec이 같은지 확인합니다.

두 버전이 다른 객체를 만드는 것이 아닙니다. UID와 spec을 비교하세요. status 서브리소스를 켜면 일반 생성·수정 요청의 status는 무시됩니다. 이 실습에는 AppClaim 컨트롤러가 없으므로 접수 성공을 실제 앱 배포 완료로 해석하지 마세요.

한 번 정하면 못 바꾸는 필드 만들기

v1alpha1v1 각각의 spec 에 전이 규칙을 더해 tier 를 불변으로 만듭니다. 규칙은 oldSelf 를 참조해야 하고, 거절 메시지에는 tier 가 들어가야 합니다. tier 를 그대로 둔 채 replicas 만 바꾸는 변경은 계속 통과해야 합니다.

규칙이 oldSelf 를 참조하면 그 규칙은 변경할 때만 평가됩니다. 다만 spec 전체를 oldSelf 와 비교하면 레플리카도 못 바꾸게 되어 동결이 됩니다. 묶을 필드를 이름으로 적으십시오.

테넌트가 스스로 할 수 있는 일 정하기

/root/cnpe-api/tenant-rbac.yaml 에 ServiceAccount blue-dev, Role appclaim-author, RoleBinding appclaim-author 를 씁니다. 롤은 AppClaim 에 대해 get, list, watch, create, update, patch 를 허용합니다. 바인딩은 그 ServiceAccount 를 가리켜야 합니다.

롤은 권한의 정의일 뿐이고, 바인딩을 붙여야 권한이 생깁니다. 만들고 나서 반드시 kubectl auth can-i 로 판정을 확인하십시오. 롤을 읽어서는 최종 결과를 알 수 없습니다.

닫아 둔 쪽을 판정으로 확인하기

롤에 delete 가 없고, resourcequotas 와 roles 와 rolebindings 를 만들 수 없고, secrets 를 읽을 수 없는지 kubectl auth can-i 로 확인합니다. AppClaim status의 patch·update와 kube-system의 AppClaim list도 거절되어야 합니다. 정상 get은 yes인지 대조하고, 통신·인증 오류를 no로 해석하지 마세요. 롤의 verbs, resources, apiGroups 어디에도 * 가 없어야 합니다.

열어 준 것은 앞 단계에서 봤으니 여기서는 닫힌 쪽만 봅니다. 삭제·쿼터·롤·시크릿·status 변경·다른 네임스페이스 조회는 모두 명시적인 no여야 하고, 롤 어디에도 별표가 없어야 합니다. 별표 하나면 위의 판정이 전부 뒤집힙니다.

발급에 상한을 걸고 막히는지 보기

AppClaim search 를 하나 더 만들고, /root/cnpe-api/claim-quota.yaml 에 ResourceQuota blue-claims 를 씁니다. count/appclaims.platform.labhub.io 의 상한은 2 입니다. 쿼터의 status.used 가 2 로 채워지는지 확인하고, 비어 있으면 CRD Established·API discovery·쿼터 컨트롤러를 진단하고 재확인한 뒤, 세 번째 청구가 실제로 막히는지 확인합니다.

RBAC 은 할 수 있는가를 답하고 몇 개까지는 답하지 않습니다. 개수는 쿼터가 셉니다. status.used가 비어 있다는 사실만으로 원인을 단정하지 마세요. 등록 직후에는 반영을 기다리고, 계속 비어 있으면 Established·API discovery·컨트롤러 상태를 확인합니다. 무작정 쿼터를 삭제하면 제한을 제거하게 됩니다.

플랫폼 API 의 현재 상태를 값으로 남기기

/root/cnpe-api/api-report.txtstorage_version, claims, quota_hard, tenant_can_delete 네 줄을 키=값 형식으로 적습니다. 네 값 모두 클러스터에서 직접 조회한 것이어야 합니다.

네 값 모두 조회해서 적으십시오. 저장 버전은 CRD 에서, 청구 수와 상한은 네임스페이스에서, 삭제 가능 여부는 권한 판정에서 나옵니다. 채점기가 같은 값을 다시 계산해 대조합니다.