LabHub
学习 学习路径 课程

KCA — Kyverno 认证助理

Deployment 创建成功了,却一个 Pod 都没有

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

Kyverno 정책의 작성 도구(preconditions, apiCall 컨텍스트, 변수 기본값, autogen, JSON 패치, foreach, CEL, 백그라운드 스캔)가 실제 요청에서 무엇을 거절하고 무엇을 바꾸는지 진짜 Kyverno 1.19.1 에서 확인합니다.

왜 중요한가

정책의 문법이 맞다는 것과 의도대로 판정한다는 것은 다릅니다. match 는 리소스 종류와 위치로만 고르므로, 요청 내용에 따라 규칙을 켜고 끄려면 preconditions 가, 요청에 없는 정보가 필요하면 컨텍스트가 있어야 합니다. 변수가 해석되지 않으면 규칙은 판정 대신 오류를 내고, Enforce 에서는 그 오류가 거절로 보여서 원인을 엉뚱한 곳에서 찾게 됩니다. Pod 규칙은 기본적으로 컨트롤러용 규칙으로 자동 생성되는데, 이것을 끄면 Deployment 는 성공한 것처럼 보이고 파드만 조용히 생기지 않습니다. 변형 규칙은 요청마다 다른 값을 넣거나 목록의 일부만 고칠 수 있어야 하고, 이미 클러스터에 있던 리소스는 어드미션을 다시 거치지 않으므로 백그라운드 스캔으로만 드러납니다.

단계

  1. 네임스페이스 kca-pre 를 만들고 /root/kca-write/01-limits.yaml 에 ClusterPolicy limits-for-prod(background false)를 작성해 적용하세요. 규칙 memory-limitkca-pre 의 Pod 를 match 하고, preconditions 로 라벨 tierprod 인 요청에만 적용되며(라벨이 없으면 빈 문자열로 취급), 모든 컨테이너에 resources.limits.memory 를 요구합니다(Enforce). match 에 라벨 selector 를 쓰지 않습니다.
  2. 네임스페이스 kca-prod(라벨 env=prod)와 kca-dev(라벨 env=dev)를 만들고, /root/kca-write/02-team.yaml 에 ClusterPolicy team-in-prod-ns(background false)를 적용하세요. 규칙 team-when-prod 는 두 네임스페이스의 Pod 에 대해 컨텍스트 nsenvapiCall/api/v1/namespaces/{{request.namespace}} 에서 읽어(라벨 env, 없으면 none) env 가 prod 이고 team 라벨이 없을 때 거부합니다(Enforce). 그다음 kca-dev 의 env 를 잠시 prod 로 바꿔 라벨 없는 파드를 서버 dry-run 으로 보내 본 출력 전체를 /root/kca-write/flip.txt 에 저장하고, env 를 dev 로 되돌리세요.
  3. 네임스페이스 kca-var 에 ClusterPolicy no-platform-team(background false, 규칙 not-platform)을 적용하세요. team 라벨이 platform 이면 거부하는 deny 조건인데, 처음에는 key 를 기본값 없이 {{ request.object.metadata.labels.team }} 로 씁니다. 라벨 없는 파드를 서버 dry-run 으로 보내 거절 출력 전체를 /root/kca-write/missing.txt 에 저장하세요. 그다음 /root/kca-write/03-no-platform.yaml 의 key 에 기본값(빈 문자열)을 더해 다시 적용해, 라벨 없는 파드는 받아들이고 team=platform 은 거절하게 하세요.
  4. 네임스페이스 kca-autokca-noauto 를 만들고, 각 네임스페이스의 Pod 에 readinessProbe 를 요구하는(Enforce, background false, 규칙 need-probe) ClusterPolicy probes-autoprobes-noauto/root/kca-write/04-probes.yaml 에 작성해 적용하세요. probes-noauto 에만 어노테이션 pod-policies.kyverno.io/autogen-controllers: none 을 붙입니다. 그다음 두 네임스페이스에서 kubectl create deployment web --image=nginx:1.27-alpine 을 실행하고, kca-auto 쪽 출력 전체를 /root/kca-write/auto.txt 에 저장하세요.
  5. 네임스페이스 kca-mut 를 만들고 /root/kca-write/05-requested-by.yaml 에 ClusterPolicy requested-by(규칙 annotate-user)를 작성하세요. kca-mut 의 Pod 에 patchesJson6902 의 add 연산으로 어노테이션 kca.io/requested-by 를 넣고 값은 {{request.userInfo.username}} 입니다. 먼저 spec.background 를 적지 않고 적용해 거절되는 출력 전체를 /root/kca-write/bg-denied.txt 에 저장한 뒤, background 를 false 로 두어 적용하세요. 기존 어노테이션은 보존되어야 합니다.
  6. /root/kca-write/06-pull-policy.yaml 에 ClusterPolicy pull-policy(규칙 latest-always)를 작성해 적용하세요. kca-mut 의 Pod 에 대해 foreach 로 컨테이너마다 이미지가 :latest 로 끝나는지 원소 단위 preconditions 로 보고, 그런 컨테이너만 patchStrategicMerge 로 imagePullPolicy: Always 로 바꿉니다. 태그를 고정한 컨테이너의 imagePullPolicy 는 그대로여야 합니다.
  7. 네임스페이스 kca-cel 을 만들고 /root/kca-write/07-no-priv-esc.yamlpolicies.kyverno.io/v1 ValidatingPolicy no-priv-esc 를 적용하세요. validationActions 는 [Deny], matchConstraints 는 core v1 pods 의 CREATE·UPDATE 와 namespaceSelector kubernetes.io/metadata.name: kca-cel 입니다. CEL 변수 escalating 에 securityContext.allowPrivilegeEscalation 이 명시적으로 false 가 아닌 컨테이너 이름 목록을 담고, 그 목록이 비어 있어야 통과하며 거부 메시지(messageExpression)에 그 이름들을 쉼표로 이어 보여 줍니다.
  8. 네임스페이스 kca-bg 에 라벨 없는 Pod old-nolabel 과 라벨 team=a 인 Pod old-label먼저 만든 뒤, /root/kca-write/08-audit-team.yaml 에 ClusterPolicy audit-team(background true, Audit, 규칙 team, kca-bg 의 Pod 에 team 라벨 요구)을 적용하세요. PolicyReport 에 두 파드의 결과가 나타날 때까지 기다린 뒤 /root/kca-write/report.jsonfail·pass 키로 각 결과의 파드 이름 목록을 적습니다.

참고

prod 파드만 limit 을 요구한다

네임스페이스 kca-pre 를 만들고 /root/kca-write/01-limits.yaml 에 ClusterPolicy limits-for-prod(background false)를 작성해 적용하세요. 규칙 memory-limitkca-pre 의 Pod 를 match 하고, preconditions 로 라벨 tierprod 인 요청에만 적용되며(라벨이 없으면 빈 문자열로 취급), 모든 컨테이너에 resources.limits.memory 를 요구합니다(Enforce). match 에 라벨 selector 를 쓰지 않습니다.

preconditions 의 key 는 JMESPath 변수입니다. 라벨이 없는 요청에서 변수가 해석되지 않으면 어떻게 되는지는 3단계에서 봅니다. 채점기는 tier 가 없는·dev·prod 인 파드를 서버 dry-run 으로 보내 봅니다.

네임스페이스 라벨 하나로 규칙이 켜진다

네임스페이스 kca-prod(라벨 env=prod)와 kca-dev(라벨 env=dev)를 만들고, /root/kca-write/02-team.yaml 에 ClusterPolicy team-in-prod-ns(background false)를 적용하세요. 규칙 team-when-prod 는 두 네임스페이스의 Pod 에 대해 컨텍스트 nsenvapiCall/api/v1/namespaces/{{request.namespace}} 에서 읽어(라벨 env, 없으면 none) env 가 prod 이고 team 라벨이 없을 때 거부합니다(Enforce). 그다음 kca-dev 의 env 를 잠시 prod 로 바꿔 라벨 없는 파드를 서버 dry-run 으로 보내 본 출력 전체를 /root/kca-write/flip.txt 에 저장하고, env 를 dev 로 되돌리세요.

네임스페이스 이름을 정책에 박아 넣으면 정책을 고치지 않고는 판정이 바뀌지 않습니다. apiCall 은 요청마다 API 서버의 현재 값을 읽습니다. dry-run 은 kubectl run ... --dry-run=server 입니다.

라벨이 없는 파드가 엉뚱한 이유로 거절됐다

네임스페이스 kca-var 에 ClusterPolicy no-platform-team(background false, 규칙 not-platform)을 적용하세요. team 라벨이 platform 이면 거부하는 deny 조건인데, 처음에는 key 를 기본값 없이 {{ request.object.metadata.labels.team }} 로 씁니다. 라벨 없는 파드를 서버 dry-run 으로 보내 거절 출력 전체를 /root/kca-write/missing.txt 에 저장하세요. 그다음 /root/kca-write/03-no-platform.yaml 의 key 에 기본값(빈 문자열)을 더해 다시 적용해, 라벨 없는 파드는 받아들이고 team=platform 은 거절하게 하세요.

JMESPath 로 없는 키를 읽으면 변수 치환이 실패하고, Kyverno 는 그 규칙을 판정하지 못한 오류로 다룹니다. Enforce 규칙의 오류가 요청을 어떻게 만드는지 출력의 문구를 읽어 보세요. || 연산자가 기본값을 줍니다.

Deployment 는 만들어졌는데 파드가 하나도 없다

네임스페이스 kca-autokca-noauto 를 만들고, 각 네임스페이스의 Pod 에 readinessProbe 를 요구하는(Enforce, background false, 규칙 need-probe) ClusterPolicy probes-autoprobes-noauto/root/kca-write/04-probes.yaml 에 작성해 적용하세요. probes-noauto 에만 어노테이션 pod-policies.kyverno.io/autogen-controllers: none 을 붙입니다. 그다음 두 네임스페이스에서 kubectl create deployment web --image=nginx:1.27-alpine 을 실행하고, kca-auto 쪽 출력 전체를 /root/kca-write/auto.txt 에 저장하세요.

Kyverno 는 Pod 규칙을 Deployment·ReplicaSet 등 파드 템플릿을 가진 리소스용 규칙으로 자동으로 늘립니다. 늘리지 않으면 Deployment 는 통과하고, 거절은 ReplicaSet 컨트롤러가 파드를 만들 때 일어납니다. kubectl describe rs 의 조건과 이벤트를 보세요.

요청한 사람의 이름을 파드에 새긴다

네임스페이스 kca-mut 를 만들고 /root/kca-write/05-requested-by.yaml 에 ClusterPolicy requested-by(규칙 annotate-user)를 작성하세요. kca-mut 의 Pod 에 patchesJson6902 의 add 연산으로 어노테이션 kca.io/requested-by 를 넣고 값은 {{request.userInfo.username}} 입니다. 먼저 spec.background 를 적지 않고 적용해 거절되는 출력 전체를 /root/kca-write/bg-denied.txt 에 저장한 뒤, background 를 false 로 두어 적용하세요. 기존 어노테이션은 보존되어야 합니다.

JSON 포인터에서 /~1 로 씁니다. 요청자 정보는 어드미션 요청에만 있고, 이미 저장된 리소스를 다시 훑는 백그라운드 처리에는 없습니다. 서버 dry-run 에 -o jsonpath 를 붙이면 변형된 결과를 볼 수 있습니다.

latest 태그 컨테이너만 매번 새로 받게 한다

/root/kca-write/06-pull-policy.yaml 에 ClusterPolicy pull-policy(규칙 latest-always)를 작성해 적용하세요. kca-mut 의 Pod 에 대해 foreach 로 컨테이너마다 이미지가 :latest 로 끝나는지 원소 단위 preconditions 로 보고, 그런 컨테이너만 patchStrategicMerge 로 imagePullPolicy: Always 로 바꿉니다. 태그를 고정한 컨테이너의 imagePullPolicy 는 그대로여야 합니다.

foreach 안에서 현재 원소는 element 입니다. 목록에서 특정 컨테이너만 고치려면 조건 앵커 (name) 으로 이름을 맞춥니다. JMESPath 에는 ends_with 함수가 있습니다.

CEL 한 줄이 Deployment 까지 막았다

네임스페이스 kca-cel 을 만들고 /root/kca-write/07-no-priv-esc.yamlpolicies.kyverno.io/v1 ValidatingPolicy no-priv-esc 를 적용하세요. validationActions 는 [Deny], matchConstraints 는 core v1 pods 의 CREATE·UPDATE 와 namespaceSelector kubernetes.io/metadata.name: kca-cel 입니다. CEL 변수 escalating 에 securityContext.allowPrivilegeEscalation 이 명시적으로 false 가 아닌 컨테이너 이름 목록을 담고, 그 목록이 비어 있어야 통과하며 거부 메시지(messageExpression)에 그 이름들을 쉼표로 이어 보여 줍니다.

CEL 의 filter·map 으로 목록을 만들고 has() 로 없는 필드를 먼저 확인합니다. 이 판의 ValidatingPolicy 도 파드 규칙을 파드 템플릿을 가진 리소스로 자동 생성합니다(status.autogen). 채점기는 Deployment 도 dry-run 으로 보내 봅니다.

정책보다 먼저 있던 파드도 보고서에 올랐다

네임스페이스 kca-bg 에 라벨 없는 Pod old-nolabel 과 라벨 team=a 인 Pod old-label먼저 만든 뒤, /root/kca-write/08-audit-team.yaml 에 ClusterPolicy audit-team(background true, Audit, 규칙 team, kca-bg 의 Pod 에 team 라벨 요구)을 적용하세요. PolicyReport 에 두 파드의 결과가 나타날 때까지 기다린 뒤 /root/kca-write/report.jsonfail·pass 키로 각 결과의 파드 이름 목록을 적습니다.

Audit 는 요청을 막지 않고 기록만 합니다. 이미 저장된 리소스는 어드미션을 다시 거치지 않으므로 background 가 있어야 평가됩니다. 결과는 네임스페이스의 PolicyReport(리소스마다 하나)에 모입니다.