Pod란
Pod는 쿠버네티스에서 생성하고 관리할 수 있는 배포 가능한 가장 작은 컴퓨팅 단위로, 쿠버네티스 클러스터 노드에서 컨테이너로 실행되는 하나 이상의 OCI 이미지이며 이러한 컨테이너 그룹은 스토리지 및 네트워크를 공유합니다.
다시 말해, Pod는 리눅스 네임스페이스와 cgroup을 격리하여 애플리케이션 별로 일종의 "논리 호스트"를 모델링하고, 추가적으로 하위 격리가 적용된 하나 이상의 애플리케이션 컨테이너가 이러한 논리 호스트에 포함되어 실행됩니다.
즉, Pod는 공유 네임스페이스와 공유 파일시스템 볼륨이 있는 컨테이너들의 집합과 비슷하다고 볼 수 있습니다.
Pod의 생성과 실행 과정
먼저 사용자가 kubectl을 사용해 Pod를 생성하면 API Server가 요청을 검증한 후 etcd에 Pod 정보를 저장하고, Scheduler가 해당 파드가 할당될 노드를 결정하여 API Server를 통해 etcd에 파드 정보를 수정합니다.
kubelet은 이벤트 기반 감시 메커니즘인 Watch를 사용하여 API Server와의 지속적인 연결을 통해 자신의 노드에 파드가 할당되었음을 실시간으로 감지합니다.
이후 kubelet이 자신이 관리하는 노드에 할당된 Pod를 생성하는 과정에서 리눅스의 기본 요소들이 사용되며, 대략적인 과정은 다음과 같습니다.
1. kubelet이 컨테이너 런타임과 상호작용하여 container의 root directory, 네임스페이스, cgroup을 만들고, 해당 디렉토리와 네임스페이스, cgroup을 할당해 Pause 컨테이너를 실행합니다.
2. kubelet이 CNI 플러그인을 통해 Pause 컨테이너에 IP 주소를 할당하고 Pod 간 경로를 설정하여 Pod 네트워킹을 구성합니다.
3. kubelet이 컨테이너 런타임을 통해 애플리케이션 이미지를 풀링한 후, Pause 컨테이너의 네임스페이스와 스토리지를 공유하도록 설정하여 애플리케이션 컨테이너를(또는 컨테이너 그룹을) 실행합니다.
위 과정을 통해 격리된 환경에서 실행된 애플리케이션 컨테이너와 Init 컨테이너(Pause 컨테이너)의 그룹을 Pod라고 할 수 있고, 이후 kubelet은 Pod의 상태를 주기적으로 체크하여 API Server에 보고합니다.
이러한 Pod 생성 과정을 라눅스 기본 요소들을 이용해 수동으로 실행해보며 Pod와 kubelet, 쿠버네티스에 대한 이해를 높이고 쿠버네티스가 리눅스의 기능을 어떻게 활용하는지 알아보았습니다.
리눅스 기본 요소들을 이용해 Pod 생성하기
kind를 사용해 Calico를 CNI 공급자로 사용하는 쿠버네티스 클러스터를 구축한 뒤, docker exec를 이용해 노드에 접속하여 다음 과정을 진행했습니다. (https://docs.tigera.io/calico/latest/getting-started/kubernetes/kind)
컨테이너의 Root directory 변경 - chroot
cat << EOF > chroot.sh
mkdir -p /home/pod/bin
mkdir -p /home/pod/lib
mkdir -p /home/pod/lib64
mkdir -p /home/pod/proc
cp -v /usr/bin/kill /home/pod/bin
cp -v /usr/bin/ps /home/pod/bin
cp -v /bin/bash /home/pod/bin
cp -v /bin/ls /home/pod/bin
cp -r /lib/* /home/pod/lib/
cp -r /lib64/* /home/pod/lib64/
mount -t proc proc /home/pod/proc
chroot /home/pod /bin/bash
EOF
chmod +x ./chroot.sh
./chroot.sh
위 방법으로 chroot.sh 파일을 만들고 실행하면 Root directory를 '/home/pod'로 설정하여 프로세스를 실행할 수 있고, '/home/pod' 디렉토리의 상위 파일 시스템이 프로세스로부터 격리된 것을 확인할 수 있습니다.
컨테이너가 사용할 스토리지 제공 - mount
호스트 파일 시스템과 격리되기 때문에 프로세스는 자신의 루트 디렉토리인 '/home/pod' 외부의 파일이나 디렉토리에 접근할 수 없습니다.
따라서 '/home/pod' 외부에 프로세스 실행에 필요한 파일이 있거나 프로세스의 데이터를 호스트 시스템과 공유해야 한다면 그에 필요한 파일이나 디렉토리에 프로세스가 접근할 수 있도록 해야할 것이며, 이를 달성하기 위해 mount를 사용할 수 있습니다.
예를 들어 '/home/pod/data' 디렉토리를 만들고 해당 디렉토리에 다음 명령어를 통해 '/tmp' 디렉토리를 마운트한다면 프로세스는 '/tmp' 디렉토리와 디렉토리 안의 파일들을 호스트 시스템과 공유할 수 있습니다.
mount --bind /tmp /home/pod/data
그러나 이처럼 mount를 이용해 스토리지를 공유할 경우, 프로세스가 호스트 시스템에 직접 접근할 수 있기 때문에 보안 상 위험할 수 있습니다. 이는 쿠버네티스 volume 사용 시 hostPath 기능이 운영 환경에서는 잘 사용되지 않는 이유이기도 합니다.
PID, Network 네임스페이스 격리 - unshare
chroot를 이용해 파일 시스템을 격리했지만 아직 완벽히 격리된 환경이 아니기 때문에 보안 상 위협이 존재합니다.
위와 같이 프로세스가 호스트의 전체 프로세스에 접근할 수 있고, 따라서 프로세스가 kill 명령을 이용해 중요한 시스템 프로세스를 종료할 수 있다는 문제가 남아있습니다.
또한 다음과 같이 네트워크 구성 또한 격리되지 않았다는 것을 확인할 수 있습니다.
이처럼 chroot만으로는 완전한 격리가 불가능하다는 것을 알 수 있고, 이러한 문제를 해결하기 위해 unshare 명령을 사용해 PID와 Network 네임스페이스를 격리해야 합니다.
unshare -p -n -f --mount-proc=/home/pod/proc chroot /home/pod /bin/bash
위와 같이 unshare를 이용해 chroot를 실행할 경우, 프로세스의 PID와 Network가 호스트와 격리되어 실행됨을 확인할 수 있습니다.
Pod 네트워크 구성(Pod와 Pod, Service와 Pod 간 통신) - CNI 플러그인, iptables
프로세스의 Network 네임스페이스가 호스트와 격리되었으므로 프로세스는 호스트의 네트워크 디바이스를 이용해 외부와 통신할 수 없습니다.
따라서 Pod와 Pod, Service와 Pod 간의 통신을 위해서는 커널에 라우팅 규칙을 추가하고, Pod에 IP 주소를 할당하는 등 추가적인 네트워크 구성이 필요합니다.
쿠버네티스에서 Service와 Pod 간의 통신은 kube-proxy가 iptables를 이용해 구현하고, Pod와 Pod 간의 통신은 CNI 플러그인에 의해 구현됩니다.
이러한 네트워크 구성의 경우는 수동으로 구현이 어려우므로 쿠버네티스가 어떻게 이를 구성하는지 확인 후 넘어갔습니다.
다음 명령어를 통해 kube-proxy가 생성한 iptables의 라우팅 규칙을 확인할 수 있습니다.
iptables-save
CNI 공급자마다 Pod 간의 라우팅을 구현하는 방법이 다른데, Calico의 경우 Layer 3 기술인 BGP를 활용하고 Antrea의 경우 Layer 2 기술인 OVS를 활용하여 Pod 간의 라우팅을 구현합니다.
예를 들어, Calico를 CNI 공급자로 사용해 구축된 클러스터에서 ip route 명령을 실행하면 Calico가 생성한 라우팅 경로들을 확인할 수 있습니다.
+-----------------------------+ +----------------------------+
| | | |
| | | |
| +--------+ +-------+ | | +---------+ +------+ |
| | pod +----->+service| | | | Network +---->+ pod | |
| | | | | | | | policy | | | |
| +--------+ +-+-----+ | | +----^----+ +------+ |
| 100.96 | | | | 100.96 |
| v | | | |
| iptables-----+ | | |
| | +------>node |
| |BGP,| |
| |OVS | |
+-----------------------------+ +----------------------------+
# 클러스터에서 두 Pod 간의 논리적인 데이터 경로
컨테이너 리소스 할당 - cgroup
일정 수준 격리된 환경을 구성했으므로 Pod를 생성하기 위한 다음 단계는 해당 프로세스가 사용할 리소스의 양을 조정하는 것입니다.
이를 위해서는 cgroup을 생성하고 해당 cgroup의 리소스 사용량을 설정한 뒤, 프로세스의 PID를 cgroup에 할당해야 합니다.
cgroup을 생성하고 설정하는 방법은 cgroup의 버전에 따라 다르므로 먼저 다음 명령어를 이용해 cgroup의 버전을 확인해야 합니다.
mount | grep cgroup
cgroup v2를 사용한다면 다음과 같은 방법으로 프로세스의 cgroup을 생성하고 리소스 제한을 설정할 수 있습니다.
먼저 '/sys/fs/cgroup' 디렉토리 안에 새로운 하위 디렉토리를 생성해야 합니다.
디렉토리를 생성하면 자동으로 다음과 같은 파일들이 생성된 것을 확인할 수 있고, 해당 cgroup이 사용할 리소스의 양을 해당 리소스의 이름을 가진 파일에 알맞게 설정하면 됩니다.
예를 들어, 다음과 같이 cpu.max의 내용을 수정하여 해당 cgroup의 cpu limit을 호스트 cpu의 10%로 설정할 수 있습니다.
이후 unshare를 통해 실행된 프로세스인 /bin/bash의 PID를 cgroup.procs에 추가하여 해당 프로세스를 cpu limit이 설정된 cgroup에 할당합니다.
cgroup 설정을 마친 뒤 다음 명령어를 통해 성공적으로 cpu 사용량이 제한되었음을 확인할 수 있습니다.
회고
위 과정을 통해 격리된 환경을 만들 수 있었지만 아직까진 Pod가 아니라 Init 컨테이너를 만든 것으로 볼 수 있습니다. 생성된 Init 컨테이너의 격리된 네임스페이스와 cgroup을 공유하는, 추가로 격리된(PID, cgroup 리소스 등) 하위 컨테이너 그룹을 실행한다면 해당 컨테이너 그룹을 (실제로 운영 환경에서 사용하기에는 불완전하지만) Pod라고 볼 수 있을 것입니다.
수동으로 Pod 또는 Init 컨테이너를 만들어보며 kubelet이 Pod를 만드는 과정, 쿠버네티스가 리눅스 기술을 활용해 리소스를 구현하는 방법, CNI나 CRI의 역할 등에 대해 더 깊이 이해할 수 있었고, 아직 이해가 부족한 리눅스의 cgroup이나 namespace, CNI와 kube-proxy가 Pod의 네트워크를 구성하는 방법, 여기서 다루지 못했던 CSI에 대해 더 학습할 필요가 있음을 느끼게 되었습니다.
참고자료
제이 비아스, 크리스 러브, ⌜Core Kubernetes⌟, 위키북스, 2023, 22~162쪽
jayunit100, k8sprototypes, https://github.com/jayunit100/k8sprototypes/blob/master/2020kubecon/cni_debugging.md
leduardoserrano, Components and processes for creating a Kubernetes POD, https://community.veeam.com/kubernetes-korner-90/components-and-processes-for-creating-a-kubernetes-pod-6335
Ivan Velichko, Create and Start a Container Manually With runc, https://labs.iximiuz.com/challenges/start-container-with-runc
Kamlesh Prajapati, Kubernetes: Pod Creation Flow, https://kamsjec.medium.com/kubernetes-pod-creation-flow-96b5e3543c76
Charles Vissol, Practicing cgroup v2, https://medium.com/@charles.vissol/practicing-cgroup-v2-cad6743bba0c
https://kubernetes.io/docs/reference/using-api/api-concepts/
https://kubernetes.io/docs/reference/config-api/kubelet-config.v1beta1/
https://kubernetes.io/ko/docs/concepts/workloads/pods/
'Cloud engineering' 카테고리의 다른 글
Kernel of Linux 정리 - Introduction (0) | 2024.12.16 |
---|---|
OpenStack을 이용해 로컬 환경에 클라우드 시스템 구축하기 (0) | 2024.11.21 |
kubeadm으로 구축한 쿠버네티스 클러스터에 모니터링 시스템 구축하기 (0) | 2024.10.15 |
kubeadm으로 구축한 쿠버네티스 환경에서 GitOps 구축하기 (2) | 2024.10.08 |
kubeadm 이용해 K8s cluster 구축하기 (3) | 2024.09.30 |