정책을 코드로 · 어드미션 컨트롤 개념 · 이론
요청이 etcd 에 닿기까지 — 어드미션이라는 자리
한 줄 요약
정책 엔진은 마법이 아니라 API 서버가 요청을 저장하기 직전에 남에게 물어보도록 만든 훅 하나이고, 그 훅이 어디에 끼어 있는지를 알아야 정책이 왜 안 먹는지도 왜 클러스터를 멈추는지도 이해할 수 있다.
왜 이게 필요했나
RBAC 은 "누가 무엇을 할 수 있는가"를 답한다. 개발자에게 파드 생성 권한을 주면 파드를 만들 수 있다. 그런데 조직이 정말 원하는 것은 그다음 질문이다. "만들어도 되지만, 이런 모양이어야 한다." 이미지는 사내 레지스트리에서만, 리소스 리밋은 반드시, latest 태그는 금지, 라벨은 표준을 따를 것. RBAC 의 동사(verb)와 리소스 종류로는 이 조건을 표현할 수 없다. 권한은 "만들기"라는 행위 단위이지 만들어지는 오브젝트의 내용에는 눈이 없기 때문이다.
그래서 사람들은 오랫동안 이 간극을 사람으로 메웠다. 위키에 표준을 적고, PR 리뷰에서 지적하고, 배포 후에 스캐너를 돌려 잘못된 것을 찾아냈다. 세 방법 모두 같은 약점을 갖는다. 강제력이 없거나, 이미 들어간 뒤에 발견한다. 리뷰를 통과하지 않는 경로(kubectl apply 직접 실행, CI 우회, 다른 팀의 헬름 차트)가 하나라도 있으면 규칙은 새기 시작한다.
어드미션 컨트롤은 이 문제를 구조로 푼다. 규칙을 사람이 아니라 API 서버가 반드시 지나가는 길목에 둔다. 그 길목을 통과하지 않고 클러스터에 들어오는 오브젝트는 없으므로, 경로가 몇 개든 규칙은 한 곳에만 있으면 된다.
어떻게 동작하나
kubectl apply 한 번이 API 서버 안에서 지나는 순서는 이렇다.
요청 → 인증(Authentication) → 인가(Authorization/RBAC) → 뮤테이팅 어드미션(Mutating Admission) → 스키마 검증(Object Schema Validation) → 밸리데이팅 어드미션(Validating Admission) → etcd 저장여기서 반드시 붙잡아야 할 사실이 네 가지 있다.
첫째, 인가가 먼저다. 권한이 없으면 정책은 아예 평가되지 않는다. 정책은 RBAC 을 대신하는 물건이 아니라 RBAC 을 통과한 요청을 한 번 더 거르는 물건이다. 둘의 순서를 뒤집어 생각하면 "정책으로 권한을 준다" 같은 잘못된 설계가 나온다.
둘째, 변형이 검증보다 먼저다. 이 순서는 우연이 아니라 설계다. 기본값을 채워 넣은 뒤에 검사해야, "리소스 리밋을 안 적었지만 정책이 채워 줬으니 통과" 같은 흐름이 성립한다. 동시에 함정도 여기서 나온다. 검증 규칙이 보는 오브젝트는 이미 변형 규칙이 손을 댄 뒤의 오브젝트다. 사이드카를 주입하는 변형 정책과 모든 컨테이너에 리밋을 요구하는 검증 정책을 같이 걸어 두고 주입 스펙에 리밋을 안 넣었다면, 배포가 막히면서 로그에는 사용자가 쓰지도 않은 컨테이너 이름이 찍힌다. 자기 정책에 자기가 걸리는 전형적인 형태다.
셋째, 스키마 검증이 그 사이에 있다. 변형 규칙이 스키마에 없는 필드를 만들어 내면 여기서 걸린다. 정책이 만든 결과도 결국 쿠버네티스 오브젝트여야 한다.
넷째, 웹훅은 네트워크 호출이다. API 서버가 외부 파드에 HTTP 요청을 보내고 답을 기다린다. 그래서 두 가지 안전장치가 웹훅 설정에 붙는다.
| 설정 | 의미 | 잘못 쓰면 |
| --- | --- | --- |
| timeoutSeconds | 응답을 기다리는 시간 (기본 10초, 최대 30초) | 길게 잡으면 장애 시 API 서버가 그만큼 붙잡힌다 |
| failurePolicy: Fail | 웹훅이 응답 못 하면 요청 거부 | 정책 엔진이 죽으면 클러스터의 모든 쓰기가 막힌다 |
| failurePolicy: Ignore | 웹훅이 응답 못 하면 요청 통과 | 정책 엔진이 죽은 동안 검사 없이 다 들어온다 |
| namespaceSelector | 특정 네임스페이스만 웹훅을 타게 함 | 안 걸면 kube-system 까지 웹훅에 의존한다 |
failurePolicy 는 이 코스에서 가장 중요한 한 줄이다. Fail 은 보안을 지키지만 정책 엔진의 가용성이 곧 클러스터의 가용성이 된다는 뜻이다. 어드미션 컨트롤러 파드가 전부 내려가면 파드도, 컨피그맵도, 심지어 그 컨트롤러를 복구하려는 배포조차 막힌다. 순환에 빠지는 것이다. 그래서 namespaceSelector 로 kube-system 과 정책 엔진 자신의 네임스페이스를 웹훅 대상에서 빼 두는 것은 취향이 아니라 탈출구를 남겨 두는 일이다. 실제 운영에서 웹훅 설정을 통째로 지워 클러스터를 되살리는 절차가 런북에 들어가 있는 이유도 같다.
와일드카드로 모든 리소스를 매칭하는 정책이 위험한 이유도 여기서 나온다. 그 정책의 비용은 정책 하나의 실행 시간이 아니라 API 서버로 들어오는 모든 요청에 붙는 지연세다. 매치 범위를 종류와 네임스페이스로 좁히는 작업은 성능 튜닝이 아니라 가용성 작업이다.
현장에서 만나는 모습
첫째, "정책이 하나도 안 막는다". 정책 YAML 을 아무리 노려봐도 답이 없다. 대개 원인은 웹훅이 등록되지 않았거나, 매치 블록이 실제 요청과 안 맞아 아무것도 매칭되지 않는 것이다. 아무것도 매칭되지 않는 정책은 조용히 전부 통과시키므로 잘 도는 정책과 사람 눈으로는 구분되지 않는다. 그래서 정책을 만들 때 통과해야 할 리소스와 막혀야 할 리소스를 둘 다 넣어 보는 습관이 필요하다.
둘째, 웹훅 장애가 전면 장애로 번지는 경우. 어드미션 컨트롤러 세 개가 같은 노드에 몰려 있다가 그 노드가 빠지면 failurePolicy: Fail 아래에서 클러스터 전체 쓰기가 멈춘다. 레플리카를 늘리고, PodDisruptionBudget 을 걸고, 노드 분산을 강제하는 것이 정책 도입의 필수 절차인 이유다.
셋째, 이 실습 환경의 정직한 한계. 이 환경의 kwok 클러스터에는 진짜 etcd·API 서버·컨트롤러 매니저·스케줄러가 돌지만 정책 엔진 컨트롤러는 떠 있지 않다. 그래서 잘못된 파드를 kubectl apply 해도 클러스터는 막지 않는다. 백그라운드 스캔도 돌지 않고 generate 규칙이 만든 리소스도 실제로 생기지 않는다. 대신 kyverno CLI 로 정책 엔진을 로컬에서 돌려 같은 판정 로직을 볼 수 있다. 이 문서의 웹훅 순서·failurePolicy·타임아웃은 실습이 아니라 읽기와 퀴즈로 익히고, 실습에서는 정책 자체를 쓰는 힘을 기른다.
다음 확인에서 볼 것
이어지는 퀴즈에서는 어드미션 순서, 웹훅 타임아웃과 failurePolicy, Audit·Enforce의
적용 시점을 먼저 구분한다. 그 개념을 확인한 뒤 다음 모듈에서 필수 라벨을 요구하는ClusterPolicy와 통과·거부 픽스처를 작성한다.