9.5 어드미션 제어
인증이 신원을 확인하고 인가가 권한을 판정한 뒤에도, 요청은 아직 etcd에 저장되지 않았다. 마지막 검사 단계가 어드미션 제어(admission control)다. RBAC이 “이 신원에게 Deployment 생성이 허용되는가"까지만 판단한다면, “그 Deployment의 내용이 정책에 맞는가"는 어드미션의 영역이다. 리소스 요청량이 빠진 Pod에 기본값을 채우고, 필수 라벨이 없는 리소스를 거부하고, 승인되지 않은 registry의 이미지를 차단하는 일이 전부 여기서 일어난다.
파이프라인에서의 위치
어드미션 제어는 두 단계로 나뉜다. 오브젝트를 수정하는 mutating 단계가 먼저 실행되고, 오브젝트 스키마 검증을 거친 뒤 수정 없이 판정만 하는 validating 단계가 마지막에 실행된다. mutating이 바꾼 결과물을 validating이 검사하는 순서다.
flowchart TD
R["API 요청"] --> AU["인증"]
AU --> AZ["인가"]
AZ --> M["mutating 어드미션"]
M --> S["스키마 검증"]
S --> V["validating 어드미션"]
V --> E[("etcd 저장")]
apiserver에는 어드미션 컨트롤러 수십 개가 내장돼 있고 상당수가 기본 활성이다. 이미 앞에서 여럿을 만났다. Pod에 토큰 볼륨과 imagePullSecrets를 주입하는 ServiceAccount 컨트롤러(9.3), 네임스페이스 라벨로 Pod 스펙을 검사하는 PodSecurity(9.4)가 내장 어드미션 컨트롤러다. 그 외의 대표 사례로 LimitRanger는 네임스페이스의 LimitRange 오브젝트에 따라 requests와 limits 기본값을 채워 넣고 범위를 벗어난 값을 거부하며, ResourceQuota는 네임스페이스의 리소스 총량이 할당량을 넘게 되는 생성 요청을 거부한다.
webhook으로 확장한다
내장 컨트롤러로 부족한 정책은 admission webhook으로 확장한다. MutatingWebhookConfiguration이나 ValidatingWebhookConfiguration을 등록하면, apiserver가 매칭되는 요청마다 AdmissionReview 오브젝트를 HTTPS로 외부 서비스에 전달하고 응답을 기다린다. mutating webhook은 오브젝트 수정 내용을 JSONPatch로 반환하고, validating webhook은 허용 여부와 거부 사유를 반환한다.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: policy-check
webhooks:
- name: policy.example.com
clientConfig:
service:
name: policy-webhook
namespace: policy-system
path: /validate
caBundle: <base64 CA>
rules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system", "policy-system"]
failurePolicy: Fail
timeoutSeconds: 5
admissionReviewVersions: ["v1"]
sideEffects: None주의 깊게 결정해야 하는 필드가 failurePolicy다. webhook 백엔드가 응답하지 못할 때(서비스 다운, 타임아웃, 인증서 만료) 요청을 어떻게 처리할지 정한다. Fail(기본값)은 해당 요청을 거부하고, Ignore는 webhook이 없는 것처럼 통과시킨다. 보안 정책이라면 Fail이 맞아 보이지만, 대상을 좁히지 않은 Fail은 클러스터 전체를 중단시키는 단일 장애점이 된다.
ValidatingAdmissionPolicy: webhook 없는 검증
실제 검증 로직의 상당수는 “이 필드가 이 조건을 만족하는가” 수준이다. 그 정도를 위해 webhook 서버를 만들고 인증서를 관리하고 가용성을 걱정하는 비용이 과하다는 문제의식에서 나온 것이 ValidatingAdmissionPolicy다. 1.30에서 GA 됐다. CEL(Common Expression Language) 표현식을 apiserver가 직접 평가하므로 외부 서버도, 네트워크 왕복도, failurePolicy 고민도 없다.
정책 정의와 적용 대상 연결이 분리된 구조로, RBAC의 Role과 RoleBinding 관계와 같은 모양이다.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: replica-limit
spec:
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 10"
message: "replicas는 10을 넘지 못한다"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: replica-limit-binding
spec:
policyName: replica-limit
validationActions: ["Deny"]
matchResources:
namespaceSelector:
matchLabels:
environment: devvalidationActions는 위반 시의 동작으로 Deny(거부), Warn(경고 후 허용), Audit(감사 로그 기록) 중에서 조합한다. 이름 그대로 검증 전용이라 오브젝트를 수정하는 정책은 만들지 못하며, mutating이 필요한 정책은 여전히 webhook의 영역이다.
정책 엔진: Kyverno와 OPA Gatekeeper
정책이 수십 개로 늘어나면 webhook을 직접 만드는 대신 정책 엔진을 배포하는 쪽이 일반적이다. 두 엔진 모두 admission webhook으로 동작하면서, 정책의 작성과 관리를 Kubernetes 리소스 차원으로 끌어올린다.
Kyverno는 정책 자체를 YAML로 적는다. 별도 언어 없이 Kubernetes 매니페스트와 같은 감각으로 validate와 mutate 정책을 쓰고, 다른 리소스를 자동 생성하는 generate까지 지원한다. OPA Gatekeeper는 범용 정책 엔진 OPA(Open Policy Agent)를 Kubernetes에 연결한 것으로, Rego라는 정책 언어로 ConstraintTemplate을 정의하고 Constraint로 인스턴스화한다. 표현력은 Rego 쪽이 넓지만 학습 비용이 있고, Kubernetes 정책만 필요한 팀에는 Kyverno의 진입 장벽이 낮다. 어느 쪽이든 webhook으로 동작하는 이상 failurePolicy와 대상 범위 설계는 위에서 본 그대로 적용된다.
정리
- 어드미션 제어는 인증과 인가 다음에 오브젝트 내용을 검사하는 마지막 단계로, mutating이 먼저 실행되고 validating이 그 결과를 검사한다.
- webhook의 failurePolicy: Fail은 대상 범위를 좁히지 않으면 클러스터 전체의 단일 장애점이 된다. kube-system과 webhook 자신의 네임스페이스는 제외한다.
- 단순 필드 검증은 ValidatingAdmissionPolicy(CEL, 1.30 GA)로 webhook 없이 해결하고, mutating과 복잡한 정책은 Kyverno나 OPA Gatekeeper 같은 정책 엔진으로 관리한다.