CNPA — Cloud Native Platform Engineering Associate
We turned on the policy, and the hotfix got blocked
한국어 원문으로 표시합니다.
목표
진짜 k3s 와 Kyverno 에서 플랫폼 규칙 하나(소유자 라벨과 리소스 요청)를 세 팀에 들입니다. 감사로 먼저 세고, 보고서로 위반을 찾고, 차단으로 바꿨을 때 무엇이 막히는지 겪어 본 뒤, 고칠 수 없는 대상에 기한부 예외를 주고 준수율을 계산합니다.
왜 중요한가
정책 엔진은 플랫폼의 가드레일을 코드로 강제하는 도구입니다. 그런데 규칙을 처음부터 차단으로 켜면 이미 규칙을 어긴 워크로드의 긴급 패치까지 막혀 장애 대응이 멈춥니다. 그래서 거버넌스는 보통 감사 → 보고서로 현황 파악 → 팀별 수정 → 차단 → 좁고 기한 있는 예외의 순서로 굴러갑니다. 이 실습은 그 순서의 각 단계에서 클러스터가 실제로 어떻게 반응하는지, 그리고 예외가 조용한 영구 허용으로 변하지 않게 하는 장치가 무엇인지를 확인합니다.
단계
/root/cnpa-pol/tenants.yaml에 세 네임스페이스와 Deployment 네 개를 작성해 적용하세요. 네임스페이스team-pay·team-legacy·team-vendor에는 각각 라벨platform.labhub.io/tenant를pay·legacy·vendor로 붙입니다. Deployment 는 모두 replicas 1, 컨테이너 이름app, 이미지registry.k8s.io/pause:3.10, 메타데이터와 셀렉터 라벨app.kubernetes.io/name: <이름>입니다.team-pay/checkout은 메타데이터 라벨app.kubernetes.io/owner: pay와 requests(cpu10m, memory16Mi)를 둘 다 가지고,team-legacy/report-gen은 requests 만,team-legacy/batch-sync는 owner 라벨(legacy)만,team-vendor/vendor-agent는 둘 다 없습니다./root/cnpa-pol/policy.yaml에 ValidatingPolicy(policies.kyverno.io/v1)tenant-deploy-baseline을 작성해 적용하세요.validationActions: [Audit],evaluation.background.enabled: true,matchConstraints는 apps/v1deployments의 CREATE·UPDATE 이고namespaceSelector로 라벨platform.labhub.io/tenant가 있는(Exists) 네임스페이스만 고릅니다. 검증 두 개를 이 순서로 둡니다. ① 메타데이터 라벨에app.kubernetes.io/owner가 있음, 메시지Deployment 에 app.kubernetes.io/owner 라벨이 필요합니다② 모든 컨테이너에 requests 의 cpu 와 memory 가 있음, 메시지모든 컨테이너에 cpu·memory requests 가 필요합니다.- Kyverno 가 남긴 PolicyReport 를 읽어,
tenant-deploy-baseline결과가fail인 Deployment 를/root/cnpa-pol/violations.json에 배열로 적으세요. 원소마다namespace,name,uid(보고서의 scope.uid),message(그 결과의 메시지)를 둡니다. - 정책의
validationActions를[Deny]로 바꾸세요. 그다음 legacy 팀의 긴급 패치를 흉내 내어kubectl -n team-legacy set image deploy/batch-sync app=registry.k8s.io/pause:3.9를 실행하고 그 출력(표준 에러 포함)을/root/cnpa-pol/blocked.txt에 저장하세요. 이어서kubectl -n team-legacy scale deploy/batch-sync --replicas=2를 실행하고,/root/cnpa-pol/deny-effects.json에image_update_denied,scale_allowed(불리언),batch_sync_image(지금 batch-sync 의 이미지)를 적으세요. - legacy 팀의 두 Deployment 를 규칙에 맞추세요.
report-gen에 메타데이터 라벨app.kubernetes.io/owner: legacy를,batch-sync의 컨테이너app에 requests(cpu50m, memory64Mi)를 넣습니다. 그다음 4단계에서 막혔던set image ... app=registry.k8s.io/pause:3.9를 다시 실행해 통과시키고, 두 Deployment 의 PolicyReport 결과가pass로 바뀔 때까지 기다리세요. - vendor-agent 는 업체가 주는 매니페스트라 당장 고칠 수 없습니다. 먼저
kyverno-admission-controllerDeployment 의 인자--enablePolicyException=false를--enablePolicyException=true로 바꾸고 롤아웃이 끝나기를 기다리세요. 그다음/root/cnpa-pol/exception.yaml에 PolicyException(policies.kyverno.io/v1)vendor-agent-requests를team-vendor네임스페이스에 만듭니다.policyRefs는 ValidatingPolicytenant-deploy-baseline,matchConditions는 이름이vendor-agent인 오브젝트만,expiresAt은 지금부터 30일 뒤(RFC3339, UTC)입니다. 적용 뒤 vendor-agent 에 어노테이션platform.labhub.io/reviewed=true를 붙이세요. /root/cnpa-pol/expired.yaml에 PolicyExceptionold-cron-requests를team-legacy에 만드세요. 대상은 이름이old-cron인 오브젝트, 정책은tenant-deploy-baseline,expiresAt은 지금보다 하루 전입니다. 그다음 owner 라벨도 requests 도 없는 Deploymentold-cron(이미지registry.k8s.io/pause:3.10)을team-legacy에 서버 dry-run 으로 만들어 보고, 그 출력(표준 에러 포함)을/root/cnpa-pol/expired.txt에 저장하세요.- 세 테넌트 네임스페이스의 PolicyReport 에서
tenant-deploy-baseline결과를 세어/root/cnpa-pol/compliance.json에 적으세요. 키는pass,fail,skip(개수),rate_pct(pass / (pass + fail) × 100, 소수 첫째 자리, 분모가 0 이면 100.0),exceptions(지금 만료되지 않은 PolicyException 을네임스페이스/이름문자열로, 정렬한 배열)입니다.
참고
- VM 안에 k3s 와 Kyverno 1.19 가 설치되어 있습니다. 워크로드 이미지는
registry.k8s.io/pause만 씁니다. - 정책 준비 확인:
kubectl get validatingpolicy tenant-deploy-baseline -o jsonpath='{.status.conditionStatus.ready}'. - 보고서:
kubectl get policyreport -A와-o json. 보고서 이름은 대상 리소스의 uid 입니다. - 흔한 실수: 예외 기능이 꺼진 채 PolicyException 을 만드는 것.
kubectl apply는 성공하고 경고 한 줄만 나오며, 어드미션은 계속 거절합니다. - 예외의 만료 필드 설명:
kubectl explain policyexception.spec.expiresAt --api-version=policies.kyverno.io/v1. - 흔한 실수:
matchConditions없이 예외를 만드는 것. 그 네임스페이스의 모든 Deployment 가 규칙을 벗어납니다. - Kyverno ValidatingPolicy · Kyverno Policy Exceptions · Kyverno Reporting · Kubernetes CEL · CNCF Platforms White Paper
정책 전의 세 팀
/root/cnpa-pol/tenants.yaml 에 세 네임스페이스와 Deployment 네 개를 작성해 적용하세요. 네임스페이스 team-pay·team-legacy·team-vendor 에는 각각 라벨 platform.labhub.io/tenant 를 pay·legacy·vendor 로 붙입니다. Deployment 는 모두 replicas 1, 컨테이너 이름 app, 이미지 registry.k8s.io/pause:3.10, 메타데이터와 셀렉터 라벨 app.kubernetes.io/name: <이름> 입니다. team-pay/checkout 은 메타데이터 라벨 app.kubernetes.io/owner: pay 와 requests(cpu 10m, memory 16Mi)를 둘 다 가지고, team-legacy/report-gen 은 requests 만, team-legacy/batch-sync 는 owner 라벨(legacy)만, team-vendor/vendor-agent 는 둘 다 없습니다.
정책 엔진을 들이기 전의 클러스터에는 이미 규칙을 어긴 워크로드가 섞여 있습니다. 이 단계에서는 아무것도 막지 않으므로 네 개가 모두 만들어져야 합니다. 여러 오브젝트를 --- 로 이어 한 파일에 둘 수 있습니다.
막지 않고 먼저 센다
/root/cnpa-pol/policy.yaml 에 ValidatingPolicy(policies.kyverno.io/v1) tenant-deploy-baseline 을 작성해 적용하세요. validationActions: [Audit], evaluation.background.enabled: true, matchConstraints 는 apps/v1 deployments 의 CREATE·UPDATE 이고 namespaceSelector 로 라벨 platform.labhub.io/tenant 가 있는(Exists) 네임스페이스만 고릅니다. 검증 두 개를 이 순서로 둡니다. ① 메타데이터 라벨에 app.kubernetes.io/owner 가 있음, 메시지 Deployment 에 app.kubernetes.io/owner 라벨이 필요합니다 ② 모든 컨테이너에 requests 의 cpu 와 memory 가 있음, 메시지 모든 컨테이너에 cpu·memory requests 가 필요합니다.
ValidatingPolicy 의 검증은 CEL 식이고 object 가 요청된 오브젝트입니다. 키가 있는지는 '키' in 맵 으로, 없을 수도 있는 필드는 has() 로 먼저 확인합니다. 리스트의 모든 원소에 대한 조건은 .all(c, ...) 입니다. Audit 은 거절하지 않고 결과만 남기는 동작입니다. 정책의 status.conditionStatus.ready 가 true 가 될 때까지 기다리세요. 채점기는 정책이 뒤 단계에서 Deny 로 바뀐 경우도 받아들입니다.
몰랐던 위반이 보고서에 드러났다
Kyverno 가 남긴 PolicyReport 를 읽어, tenant-deploy-baseline 결과가 fail 인 Deployment 를 /root/cnpa-pol/violations.json 에 배열로 적으세요. 원소마다 namespace, name, uid(보고서의 scope.uid), message(그 결과의 메시지)를 둡니다.
백그라운드 평가는 정책이 준비된 뒤 몇 초 안에 네임스페이스마다 PolicyReport 를 만듭니다. 보고서 하나가 리소스 하나에 대응하고 scope 에 대상이, results 에 정책별 결과가 있습니다. kubectl get policyreport -A 로 요약을 보고 -o json 으로 자세히 읽으세요. 둘 다 어긴 리소스의 메시지는 먼저 실패한 검증의 것입니다.
정책을 켜자 긴급 패치가 막혔다
정책의 validationActions 를 [Deny] 로 바꾸세요. 그다음 legacy 팀의 긴급 패치를 흉내 내어 kubectl -n team-legacy set image deploy/batch-sync app=registry.k8s.io/pause:3.9 를 실행하고 그 출력(표준 에러 포함)을 /root/cnpa-pol/blocked.txt 에 저장하세요. 이어서 kubectl -n team-legacy scale deploy/batch-sync --replicas=2 를 실행하고, /root/cnpa-pol/deny-effects.json 에 image_update_denied, scale_allowed(불리언), batch_sync_image(지금 batch-sync 의 이미지)를 적으세요.
Deny 는 새로 만드는 것뿐 아니라 이미 규칙을 어긴 오브젝트의 갱신 요청도 평가합니다. 갱신된 오브젝트 전체가 규칙을 지켜야 받아들여집니다. scale 은 Deployment 본체가 아니라 scale 하위 리소스로 가는 요청이라 규칙이 고른 리소스와 다릅니다. 이미 떠 있는 파드는 어드미션을 다시 거치지 않습니다.
규칙을 맞추자 같은 패치가 통과했다
legacy 팀의 두 Deployment 를 규칙에 맞추세요. report-gen 에 메타데이터 라벨 app.kubernetes.io/owner: legacy 를, batch-sync 의 컨테이너 app 에 requests(cpu 50m, memory 64Mi)를 넣습니다. 그다음 4단계에서 막혔던 set image ... app=registry.k8s.io/pause:3.9 를 다시 실행해 통과시키고, 두 Deployment 의 PolicyReport 결과가 pass 로 바뀔 때까지 기다리세요.
거절된 요청은 저장되지 않았으므로 먼저 규칙을 맞추는 변경이 들어가야 합니다. 그 변경 자체가 규칙을 지키는 오브젝트를 만들므로 Deny 아래에서도 받아들여집니다. requests 를 넣는 가장 짧은 방법은 kubectl set resources 입니다. 보고서는 리소스가 바뀌면 다시 평가됩니다. 이름이 리소스 uid 라서 같은 보고서가 갱신됩니다.
고칠 수 없는 외부 매니페스트에 기한부 예외
vendor-agent 는 업체가 주는 매니페스트라 당장 고칠 수 없습니다. 먼저 kyverno-admission-controller Deployment 의 인자 --enablePolicyException=false 를 --enablePolicyException=true 로 바꾸고 롤아웃이 끝나기를 기다리세요. 그다음 /root/cnpa-pol/exception.yaml 에 PolicyException(policies.kyverno.io/v1) vendor-agent-requests 를 team-vendor 네임스페이스에 만듭니다. policyRefs 는 ValidatingPolicy tenant-deploy-baseline, matchConditions 는 이름이 vendor-agent 인 오브젝트만, expiresAt 은 지금부터 30일 뒤(RFC3339, UTC)입니다. 적용 뒤 vendor-agent 에 어노테이션 platform.labhub.io/reviewed=true 를 붙이세요.
이 설치는 예외 기능이 기본으로 꺼져 있습니다. 꺼진 상태에서 예외를 만들면 경고만 나고 어드미션은 여전히 거절합니다. JSON 패치로 args 배열의 그 원소를 바꾸면 됩니다(jq 로 인덱스를 찾을 수 있습니다). 예외는 필요한 대상만 좁게 고르고 만료일을 두어야 원칙이 조용히 무너지지 않습니다. 날짜는 date -u -d '+30 days' +%Y-%m-%dT%H:%M:%SZ 로 만들 수 있습니다. 채점기는 같은 네임스페이스의 다른 이름이 여전히 거절되는지도 봅니다.
기한이 지난 예외는 아무것도 지켜 주지 않는다
/root/cnpa-pol/expired.yaml 에 PolicyException old-cron-requests 를 team-legacy 에 만드세요. 대상은 이름이 old-cron 인 오브젝트, 정책은 tenant-deploy-baseline, expiresAt 은 지금보다 하루 전입니다. 그다음 owner 라벨도 requests 도 없는 Deployment old-cron(이미지 registry.k8s.io/pause:3.10)을 team-legacy 에 서버 dry-run 으로 만들어 보고, 그 출력(표준 에러 포함)을 /root/cnpa-pol/expired.txt 에 저장하세요.
만료는 예외 오브젝트가 사라지는 것이 아니라 어드미션이 그 예외를 더 이상 쓰지 않는 것입니다. 오브젝트는 남아 있어 누가 언제까지 무엇을 허락했는지 기록으로 남습니다. dry-run 은 저장하지 않지만 어드미션 웹훅은 거칩니다. 이 단계에서 old-cron 이 실제로 만들어져서는 안 됩니다.
준수율을 보고서에서 계산한다
세 테넌트 네임스페이스의 PolicyReport 에서 tenant-deploy-baseline 결과를 세어 /root/cnpa-pol/compliance.json 에 적으세요. 키는 pass, fail, skip(개수), rate_pct(pass / (pass + fail) × 100, 소수 첫째 자리, 분모가 0 이면 100.0), exceptions(지금 만료되지 않은 PolicyException 을 네임스페이스/이름 문자열로, 정렬한 배열)입니다.
예외로 건너뛴 리소스는 fail 이 아니라 skip 으로 보고됩니다. 준수율에서 skip 을 어떻게 다룰지는 조직이 정하는 일이지만, 여기서는 분모에서 뺍니다. 만료 여부는 expiresAt 을 지금 시각과 비교해 가립니다. 보고서가 늦게 갱신될 수 있으니 vendor-agent 가 skip 으로 바뀔 때까지 기다린 뒤 세세요. 채점기는 같은 보고서를 다시 셉니다.