LabHub
배우기 러닝패스 코스

정책을 코드로 · 쿠버네티스가 스스로 검사한다 · 실습

정책을 적용했는데 아무것도 막히지 않았다

LabHub 에서 이어서 보기

목표

쿠버네티스에 기본으로 들어 있는 ValidatingAdmissionPolicy(CEL)로 파드를 검사하는 규칙을 만들고, 경고에서 차단으로 올리고, 메시지에 위반한 값을 담고, 예외와 범위와 파라미터까지 직접 붙여 봅니다.

왜 중요한가

외부 정책 엔진을 설치하지 않고도 API 서버 안에서 검증을 할 수 있게 된 것이 ValidatingAdmissionPolicy 입니다. 웹훅과 달리 네트워크 왕복도 인증서도 없고, 엔진이 죽어서 클러스터가 멈추는 일도 없습니다. 대신 배워야 할 것이 하나 늘어납니다 — 판정 규칙과 적용 범위가 서로 다른 오브젝트라는 점입니다. 이 분리 덕분에 같은 규칙을 어떤 팀에는 경고로, 어떤 팀에는 차단으로 붙일 수 있고, 규칙 값만 ConfigMap 으로 빼서 정책을 고치지 않고 바꿀 수 있습니다. 반대로 이 분리를 모르면 '정책은 분명히 적용했는데 아무것도 안 막힌다' 는 자리에서 오래 헤맵니다. 실무에서 정책을 처음 넣을 때는 항상 Warn 으로 시작해 무엇이 걸리는지 보고, 시스템 컴포넌트를 matchConditions 로 빼 둔 다음, 범위를 좁게 잡아 Deny 로 올립니다. 이 순서를 건너뛴 정책은 배포를 멈추고 되돌려지고, 그러면 아무도 다시 켜지 않습니다.

단계

1. /root/vap 에서 작업합니다(export KUBECONFIG=/root/.kube/config, kubectl config use-context kwok-lab). /root/vap/policy.yamladmissionregistration.k8s.io/v1 의 ValidatingAdmissionPolicy require-cpu-limit 을 쓰세요. matchConstraints.resourceRules 는 코어 그룹("")의 v1 pods 에 대한 CREATE·UPDATE 를 잡고, validation 은 모든 컨테이너가 resources.limits.cpu 를 가질 것을 요구합니다. 시험용 파드 둘도 만드세요 — /root/vap/pod-nocpu.yaml 은 컨테이너 web-tier 가 memory 제한만 갖고, /root/vap/pod-ok.yaml 은 컨테이너 web-full 가 cpu·memory 제한을 모두 갖습니다. 정책을 적용한 뒤 라벨이 하나도 없는 네임스페이스 team-open 을 만들고, 거기에 pod-nocpu.yamlkubectl create --dry-run=server 로 보내 그 출력을 /root/vap/01-nobinding.txt 에 저장하세요.
2. 네임스페이스 team-warn 을 만들고 라벨 cpu-limit=warn 을 붙이세요. /root/vap/binding-warn.yaml 에 ValidatingAdmissionPolicyBinding cpu-limit-warn 을 쓰세요 — policyNamerequire-cpu-limit, validationActions["Warn", "Audit"], matchResources.namespaceSelector.matchLabelscpu-limit: warn 입니다. 적용한 뒤 team-warnpod-nocpu.yaml--dry-run=server 로 보내 출력을 /root/vap/02-warn.txt 에 저장하세요(표준 오류까지 함께). 경고가 뜨는데도 파드는 만들어져야 합니다.
3. 네임스페이스 team-deny 를 만들고 라벨 cpu-limit=deny 를 붙이세요. /root/vap/binding-deny.yaml 에 두 번째 바인딩 cpu-limit-deny 를 쓰세요 — 같은 policyNamevalidationActions["Deny"], 셀렉터는 cpu-limit: deny 입니다. 적용한 뒤 team-denypod-nocpu.yaml 을 보내 출력을 /root/vap/03-deny.txt 에 저장하고, 이어서 pod-ok.yaml 도 보내 통과하는지 확인하세요. 경고용 바인딩(cpu-limit-warn)은 지우지 말고 그대로 둡니다 — 두 범위가 동시에 살아 있는 것이 실제 전개 모습입니다.
4. /root/vap/policy.yaml 을 고쳐 spec.variablesnoCpu 를 두세요 — cpu 제한이 없는 컨테이너의 이름 목록을 계산합니다. validation 은 size(variables.noCpu) == 0 으로 바꾸고, message 대신 messageExpression 으로 그 이름들과 파드 이름을 담은 메시지를 만드세요. 그리고 /root/vap/pod-two.yaml 에 컨테이너 둘을 가진 파드 probe-two 를 만드세요 — web-nolimitresources 자체가 없고, cache-ok 는 cpu·memory 제한을 모두 가집니다. 정책을 다시 적용한 뒤 team-deny 에 보내 거부 메시지를 /root/vap/04-message.txt 에 저장하세요. 메시지에는 web-nolimit 만 나와야 하고 cache-ok 는 나오면 안 됩니다.
5. /root/vap/policy.yamlspec.matchConditions 를 더하세요. 조건 두 개입니다 — skip-kube-system-sa 는 요청자가 system:serviceaccount:kube-system: 으로 시작하는 서비스 계정이면 정책을 건너뛰게 하고, skip-exempt-pods 는 파드에 라벨 cpu-limit-exempt: "true" 가 있으면 건너뛰게 합니다. /root/vap/pod-exempt.yaml 에 그 라벨이 붙은 파드 probe-exempt(컨테이너 web-tier, memory 제한만)를 만드세요. 정책을 다시 적용한 뒤 두 가지를 team-deny 에서 확인해 출력을 /root/vap/05-skipped.txt 에 저장하세요 — (1) kubectl --as=system:serviceaccount:kube-system:replicaset-controller create -n team-deny -f pod-nocpu.yaml --dry-run=server, (2) pod-exempt.yaml 을 평소대로 보내기. 둘 다 만들어져야 합니다. 라벨 없는 pod-nocpu.yaml 은 여전히 거부돼야 합니다.
6. 네임스페이스 vap-paramsteam-images(라벨 registry-check=on)를 만드세요. vap-params 에 ConfigMap allowed-registries 를 만들고 키 allowed 의 값을 정확히 registry.internal/,ghcr.io/labhub/ 로 두세요. /root/vap/policy-registries.yaml 에 두 번째 정책 allowed-registries 를 쓰세요 — spec.paramKindapiVersion: v1, kind: ConfigMap 이고, 같은 matchConstraints 로 파드를 잡고, params.data['allowed'] 를 쉼표로 나눈 목록 중 어느 것으로도 시작하지 않는 이미지를 거부합니다. /root/vap/binding-registries.yaml 에 바인딩 allowed-registries 를 쓰세요 — validationActions["Deny"], paramRef 는 그 ConfigMap(name·namespace, parameterNotFoundAction: Deny), 셀렉터는 registry-check: "on" 입니다. /root/vap/pod-img-bad.yaml 에 이미지가 docker.io/nginx:1.27 인 파드 probe-img-bad(컨테이너 web-img, cpu·memory 제한 포함)를 만들고, team-imagespod-ok.yamlpod-img-bad.yaml 을 차례로 보내 두 출력을 /root/vap/06-param.txt 에 저장하세요.
7. /root/vap/scope.sh <네임스페이스> 를 만드세요. 그 네임스페이스에 /root/vap/pod-nocpu.yaml--dry-run=server 로 보내 보고, 거부되면 DENY 한 줄과 종료 코드 3, 경고만 뜨고 만들어지면 WARN 과 2, 아무 말 없이 만들어지면 OPEN 과 0 으로 끝냅니다. 출력은 그 낱말 한 줄뿐입니다. 네임스페이스 이름이나 라벨을 읽어 판단하지 말고 실제 요청의 결과로 판정하세요 — 채점기는 team-open 의 라벨을 잠시 바꿔 가며 이 스크립트를 부르고 원래대로 되돌립니다. 만든 뒤 team-deny·team-warn·team-open 세 곳에 차례로 돌려 결과를 /root/vap/07-scope.txt<네임스페이스> <낱말> 꼴로 한 줄씩 저장하세요.
8. /root/vap/policy.yaml 에 두 번째 규칙을 더하세요 — 변수 noMem(memory 제한이 없는 컨테이너 이름 목록)과, size(variables.noMem) == 0 을 검사하는 두 번째 validation 입니다. 이 validation 의 messageExpression 에는 낱말 memory 와 그 컨테이너 이름이 들어가야 하고, 첫 번째 validation 의 메시지에는 낱말 cpu 와 그 컨테이너 이름이 들어가야 합니다. /root/vap/pod-mixed.yaml 에 파드 probe-mixed 를 만드세요 — 컨테이너 web-tier 는 memory 제한만, cache-tier 은 cpu 제한만 가집니다. 정책을 다시 적용한 뒤 이 파드를 team-warnteam-deny 에 차례로 보내 두 출력을 /root/vap/08-two-rules.txt 에 저장하세요. Warn 쪽은 두 규칙을 모두 알려 주고 Deny 쪽은 하나만 알려 줍니다.

참고

단계 8개

  1. 정책만 적용하면 아무 일도 일어나지 않는다
  2. 바인딩을 붙이자 경고가 뜨기 시작한다
  3. 같은 정책을 Deny 로 올리자 요청이 막힌다
  4. 거부 메시지가 어느 컨테이너인지 말해 준다
  5. 정책이 아예 보지 않는 요청을 만든다
  6. 규칙 값을 ConfigMap 으로 빼서 정책을 고치지 않고 바꾼다
  7. 같은 요청이 네임스페이스마다 다른 답을 받는다
  8. 규칙 둘을 한 정책에 넣자 Deny 와 Warn 이 서로 다른 것을 알려 준다