들어가며
안녕하세요?
Pod내 container들의 namespace 정보와 Pause 컨테이너의 역할 에 대해서 공부한 내용을 정리해보겠습니다.
이 글은 CloudNeta팀 가시다님의 KANS(Kubernetes Advanced Network Study) 2주차 과제로 작성되었습니다.
Pod와 Pause container
- Pod 는 1개 이상의 컨테이너를 가질 수 있습니다 : 예로는 Sidecar container 패턴이 있습니다.
- Pod 내에 실행되는 컨테이너들은 반드시 동일한 노드에 할당되며 동일한 생명 주기를 갖습니다 : Pod 삭제 시, Pod 내 모든 컨테이너가 삭제됩니다.
- Pod IP - Pod 는 노드 IP 와 별개로 클러스터 내에서 접근 가능한 IP를 할당 받으며, 다른 노드에 위치한 Pod 도 NAT 없이 Pod IP로 접근 가능 : 이건 CNI가 해줍니다.
- IP 공유 - Pod 내에 있는 컨테이너들은 서로 IP를 공유, 컨테이너끼리는 localhost 통해 서로 접근하며 포트를 이용해 구분합니다.
- pause 컨테이너가 'parent' 처럼 network ns 를 만들어 주고, 내부의 컨테이너들은 해당 net ns 를 공유합니다.
- 쿠버네티스에서 pause 컨테이너는 포드의 모든 컨테이너에 대한 "부모 컨테이너" 역할을 합니다 - Link
- pause 컨테이너에는 두 가지 핵심 책임이 있습니다.
- 첫째, 포드에서 Linux 네임스페이스 공유의 기반 역할을 합니다.
- 둘째, PID(프로세스 ID) 네임스페이스 공유가 활성화되면 각 포드에 대한 PID 1 역할을 하며 좀비 프로세스를 거둡니다.
- volume 공유 - Pod 안의 컨테이너들은 동일한 볼륨과 연결이 가능하여 파일 시스템을 기반으로 서로 파일을 주고받을 수 있습니다.
- Pod는 리소스 제약이 있는 격리된 환경의 애플리케이션 컨테이너 그룹으로 구성됩니다. CRI에서 이 환경을 PodSandbox라고 합니다.
- 포드를 시작하기 전에 kubelet은 RuntimeService.RunPodSandbox를 호출하여 환경을 만듭니다. (파드 네트워킹 설정 ’IP 할당’ 포함)
- Kubelet은 RPC를 통해 컨테이너의 수명 주기를 관리하고, 컨테이너 수명 주기 후크와 활성/준비 확인을 실행하며, Pod의 재시작 정책을 준수합니다
Pod 내 Container들이 공유하는 namespace 확인
테스트를 위해 kind로 클러스터를 만들어보겠습니다.
환경 생성은 지난 글에 작성했던 것처럼 Kind와 OrbStack을 이용했습니다.
Kind와 OrbStack를 사용해서 쉽게 Local에서 Kubernetes 사용하기
kind create cluster --config kind-2node.yaml --name myk8s
그리고 이 테스트를 위해 manifest를 갖고옵니다.
저는 CloudNeta 팀의 예제를 바로 들고 왔습니다.
myweb.yaml
apiVersion: v1
kind: Pod
metadata:
name: myweb2
spec:
containers:
- name: myweb2-nginx
image: nginx
ports:
- containerPort: 80
protocol: TCP
- name: myweb2-netshoot
image: nicolaka/netshoot
command: ["/bin/bash"]
args: ["-c", "while true; do sleep 5; curl localhost; done"]
배포하고 확인해보겠습니다.
kubectl describe pod myweb2
Name: myweb2
IP: 10.244.1.2
IPs:
IP: 10.244.1.2
Containers:
myweb2-nginx:
Container ID: containerd://f6de71e2d773cbaa9c1b2aa2fa23c2f563feb33f7020cdaf373c9e115232c3c1
Image: nginx
Port: 80/TCP
Host Port: 0/TCP
...
myweb2-netshoot:
Container ID: containerd://7d61e3e0dc1a89ffd6a56ca1bf74331d270c7c00510a22528e69220913c8611d
Image: nicolaka/netshoot
...
이제 진짜로 IP가 같은지 확인해보겠습니다.
첫 번째 컨테이너 입니다.
kubectl exec myweb2 -c myweb2-netshoot -- ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host proto kernel_lo
valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
3: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
link/tunnel6 :: brd :: permaddr 26a2:9250:89c5::
4: eth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 26:76:ff:ca:a6:1d brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.1.2/24 brd 10.244.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::2476:ffff:feca:a61d/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
두 번째 컨테이너 입니다.
kubectl exec myweb2 -c myweb2-nginx -- ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.244.1.2 netmask 255.255.255.0 broadcast 10.244.1.255
inet6 fe80::2476:ffff:feca:a61d prefixlen 64 scopeid 0x20<link>
ether 26:76:ff:ca:a6:1d txqueuelen 0 (Ethernet)
RX packets 369 bytes 9405267 (8.9 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 373 bytes 26472 (25.8 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 1499 bytes 225553 (220.2 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1499 bytes 225553 (220.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
eth0에 할당된 IP주소가 IPv4 10.244.1.2, IPv6 fe80::2476:ffff:feca:a61d 로 같다는 사실을 알 수 있습니다.
이제 netshoot 컨테이너에 진입해서 이것 저것을 확인해보겠습니다.
kubectl exec myweb2 -c myweb2-netshoot -it -- zsh
ifconfig
eth0 Link encap:Ethernet HWaddr 26:76:FF:CA:A6:1D
inet addr:10.244.1.2 Bcast:10.244.1.255 Mask:255.255.255.0
inet6 addr: fe80::2476:ffff:feca:a61d/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:369 errors:0 dropped:0 overruns:0 frame:0
TX packets:373 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:9405267 (8.9 MiB) TX bytes:26472 (25.8 KiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:2098 errors:0 dropped:0 overruns:0 frame:0
TX packets:2098 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:315731 (308.3 KiB) TX bytes:315731 (308.3 KiB)
ss -tnlp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 511 [::]:80 [::]:*
어..? 확인해봤더니..
curl로 localhost를 찔러보겠습니다.
nginx 컨테이너가 아닌데, nginx가 나오네요?
curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
당연하게도 프로세스를 보면 nginx 가 안보입니다.
ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/bash -c while true; do sleep 5; curl localhost; done
371 root 0:00 zsh
498 root 0:00 sleep 5
501 root 0:00 ps -ef
이제 이렇게 된 이유를 리눅스 네임스페이스 단위로 자세히 한번 보겠습니다.
OrbStack을 이용해 Kind로 떠 있는 Worker 노드에 접근해서 확인해보겠습니다.
컨테이너의 프로세스 정보들을 확인합니다.
ps -ef | grep 'nginx -g' | grep -v grep
root 761 682 0 05:53 ? 00:00:00 nginx: master process nginx -g daemon off;
ps -ef | grep 'curl' | grep -v grep
root 886 682 0 05:53 ? 00:00:00 /bin/bash -c while true; do sleep 5; curl localhost; done
프로세스를 변수로 지정해줍니다.
NGINXPID=$(ps -ef | grep 'nginx -g' | grep -v grep | awk '{print $2}')
NETSHPID=$(ps -ef | grep 'curl' | grep -v grep | awk '{print $2}')
echo $NGINXPID
761
echo $NETSHPID
886
이제 한 Pod안에 있는 컨테이너들의 네임스페이스 정보를 확인해보겠습니다.
컨테이너 기술 격리 글 Container 격리 기술 이해하기 에서 살펴본 내용을 일부 스스로 다시 상기해봅니다.
- time, user namespace는 격리하지 않고 호스트와 같이 사용합니다.
- net, uts, ipc namespace는 Pod 내의 컨테이너 간에 공유합니다.
- mnt, pid namespace는 컨테이너 별로 격리합니다.
- cgroup도 컨테이너 별로 격리합니다.
- pause 컨테이너는 ipc, network, uts namespace를 생성하고 유지하며, 나머지 컨테이너들이 해당 namespace 를 공유해서 사용합니다.
- 그렇게 pause 컨테이너는 유저가 실행한 특정 컨테이너가 비정상 종료되어 컨터이너 전체에서 공유되는 네임스페이스에 문제가 발생하는 것을 방지합니다.
lsns -p $NGINXPID
NS TYPE NPROCS PID USER COMMAND
4026531834 time 29 1 root /sbin/init
4026531837 user 29 1 root /sbin/init
4026533353 net 14 701 65535 /pause
4026533475 uts 14 701 65535 /pause
4026533476 ipc 14 701 65535 /pause
4026533478 mnt 11 761 root nginx: master process nginx -g daemon off;
4026533479 pid 11 761 root nginx: master process nginx -g daemon off;
4026533480 cgroup 11 761 root nginx: master process nginx -g daemon off;
lsns -p $NETSHPID
NS TYPE NPROCS PID USER COMMAND
4026531834 time 29 1 root /sbin/init
4026531837 user 29 1 root /sbin/init
4026533353 net 14 701 65535 /pause
4026533475 uts 14 701 65535 /pause
4026533476 ipc 14 701 65535 /pause
4026533481 mnt 2 886 root /bin/bash -c while true; do sleep 5; curl localhost; done
4026533482 pid 2 886 root /bin/bash -c while true; do sleep 5; curl localhost; done
4026533483 cgroup 2 886 root /bin/bash -c while true; do sleep 5; curl localhost; done
위와 같이 mnt, pid, cgroup이 격리되는 모습을 볼 수 있습니다.
이제 pause container 정보도 확인해보겠습니다.
lsns -p 701
NS TYPE NPROCS PID USER COMMAND
4026531834 time 29 1 root /sbin/init
4026531837 user 29 1 root /sbin/init
4026532920 cgroup 15 1 root /sbin/init
4026533353 net 14 701 65535 /pause
4026533474 mnt 1 701 65535 /pause
4026533475 uts 14 701 65535 /pause
4026533476 ipc 14 701 65535 /pause
4026533477 pid 1 701 65535 /pause
net, uts, ipc namespace를 공유하고 있습니다.
lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026532685 net 15 1 root unassigned /sbin/init
4026533353 net 14 701 65535 1 /run/netns/cni-81d15635-6cbc-85ab-ff2e-8f6671916680 /pause
이렇게 확인해보면 ip들을 확인해볼 수 있습니다.
nsenter -t $PAUSEPID -n ip -c addr
nsenter -t $NGINXPID -n ip -c addr
nsenter -t $NETSHPID -n ip -c addr
...
4: eth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 26:76:ff:ca:a6:1d brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.1.2/24 brd 10.244.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::2476:ffff:feca:a61d/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
...
4: eth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 26:76:ff:ca:a6:1d brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.1.2/24 brd 10.244.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::2476:ffff:feca:a61d/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
...
4: eth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 26:76:ff:ca:a6:1d brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.1.2/24 brd 10.244.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::2476:ffff:feca:a61d/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
'Kubernetes' 카테고리의 다른 글
Kubernetes Kube-proxy의 iptables proxy mode, ClusterIP 심화 탐구 (2) | 2024.09.28 |
---|---|
Calico CNI의 구조를 파악하고 통신 테스트하기 (2) | 2024.09.17 |
Container Runtime과 CRI, 그리고 Containerd 알아보기 (0) | 2024.09.08 |
Kind와 OrbStack를 사용해서 쉽게 Local에서 Kubernetes 사용하기 (2) | 2024.09.05 |
Container 격리 기술 이해하기 (3) | 2024.08.31 |