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