20장. 쿠버네티스 클러스터에 대한 정책과 거버넌스

  • 정책 : 쿠버네티스 리소스를 구성할 수 있는 방법에 대한 조건 및 제약의 집합

  • 거버넌스 : 쿠버네티스 리소스에 대한 조직적 정책을 확인하고 강제화하는 과정

쿠버네티스에는 NetworkPolicy, PodSecurityPolicy 와 같은 다양한 타입의 정책이 존재

하지만 다음과 같이 쿠버네티스 리소스가 생성되기 전에 이러한 정책을 시행하려면, 내장 정책 리소스로는 한계가 존재

  • 모든 컨테이너를 특정 컨테이너 레지스트리에서만 가져와야 한다.

  • 모든 파드에는 부서 이름과 연락처 정보가 표시돼 있어야 한다.

  • 모든 인그레스 호스트이름은 클러스터에서 고유해야 한다.

쿠버네티스의 코어 확장성 컴포넌트를 활용해 정책과 거버넌스를 달성할 수 있다.

코어 확장성 컴포넌트 쿠버네티스 클러스터의 기능을 확장 및 커스터마이징할 수 있도록 돕는 컴포넌트 ex. CRD(Custom Resource Definition), Controller

승인 흐름

image <쿠버네티스 API 서버를 통한 API 요청 흐름>

MutatingWebhookConfiguration 리소스 생성을 통해 API 서버가 승인 변경 평가를 보낼 수 있는 웹훅의 엔드포인트를 구성할 수 있음

마찬가지로 ValidatingWebhookConfiguration 리소스 생성을 통해 API 서버가 승인 검증 평가를 보낼 수 있는 웹훅의 엔드포인트를 설정할 수 있음

etcd

  • 쿠버네티스 클러스터 내 모든 리소스의 상태 및 메타데이터를 저장하는 key-value 저장소

게이트키퍼를 통한 정책과 거버넌스

쿠버네티스 자체적으로는 정책과 거버넌스 활성화를 위한 컨트롤러를 제공하지 않음

게이트키퍼 : 정의된 정책을 기반으로 리소스를 평가하고 쿠버네티스 리소스에 대해 생성이나 수정을 허용할지 여부를 결정하는 쿠버네티스 네이티브 정책 컨트롤러 (오픈소스 솔루션)

게이트키퍼는 CRD 를 사용해 구성과 관련된 새로운 쿠버네티스 자원 집합을 정의
-> kubectl 과 같은 친숙한 도구로 동작 가능

게이트키퍼는 자원에 대한 변경과 감사를 수행

개방형 정책 에이전트란?

개방형 정책 에이전트(OPA) : 쿠버네티스 클러스터에서 정책을 정의하는 도구로, Rego 라는 언어를 사용하여 정책을 작성

게이트키퍼는 이 OPA 를 쿠버네티스 클러스터에 통합할 수 있는 컨트롤러

게이트키퍼 설치

helm 패키지 매니저를 통해 게이트키퍼 설치

helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper/gatekeeper --name-template=gatekeeper --namespace gatekeeper-system --create-namespace

게이트키퍼 컴포넌트는 gatekeeper-system 네임스페이스 내에서 파드 형태로 실행되며, 승인 컨트롤러를 구성함을 확인

kubectl get pods -n gatekeeper-system

NAME                                             READY   STATUS    RESTARTS
gatekeeper-audit-6bbc9cc955-g4ppg                1/1     Running   0
gatekeeper-controller-manager-7d7f66bf68-8ts8c   1/1     Running   0
gatekeeper-controller-manager-7d7f66bf68-ltm4j   1/1     Running   0
gatekeeper-controller-manager-7d7f66bf68-x2fdw   1/1     Running   0

웹훅 구성 방식 검토 ( kubectl get validatingwebhookconfiguration -o yaml )

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
...
webhooks:
- clientConfig:
    service: # (1)
      name: gatekeeper-webhook-service
      namespace: gatekeeper-system
      path: /v1/admit
  failurePolicy: Ignore # (3)
  namespaceSelector:
    matchExpressions:
    - key: admission.gatekeeper.sh/ignore # (2)
      operator: DoesNotExist
  rules:
  - apiGroups:
    - '*'
    apiVersions:
    - '*'
    operations:
    - CREATE
    - UPDATE
    resources:
    - '*'

(1) rules 에 해당하는 모든 리소스가 gatekeeper-system 네임스페이스에서 gatekeeper-webhook-service 라는 서비스로 실행되는 웹훅으로 전송된다. image

(2) admission.gatekeeper.sh/ignore 라벨이 지정된 네임스페이스의 리소스는 정책을 적용하지 않는다.

(3) 웹훅 (gatekeeper-webhook-service 서비스) 호출 실패 시, 쿠버네티스가 해당 요청을 무시하고 계속 처리하도록 한다.

정책 구성 - 컨테이너 이미지는 특정 레지스트리에서만 가져올 수 있게

클러스터 관리자가 정책을 생성하는 방법

1️⃣ 제약 조건 템플릿(게이트키퍼에서 정책을 만들기 위한 템플릿) 생성

# allowedrepos-constraint-template.yaml

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8sallowedrepos
  annotations:
    description: Requires container images to begin with a repo string from a 
      specified list.
spec:
  crd:
    spec:
      names:
        kind: K8sAllowedRepos # ... (1)
      validation:
        # Schema for the `parameters` field
        openAPIV3Schema: # ... (2)
          properties:
            repos:
              type: array
              items:
                type: string
  targets: 
    - target: admission.k8s.gatekeeper.sh # ... (3)
      rego: | # ... (4)
        package k8sallowedrepos

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not strings.any_prefix_match(container.image, input.parameters.repos)
          msg := sprintf("container <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.initContainers[_]
          not strings.any_prefix_match(container.image, input.parameters.repos)
          msg := sprintf("initContainer <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.ephemeralContainers[_]
          not strings.any_prefix_match(container.image, input.parameters.repos)
          msg := sprintf("ephemeralContainer <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
        }

(1) K8sAllowedRepos 라는 CRD (여기서는 정책)를 정의한다.

(2) K8sAllowedRepos 정책을 생성할 때, 매개변수로 컨테이너 리포지토리 목록을 전달해야 한다.

(3) admission.k8s.gatekeeper.sh 라는 승인 컨트롤러가 정책을 실행한다.

(4) 정책의 실제 로직 / 쿠버네티스 리소스의 spec.containers, spec.initContainers, spec.ephemeralContainers 에 있는 컨테이너 이미지가 repos 리스트에 있는 레포지토리로 시작하는지 확인

2️⃣ 정책을 적용하고자 제약 조건 리소스를 생성

# allowedrepos-constraint.yaml

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
  name: repo-is-kuar-demo
spec:
  enforcementAction: deny # ... (1)
  match: # ... (2)
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - "default"
  parameters:
    repos:
      - "gcr.io/kuar-demo/"

(1) 규정을 준수하지 않는 리소스의 경우 거부된다.

  • dryrun : 정책을 실행하되, 정책 위반 리소스 목록 확인 가능

  • warn : 경고 메시지만 보낸다.

(2) 이 제약 조건의 범위를 기본 네임스페이스의 모든 파드로 제한한다.

규정을 준수하는 리소스

# compliant-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containers:
    - image: gcr.io/kuar-demo/kuard-amd64:blue
      name: kuard
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP

# pod/kuard created

규정 미준수 리소스

# noncompliant-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx-noncompliant
spec:
  containers:
    - name: nginx
      image: nginx

# Error from server (Forbidden): error when creating "STDIN": admission webhook "validation.gatekeeper.sh" denied the request

감사

enforcementAction: dryrun

# allowedrepos-constraint-dryrun.yaml

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
  name: repo-is-kuar-demo
spec:
  enforcementAction: dryrun
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - "default"
  parameters:
    repos:
      - "gcr.io/kuar-demo/"

규정 미 준수 pod 생성 후, 주어진 제약 조건에 대해 규정을 준수하지 않은 리소스 목록 확인

kubectl get constraint repo-is-kuar-demo -o yaml

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
...
status:
  auditTimestamp: "2021-07-14T20:05:38Z"
  ...
  totalViolations: 1
  violations:
  - enforcementAction: dryrun
    kind: Pod
    message: container <nginx> has an invalid ..
    name: nginx-noncompliant
    namespace: default

변형

리소스가 규정을 준수하는지 확인하는 방법 말고, 규정을 준수하도록 리소스를 수정하는 방법은 없을까?

기본적으로 게이트키퍼는 검증 승인 웹훅으로 배포되지만, 변형 승인 웹훅으로 동작하도록 구성 가능

변형 할당 리소스 생성

# imagepullpolicyalways-mutation.yaml

apiVersion: mutations.gatekeeper.sh/v1alpha1
kind: Assign
metadata:
  name: demo-image-pull-policy
spec:
  applyTo:
  - groups: [""]
    kinds: ["Pod"]
    versions: ["v1"]
  match:
    scope: Namespaced
    kinds:
    - apiGroups: ["*"]
      kinds: ["Pod"]
    excludedNamespaces: ["system"]
  location: "spec.containers[name:*].imagePullPolicy"
  parameters:
    assign:
      value: Always
  • 모든 파드의 imagePullPolicy 를 Always 로 설정

  • system 네임스페이스는 제외

데이터 복제

생성하고자 하는 리소스의 특정 필드의 값을 다른 리소스의 필드 값과 비교하려는 니즈

ex. 인그레스의 호스트네임이 클러스터 전체에서 고유한지 확인

-> 게이트키퍼는 특정 리소스를 OPA 에 캐시해 리소스 간에 비교가 가능하도록 구성할 수 있음

게이트키퍼 설정

# config-sync.yaml

apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
  name: config
  namespace: "gatekeeper-system"
spec:
  sync:
    syncOnly:
      - group: "networking.k8s.io"
        version: "v1"
        kind: "Ingress"
  • 인그레스 리소스를 캐시

  • ⚠️ 정책에 대한 평가를 수행하는 데 필요한 리소스만을 캐시해야 한다.

제약 조건 템플릿 생성

# uniqueingresshost-constraint-template.yaml

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8suniqueingresshost
  annotations:
    description: Requires all Ingress hosts to be unique.
spec:
  crd:
    spec:
      names:
        kind: K8sUniqueIngressHost
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8suniqueingresshost

        identical(obj, review) {
          obj.metadata.namespace == review.object.metadata.namespace
          obj.metadata.name == review.object.metadata.name
        }

        violation[{"msg": msg}] {
          input.review.kind.kind == "Ingress"
          regex.match("^(extensions|networking.k8s.io)$", input.review.kind.group)
          host := input.review.object.spec.rules[_].host
          other := data.inventory.namespace[_][otherapiversion]["Ingress"][name]
          regex.match("^(extensions|networking.k8s.io)/.+$", otherapiversion)
          other.spec.rules[_].host == host
          not identical(other, input.review)
          msg := sprintf("ingress host conflicts with an existing ingress <%v>", [host])
        }
  • Rego 섹션 - data.inventory 는 캐시 리소스를 의미하는 것으로, 새로운 인그레스의 host 값이 이미 존재하는(캐시에 존재하는) 다른 인그레스 리소스와 충돌하는지 검증하는 로직

메트릭

게이트키퍼는 지속적인 리소스 컴플라이언스 모니터링을 가능하게 하고자 프로메테우스 포맷으로 메트릭을 내보낸다.

정책 라이브러리

게이트키퍼 프로젝트의 핵심 신조 중 하나는 재사용 가능한 정책 라이브러리를 만드는 것

정책 라이브러리를 통해 정책을 공유하여 보일러플레이트 정책 작성 시간을 줄일 수 있음