Kubernetes
환경에서 Pod
들은 서로 문제없이 통신을 합니다.
저는 같은 Worker Node
에 Pod
이 있다면, 동일한 Container Runtime
환경에서 동작할 것이고 가상 IP 대역을 공유하기 때문에 통신이 잘 되는것이 맞다고 생각했습니다.
하지만, 다른 Worker Node
에 존재하는 Pod
들은 어떻게 서로 통신을 하는 것인지 궁금해졌습니다.
이에 대해 검색을 하던 도중 좋은 글들을 발견하여 저 나름대로 해당 글을 정리해보려고 합니다.
참고한 글
1. [번역] 쿠버네티스 네트워킹 이해하기 - 커피고래
2. [Kubernetes] Pause 컨테이너의 역할과 원리 (원문: The Almighty Pause Container) - alice님 블로그
먼저 Pod
의 네트워크에 대해 설명하기 전에, Pod
을 통해 실행되는 Container
들이 어떤 방식으로 통신하는지 알아보겠습니다.
위의 그림은 10.100.0.2
라는 IP주소를 가진 한대의 Host
에 하나의 Container
가 실행된 것을 보여주는 그림입니다.
docker0
은 해당 Host
에서 사용하는 Container Runtime
인 Docker
가 사용하는 네트워크 대역의 bridege
이며, veth0
은 Container 1
의 가상 인터페이스입니다 두 인터페이스는 172.17.0.0/24
라는 서브넷에 위치하고 있으며,docker0
은 172.17.0.0/24
서브넷의 Default Gateway
역할을 수행합니다.
Host
에서는 docker0
을 통해 Contaienr Runtime
내부의 Container 1
과 통신이 가능합니다.
다음 그림은 해당 Host
에 하나의 Container
를 더 실행한 그림입니다.
기존의 Container 1
과 동일한 서브넷에 Container 2
가 생성되었습니다.
이 그림에서도 Host
는 docker0
을 통해 내부의 Container 1
, Container 2
와 통신이 가능합니다.
또한, Container 1
과 Container 2
도 docker0
을 통해 서로 통신이 가능합니다.
Docker
에서는 link
를 이용해 Container
들이 IP가 아닌 Container Name
으로도 통신이 가능하게 해줄 수 있지만, link
에 대해서는 여기서는 설명하지 않겠습니다.
같은 시리즈의 이전 글에서 Pod
에 대해 정리하면서 Pod
은 Kubernetes
에서 Container
가 배포되는 최소 단위이며, Pod
안에는 하나 이상의 Container
가 있다는 것을 알 수 있었습니다.
위에서 보여드린 그림들을 보면 하나의 인터페이스에 하나의 Container
가 실행되고 있습니다.
실제로 Docker runtime
이 설치된 환경에서 docker run nginx --name=nginx
와 같은 명령어로 Container
를 실행한 뒤 docker ps
명령어를 수행하면 nginx
라는 이름의 하나의 Container
만이 동작하고 있는것을 확인할 수 있습니다.
그런데, 동일한 역할을 수행하는 Container
를 Kubernetes
환경에서 배포하면 어떻게 될까요?
kubectl run nginx --image=nginx
명령어를 수행한 뒤 docker ps
명령어를 수행하면 하나의 Container
만 확인할 수 있을까요?
답은 그렇지 않습니다. Kubernetes
의 Pod
으로 Container
를 배포한 뒤 docker ps
명령어를 수행하면 command
가 pause
인 컨테이너를 확인할 수 있습니다.
저는 분명 nginx
이미지로 Pod
하나를 배포해달라고 했는데 누가, 왜 마음대로 pause
컨테이너를 실행시킨 걸까요?
이번 그림은 두 개의 Container
가 하나의 인터페이스를 공유하고 있습니다.
이것은 Linux
의 Namespace
를 통해 가능한 일입니다.
우리는 Container
단위로 애플리케이션을 배포하지만 Container
는 실제로 존재하는 것이 아닌 Container Runtime
환경이 Linux
의 Namespace
와 cgroup
을 적절히 활용하여 만들어낸 논리적으로 독립적인 공간입니다.
이제 외부에서는 veth0
이라는 하나의 인터페이스를 통해 2개의 Container
에 접속할 수 있고, 두 Container
는 서로 localhost
로 통신이 가능합니다.
하지만, 동일한 인터페이스를 공유하기 때문에 동일한 port
를 동시에 사용할 수 없다는 단점도 있습니다. 예를들어 두 Container
가 모두 WEB Server
일 경우 하나의 Container
에서 80번 Port
를 사용하면 다른 하나의 Contaienr
는 80번이 아닌 다른 port
를 개방해 http
서비스를 제공해야 합니다.
이것은 제약사항이기도 하지만 이전 글의 Multi Container Pod
에서 설명드렸듯이 하나의 Pod
은 하나의 책임만 가지는 것을 원칙으로 하기 때문에 큰 문제는 아닙니다.
Pod
섹션의 서두에서 말씀드린 것 처럼 Kubernetes
의 한 Worker Node
에 접속해서 docker ps
라는 명령어를 수행하면 command
가 pause
인 Container
를 확인할 수 있습니다.
Container
환경에서 다수의 Container
에 대해 공유되는 자원 환경의 수준은 누구나 Docker
를 통해서 설정할 수 있습니다.
하지만, Container
들 사이에서 공유되는 자원들에 대해서 정확히 인지하거나 생명주기를 관리하는 일은 쉬운 일은 아닙니다.
pause
Container
는 Pod
내부의 Container
들을 위해서 일종의 부모와 같은 역할을 수행합니다.
먼저, 같은 Pod
의 Container
들이 동일한 Linux Namespace
를 공유할 수 있도록 해줍니다.
또한, Pod
에서 PID 1
(Init Process
)로서의 역할 뿐 아니라 좀비 프로세스를 거두는 기능도 함께 수행합니다.
pause
Container
는 SIGTERM
(Containr 삭제 명령
)을 받기 전, 평상시에는 sleep
상태로 존재하지만 핵심적인 역할을 수행합니다.
즉, Pod
의 pause
Container
는 Pod
가 자유롭게 생성되고 삭제되는 환경에서 사용자가 관리하기 어려운 Namespace
의 공유나 좀비 프로세스의 리소스 회수 등의 일을 수행하며, 서로 다른 Container
및 바깥과의 통신을 담당하는 핵심 Container
입니다.
결국 n개의 Container
를 가진 Pod
은 위와 같은 형태로 배포됩니다.
Pod
은 veth0
이라는 하나의 가상 인터페이스를 사용하며, 사용자가 선언한 n개의 Container
와 1개의 Pause Container
로 구성됩니다.
Pod
는 Pause Container
를 사용해 veth0
인터페이스를 생성하고, 다른 Container
들은 해당 인터페이스를 공유합니다.
Pod
의 구조 다음으로는 어떻게 Pod
들이 네트워크 통신을 하는지에 대해 알아보겠습니다.
먼저 네트워크의 구성도를 살펴보겠습니다.
위의 구성도에서 Worker Node
와 Docekr
가 사용하고 있는 IP 대역은 다음과 같습니다.
Worker Node
: 10.100.0.0/24
Docker Runtime
: 172.17.0.0/24
먼저 위의 그림을 통해 알 수 있는 것은 Docker
는 docker0
이라는 bridege
를 통해 전혀 다른 가상 IP대역을 사용한다는 것입니다.
10.100.0.2의 IP를 가진 Worker Node
와 10.100.0.3의 IP를 가진 Worker Node
는 각 Node
의 eth0
과 router/gateway
를 이용해서 통신이 가능합니다.
10.100.0.2과 10.100.0.3의 Worker Node
는 10.100.0.1을 Default Gateway
로 하고 설정된 라우팅 테이블에 따라 패킷을 전달하게 됩니다.
만약, 왼쪽 그림의 Worker Node
만이 존재한다고 가정해보겠습니다.
Default Gateway
에 172.17.0.2을 Destination으로 하는 패킷이 도달할 경우 라우팅 테이블이 설정되어 있지 않다면 해당 패킷은 정상적으로 Container
에 전달되지 않을 것입니다.
하지만, Default Gateway
에 172.17.0.2로 가는 패킷은 10.100.0.2라는 IP를 가진 Worker Node
로 라우팅해주는 정책이 설정되어 있다면, 해당 패킷은 Worker Node
에 도달하고, Worker Node
는 내부 iptables에 의해 해당 패킷을 docker0
브릿지로 전달할 것입니다.
그리고 docker0
브릿지는 veth0
인터페이스로 패킷을 전달하고, 해당 인터페이스는 port
에 따라 적절한 Container
에 패킷을 전달할 것입니다.
하지만, 위의 그림을 보면 두 Worker Node
의 Docker Runtime
이 사용하는 IP대역이 172.17.0.0/24로 동일합니다.
이럴 경우 10.100.0.1의 Default Gateway
에 172.17.0.0/24 대역에 대해 설정할 수 있는 라우팅 정책은 하나이기 때문에 두 Worker Node
중 하나의 Docker Runtime
환경에는 패킷을 전달할 수 없게 됩니다.
환경 구성시 별다른 설정을 하지 않는다면 이렇게 Container Runtime
의 IP대역이 겹치는 일은 어렵지 않게 발생 할 것입니다.
Kubernetes
는 환경을 구성할 때 Contaienr Runtime
환경끼리 IP가 겹치는 문제점을 해결해줍니다.
첫번째로, 각 Node
의 bridge
IP가 겹치지 않도록, Kubernetes
환경에서 사용할 bridge
의 IP 주소 대역을 생성하고, 각 Node
에 위치한 bridge
들에 해당 주소 대역안에서 겹치지 않는 주소 대역을 할당합니다.
즉Node
별로 Container Runtime
의 서브넷 대역을 나눠줍니다.
10.100.0.2 Node
는 10.0.1.0/24 주소대역만을 사용하도록 하고, 10.100.0.3 Node
는 10.0.2.0/24 주소대역만 사용하도록 하면 Container Runtime
의 IP가 겹칠 수 있다는 문제점을 해결할 수 있습니다.
두번째로, 10.100.0.1이라는 Gateway
에 어떤 Node
로 가야 bridge
를 통해 원하는 IP를 가진 veth0
에 접근할 수 있는지 라우팅 테이블을 설정합니다.
이런 가상 네트워크 인터페이스와 bridge와 라우팅 rule의 조합을 overelay network
라고 합니다.
위의 그림은 Kubernetes
네트워크의 구성도입니다.
위의 Gateway
에는 Container Runtime
으로 가는 트래픽들에 대한 라우팅 정책이 설정되어 있을 것입니다.
Kubernetes
에서 실제 통신은 Service
라는 추상화된 layer를 통해서 이루어집니다.
Service
는 일종의 softeware-defined-proxy
역할을 수행하며 Kubernetes Cluster
에서 Pod
이나 Node
가 변경되더라도 동일한 기능을 제공하는 애플리케이션에 접근할 수 있도록 해줍니다.
위의 구성도에서 10.100.0.1이라는 IP를 가진 Gateway
를 통해 Overray Network
가 구성되며, 통신을 수행하게 됩니다.
그렇다면, 이 Gateway
는 뭘까요?
Kubernetes
환경에 따라 다르지만 실제 Gateway
장비일수도 있고, 아니면 Node
들에 Calico
와 같은 CNI
에 의해 설정된 Routing Table
일수도 있습니다.
위의 환경은 임의로 구성한 Minikube Kubernetes Cluster
의 Node
에서 ifconfig
명령어를 수행한 화면입니다.
보시다시피 docker0
라는 bridge
를 확인할 수 있습니다.
위의 사진은 동일한 Node
에서 route -n
명령을 통해 라우팅 설정 정보를 조회한 화면입니다.
보시다시피 172.17.0.0/16 대역은 docker0
인터페이스로 패킷을 전달하라는 설정 정책을 확인할 수 있습니다.
현재 환경은 Minikube
로 구성되어있어 다른 Node
의 Container Runtime
으로 가는 패킷에 대한 정보가 기재되어 있지 않지만, 만약 여러대의 Worker Node
가 존재할 경우
Worker Node
들에서 route -n
명령어를 수행했을 때,
10.0.1.0/24로 가는 패킷은 10.100.0.2로 전달하고,
10.0.2.0/24로 가는 패킷은 10.100.0.3으로 전달하는 설정값이 추가 될 것입니다.