정책을 코드로 · 파드 보안 표준을 라벨 하나로 켠다 · 이론
Pod Security Standards 와 Admission — 등급과 모드는 다른 축이다
한 줄 요약
Pod Security Standards 는 무엇을 금지하는가(privileged · baseline · restricted)를 정하고 Pod Security Admission 은 위반을 어떻게 다루는가(enforce · audit · warn)를 정하며, 이 둘은 서로 다른 축이라 네임스페이스 라벨에서 자유롭게 조합된다.
왜 이게 필요했나
파드 스펙에는 호스트를 통째로 여는 스위치가 여럿 있다. hostNetwork, hostPID, privileged: true, hostPath 볼륨, 임의의 capability 추가. 하나만 켜도 컨테이너 격리가 사실상 사라진다. 그런데 이 필드들은 정상적인 인프라 워크로드(CNI 에이전트, 로그 수집기, 노드 익스포터)가 실제로 쓰는 것이기도 하다. "금지"가 아니라 "어디까지 허용할 것인가"를 정해야 하는 문제라서 단순한 금지 목록으로는 풀리지 않았다.
PodSecurityPolicy 가 그 자리를 맡았다가 v1.25 에서 제거됐다. PSP 는 사용자가 아니라 파드를 만드는 주체(대개 컨트롤러의 서비스 어카운트)의 권한을 봤고, 파드에 여러 PSP 가 걸릴 때 어느 것이 적용될지 예측하기 어려웠다. 대신 들어온 것이 Pod Security Admission 이다. 규칙은 프로젝트가 정해서 세 등급으로 고정했고, 적용 단위는 네임스페이스 라벨 하나로 단순화했다.
어떻게 동작하나
축 하나 — 등급(Pod Security Standards).
| 등급 | 성격 |
| --- | --- |
| privileged | 제약 없음. 시스템·인프라 워크로드용 |
| baseline | 알려진 권한 상승을 막는 최소 제약. 기본 파드 스펙은 그대로 통과한다 |
| restricted | 파드 하드닝 모범 사례를 강하게 요구한다 |
baseline 이 막는 것은 호스트 네임스페이스 공유, privileged, 허용 목록 밖 capability, hostPath 볼륨, 호스트 포트, AppArmor·SELinux·seccomp 우회 같은 것들이다. restricted 는 여기에 볼륨 종류 제한, allowPrivilegeEscalation: false, runAsNonRoot: true, runAsUser 를 0 으로 두지 않기, seccomp 프로파일을 명시적으로 지정, 모든 capability 를 drop 하고 NET_BIND_SERVICE 만 허용을 더한다. 여기서 중요한 차이가 있다. baseline 은 "이상한 것을 안 쓰면" 통과하지만, restricted 는 평범한 매니페스트도 통과하지 못한다. 아무것도 안 적은 컨테이너는 seccomp 프로파일이 없고 capability 를 drop 하지 않았기 때문이다.
축 둘 — 모드(Pod Security Admission).
| 모드 | 위반일 때 |
| --- | --- |
| enforce | 파드를 거부한다 |
| audit | 감사 로그의 이벤트에 어노테이션을 더하고 통과시킨다 |
| warn | 사용자에게 경고를 보여 주고 통과시킨다 |
두 축은 라벨로 만난다. 모드마다 라벨이 둘씩이다.
apiVersion: v1kind: Namespacemetadata: name: my-baseline-namespace labels: pod-security.kubernetes.io/enforce: baseline pod-security.kubernetes.io/enforce-version: v1.30 pod-security.kubernetes.io/audit: restricted pod-security.kubernetes.io/warn: restricted이 조합이 도입 절차 그 자체다. 지금은 baseline 만 강제하고, restricted 위반은 경고와 감사로 세어 본다. 한 네임스페이스가 모드 셋을 모두 설정할 수 있고 모드마다 다른 등급을 줄 수 있으므로, "다음 목표를 미리 켜 두고 관찰하는" 일이 라벨 두 줄로 된다.
-version 접미를 빠뜨리면 업그레이드 날에 배포가 멈춘다. 버전 라벨의 값은 유효한 쿠버네티스 마이너 버전이거나 latest 이고, 적지 않으면 latest 로 동작한다. 등급의 내용은 판마다 바뀐다 — 실제로 v1.25 에서 restricted 가 달라졌다. latest 로 두면 클러스터를 올린 날 아무 매니페스트도 안 바꿨는데 어제 되던 배포가 막힌다. 반대로 버전을 고정해 두면 등급이 강해져도 그날은 아무 일도 일어나지 않고, 버전을 올리는 일정은 사람이 정한다. 정책 변경과 클러스터 업그레이드를 분리하는 장치다.
어드미션은 요청을 보는 기구다. 이미 떠 있는 파드는 막지 못한다. 라벨을 붙인 뒤에도 위반 파드는 계속 돌고, 그것이 재시작되거나 다시 스케줄될 때에야 거부된다. 그래서 전환 전에 위반을 미리 뽑아 보는 절차가 필요하다. 라벨 명령을 서버 dry-run 으로 돌리면 그 네임스페이스에 이미 있는 파드의 위반이 경고로 나온다.
kubectl label --dry-run=server --overwrite ns payments \ pod-security.kubernetes.io/enforce=restricted문서에 적힌, 놓치기 쉬운 사실이 하나 더 있다. enforce 는 워크로드 리소스에 적용되지 않고 그 결과로 만들어지는 파드에만 적용된다. audit 과 warn 은 Deployment 같은 워크로드에도 적용된다. 그래서 위반 템플릿을 가진 Deployment 는 kubectl apply 가 성공하고(경고는 뜬다), 파드가 안 생기는 것은 몇 초 뒤 ReplicaSet 이벤트에서야 드러난다. "배포는 됐는데 파드가 없다"는 문의의 흔한 원인이다.
면제(exemption)는 라벨이 아니라 어드미션 컨트롤러 설정 파일에 적고, 축은 셋이다 — 사용자 이름, RuntimeClass 이름, 네임스페이스. 문서는 컨트롤러 서비스 어카운트를 면제하지 말라고 경고한다. replicaset-controller 를 면제하면 워크로드를 만들 수 있는 모든 사람이 암묵적으로 면제된다.
현장에서 만나는 모습
첫째, restricted 를 곧바로 enforce 로 켠 팀. 평범한 매니페스트가 전부 막힌다. securityContext 를 한 번도 안 적어 본 팀에게는 "모든 Deployment 를 고치라"는 뜻이라, 대개 하루 만에 라벨이 내려간다. 순서는 warn·audit 으로 restricted 를 켜 두고 enforce 는 baseline 에 두는 것이다.
둘째, 시스템 네임스페이스에 restricted 를 건 경우. CNI 에이전트나 노드 익스포터가 재시작되는 순간 뜨지 못한다. 클러스터가 스스로 복구하지 못하는 상태가 되는데, 원인이 라벨이라는 것은 파드 이벤트를 열어 봐야 안다.
셋째, 클러스터 업그레이드 날의 장애. -version 없이 켜 둔 네임스페이스에서만 배포가 막힌다. 매니페스트도 정책도 안 바뀌었으므로 원인을 찾는 데 오래 걸린다. 버전 고정은 게으름이 아니라 변경 관리다.
넷째, 지표로 보는 법. kube-apiserver 가 pod_security_evaluations_total, pod_security_errors_total, pod_security_exemptions_total 을 내보낸다. audit 모드로 켜 둔 기간에 이 값들을 대시보드에 올려 두면 "enforce 로 올려도 되는가"에 숫자로 답할 수 있다.
참고 문서
- Pod Security Admission: https://kubernetes.io/docs/concepts/security/pod-security-admission/
- Pod Security Standards: https://kubernetes.io/docs/concepts/security/pod-security-standards/
- 네임스페이스 라벨로 표준 적용하기: https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-namespace-labels/
- PodSecurityPolicy 에서 이전하기: https://kubernetes.io/docs/tasks/configure-pod-container/migrate-from-psp/
다음 실습에서 할 것
kwok 클러스터의 네임스페이스에 라벨을 직접 붙여 가며 전환 절차를 한 바퀴 돈다. 위반 파드를 먼저 띄워 두고, --dry-run=server 로 라벨을 시험해 기존 위반이 경고로 나오는 것을 확인하고, warn 과 audit 만 켠 상태에서 같은 파드가 통과하는 것을 본 뒤 enforce 로 올려 거부되는 것을 확인한다. -version 을 고정한 네임스페이스와 latest 인 네임스페이스를 나란히 두고, 위반 템플릿을 가진 Deployment 가 apply 에는 성공하면서 파드는 안 생기는 장면도 직접 만든다.