Admission Controller
K8s API 서버가 매니페스트를 etcd에 저장하기 직전 단계에서 검사 · 변형하는 admission의 모델을 다룹니다. Mutating · Validating 두 종류, 빌트인 컨트롤러 (LimitRanger · ResourceQuota · PodSecurity 등), Webhook 메커니즘, 그리고 그 위에 얹힌 두 정책 엔진 OPA Gatekeeper (Rego)와 Kyverno (YAML)의 비교까지 한 사이클로 정리합니다.
14장 RBAC / NetworkPolicy / ResourceQuota에서 RBAC이 K8s API의 권한을, NetworkPolicy가 Pod 사이 트래픽을, ResourceQuota가 자원 합계를 통제한다고 정리했습니다. 이번 챕터의 주제는 그 위에 한 층 더 얹히는 정책의 결입니다 — 매니페스트 자체의 모양을 강제하는 정책입니다. “limits 없는 컨테이너는 만들 수 없다”, “이미지는 우리 ECR 레지스트리에서만 받아야 한다”, “모든 워크로드에 owner 라벨이 필수다” 같은 규칙은 RBAC 만으로는 표현할 수 없습니다. 이런 규칙이 들어가는 곳이 K8s API 서버의 Admission 단계이고, 그 단계에 정책 엔진을 끼워 넣는 두 도구가 OPA Gatekeeper와 Kyverno입니다.
이번 챕터의 끝에서는 “cluster-admin 묶음 금지”, “limits 없는 컨테이너 거부”, “owner 라벨 강제” 같은 운영 정책을 매니페스트 차원에서 강제하는 길이 손에 들어옵니다. 14장 §“흔한 함정 — 너무 넓은 ClusterRole"에서 짚었던 안티패턴 차단의 본격적인 도구가 본 챕터에서 열립니다.
Admission 단계 — 매니페스트가 etcd로 들어가기 직전 #
kubectl apply -f my-pod.yaml을 친 순간부터 그 매니페스트가 etcd에 저장되기까지의 흐름은 단순한 한 줄이 아닙니다. K8s API 서버는 다음 다섯 단계를 차례로 통과시킵니다.
1. 인증 (Authentication) — 누가 호출했는가
2. 권한 (Authorization) — RBAC 검사. 호출자가 이 동사·자원을 쓸 수 있는가
3. Mutating Admission — 매니페스트를 변형 (defaulting, sidecar 주입 등)
4. Validating Admission — 매니페스트가 정책을 만족하는가
5. etcd 저장3, 4단계가 이번 챕터의 주제인 Admission Controller입니다. 인증과 RBAC을 통과한 요청이라도 이 단계에서 거부될 수 있고, 저장되기 전에 매니페스트 자체가 변형될 수 있습니다.
Mutating vs Validating #
두 종류의 차이는 명확합니다.
- Mutating Admission — 매니페스트를 변형 합니다. 예: 모든 Pod에 sidecar 컨테이너를 자동 주입, 누락된 라벨 자동 채우기, 기본값 적용. 같은 객체에 여러 mutating controller가 차례로 적용될 수 있습니다.
- Validating Admission — 매니페스트를 검사만 합니다. 통과 또는 거부. 변형은 일어나지 않습니다. 모든 mutating이 끝난 뒤의 최종 매니페스트를 본다는 점이 중요합니다.
순서는 항상 mutating → validating입니다. 변형이 모두 끝난 매니페스트가 검사 단계에 넘어가므로, validating 룰은 “최종 형태가 정책을 만족하는가"만 평가하면 됩니다.
빌트인 Admission Controller #
K8s API 서버 안에는 이미 여러 admission controller가 컴파일되어 있습니다. 운영 클러스터에서 자주 만나는 것들을 짚어 둡니다.
| 컨트롤러 | 종류 | 역할 |
|---|---|---|
NamespaceLifecycle | Validating | 삭제 중인 네임스페이스에 객체 생성 차단 |
LimitRanger | Mutating + Validating | LimitRange의 기본값 적용 + 위반 거부 |
ResourceQuota | Validating | ResourceQuota 합계 초과 시 거부 |
ServiceAccount | Mutating | Pod에 default ServiceAccount 자동 부착 |
PodSecurity | Validating | Pod Security Standards 강제 (1.25+ stable) |
DefaultStorageClass | Mutating | PVC에 기본 SC 자동 채우기 |
11장 resources.requests / limits의 LimitRange와 14장의 ResourceQuota가 실제로 동작하는 단계가 이 admission 단계입니다. 매니페스트가 ResourceQuota의 합계를 넘기면 ResourceQuota admission controller가 4단계에서 거부합니다. 빌트인 컨트롤러는 --enable-admission-plugins API 서버 플래그로 활성화 · 비활성화됩니다.
Webhook — 외부에서 admission 단계에 끼어들기 #
빌트인 컨트롤러는 K8s 코드에 내장되어 있어서 사용자가 정의를 바꿀 수 없습니다. 운영 팀이 자기만의 정책을 admission 단계에 끼워 넣고 싶으면 Webhook을 씁니다. 두 종류가 있습니다.
MutatingWebhookConfiguration— 외부 HTTP 서비스에 매니페스트를 보내고, 그 서비스가 변형된 매니페스트를 돌려줍니다.ValidatingWebhookConfiguration— 외부 HTTP 서비스에 매니페스트를 보내고, 그 서비스가 allow / deny를 돌려줍니다.
K8s API 서버는 이 webhook의 호출 결과를 보고 요청을 그대로 통과시키거나 변형하거나 거부합니다. OPA Gatekeeper와 Kyverno는 둘 다 이 webhook 메커니즘 위에 얹힌 정책 엔진입니다. K8s에 새로운 admission 종류를 추가하는 것이 아니라, 표준 webhook을 잘 쓰도록 추상화한 도구입니다.
OPA Gatekeeper — Rego로 표현하는 정책 #
OPA (Open Policy Agent)는 K8s 외부에도 쓰이는 범용 정책 엔진입니다. Rego라는 자체 언어로 정책을 적고, OPA 엔진이 그 정책을 평가합니다. Gatekeeper는 OPA를 K8s admission webhook으로 감싼 도구입니다.
Gatekeeper의 핵심 객체는 둘입니다.
ConstraintTemplate— Rego로 적은 정책의 청사진. “이런 종류의 정책을 정의한다”Constraint— ConstraintTemplate의 인스턴스. “이 정책을 어떤 자원에 어떤 매개변수로 적용한다”
이 둘의 분리가 Gatekeeper의 모델입니다. 정책의 “형태"는 ConstraintTemplate에 한 번 적어 두고, 그 형태에 매개변수를 넣어 여러 번 인스턴스화하는 방식입니다. 두 객체 모두 K8s의 CRD로 구현되어 있다는 점은 18장 CRD와 Operator 패턴의 모델 위에 정책 엔진이 얹힌 모양임을 짚어 둡니다.
ConstraintTemplate 예시 — 필수 라벨 강제 #
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
required := input.parameters.labels
provided := input.review.object.metadata.labels
missing := required[_]
not provided[missing]
msg := sprintf("Missing required label: %v", [missing])
}rego 블록 안의 코드가 진짜 정책입니다. input.review.object는 admission 단계의 매니페스트이고, input.parameters는 Constraint에서 넘긴 매개변수입니다. violation[...]이 비어 있지 않으면 매니페스트가 거부됩니다. ConstraintTemplate을 적용하면 K8s에 K8sRequiredLabels라는 새 CRD가 생깁니다.
Constraint 예시 — 위 템플릿의 인스턴스 #
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: namespace-must-have-owner
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["owner", "team"]이 Constraint를 적용하면 그 순간부터 새로 만드는 모든 Namespace에 owner와 team 라벨이 없으면 admission 단계에서 거부됩니다.
$ kubectl create ns test
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request:
[namespace-must-have-owner] Missing required label: owner
[namespace-must-have-owner] Missing required label: teamGatekeeper의 부가 기능 #
Gatekeeper에는 정책 평가 외에 몇 가지 운영 친화 기능이 있습니다.
- dry-run / audit 모드 — Constraint의
enforcementAction: dryrun으로 적용하면 거부하지 않고 violation만 기록합니다. 정책을 실서비스에 강제 적용하기 전에 영향 범위를 측정하는 데 씁니다. Config객체로 평가 대상 제한 —kube-system같은 시스템 네임스페이스를 평가에서 제외할 수 있습니다.- 외부 데이터 referrer — Constraint가 OPA의
data객체를 참조해 다른 K8s 객체나 외부 데이터를 보고 정책을 평가할 수 있습니다.
Kyverno — YAML로 표현하는 정책 #
Kyverno는 OPA Gatekeeper와 같은 카테고리의 도구이지만 접근이 다릅니다. 새 언어를 배우지 않고 YAML로 정책을 적는다는 게 Kyverno의 가장 큰 차별점입니다. K8s 사용자는 이미 YAML에 익숙하므로, 정책 도입의 진입 장벽이 낮습니다.
Kyverno의 세 가지 동작 #
Kyverno의 정책은 셋 중 하나 (또는 그 이상)를 합니다.
- validate — 매니페스트가 규칙을 만족하는지 검사 (Validating Admission)
- mutate — 매니페스트를 변형 (Mutating Admission)
- generate — 다른 객체를 자동으로 만들어 냄 (Kyverno 만의 기능)
generate는 admission 단계 자체의 동작은 아니지만, “Namespace가 만들어지면 그 안에 기본 NetworkPolicy를 자동 생성” 같은 패턴을 한 정책으로 표현합니다. 14장의 default-deny + allow-dns 한 묶음을 새 네임스페이스에 자동으로 깔아 두는 식의 운영 패턴이 흔한 예입니다.
Validate 예시 — limits 없는 컨테이너 거부 #
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
spec:
validationFailureAction: Enforce
rules:
- name: require-cpu-memory-limits
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Pod must have CPU and memory limits."
pattern:
spec:
containers:
- resources:
limits:
memory: "?*"
cpu: "?*"pattern 안의 ?*은 “어떤 값이든 좋지만 비어 있으면 안 된다"는 뜻입니다. 이 정책이 적용되면 모든 새 Pod의 모든 컨테이너에 limits.cpu와 limits.memory가 모두 적혀 있어야 합니다. 11장에서 다룬 자원 모델을 admission 차원에서 강제하는 패턴으로, LimitRange의 defaultRequest / default와 짝으로 두면 매니페스트의 자원 표기를 강제할 수 있습니다.
Mutate 예시 — 모든 Pod에 라벨 자동 추가 #
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-labels
spec:
rules:
- name: add-managed-by
match:
any:
- resources:
kinds: ["Deployment", "StatefulSet"]
mutate:
patchStrategicMerge:
metadata:
labels:
managed-by: platform-team이 정책이 적용되면 매니페스트에 managed-by 라벨이 없어도 admission 단계에서 자동으로 추가됩니다. 코드 한 줄 안 바꾸고 라벨 표준을 강제하는 길입니다.
Gatekeeper vs Kyverno — 둘 중 무엇을 쓸까 #
두 도구의 비교를 한 표로 정리합니다.
| 차원 | OPA Gatekeeper | Kyverno |
|---|---|---|
| 정책 언어 | Rego (새로 배워야 함) | YAML |
| 표현력 | 매우 높음 (튜링 완전한 Rego) | 보통 (선언적 패턴 매칭) |
| 학습 곡선 | 가파름 | 낮음 |
| 정책 동작 | validate, mutate (1.0+) | validate, mutate, generate, cleanup |
| K8s 외 정책 | OPA 자체는 K8s 외에도 사용 가능 | K8s 전용 |
| 정책 라이브러리 | 풍부 (gatekeeper-library) | 풍부 (kyverno/policies) |
선택의 결은 보통 다음 결을 따라갑니다.
- Rego의 학습 부담을 감수할 수 있고, 같은 정책 엔진을 K8s 외에도 쓰고 싶다면 Gatekeeper가 자연스럽습니다. 큰 조직에서 정책 일관성을 여러 시스템에 걸쳐 가져갈 때 유리합니다.
- K8s 운영 팀이 정책을 직접 쓰고 유지보수하길 원한다면, 정책 작성 자체의 진입 장벽이 가장 큰 비용이라면 Kyverno가 빠릅니다. 도입 첫 달의 학습 비용 차이가 큽니다.
두 도구 모두 운영 규모로 굴러간 실적이 충분합니다. 표현력이 압도적으로 필요한 경우가 아니라면 Kyverno를 먼저 고려하고, Rego의 표현력이 정말 필요해진 시점에 Gatekeeper로 넘어가는 흐름도 자연스럽습니다.
운영 시 잡아 둘 원칙 #
Admission webhook을 도입할 때 운영 측면에서 반드시 잡아 두어야 하는 원칙 몇 가지를 짚습니다.
1. failurePolicy의 두 선택 — Fail vs Ignore #
webhook을 호출할 수 없을 때 (타임아웃, 네트워크 단절, 정책 엔진 Pod 다운) API 서버가 어떻게 행동할지를 정하는 필드입니다.
failurePolicy: Fail— webhook이 응답 못 하면 요청 거부. 정책이 우회되는 일이 없지만, 정책 엔진의 가용성이 클러스터 전체의 가용성과 묶입니다. 정책 엔진이 죽으면 새 워크로드를 띄울 수 없습니다.failurePolicy: Ignore— webhook이 응답 못 하면 그냥 통과. 가용성은 좋지만 정책이 우회됩니다.
운영의 정공법은 중요 정책은 Fail, 부수적 정책은 Ignore로 갈라 두는 것입니다. 그리고 정책 엔진 자체를 다중화 (replicas 2 이상) 하고 PodDisruptionBudget으로 보호하는 게 기본입니다. 30장 업그레이드 전략의 PDB가 본 챕터의 webhook 가용성과 직접 묶입니다.
2. namespaceSelector로 시스템 네임스페이스 제외 #
kube-system, kube-public 같은 K8s 자체 워크로드가 도는 네임스페이스는 보통 정책 평가에서 제외합니다. 클러스터 부팅 자체가 정책에 막히는 사고를 방지하는 안전장치입니다.
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system", "kube-public", "kube-node-lease"]3. dry-run으로 점진 도입 #
운영 클러스터에 새 정책을 곧장 enforce 모드로 적용하면 기존 워크로드의 update가 줄줄이 깨질 수 있습니다. 표준 흐름은 다음과 같습니다.
1. dry-run 모드로 적용 (Gatekeeper의 dryrun, Kyverno의 Audit)
2. 일정 기간 violation 로그 수집 → 영향 범위 측정
3. 위반 워크로드를 우선 정리
4. enforce 모드로 전환이 사이클을 건너뛰면 기존 매니페스트가 줄줄이 거부되어 GitOps 동기화가 멈추는 사고로 이어집니다. 정책의 의도가 옳더라도 도입 절차는 점진적이어야 합니다. GitOps와의 결합 패턴은 20장 GitOps에서 한 번 더 다룹니다.
4. webhook latency 모니터링 #
Admission webhook은 모든 매니페스트 변경의 critical path에 있습니다. 정책 엔진이 느려지면 kubectl apply도 같이 느려집니다. Gatekeeper / Kyverno 모두 자체 메트릭을 노출하므로, P99 latency와 거부율을 19장 옵저버빌리티에서 다룰 옵저버빌리티 스택에 묶어 두는 게 표준입니다.
연습문제 #
- 본인의 클러스터에 적용된 admission controller를 확인합니다 (
kubectl get validatingwebhookconfigurations,kubectl get mutatingwebhookconfigurations). 그 중 Kyverno 또는 Gatekeeper가 있다면 어떤 ClusterPolicy / Constraint가 적용되어 있는지 한 표로 정리하고, 14장의 RBAC · NetworkPolicy · ResourceQuota와 어떤 결로 다른 정책인지 한 단락으로 비교합니다. - Kyverno 또는 Gatekeeper를 로컬 클러스터에 설치하고, “모든 Pod의 모든 컨테이너에
limits.cpu/limits.memory가 있어야 함” 정책을 dry-run 모드로 적용합니다. limits가 빠진 Deployment를 apply 했을 때 admission 단계에서 어떤 메시지가 violation으로 기록되는지 캡처하고, 같은 정책을 enforce 모드로 바꿨을 때 같은 매니페스트가 어떻게 거부되는지를 단계별로 기록합니다. 11장의 LimitRange와 본 챕터 정책의 책임이 어떻게 다른지 한 단락으로 정리합니다. failurePolicy: Fail로 설정된 webhook이 응답 못 할 때의 시나리오를 시뮬레이션으로 적어 봅니다 — 정책 엔진 Pod가 모두 죽었을 때 새 Deployment를 apply 하면 어떤 에러가 나는지, 그리고failurePolicy: Ignore였다면 어떤 차이가 나는지. 정책 엔진의 가용성이 클러스터의 가용성과 어떻게 묶이는지를 §“failurePolicy의 두 선택"의 모델로 정리합니다.
한 줄 요약: K8s API 서버는 매니페스트를 etcd에 저장하기 전 admission 단계에서 mutating (변형) → validating (검사) 순서로 통과시킨다. 빌트인 컨트롤러 (LimitRanger · ResourceQuota · PodSecurity 등) 외에 webhook으로 외부 정책 엔진을 끼워 넣을 수 있고, 두 표준이 OPA Gatekeeper (Rego, 표현력 강)와 Kyverno (YAML, 진입 장벽 낮음) 다. 운영 도입은 failurePolicy · 시스템 네임스페이스 제외 · dry-run 점진 도입 · webhook latency 모니터링의 네 원칙으로 받친다.
다음 챕터 #
본 챕터의 Gatekeeper와 Kyverno가 모두 K8s의 CRD (CustomResourceDefinition)로 정의된 새 객체 종류 위에 얹혀 있다는 점을 한 번 짚어 두었습니다. ConstraintTemplate · K8sRequiredLabels · ClusterPolicy 같은 객체는 K8s 본체의 표준 객체가 아니라, 두 도구가 CRD로 정의한 사용자 정의 자원입니다. 다음 챕터의 주제는 그 CRD 자체입니다 — K8s API를 새 객체 종류로 확장하는 길.
18장 CRD와 Operator 패턴에서는 CustomResourceDefinition으로 새 객체 종류를 정의하는 매니페스트, 그 객체를 운영하는 controller-runtime 기반의 Operator 패턴, 그리고 운영에서 만나는 대표적인 Operator (CloudNativePG · cert-manager · Argo CD 등)의 모델까지 한 사이클로 다룹니다. 본 챕터까지가 K8s의 표준 객체를 다루는 깊이였다면, 다음 챕터는 K8s 자체를 자기 도메인에 맞게 확장하는 길의 출발점입니다.