# 20장. 쿠버네티스 클러스터에 대한 정책과 거버넌스
- 정책 : 쿠버네티스 리소스를 구성할 수 있는 방법에 대한 조건 및 제약의 집합
- 거버넌스 : 쿠버네티스 리소스에 대한 조직적 정책을 확인하고 강제화하는 과정
쿠버네티스에는 NetworkPolicy, PodSecurityPolicy 와 같은 다양한 타입의 정책이 존재
하지만 다음과 같이 쿠버네티스 리소스가 생성되기 전에 이러한 정책을 시행하려면, 내장 정책 리소스로는 한계가 존재
- 모든 컨테이너를 특정 컨테이너 레지스트리에서만 가져와야 한다.
- 모든 파드에는 부서 이름과 연락처 정보가 표시돼 있어야 한다.
- 모든 인그레스 호스트이름은 클러스터에서 고유해야 한다.
쿠버네티스의 `코어 확장성 컴포넌트`를 활용해 정책과 거버넌스를 달성할 수 있다.
> 코어 확장성 컴포넌트
> 쿠버네티스 클러스터의 기능을 확장 및 커스터마이징할 수 있도록 돕는 컴포넌트
> ex. CRD(Custom Resource Definition), Controller
## 승인 흐름

<쿠버네티스 API 서버를 통한 API 요청 흐름>
`MutatingWebhookConfiguration` 리소스 생성을 통해 API 서버가 **승인 변경** 평가를 보낼 수 있는 웹훅의 엔드포인트를 구성할 수 있음
마찬가지로 `ValidatingWebhookConfiguration` 리소스 생성을 통해 API 서버가 승인 검증 평가를 보낼 수 있는 웹훅의 엔드포인트를 설정할 수 있음
> etcd
> - 쿠버네티스 클러스터 내 모든 리소스의 상태 및 메타데이터를 저장하는 key-value 저장소
## 게이트키퍼를 통한 정책과 거버넌스
쿠버네티스 자체적으로는 정책과 거버넌스 활성화를 위한 컨트롤러를 제공하지 않음
[게이트키퍼](https://open-policy-agent.github.io/gatekeeper/website/docs/) : 정의된 정책을 기반으로 리소스를 평가하고 쿠버네티스 리소스에 대해 생성이나 수정을 허용할지 여부를 결정하는 쿠버네티스 네이티브 정책 컨트롤러 (오픈소스 솔루션)
게이트키퍼는 CRD 를 사용해 구성과 관련된 새로운 쿠버네티스 자원 집합을 정의
-> kubectl 과 같은 친숙한 도구로 동작 가능
게이트키퍼는 자원에 대한 변경과 감사를 수행
### 개방형 정책 에이전트란?
[개방형 정책 에이전트(OPA)](https://www.openpolicyagent.org/) : 쿠버네티스 클러스터에서 정책을 정의하는 도구로, Rego 라는 언어를 사용하여 정책을 작성
게이트키퍼는 이 OPA 를 쿠버네티스 클러스터에 통합할 수 있는 컨트롤러
### 게이트키퍼 설치
**helm 패키지 매니저를 통해 게이트키퍼 설치**
``` shell
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 네임스페이스 내에서 파드 형태로 실행되며, 승인 컨트롤러를 구성함을 확인**
``` shell
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` )**
``` 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` 라는 서비스로 실행되는 웹훅으로 전송된다.
(2) `admission.gatekeeper.sh/ignore` 라벨이 지정된 네임스페이스의 리소스는 정책을 적용하지 않는다.
(3) 웹훅 (gatekeeper-webhook-service 서비스) 호출 실패 시, 쿠버네티스가 해당 요청을 무시하고 계속 처리하도록 한다.
### 정책 구성 - 컨테이너 이미지는 특정 레지스트리에서만 가져올 수 있게
**클러스터 관리자가 정책을 생성하는 방법**
1️⃣ 제약 조건 템플릿(게이트키퍼에서 정책을 만들기 위한 템플릿) 생성
``` yaml
# 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️⃣ 정책을 적용하고자 제약 조건 리소스를 생성
``` yaml
# 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) 이 제약 조건의 범위를 기본 네임스페이스의 모든 파드로 제한한다.
**규정을 준수하는 리소스**
``` yaml
# 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
```
**규정 미준수 리소스**
``` yaml
# 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`
``` yaml
# 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`
``` yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
...
status:
auditTimestamp: "2021-07-14T20:05:38Z"
...
totalViolations: 1
violations:
- enforcementAction: dryrun
kind: Pod
message: container has an invalid ..
name: nginx-noncompliant
namespace: default
```
### 변형
리소스가 규정을 준수하는지 확인하는 방법 말고, 규정을 준수하도록 리소스를 수정하는 방법은 없을까?
기본적으로 게이트키퍼는 검증 승인 웹훅으로 배포되지만, 변형 승인 웹훅으로 동작하도록 구성 가능
**변형 할당 리소스 생성**
``` yaml
# 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 에 캐시해 리소스 간에 비교가 가능하도록 구성할 수 있음
**게이트키퍼 설정**
``` yaml
# 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"
```
- 인그레스 리소스를 캐시
- ⚠️ 정책에 대한 평가를 수행하는 데 필요한 리소스만을 캐시해야 한다.
**제약 조건 템플릿 생성**
``` yaml
# 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 값이 이미 존재하는(캐시에 존재하는) 다른 인그레스 리소스와 충돌하는지 검증하는 로직
### 메트릭
게이트키퍼는 지속적인 리소스 컴플라이언스 모니터링을 가능하게 하고자 프로메테우스 포맷으로 메트릭을 내보낸다.
### 정책 라이브러리
게이트키퍼 프로젝트의 핵심 신조 중 하나는 재사용 가능한 정책 라이브러리를 만드는 것
정책 라이브러리를 통해 정책을 공유하여 보일러플레이트 정책 작성 시간을 줄일 수 있음