KCA — Kyverno 인증 어소시에이트 · mutate 와 generate · 이론
generate 는 왜 조용히 실패하는가
한 줄 요약
mutate 는 admission 에서 오브젝트를 고쳐서 돌려준다. generate 는 admission 에서 아무것도 만들지 않고 UpdateRequest 를 남긴 뒤 background 컨트롤러가 실제 생성을 한다. 이 차이 때문에 generate 는 admission 이 성공하고 리소스만 안 생기는, 가장 알아채기 어려운 형태로 실패한다.
왜 이게 필요했나
mutate 가 있는 이유는 검사만으로는 사람이 지치기 때문이다. "모든 파드에 팀 라벨을 붙이세요" 를 validate 로만 강제하면 수백 개 매니페스트에 같은 줄을 넣게 되고, 빠뜨린 팀은 배포가 막힌다. 조직 전체에 예외 없이 적용되는 값이라면 사람이 쓰게 하지 말고 시스템이 넣는 편이 낫다.
generate 는 다른 문제를 푼다. 네임스페이스가 만들어지는 순간에만 알 수 있는 사실이 있다. 새 네임스페이스에 default-deny NetworkPolicy 를 넣고 싶다면, 네임스페이스 생성 이벤트에 반응해야 한다. GitOps 로는 "네임스페이스가 생긴 뒤" 라는 시점을 표현하기 어렵고, 사람에게 맡기면 잊는다.
어떻게 동작하나
mutate 의 문법은 두 가지다. patchStrategicMerge 는 쿠버네티스의 전략적 병합 패치라 배열을 이름 키로 병합한다. 컨테이너 목록에 사이드카를 하나 더하는 것처럼 "기존을 남기고 얹는" 작업에 알맞다. patchesJson6902 는 RFC 6902 JSON Patch 라 op/path/value 로 정확한 위치를 지목한다. 경로가 슬래시 표기이고, 키 안에 슬래시가 들어가면 ~1 로 이스케이프해야 한다는 점이 실무에서 자주 걸린다. kca.io/owner 어노테이션을 추가하려면 경로가 /metadata/annotations/kca.io~1owner 가 된다. foreach mutate 는 컬렉션의 원소마다 패치를 적용한다.
generate 의 문법은 data(정책에 값을 직접 쓴다)와 clone(다른 네임스페이스의 리소스를 복제한다) 둘로 갈린다. 둘을 함께 쓸 수는 없다. 여기에 synchronize 가 붙는다. true 면 원본이 바뀔 때 생성물도 따라 바뀌고, 생성물을 손으로 고치면 되돌린다.
문제는 이 편의가 공짜가 아니라는 점이다. synchronize 는 대상 네임스페이스 수만큼 감시와 쓰기를 늘린다. 네임스페이스 다섯 개짜리 클러스터에서는 아무 느낌이 없지만 수백 개짜리에서는 background 컨트롤러의 상시 부하가 된다. 그리고 이 컨트롤러는 최소 권한만 갖고 설치된다. 표준 리소스가 아닌 것을 generate 하기 시작하면 그 리소스에 대한 권한을 쓰는 쪽이 붙여야 하고, 권한이 없으면 admission 은 성공하고 리소스만 조용히 안 생긴다.
그래서 진단 순서가 정해져 있다. 생성물이 안 보일 때 정책 YAML 부터 들여다보지 말고 UpdateRequest 를 먼저 본다. kubectl -n kyverno get updaterequests 가 비어 있으면 match 가 안 된 것이고, 요청은 있는데 리소스가 없으면 background 컨트롤러의 권한이나 동작 문제다. 이 한 번의 분기로 문제 범위가 절반으로 줄어든다. 권한 확인은 kubectl auth can-i <동사> <리소스> --as system:serviceaccount:kyverno:kyverno-background-controller 로 한다. 그리고 네임스페이스를 만든 직후 곧바로 생성물을 확인하면 아직 없을 수 있다는 점도 기억해야 한다. 이것은 버그가 아니라 설계다.
마지막으로 mutate 의 운영상 반대급부를 짚고 넘어가자. mutate 로 넣은 값은 Git 에 보이지 않는다. Helm values 에 넣으면 리뷰되고 태그로 롤백되지만, mutate 로 넣으면 클러스터에서만 보인다. 반년 뒤에 왜 이 어노테이션이 붙어 있는지 아무도 모르는 상태가 되고, 매니페스트와 실제 오브젝트가 다르다는 사실이 GitOps 도구의 드리프트 감지와 계속 싸운다. 조직 전체에 강제해야 하는 값만 mutate 로 두고, 팀이 바꿀 수 있어야 하는 값은 차트에 두는 것이 기준이다.
현장에서 만나는 모습
저자의 홈랩은 GitOps 를 ArgoCD 로 굴리고 있고 10.0.0.201 로 떠 있다. 여기서 mutate 와 GitOps 가 충돌하는 모습을 직접 보게 되는데, 조정 루프가 Git 의 매니페스트와 클러스터 실제 상태를 계속 비교하는 도구이기 때문이다. Kyverno 가 넣은 라벨이나 사이드카는 Git 에 없으므로 diff 에 잡히고, 도구가 그것을 지우려 들면 Kyverno 가 다시 넣는다. 해결은 diff 에서 그 경로를 빼거나 mutate 대신 차트 기본값으로 옮기는 것인데, 이 판단을 미루면 두 컨트롤러가 서로를 되돌리는 상태가 오래 이어진다.
또 하나 이 클러스터에서 반복 확인한 것이 권한과 관측의 관계다. GPU Operator 사고나 KubeVirt 사고 모두 "상태 표시는 정상인데 실제로는 동작하지 않는" 형태였고, generate 의 조용한 실패도 정확히 같은 계열이다. 그래서 generate 를 쓰는 정책을 배포할 때는 정책 자체보다 먼저 background 컨트롤러의 권한을 확인하는 절차를 넣어 두는 편이 낫다.
다음 실습에서 할 것
/root/kca-mutate/ 에 라벨 주입과 사이드카 주입 mutate 규칙, JSON Patch 규칙, NetworkPolicy 를 만드는 generate 규칙과 ConfigMap 을 복제하는 clone 규칙을 작성한다. 그리고 background 컨트롤러가 복제 원본을 읽을 수 있도록 Role 과 RoleBinding, 서비스어카운트를 실제로 만들고 auth can-i 로 검증한다.