쿠버네티스에 정책 엔진 하나는 깔아야 한다는 얘기가 나온 게 벌써 몇 년째인지 모르겠다. 우리 팀도 처음엔 "PSP 사라지면 그때 가서 보자"고 미뤘는데, 결국 PSP 제거되고, NetworkPolicy 강제도 필요해지고, 이미지 서명 검증 요건까지 붙으면서 더 이상 미룰 수가 없었다.
선택지는 사실상 둘이다. Kyverno 아니면 OPA Gatekeeper. 둘 다 CNCF 프로젝트고, 둘 다 어드미션 컨트롤러로 동작한다. 그래서 처음엔 "어차피 비슷하겠지" 싶었는데, 직접 양쪽을 작은 클러스터에 나눠 깔고 두어 달 굴려보니 꽤 결이 다른 도구라는 걸 알게 됐다. 이 글은 그 비교 노트다.
정책을 어떻게 쓰는가 — YAML vs Rego
이 차이가 가장 크다.
Gatekeeper는 OPA를 베이스로 깔고 있어서 정책을 Rego라는 별도 DSL로 짜야 한다. 예를 들어 모든 파드에 team 레이블을 강제하는 정책은 ConstraintTemplate에 Rego를 넣고, Constraint로 스코프를 거는 2단 구조다.
# ConstraintTemplate
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
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는 강력하다. 표현력도 좋고, 클러스터 외부 데이터까지 끌어와서 의사결정에 쓸 수 있다. 근데 실무자 입장에서 보면 진입장벽이 만만치 않다. violation[{...}]이 왜 결과를 뱉는지, [_]가 뭘 의미하는지 한 번씩 다 검색해본 경험이 있을 거다.
Kyverno는 같은 정책을 그냥 YAML로 쓴다.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-team-label
spec:
validationFailureAction: Enforce
rules:
- name: check-team-label
match:
any:
- resources:
kinds: [Pod]
validate:
message: "team 레이블은 필수입니다"
pattern:
metadata:
labels:
team: "?*"
YAML 자체에 익숙한 사람이라면 5분 안에 읽고 쓸 수 있다. 우리 팀에서 정책 PR 리뷰가 누구나 가능해진 건 Kyverno로 갈아탄 직후부터다.
할 수 있는 일의 범위
Gatekeeper는 본질적으로 "검증(validation) 엔진"이다. 1.14 버전에서 mutation이 GA로 들어왔지만, 여전히 무게 중심은 거절/허용 판단에 있다.
Kyverno는 여기서 한 발 더 나간다.
- Generate: 새 네임스페이스가 만들어지면 자동으로 기본 NetworkPolicy, ResourceQuota, default ConfigMap을 생성해준다. 이게 생각보다 어마어마한 차이다. 멀티테넌트 클러스터에서 "네임스페이스 생기면 deny-all NetworkPolicy 깔아라"를 사람이 안 잊고 하긴 거의 불가능한데, Kyverno는 그냥 정책으로 박아둘 수 있다.
- Mutate: 파드에 imagePullSecret을 자동으로 주입하거나, sidecar 컨테이너 라벨을 정규화하는 식.
- VerifyImages: cosign 서명 검증을 정책 한 줄로 넣는다. Gatekeeper로 같은 걸 하려면 외부 검증기와 ExternalData 프로바이더를 따로 붙여야 한다.
특히 이미지 서명 쪽은 작년 4월 Kyverno 1.14에서 ImageValidatingPolicy라는 별도 CRD로 분리되면서 더 깔끔해졌다. JSON 페이로드 어디서든 이미지 참조를 추출해서 검증할 수 있게 됐는데, 이건 Argo Workflows나 Tekton 같은 데서 동적으로 이미지를 결정하는 워크플로에 꽤 유용하다.
운영하면서 느낀 차이
성능 얘기를 해야 한다.
Gatekeeper는 OPA 엔진이 정책을 컴파일해서 메모리에 띄워두기 때문에 평가 속도 자체는 빠르다. 다만 클러스터의 모든 리소스를 OPA 캐시에 동기화해두고 cross-resource 검증을 하기 때문에, 클러스터가 커지면 메모리가 꽤 든다. 노드 80대, 파드 4천 개 정도 되는 클러스터에서 Gatekeeper audit이 1.5GB 메모리를 먹는 걸 본 적이 있다.
Kyverno는 정책별로 독립적으로 평가하는 구조라 메모리 사용량이 비교적 평탄하다. 대신 정책 수가 많아지면 어드미션 지연이 누적될 수 있어서, 우리는 처음에 30개 넘는 정책을 한 번에 켰다가 파드 생성 P99가 800ms까지 튀는 걸 봤다. 정책별 selector를 좁히고, background: false로 한 번에 돌릴 정책을 줄이고 나니 정상 범위로 돌아왔다.
디버깅 측면에서는 Kyverno가 좀 더 친절하다. PolicyReport CRD로 위반 내역이 클러스터 안에 그대로 쌓이고, kubectl describe로 정책 거절 이유를 바로 볼 수 있다. Gatekeeper도 audit 결과를 CR로 남기긴 하는데, Rego 평가 실패 메시지가 가끔 친절하지 않다. "object differs at path /spec/..." 같은 메시지를 보고 사람들이 슬랙에 물어보러 오는 빈도가 우리 팀에선 Kyverno가 확연히 적었다.
그래서 우리는
결론부터 쓰자면 우리 팀은 Kyverno를 골랐다. 이유는 세 가지다. 첫째, 정책 작성을 SRE만이 아니라 앱 팀까지 같이 할 수 있어야 했다. 둘째, NetworkPolicy/Quota 자동 생성이 멀티테넌트 운영에 매우 편리했다. 셋째, 이미지 서명 검증이 1.14에서 더 깔끔해진 게 결정타였다.
다만 Gatekeeper가 더 나은 경우도 분명 있다. 쿠버네티스 외부까지 통일된 정책 언어로 묶고 싶은 경우 — 예를 들어 Terraform plan, CI 파이프라인, API 게이트웨이까지 같은 Rego로 검증하고 싶다면 OPA가 압도적으로 유리하다. 실제로 어느 큰 핀테크 사례를 보면 클러스터 어드미션은 Kyverno로, 인프라/CI 정책은 OPA로 양쪽을 쓰는 곳도 있다. 한 도구로 다 끝내려고 하지 말고 책임 영역을 나누는 것도 답이다.
아직 Kyverno 1.14의 ValidatingPolicy / ImageValidatingPolicy CRD는 우리도 마이그레이션 검토 중이라 자세히 못 다뤘다. 이건 충분히 굴려본 뒤에 따로 글을 쓰려고 한다. 혹시 이미 운영 중인 분 있으면 어드미션 지연 어떤지 댓글 좀 달아주세요.