Kubernetes

Pod내 container들의 namespace 정보와 Pause 컨테이너의 역할 알아보기

mokpolar 2024. 9. 8. 15:56
반응형

들어가며

안녕하세요?
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

 

반응형