Pod란?
Pod란 Kubernetes의 기본 단위로, 하나 이상의 컨테이너를 포함하는 컨테이너의 집합이다.
이런 Pod는 여러 컨테이너가 함께 배치되어 서로 네트워크와 스토리지 자원을 공유하는 격리된 환경을 제공하는데,
이런 점으로 볼 때 K8S 환경에서 Pod하나는 하나의 어플리케이션의 기본 배포 단위로 보기도 한다.
(물론 다른 식으로 Pod간 통신이나 여러 동작들이 있을 수 있다.)
Pod는 아래의 속성을 가진다고 할 수 있다.
- 컨테이너 그룹: 하나의 Pod 내에는 여러 컨테이너가 포함될 수 있다.
- Pod 내에서 실행되는 모든 컨테이너는 동일한 노드에서 실행되며, Pod의 생명 주기를 공유한다. 즉, Pod가 삭제되면 그 안의 모든 컨테이너도 함께 삭제된다.
- IP와 네트워크: 각 Pod은 클러스터 내에서 고유한 IP를 할당받는다. 이를 통해 Pod 간 통신이 가능하며, 다른 노드에 위치한 Pod도 NAT 없이 IP로 접근할 수 있다.
- 다른 노드에 있는 Pod에 접근할 때 CNI(Container Network Interface) 플러그인을 통해 통신한다.
- 동일한 Pod 내의 컨테이너들은 서로의 IP를 공유한다.
- 컨테이너들은 localhost를 통해 서로 통신할 수 있으며, 포트를 통해 구분한다.
- 볼륨 공유: Pod 안의 모든 컨테이너는 동일한 볼륨을 사용할 수 있으며, 이를 통해 파일 시스템을 기반으로 데이터를 주고받을 수 있다.
- PodSandbox: CRI에서 Pod의 격리된 환경을 PodSandbox라고 하는데, PodSandbox는 Pod의 네트워크와 파일 시스템을 설정하며, kubelet은 RuntimeService.RunPodSandbox를 호출하여 이 환경을 생성한다.
Pause 컨테이너란?
Pod를 설명하는 이미지를 잘 보면 Pause 라는 이름의 컨테이너가 한개 생성된 것을 볼 수 있다.
해당 컨테이너는 일반적인 어플리케이션 구동의 목적과는 다른 의도로 Pod안에 생성되는데 모든 Pod에 하나씩 생성되어 일종의 부모 컨테이너 역할을 수행한다.
Pause 컨테이너 확인
위의 그림처럼 실제로 Pod의 컨테이너들이 몇몇 네임스페이스를 Pause 컨테이너의 네임스페이스로 사용하는지 확인해보자.
아래의 네임스페이스 관련 내용을 기억하면 좀 더 이해하기 쉽다.
이전에 실습한 kind로 클러스터와 테스트용 Pod를 생성하고 노드로 접속해 아래의 정보를 확인해보자.
root@myk8s-worker:/# pstree -aln
systemd
|-systemd-journal
|-containerd
| `-17*[{containerd}]
|-kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime-endpoint=unix:///run/containerd/containerd.sock --node-ip=172.18.0.2 --node-labels= --pod-infra-container-image=registry.k8s.io/pause:3.10 --provider-id=kind://docker/myk8s/myk8s-worker --runtime-cgroups=/system.slice/containerd.service
| `-16*[{kubelet}]
...
`-containerd-shim -namespace k8s.io -id 51ae1af67eb8565aafcc07b40a640b7d6fc92222a1ffe036fdcac584b1c05f62 -address /run/containerd/containerd.sock
|-10*[{containerd-shim}]
|-pause
`-python3 -m kube_ops_view
`-2*[{python3}]
root@myk8s-worker:/# pstree -aclnpsS
systemd,1
|-systemd-journal,166
|-containerd,181
| |-{containerd},182
| |-{containerd},183
...
`-containerd-shim,1093 -namespace k8s.io -id 51ae1af67eb8565aafcc07b40a640b7d6fc92222a1ffe036fdcac584b1c05f62 -address /run/containerd/containerd.sock
|-{containerd-shim},1094
...
|-{containerd-shim},1102
|-pause,1113,ipc,mnt,net,pid,uts
|-{containerd-shim},1119
`-python3,1184,ipc,mnt,net,pid,uts -m kube_ops_view
|-{python3},1202
`-{python3},1203
pstree 명령어로 프로세스 구조를 확인해보면 systemd라는 1번 pid 프로세스를 확인할 수 있다.
해당 네임스페이스를 조회해보자.
root@myk8s-worker:/# lsns -p 1
NS TYPE NPROCS PID USER COMMAND
4026531834 time 15 1 root /sbin/init
4026531837 user 15 1 root /sbin/init
4026532377 mnt 9 1 root /sbin/init
4026532378 uts 13 1 root /sbin/init
4026532379 ipc 9 1 root /sbin/init
4026532380 pid 9 1 root /sbin/init
4026532381 net 13 1 root /sbin/init
4026532487 cgroup 15 1 root /sbin/init
root@myk8s-worker:/# lsns -p $$
NS TYPE NPROCS PID USER COMMAND
4026531834 time 15 1 root /sbin/init
4026531837 user 15 1 root /sbin/init
4026532377 mnt 9 1 root /sbin/init
4026532378 uts 13 1 root /sbin/init
4026532379 ipc 9 1 root /sbin/init
4026532380 pid 9 1 root /sbin/init
4026532381 net 13 1 root /sbin/init
4026532487 cgroup 15 1 root /sbin/init
1번 프로세스는 /sbin/init 명령어로 기동되었고, 정의된 네임스페이스를 확인할 수 있다.
그럼 맨 하단에 kube_ops_view라는 임의의 Pod가 올린 프로세스(python3,1184,ipc,mnt,net,pid,uts -m kube_ops_view)와 해당 Pod에 있는 Pause 프로세스(pause,1113,ipc,mnt,net,pid,uts)를 확인해보자.
root@myk8s-worker:/# lsns -p 1113
NS TYPE NPROCS PID USER COMMAND
4026531834 time 15 1 root /sbin/init
4026531837 user 15 1 root /sbin/init
4026532487 cgroup 15 1 root /sbin/init
4026532866 net 2 1113 65535 /pause
4026532973 mnt 1 1113 65535 /pause
4026532974 uts 2 1113 65535 /pause
4026532975 ipc 2 1113 65535 /pause
4026532976 pid 1 1113 65535 /pause
root@myk8s-worker:/# lsns -p 1184
NS TYPE NPROCS PID USER COMMAND
4026531834 time 15 1 root /sbin/init
4026531837 user 15 1 root /sbin/init
4026532487 cgroup 15 1 root /sbin/init
4026532866 net 2 1113 65535 /pause
4026532974 uts 2 1113 65535 /pause
4026532975 ipc 2 1113 65535 /pause
4026532977 mnt 1 1184 1000 python3 -m kube_ops_view
4026532978 pid 1 1184 1000 python3 -m kube_ops_view
net/uts/ipc의 네임스페이스를 보면 kube_ops_view 컨테이너는 pause 컨테이너가 생성한 것을 공유해서 사용하는 것을 확인할 수 있다.
그 외에 time/user/cgroup은 별도로 격리하지 않은 노드 시스템의 네임스페이스를 사용하고, mnt와 pid만 자신의 컨테이너를 위한 별도 네임스페이스를 사용하는 것을 확인할 수 있다.
Pause 컨테이너의 네임스페이스를 공유하는 컨테이너들
그럼 같은 Pod에 있는 pause 컨테이너가 아닌 다른 컨테이너와 비교해보자.
cat <<EOF | kubectl apply -f -
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"] # 포드가 종료되지 않도록 명령어 추가
terminationGracePeriodSeconds: 0
EOF
root@DESKTOP-O4EPQ9T:~# kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myweb2 2/2 Running 0 46s 10.244.1.3 myk8s-worker <none> <none>
myweb2라는 Pod에 기동된 2개의 컨테이너에 명령을 내려 컨테이너의 IP를 확인해보자
root@DESKTOP-O4EPQ9T:~# kubectl exec myweb2 -c myweb2-nginx -- ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.244.1.3 netmask 255.255.255.0 broadcast 10.244.1.255
...
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
...
root@DESKTOP-O4EPQ9T:~# 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
...
2: eth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 9a:14:51:e4:28:38 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.1.3/24 brd 10.244.1.255 scope global eth0
valid_lft forever preferred_lft forever
...
다른 컨테이너인데 IP가 같은 것을 확인할 수 있다.
해당 Pod가 기동된 노드에 접속해서 좀 더 상세히 보자.
# 같은 Pod ID를 가지고 있는 myweb2-netshoot, myweb2-nginx 컨테이너
root@myk8s-worker:/# crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
9711e56ba8d6c 27b858cdcd8ac 5 minutes ago Running myweb2-netshoot 0 c909d4b74d70d myweb2
9fc92bca4fdfc 39286ab8a5e14 5 minutes ago Running myweb2-nginx 0 c909d4b74d70d myweb2
d024675d8d33d a645de6a07a3d 32 minutes ago Running kube-ops-view 0 51ae1af67eb85 kube-ops-view-657dbc6cd8-s6f4n
2d23c6b95c5bb 12968670680f4 34 minutes ago Running kindnet-cni 0 c8c4bf183272d kindnet-c8dpp
98a569c3525f1 af3ec60a3d89b 34 minutes ago Running kube-proxy 0 9aa72cdaf2f19 kube-proxy-wbqpj
# 각 컨테이너의 프로세스를 확인한다.
root@myk8s-worker:/# ps -ef | grep 'nginx -g' | grep -v grep
root 1632 1553 0 16:17 ? 00:00:00 nginx: master process nginx -g daemon off;
root@myk8s-worker:/#
root@myk8s-worker:/# ps -ef | grep 'curl' | grep -v grep
root 1757 1553 0 16:17 ? 00:00:00 /bin/bash -c while true; do sleep 5; curl localhost; done
# 각 컨테이너가 사용하는 네임스페이스를 확인
root@myk8s-worker:/# lsns -p 1632
NS TYPE NPROCS PID USER COMMAND
4026531834 time 28 1 root /sbin/init
4026531837 user 28 1 root /sbin/init
4026532487 cgroup 28 1 root /sbin/init
4026532979 net 12 1573 65535 /pause
4026533087 uts 12 1573 65535 /pause
4026533088 ipc 12 1573 65535 /pause
4026533090 mnt 9 1632 root nginx: master process nginx -g daemon off;
4026533091 pid 9 1632 root nginx: master process nginx -g daemon off;
root@myk8s-worker:/# lsns -p 1757
NS TYPE NPROCS PID USER COMMAND
4026531834 time 28 1 root /sbin/init
4026531837 user 28 1 root /sbin/init
4026532487 cgroup 28 1 root /sbin/init
4026532979 net 12 1573 65535 /pause
4026533087 uts 12 1573 65535 /pause
4026533088 ipc 12 1573 65535 /pause
4026533092 mnt 2 1757 root /bin/bash -c while true; do sleep 5; curl localhost; done
4026533093 pid 2 1757 root /bin/bash -c while true; do sleep 5; curl localhost; done
왜 Pause 컨테이너의 특정 네임스페이스만 공유할까?
지금까지 테스트를 통해 Pause 컨테이너의 net/uts/ipc 네임스페이스만 공유하는 것을 알 수 있다.
그런데 왜 mnt와 pid 네임스페이스는 공유하지 않을까?
이는 컨테이너의 격리와 통신 사이에서의 타협점이라고 볼 수 있을 것 같다.
공유되는 네임스페이스
- IPC (Inter-Process Communication) 네임스페이스 : Pod 내 컨테이너 간 효율적인 데이터 교환과 동기화를 지원
- UTS (UNIX Time-sharing System) 네임스페이스 : 호스트 이름과 도메인 이름을 공유하고 해당 호스트 이름으로 통신을 지원
- Network 네임스페이스 : Pod 내 컨테이너 간 localhost 통신이 가능하며, 단일 IP 주소로 Pod를 대표할 수 있음
공유되지 않는 네임스페이스
- MNT (Mount) 네임스페이스 : 한 컨테이너의 파일 시스템 변경이 다른 컨테이너에 영향을 주지 않아야 한다.
- 필요한 경우 볼륨을 통해 특정 디렉토리를 공유할 수는 있음
- PID (Process ID) 네임스페이스 : 각 컨테이너는 자체 프로세스 트리를 가짐으로써 한 컨테이너의 프로세스가 다른 컨테이너의 프로세스에 직접 접근하지 못하도록 제한
- 단, shareProcessNamespace 필드를 통해 PID 네임스페이스 공유를 선택적으로 활성화할 수는 있다.
- https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/
- Windows는 지원되지 않음 : https://kubernetes.io/ko/docs/concepts/windows/intro/#compatibility-v1-pod
(추가) Pause 컨테이너는 왜 생겨났을까?
스터디 중에 Pause 컨테이너의 네트워크 네임스페이스 왜 공유할까라는 질문에 이어 좀 더 찾아보다, Pause 컨테이너의 목적이 뭐였을까로 이어지게 되었다.
Pause 컨테이너의 소스코드만 보면 주된 목적을 볼 수 있는데
...
...
static void sigreap(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}
int main(int argc, char **argv) {
...
if (getpid() != 1)
/* Not an error because pause sees use outside of infra containers. */
fprintf(stderr, "Warning: pause should be the first process\n");
...
if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,
.sa_flags = SA_NOCLDSTOP},
NULL) < 0)
return 3;
for (;;)
pause();
fprintf(stderr, "Error: infinite loop terminated\n");
return 42;
}
pid가 1인 init 프로세스로 시작해서 프로세스 트리를 해당 프로세스 부터 시작하도록 하고, Pod가 종료될 때 pause 컨테이너( 즉 pid 1인 프로세스)가 최종적으로 종료됨으로 처음부터 Pod의 부모 컨테이너 역할을 하도록 하기 위해 설계되었을 것으로 볼 수 있다.
리눅스 시스템에서 pid가 1인 프로세스는 특별한 의미를 가지는데, 부모 프로세스가 없는 Orphan Process(고아 프로세스)를 Zombie Process가 되지 않도록 조치해주는 역할을 한다.
몇가지 Kubernetest에 대한 이력을 찾아보면 예전에는 Pod의 Pause 컨테이너의 PID 네임스페이스를 다른 컨테이너도 사용하도록 했다가 제거한 것으로 보이는데, 그 당시 Pause 컨테이너는 Pod에서 프로세스가 비정상 종료되었을 때 부모 프로세스가 없는 고아 프로세스를 정리하는 역할을 수행했을 것이다.
- Disable pid namespace sharing : https://github.com/containerd/cri/pull/175
그 당시에는 왜 그렇게 수행했는지는 모르지만 추측하면 각 컨테이너 내의 프로세스가 비정상 종료되는 경우가 많이 발생해서 pause 컨테이너가 부모 프로세스 역할을 수행하지 않았나 싶다.
지금은 각 컨테이너에 대한 init 프로세스가 존재해 각 컨테이너의 init 프로세스가 고아 프로세스를 정리하기 때문에 pause 컨테이너의 PID 네임스페이스를 공유하지 않는다.
다 쓰고 나니 굉장히 정리가 잘되어있는 블로그 글이 있어 추가한다.
시간날 때 더 읽어보자
- Zombie process reaping 에 대하여, Container에서 고려할 부분들 : https://blog.hyojun.me/4