여러 개발자 또는 팀에서 동일 Kubernetes 클러스터를 사용하는 경우에는 일관된 규칙, 또는 정책을 적용하는 것이 중요합니다. 안정적인 운영뿐만 아니라 보안 취약점을 최소화할 수도 있기 때문인데요. 여기서 말하는 정책이라 하면…

  • 모든 워크로드에 특정 보안 설정을 강제하거나,
  • Pod의 리소스 사용량을 제한하고,
  • 필수 레이블이 무조건 포함되도록 하는 등이 있습니다.

이런 클러스터의 정책을 문서로만 관리하고 개발자/운영자가 직접 지키도록 한다면 실수(휴먼 에러)가 발생하기 쉽습니다. 게다가 리소스를 배포할 때마다 Kubernetes 리소스 매니페스토를 일일이 검토해야 한다면, 클러스터 운영 효율성 측면에서도 안 좋겠죠?

그래서 클러스터에 적용될 정책을 코드로 정의하고, 시스템이 이를 자동으로 강제하는 방식이 등장했습니다. Policy as Code(PaC)라고 하는 개념인데요. 클러스터에 리소스가 배포되기 전에 정의된 정책을 준수하는지 자동으로 검증하고, 위반 시에는 배포를 차단해서 클러스터의 보안성과 안정성을 이끌어내는 방식입니다.

그리고 Kubernetes 클러스터에서 PaC를 구현하기 위해 나온 오픈소스 정책 엔진(Policy Engine)이 바로, Kyverno입니다.

500


Kubernetes 클러스터에 정책이 필요할 땐 Kyverno를 써보자

Kyverno는 Kubernetes 환경에서 정책을 관리하고 시행하기 위해 설계된 오픈소스 정책 엔진입니다. 클러스터에 들어오는 모든 리소스 생성 및 변경 요청을 감지하고, 정의된 정책에 따라 허용, 차단 또는 수정 작업을 하는 ‘문지기’라고 할 수 있죠.

그리고 Kyveno는 Kubernetes의 Admission Control 웹훅 기능을 기반으로 동작하는데요. 좀 더 풀어서 설명하자면, 사용자가 kubectl apply와 같은 명령어로 Kubernetes 리소스를 생성하거나 변경하려고 할 때 Kubernetes API 서버가 해당 요청을 Kyverno에게 전달해서 정책 검증을 요청하는 식입니다. 이때 Kyverno는 요청 내용을 검토하고 정책에 따라서 허용/거부 여부를 Kubernetes API 서버에 다시 알려줌으로써 정책을 강제할 수 있는 겁니다.

그렇다면 우리가 Kubernetes 클러스터의 정책 관리를 위해 Kyverno를 사용해야 하는 이유는 무엇일까요? Kyverno에는 아래와 같이 특장점이 있기 때문입니다.

  1. ClusterPolicy 또는 Policy와 같은 Kubernetes CRD로 정책 정의

    • 클러스터 전체에 적용될 정책은 ClusterPolicy로, Namespace 단위로 적용될 정책은 Policy로 정의 가능합니다.
    • 일반적인 Kubernetes 리소스를 다루는 것과 동일하게 kubectl을 사용하여 정책을 생성, 조회, 수정, 삭제할 수 있습니다.
    • GitOps 워크플로우를 적용하여 정책의 버전을 관리하기에도 용이합니다.
  2. 정책 결과를 Kubernetes 이벤트나 PolicyReport를 통해 확인 가능

    • 사용자는 kubectl get events 또는 kubectl describe와 같은 익숙한 명령어로 정책의 결과를 쉽게 확인할 수 있습니다.
    • 정책 디버깅이나 클러스터 상태 감사(Audit)가 더욱 편리해집니다.
  3. 리소스 생성 요청 차단 외에 리소스 수정 또는 생성 기능도 제공

    • 정책을 위반하는 요청을 차단(Validate)하는 기능 외에도, 리소스에 특정 내용을 자동으로 추가하는 수정(Mutate) 기능과, 특정 조건 만족 시 다른 관련 리소스를 자동으로 생성(Generate)하는 기능을 제공합니다.
    • 클러스터 운영을 자동화하고 개발자의 편의성을 높이는 데에 도움이 됩니다.

이런 Kyverno에는 다양한 기능을 제공하고 있는데요. 그 중에서도 아래 핵심 기능 3가지를 이번 실습 가이드에서 직접 사용해볼 예정입니다.

  1. 검증(Validate)

    • Kyverno의 가장 기본적인 규칙으로, 클러스터에 생성되거나 수정되는 리소스가 정의된 조건을 만족하는지 확인합니다.
  2. 수정(Mutate)

    • Kubernetes 리소스 명세 중 특정 값이 누락되었을 때 기본값을 자동으로 추가하거나, 모든 리소스에 공통적인 레이블 또는 어노테이션을 붙이는 등 리소스를 자동으로 수정합니다.
  3. 생성(Generate)

    • 특정 리소스가 생성되는 이벤트(트리거)에 반응하여, 그와 관련된 다른 리소스들을 자동으로 생성합니다.
    • 예: 새로운 Namespace가 생성될 때마다 NetworkPolicy 또는 Role을 함께 생성

본 실습 가이드는 Kubernetes 정책 설정이나 Kyverno에 대해 궁금하시거나 도입을 고민하시는 분들에게 도움이 될 수 있도록 Kyverno의 핵심 기능 3가지에 대한 내용을 효과적으로 전달할 수 있도록 설계되었습니다.

그리고 이번 실습에서 다루는 Kyverno의 핵심 기능들은 CNCF의 Kyverno Certified Associate(KCA) 자격증의 주요 내용이기도 하니, KCA 자격증에 관심이 있으신 분들도 이번 실습을 진행해보시면 시험에 필요한 지식을 얻는 데에 큰 도움이 될 것입니다.


실습 준비: minikube 클러스터에 Kyverno 설치하기

Kyverno CLI 설치

아까 kubectl로 정책 생성부터 수정까지 모두 할 수 있다고 했는데, 왜 Kyverno CLI를 설치해야 할까요?

Kyverno 정책을 클러스터에 직접 적용하기 전에 테스트하고 검증할 수 있는 유용한 도구이기 때문입니다. 정책 YAML 파일과 테스트용 리소스 YAML 파일을 사용해서 해당 정책이 의도한 대로 동작하는지 미리 확인할 수 있거든요.

Kyverno CLI는 로컬에 직접 설치할 수도 있지만, Krew를 통해 kubectl의 플러그인 형태로 설치도 가능합니다.

Krew가 뭔지, 어떻게 사용하는지 궁금하시다면 Aiden’s Lab의 krew 핸즈온 가이드에서 확인해보세요.

Krew가 이미 설치되어 있다고 가정하고 진행할게요. kubectl 플러그인 형태로 Kyverno CLI를 설치하려면 아래 Krew 명령어를 실행합니다.

kubectl krew install kyverno

그리고 아래 명령어로 Kyverno CLI가 잘 설치되었는지 확인할 수 있습니다.

kubectl kyverno version

Kyverno CLI를 Krew로 설치하니 사용하기도 간단하죠?

이제 Kyverno 동작에 필요한 리소스들을 Kubernetes 클러스터에 배포할 차례입니다. 우리는 이번에도 빠른 클러스터 구성을 위해 minikube를 사용할 겁니다.

minikube를 설치하고 Kubernetes 클러스터를 구성하는 데에 아직 익숙치 않아도 걱정마세요. minikube 소개부터 설치, 활용법까지 자세히 다룬 Aiden’s Lab의 minikube 핸즈온 가이드를 보시면 금방 따라오실 수 있을 겁니다.

그럼 바로 다음 단계로 넘어가봅시다.

minikube 클러스터에서 Helm으로 Kyverno 배포

우리는 Kyverno를 클러스터에 배포할 때 Helm을 사용할 겁니다. 아마 아시는 분들이 계시겠지만, Helm은 Kubernetes에 배포되는 프로젝트의 각 리소스의 YAML 파일을 로컬에 저장하지 않고도 원격에서 불러와 CLI 환경에서 생성, 버전 관리, 공유까지 할 수 있는 Kubernetes 패키지 매니저 툴입니다.

Helm을 사용하면 Kyverno를 클러스터에 간편하게 배포할 수 있는데요. Kyverno의 공식 문서에서도 이처럼 권장하고 있으므로, 저희도 Helm으로 Kyverno를 배포해보겠습니다.

만약 실습 환경에 Helm이 설치되어 있지않다면 아래와 같이 Helm을 먼저 설치합니다. (Ubuntu 환경 기준)

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

그리고 아래와 같이 Helm 명령어를 실행하여 minikube 클러스터에 Kyverno를 배포합니다.

helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno -n kyverno --create-namespace

배포가 완료되면 Kyverno의 구성요소들이 정상적으로 실행 중인지 확인해야 하는데요. kubectl -n kyverno get pods 명령어로 현재 배포된 모든 Kyverno 관련 Pod의 현재 상태를 보면 됩니다.

아래와 같이 Kyverno 관련 Pod가 모두 READY 상태이면서 STATUSRunning이므로, Kyverno 구성요소들이 모두 잘 동작 중인 걸 알 수 있네요.

Kyverno CLI 설치와 Kyverno 배포가 모두 완료되었으니, 이제 Kyverno의 핵심 기능 실습을 시작해보겠습니다.


핵심 기능 실습 1 - 리소스 생성 규칙 검증(Validate)

validate 규칙은 정의된 정책을 위반하는 리소스 요청을 차단하여 Kubernetes 클러스터의 일관성을 유지하고, 잠재적인 보안 위험도 줄여줍니다.

예를 들어, ‘모든 Pod는 non-root 유저로 실행해야 한다’거나, ‘모든 Ingress는 HTTPS만 허용해야 한다’와 같은 보안 모범사례를 정책으로 만들어 강제할 수 있죠. 또한, ‘모든 리소스에는 owner 레이블이 있있어야 한다’와 같이 조직 내부의 관리 표준을 적용할 수도 있습니다.

그럼 이제 validate 규칙을 포함하는 Kyverno의 ClusterPolicy를 직접 만들어보겠습니다. 아래와 같은 요구사항이 있다고 가정해볼게요.

  • 클러스터에 생성되는 모든 Pod에는 반드시 test-for-kyverno/name 레이블이 포함되어야 함
  • 본 정책 위반 시 리소스 생성 차단

그리고 해당 요구사항을 충족하기 위한 ClusterPolicy 예제 코드(require-label.yaml)는 아래와 같이 작성할 수 있습니다.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-label
spec:
  validationFailureAction: Enforce # 정책 위반 시 리소스 생성을 차단
  rules:
    - name: check-for-name-label
      match:
        resources: # 정책이 적용될 리소스 종류 지정
          kinds:
            - Pod
      validate:
        message: "Pod에는 반드시 test-for-kyverno/name 레이블이 필요합니다."
        pattern:
          metadata:
            labels:
              test-for-kyverno/name: "?*" # 이 레이블의 존재 여부만 검사

그리고 아래 두 Deployment YAML 예제는 각각 위 정책에 위반 및 통과되는 테스트용 코드입니다.

정책에 위반되는 Deployment(test-validate-fail.yaml):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment-fail
  namespace: kyverno-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-test-app
  template:
    metadata:
      # 'test-for-kyverno/name' 레이블이 없으므로 validate 정책에 의해 생성 거부
      labels:
        app: my-test-app
    spec:
      containers:
      - name: nginx
        image: nginx:latest

정책을 따르는 Deployment(test-validate-success.yaml):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment-success
  namespace: kyverno-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      test-for-kyverno/name: my-successful-app
  template:
    metadata:
      # 'test-for-kyverno/name' 레이블이 있으므로 validate 정책 통과
      labels:
        test-for-kyverno/name: my-successful-app
    spec:
      containers:
      - name: nginx
        image: nginx:latest

테스트용 Deployment YAML 코드에서 볼 수 있듯이, 우리가 테스트할 리소스들은 kyverno-demo Namespace에 배포할 예정이므로 kubectl create ns kyverno-demo 명령어를 실행해서 해당 Namespace를 먼저 생성해주세요.

저는 실습을 위해 별도의 폴더를 생성하고, 정책 예제 코드는 모두 policies 폴더에 저장했고, 테스트용 리소스 파일들은 또 다른 resources 폴더 안에 저장했는데요. 이제 실제로 정책을 배포하기 전에, 아래 Kyverno CLI 명령어를 통해 정책이 어떻게 적용되는지 테스트해보겠습니다.

kubectl kyverno apply {Policy 파일명} -r {테스트 리소스 경로}

위 명령어 마지막에 -t 옵션을 주면, 아래와 같이 정책 테스트 결과가 표 형식으로 보기 쉽게 표시됩니다.

우리가 의도한 대로, test-validate-fail.yaml 리소스는 정책 위반, test-validate-success.yaml 리소스는 정책 통과라는 결과가 나오고 있네요. 그럼 이제 실제로 클러스터에 정책을 배포한 다음, 테스트용 리소스들도 배포해보겠습니다.

먼저 아래 명령어로 require-label.yaml 정책을 배포합니다.

ClusterPolicy 적용 주의

앞으로 배포할 Kyverno의 ClusterPolicy는 클러스터에 배포된 전체 Pod에 특정 레이블이 포함되도록 강제하므로 반드시 실습용 클러스터에서 진행해주세요. 해당 클러스터에 이미 다른 Pod가 실행 중이라면 ClusterPolicy의 영향을 받을 수 있습니다.

kubectl apply -f policies/require-label.yaml

kubectl로 배포한 Kyverno의 ClusterPolicy는 아래와 같이 조회할 수 있습니다.

그 다음, 아까 Kyverno CLI 테스트 때 정책에 위반되었던 test-validate-fail.yaml 리소스를 kubectl apply 명령어로 배포해보겠습니다.

보이는 것처럼 리소스 배포가 막혔습니다. 우리가 해당 validatemessage 필드에 지정했던 안내 메시지도 함께 나오고 있네요! 이렇게 Kyverno의 validate 규칙은 리소스 생성 전 사용자에게 정책에 위반되는 사항을 알려주고, 정책을 따르도록 유도하는 것을 알 수 있습니다.


핵심 기능 실습 2 - 리소스 자동 수정(Mutate)

다음으로 알아볼 규칙은 mutate입니다. 개발자나 운영자가 Kubernetes 클러스터에 리소스를 생성할 때 필요한 특정 필드를 명시하지 않았을 경우, mutate 규칙을 통해 자동으로 기본값을 설정할 수 있는데요.

예를 들면 Pod의 imagePullPolicy 필드 값이 명시되지 않았다면 IfNotPresent 값을 자동으로 넣어주거나, 모든 Pod에 resouces/requests 값을 자동으로 추가하는 등의 작업이 가능한 거죠. 클러스터의 안정성과 자원 효율성을 높이기 위해 활용되는 규칙입니다.

그럼 mutate 규칙을 실습해보겠습니다. 아래와 같은 요구사항이 있다고 가정해볼게요.

  • 모든 클러스터에 새로운 Pod가 생성될 때마다 managed-by라는 어노테이션이 필수로 존재해야 함
  • managed-by 필드의 기본값은 kyverno

위 요구사항을 충족하는 ClusterPolicy(add-annotation.yaml)는 아래와 같이 작성할 수 있습니다.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-annotation-simple
spec:
  rules:
    - name: add-managed-by-annotation
      match:
        resources:
          kinds:
            - Pod
      mutate:
        patchStrategicMerge: # 아래 리소스 내용을 생성되는 리소스 내용에 병합
          metadata:
            annotations:
              # 'managed-by' 어노테이션이 없으면(+) 지정한 값(kyverno)을 추가
              +(managed-by): kyverno

아까 ‘핵심 기능 1’에서 사용했던 테스트용 Deployment를 다시 실습에 사용해보겠습니다. 먼저 Kyverno CLI의 apply 명령어로 add-annotation.yaml로 정의한 mutate 규칙이 정상 작동하는지 테스트해보겠습니다. kyverno apply 명령어 양식은 ‘핵심 기능 1’에서 이미 살펴봤었죠?

명령어를 실행하면 mutate 규칙으로 인해 두 개의 Deployment 모두 managed-by 어노테이션으로 kyverno 값이 자동으로 들어가는 것을 확인할 수 있습니다. 참고로, kyverno apply는 터미널상에서 동작하기 때문에 우리가 실행한 명령어로 실제 Deployment YAML 파일이 수정된 것은 아닙니다.

이제 해당 ClusterPolicy를 실습용 클러스터에 배포 후, test-validate-success.yaml 파일의 Deployment를 배포해보겠습니다. (우리가 이미 require-label ClusterPolicy를 클러스터에 배포했기 때문에 test-validate-fail.yaml의 Deployment는 test-for-kyverno/name label을 추가하지 않는 이상 배포할 수 없습니다.)

먼저 add-annotation.yaml 파일의 정책을 배포합니다.

그리고 test-validate-success.yaml 파일의 Deployment를 배포한 다음, kubectl describe 명령어로 배포된 Deployment의 명세를 확인해보겠습니다. 지금 예제 YAML 파일에는 managed-by 어노테이션이 명시되지 않았다는 점 기억해주세요!

test-validate-success.yaml의 Deployment가 정상 배포되었고, managed-by 어노테이션의 kyverno 값이 자동으로 추가되었습니다! 이렇게 mutate 규칙으로 Kubernetes 리소스에 꼭 명시되어야 하는 필드의 기본값을 자동으로 추가하여 리소스 명세의 일관성을 보장할 수 있음을 확인했습니다.


핵심 기능 3 - 필수 리소스 자동 생성(Generate)

마지막으로 Kyverno의 핵심 기능 generate 규칙에 대해 알아보겠습니다. 특정 리소스가 생성될 경우, 필요하지만 직접 생성하기엔 반복 작업이 될 수 있는 다른 리소스들을 자동으로 생성하는 기능인데요.

예를 들면 새로운 사용자가 생성될 때마다 해당 사용자를 위한 기본 Role과 RoleBinding을 자동으로 만들어 줄 수 있습니다. 복잡한 설정 과정을 자동화하면서 일관성도 유지할 수 있어 유용한 규칙입니다.

우리는 아래 요구사항이 있다고 가정해볼게요.

  • 새로운 Namespace가 생성될 때마다 해당 Namespace를 위한 NetworkPolicy 자동 생성
  • 이 NetworkPolicy는 해당 Namespace에 속할 모든 Pod의 외부로 향하는 통신을 기본적으로 차단

이 요구사항을 충족하는 ClusterPolicy(generate-network-policy.yaml)는 아래와 같이 작성할 수 있습니다.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: generate-network-policy
spec:
  rules:
  - name: generate-default-deny-policy
    # Namespace 생성으로 해당 규칙이 트리거됨을 정의 
    match:
      resources:
        kinds:
          - Namespace
    # 위 match 조건이 충족되면 아래 리소스를 생성
    generate:
      apiVersion: networking.k8s.io/v1
      kind: NetworkPolicy
      name: default-deny-all # 생성될 NetworkPolicy의 이름
      namespace: "{{request.object.metadata.name}}" # 트리거된 Namespace의 이름을 사용
      synchronize: true # Namespace가 삭제되면 이 NetworkPolicy도 함께 삭제
      data:
        spec:
          # 해당 Namespace의 모든 Pod에 이 정책을 적용
          podSelector: {}
          # 외부로 나가는(Egress) 모든 트래픽을 차단
          policyTypes:
          - Egress

그럼 해당 ClusterPolicy를 바로 클러스터에 배포해보겠습니다.

그리고 test-for-generate-rule이라는 이름의 Namespace를 바로 만들어보겠습니다.

해당 Namespace의 NetworkPolicy를 조회해보면…

generate-network-policy.yaml ClusterPolicy에서 정의했던 default-deny-all NetworkPolicy가 자동으로 생성되었습니다! 그리고 generate.kyverno.io/policy-namegenerate.kyverno.io/rule-name Label의 값을 보면 어떤 Kyverno 정책과 규칙으로 인해 생성되었는지도 알 수 있네요.

예제 사례에서 봤던 것 이외에도, ConfigMap이나 Secret 등 추가로 생성이 필요하지만 자칫하면 반복 작업이 될 수 있는 리소스들 역시 Kyverno의 generate 규칙으로 한 번에 생성 가능합니다.


마무리

Kyverno의 ClusterPolicy는 클러스터 전체에 영향을 주는 리소스이므로, 실습용 클러스터에 배포했던 정책은 모두 제거할 것을 권해드립니다. Kubernetes 경험이 있으시다면 익숙한 kubectl delete 명령어로 한 번에 제거 가능합니다.

kubectl delete clusterpolicy require-label add-annotation-simple generate-network-policy

지금까지 Kyverno의 핵심 기능이자 규칙 3가지를 자세히 살펴보고 실습까지 진행했습니다. validate, mutate, generate 규칙은 독립적으로도 강력하지만, 서로 조합하여 사용될 때 더욱 정교하고 효과적인 정책을 구성할 수 있는데요.

특정 리소스가 생성될 때 mutate로 기본값을 추가하고, validate로 다른 필수 조건을 검증한 뒤, generate로 관련된 보조 리소스를 함께 생성하는 복합 시나리오 구현이 가능한 것이죠. 이번 글은 Kyverno를 막 시작한 분들을 위해 작성한 것이어서 이런 심화 시나리오까지 진행하진 못했지만, 추후에 다뤄볼 기회가 있으면 좋겠습니다.

이번 실습 가이드를 따라하시면서 어느새 KyvernoPolicy as Code에 대한 기본 지식을 익히셨을 거란 생각이 듭니다. 혹시 이번 주제와 관련해서 좀 더 다뤘으면 하는 내용이나 궁금한 점이 있다면 언제든 아래 피드백 양식에 남겨주세요.

그럼 다음에 더 흥미로운 주제로 찾아오겠습니다. 감사합니다.