정책을 코드로 · mutate 와 generate · 이론
mutate 와 generate — 거부 대신 채워 넣기
한 줄 요약
검증은 잘못된 것을 막고, 변형은 빠진 것을 채우고, 생성은 있어야 할 것을 만든다. 셋 중 사람의 일을 실제로 줄이는 것은 뒤의 둘이다.
왜 이게 필요했나
리소스 리밋이 없는 파드를 전부 막는 정책을 걸었다고 하자. 규칙은 지켜지지만 대가가 있다. 팀 열 개가 각자 매니페스트를 고쳐야 하고, 그 과정에서 "얼마를 넣어야 하나요"라는 문의가 열 번 온다. 조직이 원한 것은 "리밋이 없는 파드가 없는 상태"이지 "열 개 팀이 각자 고민하는 상태"가 아니었다.
변형 규칙은 이 일을 뒤집는다. 값이 없으면 조직의 기본값을 넣어 주고, 이미 값이 있으면 손대지 않는다. 그러면 규칙은 지켜지면서 아무도 막히지 않는다. 생성 규칙은 한 걸음 더 간다. 새 네임스페이스가 만들어지는 순간 기본 NetworkPolicy·ResourceQuota·LimitRange 를 같이 만들어 준다. 사람이 온보딩 체크리스트를 따라가며 세 번 kubectl apply 하던 일이 사라진다.
다만 힘이 센 만큼 부작용도 크다. 변형은 사용자가 쓴 매니페스트와 클러스터의 실제 오브젝트를 다르게 만든다. 반년 뒤에 "이 어노테이션은 누가 붙였지"라는 질문이 나오고, 배포 도구의 드리프트 감지와 계속 싸운다. 그래서 판단 기준이 필요하다. 조직 전체에 강제해야 하는 값만 변형으로 넣고, 팀이 바꿀 수 있어야 하는 값은 차트나 매니페스트의 기본값으로 둔다.
어떻게 동작하나
변형에는 세 가지 방식이 있다.
| 방식 | 언제 | 특징 |
| --- | --- | --- |
| patchStrategicMerge | 맵에 필드를 더하거나 채울 때 | 오브젝트와 같은 모양으로 써서 읽기 쉽다 |
| patchesJson6902 | 배열의 특정 위치를 다룰 때 | op·path·value 로 위치를 콕 집는다 |
| foreach | 컨테이너처럼 개수가 정해지지 않은 목록 | list 로 목록을 받고 element 로 현재 항목을 가리킨다 |
배열이 이 셋을 가르는 기준이다. Strategic Merge 는 맵에는 자연스럽지만 배열에서는 "몇 번째 항목"을 지정하기 어렵다. JSON Patch 는 /spec/containers/0/imagePullPolicy 처럼 인덱스를 쓸 수 있지만, 인덱스가 고정이라는 전제가 필요하다. 컨테이너가 몇 개인지 모를 때는 결국 foreach 로 목록을 돌아야 한다.
변형에서 가장 중요한 문법은 추가 앵커 +() 다. +(imagePullPolicy): IfNotPresent 는 "값이 없으면 넣고, 이미 있으면 건드리지 않는다"는 뜻이다. 이 앵커가 없으면 정책이 사용자가 명시한 값을 조용히 덮어쓴다. 팀이 cpu: 500m 이라고 적었는데 정책이 200m 으로 바꿔 버리면, 그 팀은 자기 매니페스트를 믿을 수 없게 된다. 조건 앵커 () 는 "이 조건이 맞을 때만 아래를 적용"이라는 다른 일을 하므로 헷갈리지 말아야 한다.
생성 규칙에는 두 가지 방식이 있다. data 는 만들 내용을 정책 안에 직접 적는 방식이고, clone 은 기존 리소스를 원본으로 삼아 복사하는 방식이다. 시크릿처럼 내용을 정책 파일에 적으면 안 되는 것은 반드시 clone 이다. 정책은 보통 git 에 들어가기 때문이다. 둘은 함께 쓸 수 없다.
synchronize: true 는 만든 뒤에도 계속 지켜본다는 뜻이다. 원본이 바뀌면 사본을 따라 바꾸고, 누가 사본을 손으로 고치면 되돌린다. 끄면 만들 때 한 번만 관여하고 그 뒤로는 손대지 않는다. 켜는 쪽이 안전해 보이지만 공짜가 아니다 — 대상 네임스페이스 수만큼 감시와 쓰기가 늘어난다.
권한도 잊으면 안 된다. 백그라운드 컨트롤러는 최소 권한으로 설치되므로, 만들려는 리소스 종류에 대한 create·update 권한을 ClusterRole 로 따로 붙여 줘야 한다. 권한이 없으면 요청은 성공하고 리소스만 조용히 안 생긴다. 가장 알아채기 어려운 종류의 실패다.
현장에서 만나는 모습
첫째, 변형과 검증이 서로 물리는 사고. 사이드카를 주입하면서 리밋을 안 넣으면, 리밋을 요구하는 검증 정책에 그 사이드카가 걸린다. 주입 스펙에 리밋을 같이 넣는 것은 취향이 아니라 요구사항이다.
둘째, 기존 리소스를 고치는 정책. mutate.targets 를 쓰면 트리거가 된 리소스가 아니라 다른 리소스를 고칠 수 있다. 어드미션이 아니라 백그라운드에서 도는 일이라 추가 권한이 필요하고, mutateExistingOnPolicyUpdate 를 켜면 정책을 고칠 때마다 기존 리소스가 한꺼번에 손질된다. 힘이 센 만큼 범위를 좁혀 쓴다.
셋째, GitOps 와의 경계. 수백 개 네임스페이스의 리소스를 생성 규칙과 동기화로 관리하는 것은 GitOps 도구가 더 잘한다. 무엇보다 git 에 남는다. 생성 규칙이 값어치 있는 자리는 네임스페이스 생성처럼 그 순간에만 알 수 있는 이벤트에 반응해야 할 때다.
넷째, 이 환경의 한계. 여기서는 백그라운드 컨트롤러가 돌지 않으므로 mutate.targets 나 생성 규칙이 실제 리소스를 만들지 않는다. 대신 kyverno CLI 가 같은 규칙을 로컬에서 평가해 변형된 매니페스트와 생성될 리소스를 보여 준다. 채점은 정책 YAML 의 구조와 그 실행 결과를 본다.
다음 실습에서 할 것
먼저 변형 실습에서 라벨을 덧붙이는 정책, 추가 앵커로 자원 기본값을 채우는 정책, JSON Patch 로 배열 항목을 고치는 규칙, 컨테이너 목록을 도는 foreach 규칙을 쓴다. 그리고 값이 이미 있는 파드에 같은 정책을 돌려 덮어쓰지 않는 것을 눈으로 확인한 뒤, 변형 전후를 대조한 보고서를 만든다. 이어지는 생성 실습에서는 새 테넌트 네임스페이스에 NetworkPolicy·ResourceQuota·LimitRange 를 한꺼번에 만들어 주는 온보딩 정책 묶음과, 그 정책이 실제로 동작하려면 필요한 RBAC 을 쓴다.