[Kubernetes] ๐ก๏ธ OPA Gatekeeper vs Kyverno: ์ฟ ๋ฒ๋คํฐ์ค ์ ์ฑ ๊ด๋ฆฌ ์์ ๊ฐ์ด๋
์ฟ ๋ฒ๋คํฐ์ค ํด๋ฌ์คํฐ๋ฅผ ์ด์ํ๋ค ๋ณด๋ฉด “๋ชจ๋ Pod์ ๋ฆฌ์์ค ์ ํ์ ์ค์ ํด์ผ ํ๋ค”, “์น์ธ๋ ๋ ์ง์คํธ๋ฆฌ์ ์ด๋ฏธ์ง๋ง ์ฌ์ฉํด์ผ ํ๋ค"์ฒ๋ผ ์กฐ์ง ์ฐจ์์ ์ ์ฑ ์ ๊ฐ์ ํด์ผ ํ๋ ์ํฉ์ด ์๊น๋๋ค. ์ด๋ฅผ ์ฝ๋ ๋ฆฌ๋ทฐ์๋ง ์์กดํ๋ฉด ๋๋ฝ์ด ๋ฐ์ํ๊ธฐ ์ฝ์ต๋๋ค. OPA Gatekeeper์ Kyverno๋ ์ด๋ฐ ์ ์ฑ ์ ํด๋ฌ์คํฐ ๋ ๋ฒจ์์ ์๋์ผ๋ก ๊ฐ์ ํ๋ ๋ํ์ ์ธ ๋๊ตฌ์ ๋๋ค.
๐ Kubernetes ์ ์ฑ ๊ด๋ฆฌ๋? #
์ฟ ๋ฒ๋คํฐ์ค๋ ๋ฆฌ์์ค ์์ฑยท์์ ยท์ญ์ ์์ฒญ์ด API ์๋ฒ์ ๋๋ฌํ์ ๋ ์ด๋ฅผ ๊ฒ์ฌํ๋ Admission Controller ํ๋ฌ๊ทธ์ธ์ ์ ๊ณตํฉ๋๋ค.
1kubectl apply
2 โ
3API Server
4 โ
5Authentication โ Authorization โ Admission Control โ etcd ์ ์ฅ
6 โ
7 Mutating / Validating Webhook
8 (Gatekeeper / Kyverno ๊ฐ ์ฌ๊ธฐ์ ๋์)๋ ๋๊ตฌ ๋ชจ๋ Webhook ํํ๋ก ๋์ํ๋ฉฐ ์์ฒญ์ ๊ฐ๋ก์ฑ ์ ์ฑ ์ค์ ์ฌ๋ถ๋ฅผ ๊ฒ์ฌํฉ๋๋ค.
Tip: ์ฟ ๋ฒ๋คํฐ์ค 1.25์์ Pod Security Policy(PSP)๊ฐ ์ ๊ฑฐ๋ ์ดํ OPA Gatekeeper์ Kyverno๊ฐ PSP์ ์ค์ง์ ์ธ ๋์์ผ๋ก ์๋ฆฌ์ก์์ต๋๋ค.
๐ต OPA Gatekeeper #
๊ฐ๋ ๋ฐ ์ํคํ ์ฒ #
OPA(Open Policy Agent) ๋ CNCF์์ ๊ด๋ฆฌํ๋ ๋ฒ์ฉ ์ ์ฑ ์์ง์ ๋๋ค. Gatekeeper๋ OPA๋ฅผ ์ฟ ๋ฒ๋คํฐ์ค Admission Webhook๊ณผ ์ฐ๊ฒฐํ๋ ๋ธ๋ฆฌ์ง ์ญํ ์ ํฉ๋๋ค.
1API Server
2 โ AdmissionReview ์์ฒญ
3 โผ
4Gatekeeper Webhook
5 โ Rego ์ ์ฑ
ํ๊ฐ
6 โผ
7OPA (์ ์ฑ
ํ๊ฐ ์์ง)
8 โ allow / deny ๋ฐํ
9 โผ
10API Server (์์ฒญ ํ์ฉ or ๊ฑฐ๋ถ)Gatekeeper v3.0๋ถํฐ๋ OPA Constraint Framework๋ฅผ ๊ธฐ๋ฐ์ผ๋ก CRD(Custom Resource Definition)๋ฅผ ํตํด ์ ์ฑ ์ ์ ์ธ์ ์ผ๋ก ๊ด๋ฆฌํฉ๋๋ค.
ConstraintTemplate โ ์ ์ฑ ํ ํ๋ฆฟ ์ ์ #
ConstraintTemplate์ ์ ์ฑ
์ ๋ก์ง(Rego)๊ณผ ํ๋ผ๋ฏธํฐ ์คํค๋ง๋ฅผ ์ ์ํ๋ ์ฒญ์ฌ์ง์
๋๋ค.
ํ ๋ฒ ์ ์ํ๋ฉด ์ฌ๋ฌ ํด๋ฌ์คํฐ, ์ฌ๋ฌ ๋ค์์คํ์ด์ค์ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค.
1apiVersion: templates.gatekeeper.sh/v1beta1
2kind: ConstraintTemplate
3metadata:
4 name: k8srequiredlabels
5spec:
6 crd:
7 spec:
8 names:
9 kind: K8sRequiredLabels
10 validation:
11 openAPIV3Schema:
12 properties:
13 labels:
14 type: array
15 items:
16 type: string
17 targets:
18 - target: admission.k8s.gatekeeper.sh
19 rego: |
20 package k8srequiredlabels
21
22 deny[{"msg": msg, "details": {"missing_labels": missing}}] {
23 provided := {label | input.review.object.metadata.labels[label]}
24 required := {label | label := input.parameters.labels[_]}
25 missing := required - provided
26 count(missing) > 0
27 msg := sprintf("ํ์ ๋ ์ด๋ธ์ด ๋๋ฝ๋์์ต๋๋ค: %v", [missing])
28 }Constraint โ ์ ์ฑ ์ธ์คํด์ค ์์ฑ #
ConstraintTemplate์ผ๋ก ๋ง๋ค์ด์ง CRD๋ฅผ ์ธ์คํด์คํํ์ฌ ์ค์ ์ ์ฑ
์ ์ ์ฉํฉ๋๋ค.
match ํ๋๋ก ์ ์ฉ ๋์์ ์ธ๋ฐํ๊ฒ ์ง์ ํ ์ ์์ต๋๋ค.
1apiVersion: constraints.gatekeeper.sh/v1beta1
2kind: K8sRequiredLabels
3metadata:
4 name: ns-must-have-owner
5spec:
6 match:
7 kinds:
8 - apiGroups: [""]
9 kinds: ["Namespace"]
10 parameters:
11 labels: ["owner", "env"]์ ์ ์ฑ
์ ์ฉ ํ owner, env ๋ ์ด๋ธ ์์ด ๋ค์์คํ์ด์ค๋ฅผ ์์ฑํ๋ฉด ์๋์ฒ๋ผ ๊ฑฐ๋ถ๋ฉ๋๋ค.
1Error from server ([ns-must-have-owner] ํ์ ๋ ์ด๋ธ์ด ๋๋ฝ๋์์ต๋๋ค: {"env", "owner"})์ค์น #
1# Helm์ผ๋ก ์ค์น
2helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
3helm install gatekeeper/gatekeeper \
4 --name-template=gatekeeper \
5 --namespace gatekeeper-system \
6 --create-namespace1# ๋๋ manifest๋ก ์ค์น
2kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml์ค์น ํ gatekeeper-system ๋ค์์คํ์ด์ค์ ์ปจํธ๋กค๋ฌ ํ๋๊ฐ ์์ฑ๋ฉ๋๋ค.
1kubectl get pods -n gatekeeper-system
2# NAME READY STATUS
3# gatekeeper-audit-xxxxxxxxx 1/1 Running
4# gatekeeper-controller-manager-xxxxxxxxx 1/1 RunningAudit ๊ธฐ๋ฅ #
Gatekeeper๋ Audit ๊ธฐ๋ฅ์ ํตํด ์ด๋ฏธ ๋ฐฐํฌ๋ ๋ฆฌ์์ค๋ ์ ์ฑ ์๋ฐ ์ฌ๋ถ๋ฅผ ๊ฐ์ฌํฉ๋๋ค. ์ ์ฑ ์ ์ฉ ์์ ์ด์ ์ ์์ฑ๋ ๋ฆฌ์์ค๊น์ง ์๊ธ ๊ฒ์ฌํ ์ ์์ต๋๋ค.
1# ์ ์ฑ
์๋ฐ ๋ฆฌ์์ค ํ์ธ
2kubectl get k8srequiredlabels ns-must-have-owner -o yaml
3# status.violations ํ๋์ ์๋ฐ ๋ฆฌ์์ค ๋ชฉ๋ก์ด ํ์๋ฉ๋๋ค๋ํ ํด๋ฌ์คํฐ ๋ด ๋ฆฌ์์ค๋ฅผ Gatekeeper์ ๋ณต์ ํ์ฌ ์ ์ฑ ๊ฐ ๋ฐ์ดํฐ ์ฐธ์กฐ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
1apiVersion: config.gatekeeper.sh/v1alpha1
2kind: Config
3metadata:
4 name: config
5 namespace: gatekeeper-system
6spec:
7 sync:
8 syncOnly:
9 - group: ""
10 version: "v1"
11 kind: "Namespace"๐ข Kyverno #
๊ฐ๋ ๋ฐ ์ํคํ ์ฒ #
Kyverno๋ Kubernetes Native ์ ์ฑ ์์ง์ ๋๋ค. ์ ์ฑ ์ Rego ๊ฐ์ ๋ณ๋ ์ธ์ด ์์ด ์์ YAML(CR)๋ก ํํํ ์ ์์ด ํ์ต ๊ณก์ ์ด ๋ฎ์ต๋๋ค.
1API Server
2 โ AdmissionReview ์์ฒญ
3 โผ
4Kyverno Webhook
5 โ ClusterPolicy / Policy ํ๊ฐ
6 โโโ Mutate โ ๋ฆฌ์์ค ์๋ ์์ ํ ํ์ฉ
7 โโโ Validate โ ๊ฒ์ฆ ์คํจ ์ ๊ฑฐ๋ถ
8 โโโ Generate โ ์ ๋ฆฌ์์ค ์๋ ์์ฑKyverno๋ 4๊ฐ์ ์ปจํธ๋กค๋ฌ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
| ์ปจํธ๋กค๋ฌ | ์ญํ |
|---|---|
| Webhook | AdmissionReview ์์ฒญ ์ฒ๋ฆฌ |
| Monitor | ํ์ ์ค์ (์นํ ๋ฑ) ์ ์ง ๊ด๋ฆฌ |
| PolicyController | ์ ์ฑ CR ๋ณ๊ฒฝ ๊ฐ์ |
| GenerateController | Generate ์ ์ฑ ์ผ๋ก ์์ฑ๋ ๋ฆฌ์์ค ๊ด๋ฆฌ |
์ค์น #
1# Helm์ผ๋ก ์ค์น (๊ถ์ฅ)
2helm repo add kyverno https://kyverno.github.io/kyverno/
3helm repo update
4helm install kyverno kyverno/kyverno \
5 -n kyverno \
6 --create-namespace1# ๋๋ manifest๋ก ์ค์น
2kubectl create -f https://raw.githubusercontent.com/kyverno/kyverno/main/config/install.yamlโ ๏ธ Kyverno๋ Kubernetes 1.14 ์ด์์ด ํ์ํฉ๋๋ค. ๊ณ ๊ฐ์ฉ์ฑ ํ๊ฒฝ์์๋
replicaCount=3์ผ๋ก ์ค์ ์ ๊ถ์ฅํฉ๋๋ค.
์ ์ฑ ์ ํ #
Kyverno ์ ์ฑ
์ ClusterPolicy(ํด๋ฌ์คํฐ ์ ์ฒด) ๋๋ Policy(๋ค์์คํ์ด์ค ๋ฒ์) ๋ ๊ฐ์ง ์ค์ฝํ๋ก ์์ฑํฉ๋๋ค.
| ์ ํ | ์ค๋ช | ์คํ ์์ |
|---|---|---|
| Validate | ๋ฆฌ์์ค๊ฐ ์ ์ฑ ์ ์ค์ํ๋์ง ๊ฒ์ฆ | ์์ฑยท์์ ์ |
| Mutate | ๋ฆฌ์์ค๋ฅผ ์๋์ผ๋ก ์์ | ์์ฑยท์์ ์ (Validate ์ ) |
| Generate | ์๋ก์ด ๋ฆฌ์์ค๋ฅผ ์๋ ์์ฑ | ํธ๋ฆฌ๊ฑฐ ๋ฆฌ์์ค ์์ฑ ์ |
| Cleanup | ๋ถํ์ํ ๋ฆฌ์์ค๋ฅผ ์๋ ์ญ์ | ์ฃผ๊ธฐ์ ์คํ |
Validate ์์ : ํ์ ๋ ์ด๋ธ ๊ฐ์ #
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4 name: require-app-label
5spec:
6 validationFailureAction: Enforce # Audit์ผ๋ก ๋ณ๊ฒฝํ๋ฉด ๊ฐ์ฌ ๋ชจ๋
7 rules:
8 - name: check-app-label
9 match:
10 any:
11 - resources:
12 kinds:
13 - Pod
14 validate:
15 message: "๋ ์ด๋ธ 'app.kubernetes.io/name' ์ด ํ์ํฉ๋๋ค."
16 pattern:
17 metadata:
18 labels:
19 app.kubernetes.io/name: "?*"์ ์ฑ ์๋ฐ ์ ์๋ ๋ฉ์์ง์ ํจ๊ป ๊ฑฐ๋ถ๋ฉ๋๋ค.
1Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
2resource Pod was blocked due to the following policies: require-app-labelMutate ์์ : ๋ฆฌ์์ค ์ ํ ์๋ ์ฃผ์ #
๋ ์ด๋ธ์ด๋ ๋ฆฌ์์ค ์ ํ์ด ์๋ Pod์ ๊ธฐ๋ณธ๊ฐ์ ์๋์ผ๋ก ์ฝ์ ํฉ๋๋ค.
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4 name: add-default-resources
5spec:
6 rules:
7 - name: add-resource-limits
8 match:
9 any:
10 - resources:
11 kinds:
12 - Pod
13 mutate:
14 patchStrategicMerge:
15 spec:
16 containers:
17 - (name): "*"
18 resources:
19 limits:
20 +(memory): "256Mi"
21 +(cpu): "500m"Tip:
+()ํ๊ธฐ๋ ํด๋น ํ๋๊ฐ ์์ ๋๋ง ๊ฐ์ ์ถ๊ฐํฉ๋๋ค. ๊ธฐ์กด ์ค์ ์ ๋ฎ์ด์ฐ์ง ์์ต๋๋ค.
Generate ์์ : ๋ค์์คํ์ด์ค ์์ฑ ์ NetworkPolicy ์๋ ์์ฑ #
์ ๋ค์์คํ์ด์ค๊ฐ ์์ฑ๋ ๋ ๊ธฐ๋ณธ NetworkPolicy๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค.
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4 name: default-network-policy
5spec:
6 rules:
7 - name: default-deny-ingress
8 match:
9 any:
10 - resources:
11 kinds:
12 - Namespace
13 generate:
14 apiVersion: networking.k8s.io/v1
15 kind: NetworkPolicy
16 name: default-deny-ingress
17 namespace: "{{request.object.metadata.name}}"
18 synchronize: true
19 data:
20 spec:
21 podSelector: {}
22 policyTypes:
23 - Ingressโ๏ธ OPA Gatekeeper vs Kyverno ๋น๊ต #
| ํญ๋ชฉ | OPA Gatekeeper | Kyverno |
|---|---|---|
| ์ ์ฑ ์ธ์ด | Rego (์ ์ฉ DSL) | YAML (Kubernetes ๋ค์ดํฐ๋ธ) |
| ํ์ต ๊ณก์ | ๋์ (Rego ํ์ต ํ์) | ๋ฎ์ |
| ์ ์ฉ ๋ฒ์ | ๋ฉํฐ ํ๋ซํผ (์ฟ ๋ฒ๋คํฐ์ค ์ธ ์ง์) | ์ฟ ๋ฒ๋คํฐ์ค ์ ์ฉ |
| ์ ์ฑ ์ ํ | Validate, Audit | Validate, Mutate, Generate, Cleanup |
| Mutate ์ง์ | ์ ํ์ | ๊ฐ๋ ฅํ ๊ธฐ๋ณธ ์ง์ |
| Generate ์ง์ | โ | โ |
| Audit | โ (๋ด์ฅ) | โ (๋ด์ฅ) |
| CLI ๋๊ตฌ | OPA CLI | Kyverno CLI |
| CNCF ๋ฑ๊ธ | Graduated | Incubating |
| ์ปค๋ฎค๋ํฐ ์ฑ์๋ | ๋์ | ๋น ๋ฅด๊ฒ ์ฑ์ฅ ์ค |
๐ฏ ์ด๋ค ๊ฒ์ ์ ํํ ๊น? #
OPA Gatekeeper๋ฅผ ์ ํํด์ผ ํ ๋:
- ์ฟ ๋ฒ๋คํฐ์ค ์ธ ๋ค๋ฅธ ์์คํ (Terraform, API ์๋ฒ ๋ฑ)์๋ ๋์ผํ ์ ์ฑ ์์ง์ ์ฌ์ฉํ๊ณ ์ถ์ ๊ฒฝ์ฐ
- ์ ์ฑ ๋ก์ง์ด ๋ณต์กํ์ฌ Rego์ ํํ๋ ฅ์ด ํ์ํ ๊ฒฝ์ฐ
- ์ด๋ฏธ OPA ์ํ๊ณ๋ฅผ ์ฌ์ฉ ์ค์ธ ๊ฒฝ์ฐ
Kyverno๋ฅผ ์ ํํด์ผ ํ ๋:
- ์ฟ ๋ฒ๋คํฐ์ค ์ ์ฉ ์ ์ฑ ๊ด๋ฆฌ๊ฐ ๋ชฉ์ ์ธ ๊ฒฝ์ฐ
- Rego ํ์ต ์์ด ๋น ๋ฅด๊ฒ ๋์ ํ๊ณ ์ถ์ ๊ฒฝ์ฐ
- Mutate, Generate ๋ฑ ๋ค์ํ ์ ์ฑ ์ ํ์ด ํ์ํ ๊ฒฝ์ฐ
- GitOps ํ๊ฒฝ์์ YAML ๊ธฐ๋ฐ์ผ๋ก ์ ์ฑ ์ ๊ด๋ฆฌํ๊ณ ์ถ์ ๊ฒฝ์ฐ
Tip: ๋ ๋๊ตฌ๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ๊ฒ๋ ๊ฐ๋ฅํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ณต์กํ ๊ฒ์ฆ ์ ์ฑ ์ OPA Gatekeeper๋ก, ๋ฆฌ์์ค ์๋ ๋ณํยท์์ฑ์ Kyverno๋ก ์ญํ ์ ๋ถ๋ดํ ์ ์์ต๋๋ค.