์ฟ ๋ฒ๋คํฐ์ค๋ ๋ค๋ฅธ ํ๋ซํผ์ฒ๋ผ ์ธ์ฆ(Authentication)/์ธ๊ฐ(Athorization) ๋ฅผ ์ ๊ณตํด์ค๋๋ค. ๋ฐ๋ผ์ ์ธ์ฆ๋ฐ์ง ๋ชปํ ์ฌ์ฉ์๋ผ๋ฉด 401 (UnAuthorized) ๋ฅผ ์๋ต ๋ฐ๊ณ , ์ธ์ฆ์ ๋์์ง๋ง ๊ถํ์ด ์๋ค๋ฉด 403(Forbidden) ์๋ต์ ๋ฐ๊ฒ ๋์ฃ .
์์ฃผ ํ๋ฒํ ์ธ์ฆ/์ธ๊ฐ ํ๋ก์ธ์ค์ ๋๋ค. ๊ทธ๋ฐ๋ฐ ์ธ์ฆ/์ธ๊ฐ๊ฐ ์๋ฃ๋์๋ค๊ณ ์ฟ ๋ฒ๋คํฐ์ค ํ๊ฒฝ์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ๊ฑด ์๋๋๋ค. ์ฌ์ฉ์๊ฐ ๋ณด๋ธ ์์ฒญ์ ์ ์ฉํ๊ธฐ ์ง์ ํ ๊ตฐ๋ฐ๋ฅผ ๋ ๋ค๋ฆฝ๋๋ค. ๊ทธ๊ฒ์ด ๋ฐ๋ก Adimission Controller ์ ๋๋ค.
์ด๋ฒ ๊ธ์์๋ Adimission Controller ์ ๋ํ ๊ฐ๋ ์ ๊ฐ๋จํ๊ฒ ์ค๋ช ํ๊ณ , Adimission Controller ๋ฅผ ๊ตฌํํด๋ณผ ์๊ฐ์ ๋๋ค. ๐
Admission Controller ๊ฐ ๋ญ์ฃ ?
๊ณต์ ๋ฌธ์์ ์๋ Admission Controller ๊ด๋ จ ๊ธ์ ๋ฒ์ญํด๋ณด์๋ฉด ์ด๋ ์ต๋๋ค.
Admission Controller ๋ ํด๋ฌ์คํฐ์ ์ฌ์ฉ ๋ฐฉ์์ ์ ์ดํ๋ ํ๋ฌ๊ทธ์ธ์ ๋๋ค. ์ธ์ฆ/์ธ๊ฐ๋ API ์์ฒญ์ Admission Controller ์ด ๊ฐ๋ก์ฑ์ด ์์ฒญ ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํ๊ฑฐ๋ ์์ฒญ์ ๋ชจ๋ ๊ฑฐ๋ถํ ์ ์๋ ๊ฒ์ดํธํคํผ๋ก ์๊ฐํ ์ ์์ต๋๋ค.
์ด ๊ทธ๋ฆผ์ด ๋ฐ๋ก Admission Controller ์ Phases ์ด์ฃ . ์ธ์ฆ(Authentication)/์ธ๊ฐ(Athorization) ๋ฅผ ๊ฑฐ์น ํ Mutating Admission ๊ณผ Validating Admission ์ด API Request ๋ฅผ ๊ฐ๋ก์ฑ๊ฒ ๋๋๋ฐ ์ด 2๊ฐ๊ฐ Admission Controller ์ ์ปดํฌ๋ํธ์ ๋๋ค.
๊ฐ๋ก์ฑ API Request ๋ WebHook ๋ฐฉ์์ผ๋ก Admission Controller Server ๋ก ๋ณด๋ด์ง๊ฒ ๋ฉ๋๋ค. ๊ทธ๋ผ ์ฌ๊ธฐ์ ์์ฒญ์ ๋ณ๊ฒฝํ๊ฑฐ๋ ๊ฑฐ๋ถํ ์ ์๋ ๋ก์ง์ ๊ตฌํํ๊ฒ ๋์ฃ .
Mutating Admission ์ ์ฃผ๋ก API Request ์ ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํ๋ ์ฉ๋์ด๊ณ , Validating Admission ์ ์ฃผ๋ก API Request ๋ฅผ ๊ฑฐ๋ถํ ์ง๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
๋ ผ๋ฆฌ์ ์ผ๋ก๋ ์ด ๋์ ์ญํ ์ด ๊ฐ๊ฐ ๋ค๋ฅด์ง๋ง Validating Admission ์์ ๊ผญ ๊ฑฐ๋ถ๋ง ํ ํ์ ์์ด ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํด๋ ๋ฌด๋ฐฉํฉ๋๋ค.
Admission Controller ์ ๋ํ์ ์ธ ์๋ก๋ LimitRanger ๊ฐ ์์ต๋๋ค. ์์์ Admission Controller ์ ํ๋ฌ๊ทธ์ธ์ด๋ผ๊ณ ๋ง์๋๋ ธ์ฃ ? 30๊ฐ ์ ๋์ ํ๋ฌ๊ทธ์ธ์ ์ฟ ๋ฒ๋คํฐ์ค์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํด์ฃผ๋๋ฐ LimitRanger ์ ๊ทธ ์ค ํ๋์ ๋๋ค.
LimitRanger ์ ์ฟ ๋ฒ๋คํฐ์ค์ ํ๋๋ฅผ ๋ฐฐํฌํ ๋ ๋ง์ฝ resource ๋ฅผ ์ง์ ํ์ง ์์๋ค๋ฉด default resource ๋ฅผ ์์ฑํ์ฌ ์๋์ผ๋ก ์ค์ ํด๋์ resource ๋ฅผ ๊ฐ๊ฒ๋๊ฑฐ๋, resource ๋ฅผ ์ง์ ํ์ง ์์ ํ๋๋ ๋ฐฐํฌ ์ ํ์ ํ ์ ์๋๋ก ๋์์ค๋๋ค.
์ ์ฉํ๋ ๋ฐฉ๋ฒ๋ ์ด๋ ต์ง ์์ต๋๋ค. kube-apiserver.yaml ์์ ์๋์ ๊ฐ์ด ์ถ๊ฐ๋ง ํด์ฃผ๋ฉด ๋ฉ๋๋ค.
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.178.0.2:6443
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --encryption-provider-config=/etc/kubernetes/etcd/ec.yaml
- --anonymous-auth=true
- --advertise-address=10.178.0.2
- --allow-privileged=true
- --authorization-mode=Node,RBAC
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --enable-admission-plugins=LimitRanger # ์ถ๊ฐ
...
LimitRanger ๋ง๊ณ ๋์ฌ๊ฒจ๋ณผ๋งํ ํ๋ฌ๊ทธ์ธ์ PodSecurityPolicy ๊ฐ ์๋๋ฐ์. ์ด ํ๋ฌ๊ทธ์ธ Pod ์ ๊ด๋ จ๋ ๋ณด์ ์ ์ฑ ์ ์ง์ ํ ์ ์๊ฒ ๋์์ฃผ๋ ํ๋ฌ๊ทธ์ธ์ ๋๋ค.
์๋ฅผ ๋ค๋ฉด SecurityContext ๋ 1000 ์ด ์๋ root ๋ก ์์ฑํ๋ ค๊ณ ํ๋ค๋ฉด ํ๋ ์์ฑ์ ๊ฑฐ๋ถํ ์ ์๋ ์ ์ฑ ์ ์ ์ฉํ ์ ์์ฃ .
ํ์ง๋ง PodSeuciryPolicy ๋ ์ฟ ๋ฒ๋คํฐ์ค v1.21 ์์ deprecated ๋์๊ณ , v1.25 ์์ ์์ ํ ์ญ์ ๋๊ธฐ ๋๋ฌธ์ ์ฌ์ฉํ ์๊ฐ ์์ต๋๋ค. ๊ทธ๋ผ ์ด๋ฌํ ์ ์ฑ ์ ์ด๋ป๊ฒ ์์ฑํด์ ์ ์ฉํด์ผ ํ ๊น์?
๋ต์ ๊ฐ๋จํฉ๋๋ค. ๋ง๋ค๋ฉด ๋์ฃ ๐ค
๊ทธ๋ผ ํ๋ฒ ๊ฐ๋จํ๊ฒ Admission Controller ๋ฅผ ๋ง๋ค์ด๋ณด์ฃ !
Admission Controller ๋ฅผ ์ง์ ๋ง๋ค์ด๋ ๋์ง๋ง, ์ด๋ฏธ ์ ๋ง๋ค์ด์ง CNCF ํ๋ก์ ํธ ์ผ๋ถ์ธ OPA Gatekeeper ๋ผ๋ ๊ฒ์ด ์์ต๋๋ค. ์ฟ ๋ฒ๋คํฐ์ค์์ ๋งค์ฐ ๋ฐ๊ณ ์๋ ํ๋ก์ ํธ์ด๊ธฐ๋ ํ๊ณ , ๊ฐ๋ฐํด์ผํ๋ค๋ ๋ฒ๊ฑฐ๋ก์ ๋๋ฌธ์ ์๋ง ๋ง์ ๋ถ๋ค์ด OPA Gatekeeper ๋ฅผ ์ฌ์ฉํ์ง ์์๊น ๋ผ๋ ์๊ฐ์ ๋ญ๋๋ค.
๊ทธ๋๋ ์๋ฆฌ๋ ๋ฌด์์ธ์ง ํ์ ํ๊ณ ์ถ์ผ๋ ๋ง๋ค์ด๋ณด์ฃ .
Admission Controller ๋ฅผ ๋ง๋ค์ด๋ณด์!
๋งค์ฐ ๊ฐ๋จํ๊ฒ ๋ง๋ค๊ฒ์ด๊ธฐ ๋๋ฌธ์ Node.js ์ Express ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค!
Node ๋ฒ์ ์ 18.5 ์์ ์งํ๋ฉ๋๋ค.
Express ํ๋ก์ ํธ๋ฅผ ์์ฑํด์ค๋๋ค. ์ดํ package.json ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
"scripts": {
"start": "node ./bin/www",
"dev": "nodemon app start",
"prod": "pm2-runtime start app.js -i 1"
}
ํ๋ก์ ํธ ๊ด๋ จ npm ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ๊ณ , ์ ์์ ์ผ๋ก ๋์ํ๋ ์ง ํ์ธํฉ๋๋ค.
npm install -i nodemon pm2
npm run dev
Admission Controller Server ๋ ๋ฐ๋์ TLS ํต์ ์ ํด์ผํฉ๋๋ค.
์ฆ, http ํต์ ์ด ์๋ https ๋ก ํต์ ํด์ผํฉ๋๋ค.
์ฝ๋๋ก๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
const bodyParser = require('body-parser');
const express = require('express');
const fs = require('fs');
const https = require('https');
const app = express();
app.use(bodyParser.json());
# ์๋ฒ 8443 ๋ฆฌ์ค๋
const port = 8443;
# tls ํต์ ์ ์ฌ์ฉํ ca.crt, server.crt, server.key ์ฝ๊ธฐ
const options = {
ca: fs.readFileSync('ca.crt'),
cert: fs.readFileSync('server.crt'),
key: fs.readFileSync('server.key'),
};
# https ํต์
const server = https.createServer(options, app);
# ์๋ฒ ์คํ
server.listen(port, () => {
console.log(`Server running on port ${port}/`);
});
๊ทธ๋ผ ์ด๋ฒ์ ca.crt, server.crt, server.key ๋ฅผ ์์ฑํด์ฃผ์ฃ .
# CA Key ์ CA CRT ์์ฑ
# X.509๋ PKI ๊ธฐ์ ์ค์์ ๊ฐ์ฅ ๋๋ฆฌ ์๋ ค์ง ํ์ค ํฌ๋งท
openssl req -nodes -new -x509 -keyout ca.key -out ca.crt -subj "/CN=Admission Controller Webhook Demo CA" -sha256
# ์๋ฒ Key ์์ฑ
openssl genrsa -out server.key 2048
# server.conf ์์ฑ
cat >server.conf <<EOF
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
CN = admission-controller-server.default.svc
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = admission-controller-server.default.svc
EOF
# CSR ์์ฑ
openssl req -new -key server.key -out server.csr -config server.conf
# Server CRT ์์ฑ
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -extensions v3_req -extfile server.conf -sha256
์ ๋ช ๋ น์ด๋๋ก ์งํํ๋ ๊ฑธ ์ถ์ฒ๋๋ฆฝ๋๋ค.
๊ทธ๋ผ ์ด์ health check ์ฉ ์๋ํฌ์ธํธ์ webhook ์ฉ ์๋ํฌ์ธํธ ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค.
app.get('/health', (req, res) => {
res.send('ok');
});
app.post('/', (req, res) => {
if (req.body.request === undefined) {
res.status(400).send();
return;
}
console.log(req.body); // DEBUGGING
const { request: { uid } } = req.body;
res.send({
apiVersion: "admission.k8s.io/v1",
kind: "AdmissionReview",
response: {
uid,
allowed: validate(req)
}
})
});
// Create Pod ์ ๋ํ ์ ํ
function validate(req) {
if (req.body['request']['object']['kind'] == 'Pod' && req.body['request']['operation'] == 'CREATE') {
return false
} else {
return true
}
}
GET /health ๋ health check ์ฉ ์๋ํฌ์ธํธ์ด๊ณ , POST / ๋ webhook ์ฉ ์๋ํฌ์ธํธ ์ ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฐ๋จํ๊ฒ Create Pod ์ ๋ํ ์ด๋ฒคํธ๋ฅผ ๊ฑฐ๋ถํฉ๋๋ค.
์ฆ, Pod ์์ฑ์ ์ ํ๋์ง๋ง Pod ์ธ์ Deploy ๋ Service ๋ฑ์ ์์ฑ์ด ๊ฐ๋ฅํฉ๋๋ค.
์ฐธ๊ณ ๋ก ์ฟ ๋ฒ๋คํฐ์ค๊ฐ webhook ์ผ๋ก ๋ณด๋ด๋ Request ๊ฐ์ฒด๋ ์๋์ ๊ฐ์ต๋๋ค.
์์ธํ ์ ๋ณด๋ ์ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "687a5fd9-0939-4d17-a398-d80582b199f8",
"kind": {
"group": "",
"version": "v1",
"kind": "Pod"
},
"resource": {
"group": "",
"version": "v1",
"resource": "pods"
},
"requestKind": {
"group": "",
"version": "v1",
"kind": "Pod"
},
"requestResource": {
"group": "",
"version": "v1",
"resource": "pods"
},
"name": "hello-pod",
"namespace": "default",
"operation": "CREATE",
"userInfo": {
"username": "kubernetes-admin",
"groups": []
},
"object": {
"kind": "Pod",
"apiVersion": "v1",
"metadata": [],
"spec": [],
"status": []
},
"oldObject": null,
"dryRun": "false",
"options": {
"kind": "CreateOptions",
"apiVersion": "meta.k8s.io/v1",
"fieldManager": "kubectl-client-side-apply",
"fieldValidation": "Strict"
},
"apiVersion": "v1"
}
}
๊ทธ๋ผ ์ด์ ์ด๋ฏธ์ง๋ก ๋ง๋ค ์ ์๋๋ก Dockerfile ์ ์์ฑํ์ฃ .
FROM node:12.18.2
RUN mkdir /var/node
COPY ./ /var/node
WORKDIR /var/node
RUN npm i
RUN npm i -g pm2
CMD [ "npm", "run", "prod" ]
๋ชจ๋ ์ค๋น๊ฐ ๋๋ฌ์ต๋๋ค. ๋ฐ๋ก ์ฟ ๋ฒ๋คํฐ์ค ํ๊ฒฝ์์ ๋ฐฉ๊ธ ๋ง๋ Adimission Controller Server ๋ฅผ ๋ฐฐํฌํด๋ณด์ฃ ๐
์ฟ ๋ฒ๋คํฐ์ค์ ๋ฐฐํฌ
์ ์ฟ ๋ฒ๋คํฐ์ค ํด๋ฌ์คํฐ๋ kubeadm ์ผ๋ก ๋ง๋ค์์ผ๋ฉฐ, v1.25 ์ ๋๋ค.
admission-controller-server.yaml ์ ๋ง๋ญ๋๋ค.
apiVersion: v1
kind: Service
metadata:
name: admission-controller-server
spec:
ports:
- port: 443
protocol: TCP
targetPort: 8443
selector:
run: admission-controller-server
---
apiVersion: v1
kind: Pod
metadata:
labels:
run: admission-controller-server
name: admission-controller-server
spec:
containers:
- image: kingbj0429/admission-controller-server
name: admission-controller-server
ports:
- containerPort: 8443
imagePullPolicy: Always
livenessProbe:
httpGet:
port: 8443
path: /health
scheme: HTTPS
readinessProbe:
httpGet:
port: 8443
path: /health
scheme: HTTPS
์ฌ๊ธฐ์ ์ ๋ง์ ๋ง ์ค์ํ๊ฒ ์๋๋ฐ Service ์ .metadata.name ์ ์ server.crt ๋ฅผ ์์ฑํ๊ธฐ ์ํด ์ฌ์ฉํ๋ server.conf ์ ๋ช ์ํ CN ๊ณผ ์ด๋ฆ์ด ๊ฐ์์ผ ํฉ๋๋ค.
Service ์ด๋ฆ์ด admission-controller-server ์ด๊ณ , Namespace ๊ฐ default ์ด๋ admission-controller-server.default.svc ๊ฐ ๋์ด์ผ ํฉ๋๋ค.
์ด์ ๋ง์ง๋ง ๋จ๊ณ์ธ WebhookConfiguration ์ ์์ฑํด์ฃผ์ฃ . mutate ์ valid 2 ์ข ๋ฅ๊ฐ ์๋๋ฐ, ์ฌ๊ธฐ์ valid ๋ก ์์ฑํ๊ฒ ์ต๋๋ค.
์์ธํ ์ฌํญ์ ์ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: "pod-policy.example.com"
webhooks:
- name: "pod-policy.example.com"
rules:
- apiGroups: ["*"]
apiVersions: ["*"]
operations: ["CREATE"]
resources: ["*"]
scope: "Namespaced"
clientConfig:
service:
namespace: "default"
name: "admission-controller-server" # CN ๊ณผ ์ผ์นํด์ผํจ
caBundle: $(cat ca.crt | base64 | tr -d '\n')
admissionReviewVersions: ["v1"]
sideEffects: None
timeoutSeconds: 5
๊ฐ๋จํ ์ค๋ช ํ์๋ฉด CREATE ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๊ฒฝ์ฐ clientConfig ์ ๋ฑ๋ก๋ Service ๋๋ฉ์ธ(admission-controller-server)์ผ๋ก webhook ์ ๋ณด๋ด๊ฒ ๋ฉ๋๋ค.
๋ชจ๋ ์ค๋น๊ฐ ๋๋ฌ์ผ๋ ์ ๋๋ก ๋์๋ ์ง ํ์ธํด๋ณด์ฃ .
Admission Controller Server ๊ฒ์ฆ
์์ฃผ ์ฌํํ Pod ๋ฅผ ์์ฑํด๋ณด์ฃ . ์ ๋๋ก ๋ง๋ค์๋ค๋ฉด ์ ํ์ด ๋๊ฒ ์ฃ ?
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
Admission Webhook Denied ๊ฐ ๋ ๊ฑธ ๋ณด๋ ๋ฌธ์ ๊ฐ ์์ด ๋ณด์ ๋๋ค. ๊ทธ๋ผ ๋ค๋ฅธ ๋ฆฌ์์ค๋ ์ด๋จ๊น์?
Error from server: error when creating "nginx.yaml": admission webhook "pod-policy.example.com" denied the request without explanation
Deploy ๋ก ํ ์คํธ ํด๋ณด์ฃ .
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
์์ฑ์ด ์์ฃผ ์๋ฉ๋๋ค.
deployment.apps/nginx-deployment created
์ด๋ฒ ๊ธ์์๋ Admission Controller ์ ๋ํด ์์๋ณด๊ณ ์ง์ Server ๋ฅผ ์์ฑ๊น์ง ํด๋ณด์์ต๋๋ค.
๊ธด ๊ธ ์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค ๐
์ฝ๋๋ ๊นํ์ ์์ต๋๋ค