만약, Kubernetes cluster network 외부에서 kubernetes service에 access한다고 가정하면
아래와 같이 kubectl port-forward 명령으로 외부 IP traffic을 허용할 수 있다.
Example: Jenkins Web Dashboard 접속한다고 가정하고 테스트해보기
구성 예시:
kubernetes service의 port는 8080
Web browser로 접속할 port는 8180
즉, 외부에서 8180으로 접속시도하면, 8080으로 port forward하도록 하려면, 아래와 같이 명령을 수행하면 된다.
##
## --addresss=0.0.0.0은 모든 IP Address를 수용하겠다는 뜻이다.
##
## 8180:8080은 Kubernetes Cluster 외부의 Client가 8180 포트로 접속 요청하면
## Kubernetes Cluster 내부의 8080 서비스 포트로 Forward해주겠다는 뜻이다.
##
$ kubectl port-forward --address=0.0.0.0 \
$(kubectl get pod -l app.kubernetes.io/name=jenkins -o jsonpath='{.items[0].metadata.name}') \
8180:8080
위와 같이 설정하고 Web browser에서 bastion node로 접속 시도한다. (아래 예제 명령처럼 접속한다.)
$ curl http://bastion.my-kube.node:8180/
위 예시는 Pod Name을 이용한 것이고, Service 이름을 이용해서도 port-forward 설정할 수 있다. (아래 명령을 참고)
##
## Cluster 밖의 Client가 9999 port 번호로 요청하면,
## 80 port 번호로 바꾸어서 almighty 네임스페이스의 almighty 서비스로 forwarding한다.
##
$ kubectl port-forward --address=0.0.0.0 -n almighty service/almighty 9999:80
Forwarding from 0.0.0.0:9999 -> 8080
Handling connection for 9999
...
참고: 위 kubectl port-forward 명령을 수행한 node가 bastion이라면, web browser가 접속할 Server(Target) 주소도 bastion의 IP address이어야 한다.
위 블로그를 따라 해도 Ingress Gateway가 잘 동작하고, 아래와 같이 내가 만든 Practice YAML을 이용해도 Ingress Gateway가 잘 동작한다.
Practice
일반적인 준비 절차:
K8s cluster에 'ingress controller'가 동작 중인지 확인한다.
(만약 설치된 ingress controller가 없으면, nginx ingress controller를 설치할 것!)
$ kubectl get all -n ingress-nginx
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-controller-2bzz7 1/1 Running 0 6h23m
pod/ingress-nginx-controller-5rnw8 1/1 Running 0 6h23m
pod/ingress-nginx-controller-69jf2 1/1 Running 0 6h23m
pod/ingress-nginx-controller-j9nwr 1/1 Running 0 6h22m
pod/ingress-nginx-controller-xzc79 1/1 Running 0 6h22m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx-controller LoadBalancer 10.233.15.71 10.10.9.180 80:31302/TCP,443:31071/TCP 57d
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/ingress-nginx-controller 5 5 5 5 5 kubernetes.io/os=linux 57d
가능하면, 위 예제처럼 service type이 ‘LoadBalancer’가 되도록하고, cluster 외부에서 직접 접속 가능한 주소를 할당한다. (예: 10.10.9.180) 위 예제대로라면, Cluster 밖에 있는 Web Browser는 80, 443 포트로 접속 가능하다.
Ingress Object 설정 및 생성
내 Pod에 접근할 수 있는 Ingress object를 생성한다.
아래와 같이 내 서비스에 접근할 수 있도록 Ingress manifest 파일을 작성한다.
MetalLB의 버전에 따라 설치 결과가 조금씩 달라서 MetalLB를 설치한 날짜를 밝힌다.
아래 절차를 따라하면 MetalLB 설치, 그리고 예제 앱 테스트 모두 잘 동작한다.
MetalLB 설치에 관한 공식 문서는 아래 Web Docs를 참고 https://metallb.universe.tf/installation/
위 Web Docs는 여러 도구를 이용한 설치 방법을 설명하지만, 나는Manifest 방식을 이용하여 MetalLB를 설치했다.
위 Web docs를 보면서 따라해보면, 막힘없이 쭉쭉~ 잘 설치되었다.
설치 완료한 이후에 LB 테스트를 해봤는데 잘 동작했다.
내가 설치한 환경은 이렇다.
Kubernetes: v1.25.3 CNI: Calico v3.24.5
내가 Web docs를 보면서 수행했던 명령 및 설정 파일 편집한 내역만 추려보면 이렇다.
strict ARP mode 활성화하기
아래 예제처럼 kube-proxy configmap을 수정한다.
(strictARP항목을false에서true로 변경)
## 아래 명령을 수행
$ kubectl edit configmap -n kube-system kube-proxy
... 중간 생략 ...
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
strictARP: true ## 이 부분을 수정한다. false -> true
... 중간 생략 ...
Manifest 이용하여 Metal LB 설치하기
아래 명령을 따라서 수행한다.
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml
##
## MetalLB가 잘 설치되었다면,
## 아래와 같이 metallb-controller와 metallb-speaker pod가 Running 상태로 보일 것이다.
##
$ kubectl get -n metallb-system pod
NAME READY STATUS RESTARTS AGE
controller-84d6d4db45-g94mh 1/1 Running 0 31s
speaker-6jqzw 1/1 Running 0 31s
speaker-hlrg7 1/1 Running 0 31s
speaker-sq94q 1/1 Running 0 31s
## 참고: speaker가 running 상태가 되려면 대략 25~30초 정도 걸린다.
## Pod의 상태가 ConfigErr로 보인다고 당황해하지 않아도 된다.
## 30초 후에 Running 상태로 변경될거다.
주의할 점: 위와 같이 L2Advertisement, IPAddressPool을 변경하고 적용해도 실제로 Service resource가 생성될 때, 과거의 IPAddressPool을 이용한다. 아마 MetalLB 버그 같은데, 과감하게 metallb-system 네임스페이스의 controller pod를 종료시키고 재기동하면 새로운 IPAddressPool이 적용된다. (아래 명령을 참고)
$ kubectl get -n metallb-system pod -l component=controller
NAME READY STATUS RESTARTS AGE
controller-84d6d4db45-vgt9r 1/1 Running 0 6m12s
$ kubectl delete -n metallb-system pod controller-84d6d4db45-vgt9r
## 20초 정도 후에 Pod가 재기동되고, 그 뒤로 새로운 IPAddressPool이 정상적으로 반영된다.
Example App을 이용하여 MetalLB 동작 확인
$ cat >> my-example.yaml <<-EOF
apiVersion: v1
kind: Pod
metadata:
name: almighty
labels:
app: almighty
spec:
terminationGracePeriodSeconds: 3
containers:
- name: almighty
image: docker.io/andrewloyolajeong/almighty:0.2.4
---
apiVersion: v1
kind: Service
metadata:
name: almighty
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app: almighty
ports:
- name: myweb
protocol: TCP
port: 8080
targetPort: 8080
- name: yourweb
protocol: TCP
port: 80
targetPort: 80
EOF
$
$ kubectl create ns ns1
$ kubectl apply -n ns1 -f my-example.yaml
$ kubectl get -n ns1 svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
almighty LoadBalancer 10.104.156.217 10.1.4.150 8080:30859/TCP,80:31917/TCP 116s
$
##
## 위 결과에서 EXTERNAL-IP에 적절한 IP Address가 출력되면, 잘 되는 것이다.
## 그리고 아래와 같이 실제로 MetalLB가 할당해준 10.1.4.150 주소를 통해 Networking이 되는지 확인한다.
##
$ curl 10.1.4.150:8080
Hello from example application. (written by Andrew)
$ curl 10.1.4.150:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
$
올레~~~ 잘 동작한다 ^^
참고: ping 명령으로 EXTERNAL-IP 통신 여부를 확인하지 말 것 !!!
MetalLB 설치하고, 많은 사용자들이 실수하는 것이 아래처럼 ping 명령으로 External-IP로 ICMP 패킷이 전달되는지 체크한다.
$ ping 10.10.12.43
MetalLB가 만든 External-IP 주소는 K8S Cluster의 특정 Worker Node의 Netfilter에만 존재하는 Chain Rule일 뿐이다.
즉, OS Kernel이 물린 Network Port의 MAC Address에 Binding한 IP Address가 아니다보니, OS Kernel이 ICMP Echo Request에 대해 반응할리 없다. 단, K8s LoadBalancer 구현체(즉, MetalLB)의 구현에 따라서 MetalLB가 직접 ICMP Echo Response를 만들 수는 있지만, 이것은 구현체의 버전에 따라 다를 수 있으니까 ICMP Echo(즉, Ping)에 대한 테스트를 시도하지 않는 것이 좋다.
아래와 같이 CURL을 이용하여 테스트하는 것이 정확한 테스트 방법이다.
$ curl https://10.10.12.43:8443
다시 한번 강조한다. 절대 ping 명령으로 MetalLB의 External-IP 동작 유무를 체크하지 말자 !!!
참고: Address 설정은 모든 대역의 주소가 다 가능한다.
아래 설정 예제처럼 특정 addresses 대역을 사용하겠다고 지정하면, MetalLB Speaker가 알아서 동일한 Broadcast Domain에 해당하는 Network Port로 GARP(Gratuitous ARP)를 Broadcasting한다.
$ vi metallb/values.yaml
... 중간 생략 ...
configInline:
address-pools:
- name: default
protocol: layer2
addresses:
- 10.10.12.40/29
... 중간 생략 ...
따라서 Worker Node에 Network Port가 여러 개 있다면, 위 address-pools에 설정된 ip address 대역 중에서 broadcast domain이 일치하는 Network Port로 GARP 패킷이 흘러나간다.
참고: 특정 Service에 특정 External-IP(Public IP)를 고정해서 사용하기
External-IP는 Kubernetes Cluster 밖에 있는 Client App이 접근하기 위한 주소이기 때문에 고정하는게 운영상 편하다.
그런데 MetalLB는 IP Address Range를 설정하도록 되어 있기 때문에 K8s Service 생성시 어떤 External-IP가 K8s Service에 할당될지 알 수 없다.
이럴 때 아래와 같이 K8s Service에 설정을 추가하면 특정 External-IP(즉, Public IP Address)를 고정해서 사용할 수 있다.
$ cat my-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: mysvc
(... 중간 생략 ...)
spec:
type: LoadBalancer
## MetalLB의 IP Address Range가 211.10.3.160 ~ 190 이라고 가정하고
## 이 mysvc에 211.10.3.165 주소를 고정해서 사용하고 싶아면 아래와 같이 사용한다.
loadBalancerIP: 211.10.3.165
(... 중간 생략 ...)
Kubernetes cluster 운영 환경에서 Pod간 주고 받은 IP packet을 tcpdump하는 방법을 알아보겠다.
방식 A: Container 내부에 tcpdump 프로그램을 설치하고, Container 내부에서 직접 tcpdump 명령을 수행
$ kubectl exec -n my-namespace my-pod -c my-container -it -- bash
(In-Container) $ apt install -y tcpdump
... 중간 생략 ...
##
## 참고: Kubernetes Pod 내부의 기본 NIC은 eth0이다.
## 만약, Multus CNI를 사용해서 Multi-NIC을 사용하는 경우라면,
## eth0 이외에 net1, net2 같은 이름으로 NIC이 있을 것이다.
## 따라서, 내가 운영하는 CNI가 1개인지 또는 어떤 종류의 CNI이 인지에 따라
## NIC의 이름이 다를 수 있으니까, ifconfig 명령 또는 `ip a` 같은 명령으로
## NIC 이름을 확인하고 tcpdump 명령을 수행해야 한다.
##
(In-Container) $ tcpdump -i eth0 -w my-capture.pcap
... 중간 생략 ...
위와 같이 생성한 pcap 파일은 Pod 외부에서 볼 수 없기 때문에
1) Pod 내부에서 scp 명령으로 외부로 옮겨주던지
2) 또는 NFS, Ceph storage 같은 PV를 이용하여 저장하는 것을 권장한다.
방식 B: Worker node에서 특정 Pod의 namespace를 이용하여 tcpdump 명령을 수행
예제를 이용하여 설명해보겠다.
'productpage' Pod가 주고 받은 IP packet을 tcpdump하려는 상황을 가정해보자.
우선 'productpage' Pod가 어느 worker node에 있는지 확인한다.
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
... 중간 생략 ...
productpage-v1-6b746f74dc-xfjdz 2/2 Running 0 2d17h 10.244.2.4 worker-1
... 중간 생략 ...
'productpage' pod가 worker-1 node에 있으니까, worker-1에 ssh 접속하여 아래의 스크립트를 수행한다.
##
## Filename : run-tcpdump.sh
##
#!/usr/bin/bash
container_id=$(docker container ls | grep k8s_istio-proxy_productpage | awk '{ print $1 }')
echo "Container ID: $container_id"
container_pid=$(docker inspect -f '{{.State.Pid}}' $container_id)
echo "Container PID: $container_pid"
nsenter -t $container_pid -n /bin/bash -xec 'tcpdump -i any -w productpage-envoy-any.pcap'
## NOTE: After running this script to capture the packet,
## Use the packet filter as 'tls or http' with your Wireshark program !
위 `run-tcpdump.sh` 스크립트를 수행하고 나서 만들어진 pcap 파일을 Wireshark으로 열어본다.
그런데, 위 "방식 A, 방식 B"는 Kubernetes를 사용했을 때나 가능한 얘기이고, OCP(Openshift Container Platform)을 사용하는 경우라면 위 방식으로 container 내부의 network packet을 dump할 수 없다 !!! 왜? OCP, OKD Cluster의 Worker Node에는 'docker' command가 없다. 그러면 어떻게 하는가? 아래와 같이 "방식 C"를 따라해야 한다.
방식 C: OCP의 Node에서 특정 Pod의 namespace를 이용하여 tcpdump 명령을 수행
예제를 이용하여 설명해보겠다.
(worker2 node에 Pod가 구동되어 있다고 가정하고 설명한다)
##
## Working Location: Bastion Node, or PC (MacBook)
##
$ oc debug node/worker1.andrew.space
Starting pod/worker1.andrew.space-debug ...
To use host binaries, run `chroot /host`
Pod IP: 10.10.11.12
If you don't see a command prompt, try pressing enter.
sh-4.4# chroot /host
sh-4.4# container_id=$(crictl ps | grep my-example-app | awk '{ print $1 }')
sh-4.4# echo $container_id
378f096238fdd
sh-4.4# container_pid=$(crictl inspect $container_id | jq '.info.pid')
sh-4.4# echo $container_pid
3458673
sh-4.4# nsenter -t $container_pid -n /bin/bash -xec 'tcpdump -i eth0'
... TCPDUMP 출력 결과 생략 ...
만약, OCP 또는 OKD Cluster를 구축할 때, OS를 CoreOS를 사용한 경우라면 위와 같이 `nsenter -t .... 'tcpdump -i eth0'` 명령을 사용할 수 없다. 왜냐고? CoreOS는 TCPDUMP 명령을 가지고 있지 않기 때문이다. ㅠㅠ 단, 임시로 CoreOS에 tcpdump command를 설치하고 위 절차를 따라하는 꼼수가 있기는 하다.
##
## brew 명령으로 bash를 설치한다.
##
$ brew install bash
...
...
$ which -a bash
/usr/local/bin/bash
/bin/bash
$ /usr/local/bin/bash --version
GNU bash, version 5.1.8(1)-release (x86_64-apple-darwin20.3.0)
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
##
## /etc/shells에 '/usr/local/bin/bash'를 추가한다.
##
$ sudo vim /etc/shells
/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
/usr/local/bin/bash ## 이 라인을 추가한다.
##
## Set default shell
##
$ chsh -s /usr/local/bin/bash
## TODO: 위 chsh 명령을 수행한 후에
## 반드시 기존 iTerm2 터미널을 종료하고, 새로운 iTerm2 터미널을 열어야
## 새로운 버전의 bash 명령을 사용할 수 있다.
$ echo $BASH_VERSION
5.1.8(1)-release
Bash auto-completion
MacOS에 설치된 Bash가 v4.1+ 인 것을 확인했다면, 아래 Web docs를 따라서 설정해주면 모든 것이 끝!
위 Web Site의 Docs를 읽으면서, 아래 내가 작성한 문서를 비교하면서 작업해야 한다.
위 Web Docs만 따라하면 bash 패키지 설치 디렉토리가 설치 대상 장비마다 달라서, 잘못된 설정을 하게 된다.
##
## 아래 문서를 따라서 설정할 때, 주의할 점이 있다.
## https://kubernetes.io/docs/tasks/tools/included/optional-kubectl-configs-bash-mac/
## 위 문서의 설명을 따라하다보면, bash-completion@2를 설치하고 나서,
## .bash_profile에 바로 bash_completion.sh 정보를 작성하라고 되어 있는데
## 이때, Web Docs에 있는 것을 Copy & Paste하면 안 된다.
## 왜냐고?
## bash-completion@2가 minor 버전이 릴리즈될 때마다 설치 디렉토리가
## - /usr/local/etc/... 인 경우가 있고,
## - /opt/homebrew/... 인 경우가 있기 때문에
## Web Docs만 따라하면 제대로 설정할 수 없다.
## 따라서 아래 설치 명령의 제일 밑에 출력되는 내용을 copy해서 .bash_profile에 붙여 넣어야 한다.
## 즉, 결론적으로
## Kubernetes의 Web Docs는 전체 절차만 이해하는 수준으로 읽고,
## 아래 명령의 출력 결과에 나오는 안내 글을 읽고 따라서 설정 작업을 해야 잘 동작한다.
##
$ brew install bash-completion@2
... 중간 생략 ...
## 주의: 바로 이 안내 글을 꼭 주의깊게 읽고, .bash_profile 설정 작업을 해야 한다.
Add the following line to your ~/.bash_profile:
[[ -r "/opt/homebrew/etc/profile.d/bash_completion.sh" ]] && . "/opt/homebrew/etc/profile.d/bash_completion.sh"
==> Summary
🍺 /opt/homebrew/Cellar/bash-completion@2/2.11: 753 files, 1MB
$
Istio를 설치할 때, 다양한 설치 방법이 있다. 그래서 Istio를 처음 접할 때, 다양할 설치 방법 때문에 무엇부터 시작해야 할지 망설이게 된다. 이런 고민을 조금이라도 덜기 위해 각 설치 방법의 장점과 단점을 알아보고자 한다.
우선, 가장 흔하게 사용하는 Istio 설치 방법은 아래의 4가지가 있다.
istioctl install <-- 내가 생각하는 Best Choice
istio operator
istioctl manifest generate
helm chart
각 설치 방법의 특징과 장점 및 단점은 아래와 같다.
istioctl install
`istioctl` 명령 도구를 사용하여 istio를 설치하는 것이 가장 보편적인 설치 방법이다. 그리고 'istio dev community`에서 이 설치 방법을 권장하고 있다.
Pros:
`istioctl` 명령 도구가 설정 오류에 대한 확인을 해준다.
`istioctl` 명령 도구가 'IstioOperator' API를 사용한다.
'In-cluster privileged pods'가 필요하지 않다. `istioctl` 명령 도구를 이용해서 설정 변경이 필요할 때마다 설정을 적용해준다.
Cons:
다른 설치 방식에 비해서 `istioctl` 명령 도구 프로그램의 버전을 실제 istio의 버전과 맞추어줘야 하는 일이 필요하다.
istio operator
Pros:
`istioctl` 같은 별도의 명령 도구 프로그램이 없이, kubernetes의 Operator 패턴을 이용하면 된다.
Cons:
'Istio Operator'가 istio가 설치되는 kubernetes cluster 내부에 있어야 하고, 상당히 높은 cluster management 권한을 가지게 되므로, 자칫하면 보안(security)가 허술해지는 실수를 범할 수 있다.
istioctl manifest generate
나처럼 Istio 자체를 분석하려는 사람이 쓰는 방법이지 않을까 싶다. Kubernetes와 Istio를 많이 아는 사람이 아니라면, 이 방법은 복잡하게 느껴질 수 있을 것이다. Kubernetes와 Istio를 파헤치고 배우는 학생이라면 한번쯤 시도해볼만한 설치 방법이다. (나의 개인적인 느낌으로 하는 말이다 ^^)
어짜피 istioctl이나 istio operator도 원본 Application의 Pod manifest를 hooking하여 istio에 필요한 설정 항목을 Patch하는 것이기 때문에, Istio 및 app이 구동하는 과정을 동일한 흐름일 것이다.
Pros:
`istioctl`이 생성한 chart를 사람이 직접 열람할 수 있다.
Cons:
chart, manifest를 직접 이용하는 방식이기 때문에 `istioctl`의 설정 검증 같은 절차가 없다.
초보자가 사용하기에 절차가 복잡하다.
Error reporting이 허술하다.
Helm chart
개인적인 생각에 helm chart에 익숙한 사용자라면 써볼만한 설치 방식이지만, 설정 validation이 허술해서 권장할 만한 설치 방법은 아닌 듯 하다.
결론
개발자가 Istio를 스터디하고, 분석해야 하는 상황이 아니라면 `istioctl` 명령 도구를 이용하여 istio를 설치하고 관리하는 것이 좋다. 아래 Web Docs를 따라하는 것이 Best Choice 라고 생각한다.
위 Web Docs가 너무 길어서 다 읽고, 따라하기 귀찮다면, 아래 2개의 명령만 수행해보길~~~
## Istio Controller 설치 및 Ingress, Egress Gateway까지 한번에 설치하려면, 아래 명령을 수행
$ istioctl install --set profile=demo
## Test가 끝나고, Istio 관련 모든 Kubernetes Resource를 삭제하려면, 아래 명령을 수행
$ istioctl x uninstall --purge
만약, Istio를 포함해서 Example App이 실제로 돌아가는 것까지 테스트하고 싶다면 아래 Web Docs를 따라하는 것이 좋다.
## 참고로, scc는 namespace마다 있는 것이 아니다.
## 따라서 namespace를 지정하지 않고, 아래 명령처럼 scc를 생성한다.
$ oc apply -f my-scc.yaml
이렇게 생성한 SCC를 내가 사용할 ServiceAccount와 binding한다.
$ oc create ns andrew-project
$ oc adm policy add-scc-to-group andrew-scc system:serviceaccounts:andrew
$ oc adm policy add-scc-to-user andrew-scc -z andrew -n andrew-project
# SCC 정보를 확인하는 명령
$ oc describe scc andrew-scc
# 'andrew' ServiceAccount로부터 'andrew-scc' SCC를 삭제하는 명령
$ oc adm policy remove-scc-from-user andrew-scc -z andrew -n andrew-project
추가로 Pod Deploy할 때, 아래와 같이 'securityContext' 항목을 설정해야 한다.
# File name: my-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ubuntu-pv
labels:
app: ubuntu-pv
spec:
replicas: 1
selector:
matchLabels:
app: ubuntu-pv
template:
metadata:
labels:
app: ubuntu-pv
spec:
## NOTE: 이 부분을 추가 !!!
securityContext:
runAsUser: 0
containers:
- name: ubuntu-pv
image: myharbor.andrew.space:8080/my-ubuntu:0.1.2
## NOTE: 이 부분을 추가 !!!
securityContext:
capabilities:
add:
- "NET_ADMIN"
- "NET_RAW"
- "NET_BIND_SERVICE"
여기까지는 그냥 SecurityContextConstraints에 관한 이해없이 따라해도 잘 동작한다.
그런데 만약 SecurityContextConstraints에 대해서 깊이 있는 지식을 얻고 싶다면, 아래의 Red Hat Blog를 읽어볼 것을 추천한다.
SecurityContextConstraints 방식으로 Process의 실행 권한을 제어하는 방법 외에도 Linux에서 전통적으로 사용하는 Stick Bit(SETUID)와 File Capability 방식으로 Binary File을 만들고, Container Image를 미리 제작하는 방법도 같이 소개하고 있다.
어떤 방식이 더 편하고, 보안(Security)이 우수한지는 사용하는 사람이 판단할 일이다.
App을 개발해서 Container image로 build하여 Kubernetes cluster에 배포하다보면, 어느 순간 비슷하면서 반복적인 YAML 파일을 작성하는 나를 보게된다.
좀더 구체적으로 얘기하자면, HelloWorld라는 App을 구성하는 Pod가 hw-frontend, hw-backend, hw-db 등 3개의 Pod로 구성되어 있다면 Kubernetes cluster에 HelloWorld App을 배포하기 위해서는 이 3개의 Pod를 모두 배포할 수 있는 deployment.yaml을 작성해야 한다.
아마 Kubernetes 운영 경험이 있는 개발자라면 Helm chart로 반복적이거나 뻔한 YAML code를 template 파일로 만들고, 변경되어야 할 부분만 values.yaml로 작성할 것이다.
그런데 이 helm chart도 일정 부분의 반복적이고 루틴한 작업이 계속 만들어지게 된다.
뻔하면서 반복되는 비슷하지만 약간씩 다른 설정 작업을 기계가 대신 해주면 좋겠다는 소망이 생긴다.
(아마 Kubernetes를 1년 정도 사용해서 App을 반복 배포하다보면, 이런 느낌적인 느낌이 생길 듯 ^^)
그래서 이런 저런 고민을 해결할 방법을 찾다가 'Mutating Admission Controller + WebHook'이라는 Kubernetes에 내장된 기능을 발견했다.
서론이 길어졌다.
이제 본론으로 들어가서, Kubernetes가 제공하는 Admission controller가 무엇이고 일반 개발자가 이 기능을 어떻게 확장할 수 있는지 설명해보겠다.
Mutating WebHook 사용 사례 (Use Case)
Admisson controller를 통한 Mutating Webhook 기능을 개발하는 사례는 많이 다양할 것이다.
내가 Mutating Webhook 서버를 개발한 Case를 나열해보면,
특정 namespace에 기동하는 모든 pod의 /etc/hosts에 'ip address + domain name'을 Patch하기 더 구체적으로 설명하면, Pod의 Deployment를 위한 원본 YAML(Manifest)에 일괄적으로 'hostAliases' item을 추가하는 기능이 필요했고, 이 기능을 Mutating Webhook 서버에 개발했다.
사내에서 사용하는 운영 관리용 Container(EMS, O&M Container)를 모든 Pod에 Inject하기 더 구체적으로 설명하면, Pod의 Deployment를 위한 원본 YAML(Manifest)에 일괄적으로 Sidecar Container를 Patch하는 기능을 수행하도록 Mutating Webhook 서버를 개발했다.
특정 조건(namespace, labels)을 만족하는 Pod가 있는 검사하여, 해당 Pod에만 /my_volume/pkg 같은 storage를 Mount하도록 원본 Menifest를 Patch하기
Admission Controller와 Mutating WebHook의 개념 및 동작 원리
## TODO: 이 부분은 작성 중... (언제 그림을 그리고 글을 작성하나 ~~~, 2021년이 끝나기 전에 작성을 마무리해야 할텐데 ㅠㅠ)
Practical Practice (실전 예제)
실제 동작하는 그럴 듯한 Mutating WebHook Server의 code를 작성해보자.
Mutating WebHook Server를 구현하기 위한 Programming language는 아무거나 사용해도 상관없지만, 나는 Golang이 편해서 Golang을 예제 코드를 작성했다.
처음부터 모든 코드를 작성할 필요가 없다. 아래 stackrox 라는 github repository에 완성도가 높고, 간단하게 만들어 놓은 예제 코드가 있니, 이 Example Code를 git clone해서 사용하면 된다.