번역

[번역] 쿠버네티스 네트워크 이해하기: Pod

hayz 2022. 1. 27. 23:31

본 문서는 해당 글; https://medium.com/google-cloud/understanding-kubernetes-networking-pods-7117dd2872 7을 번역하였습니다.

 

본 문서는 쿠버네티스 클러스터 안에서의 다양한 네트워크 동작들에 대해서 살펴본다. 쿠버네티스는 지능적인 디자인 요소들을 내포하고 있는 강력한 플랫폼이다. 하지만, pod 네트워크, service 네트워크, 클러스터 IP, 컨테이너 포트, host 포트, node 포트 등에 관한 얘기들에 있어서는 꽤 혼란스럽다. 우리는 일하면서 이러한 부분에 대해 많이 얘기를 나누고, 어떤 부분이 고장나거나 고치기를 원하면 모든 레이어들을 중단한다. 하지만 만약 각 레이어들이 어떻게 동작하는지를 알게 된다면 보다 똑똑하게 해결할 수 있다.

본 문서에서는 세 파트로 나눠서 설명한다. 첫번째 파트는 컨테이너와 pod를 살펴본다. 두번째 파트는 pod 의 일시적인 문제를 해결하는 추상적인 레이어의 service 에 대하여 살펴본다. 마지막 파트에서는 ingress 와 클러스터 외부에서 pod 로 트래픽이 들어오는 과정에 대해서 살펴본다. 한가지 알아야 할 점은, 본 문서는 컨테이너와 pod 에 대한 기본적인 내용을 다루지는 않는다. 기본적인 내용을 다루기 위해서는 docker 홈페이지와 쿠버네티스 매뉴얼을 통해 익혀야 한다. (원문: Lastly a basic familiarity with networking and IP address spaces will be helpful.)

Pods

pod 가 뭔가? pod 는 하나 혹은 두개 이상의 컨테이너로 이루어져 있고 동일한 host 상으로 묶여 있다. 또한, 네트워크 스택과 볼륨과 같은 리소스를 공유할 수 있다. pod 는 쿠버네티스 상에서 어플리케이션을 구축하기 위한 가장 최소 단위이다. 위에서 말한 “네트워크 스택을 공유" 한다는 건 어떤 의미인가? 실질적인 부분에서의 의미는 pod 에 속한 모든 컨테이너들은 각자 localhost 에 접근할 수 있다는 것이다. 만약 80번 포트에서 nginx 가 돌아가는 컨테이너와 scrapyd 가 돌아가는 컨테이너가 있을 때, 두 컨테이너 모두 http://localhost:80 에 연결할 수 있다. 그럼 어떻게 이렇게 localhost 에 파드 안의 컨테이너가 연결할 수 있을까? 로컬 머신에서 도커 컨테이너를 띄워 한번 실험해보자.

위에서 부터 살펴보면, 먼저 eth0 이라고 하는 네트워크 인터페이스가 로컬 머신에 연결되어 있다. eth0 은 docker0 이라고 하는 브릿지에 연결되어 있고, veth0 이라고 하는 가상 네트워크 인터페이스가 연결되어 있다. 위 예제에서는 docker0과 veth0 이 172.17.0.0/24 인 같은 네트워크로 연결되어 있다. 이 네트워크에서 docker0은 172.17.0.1 로 veth0 에 대한 default gateway 로 설정되어 있다. veth0 안의 네트워크 네임스페이스에 속한 컨테이너는 veth0 만 가지고 바깥의 docker0 과 eth0 과 소통한다. 이제 두번째 컨테이너를 띄워본다.

위에서 보는 것처럼 새로운 컨테이너가 추가되었을 때, 동일한 docker0 브릿지에 연결 되고 veth1 이라고 하는 네임스페이스가 새로 생성된다. veth1 은 172.17.0.3 으로 할당되며, 첫번째 컨테이너와 같은 네트워크 로직으로 설정된다. 그리고 두 컨테이너는 docker0 브릿지를 통해 서로 통신할 수 있다.

[ * Dan Nissenbaum pointed out that this description omits some detail. For background see our brief discussion at the end of the post.]
[ * 12/15/2018: my previous update really got the lower levels of this wrong. The connection between a container and the bridge is established over a pair of linked virtual ethernet devices, one in the container network namespace and the other in the root network namespace. For a great overview of this subject see Kristen Jacobs’ awesome “Container Networking From Scratch” talk given this week at Kubecon 2018 in Seattle (slide link at the bottom). I really enjoyed Kristen’s presentation and I plan to shamelessly copy it in a future post on container networking.]
[ * 9/9/2019: this article by Ifeanyi Ubah does a great job of exploring linux network namespaces and veth pairs]

위 과정을 보면 완성된것 같지만 아직 쿠버네티스 pod 의 “공유 네트워크 스택” 이 적용되지 않았다. 하지만, 다행히도 네임스페이스들은 굉장히 유연하다. 도커는 컨테이너를 시작하면서 새롭게 가상 네트워크를 만들기 보다, 기존의 존재하는 네트워크 인터페이스를 이용하도록 할 수 있다. 아래 그림은 위 그림에서 두 컨테이너가 네트워크를 공유하도록 수정되었다.

이제 위 그림 처럼 두번째 컨테이너가 개인의 veth1 를 가지지 않고, veth0 으로 통합된 것을 볼 수 있다. 이는 몇가지의 결과를 가져왔는데, 첫번째로 두 컨테이너 모두 같은 ip 주소를 이용하여 외부에서 접근된다는 것이고, 두 컨테이너는 같은 호스트 안에서 서로 다른 port 로 할당되어 연결할 수 있다는 것이다. 또한, 이 의미는 두 컨테이너가 같은 port 를 할당 할 수 없음을 의미한다. 이 방법으로 프로세스들은 컨테이너를 격리하면서, 간단하게 네트워크를 연결하게 되는 장점을 가지게 되었다.

위와 같은 구조를 위해 쿠버네티스는 pod 를 생성할 때 특수 목적의 컨테이너인 pause 컨테이너를 생성하여 각 컨테이너의 네트워크 인터페이스를 제공한다. 만약 ssh 로 pod 가 실행중인 쿠버네티스 노드에 접속해서 docker ps 명령을 입력하면, pause 커맨드를 실행하고 있는 컨테이너를 하나 이상 발견 할 수 있을 것이다. pause 커맨드는 신호가 들어올 때까지 현재 프로세스를 대기하기 때문에, pause 컨테이너는 SIGTERM 신호가 올 때까지 sleep 을 제외하고 어떤 동작도 하지 않는다. 그럼에도 불구하고, pause 컨테이너는 다른 컨테이너들과 혹은 외부와 통신할 수 있게 가상 네트워크 인터페이스를 제공하기 때문에 pod 의 심장이라고 할 수 있다. 그렇기 때문에, 실제 pod 의 구성 요소는 엄밀하게 아래와 같다고 볼 수 있다.

Pod 네트워크

모든게 완벽하지만, 각자 통신할 수 있는 컨테이너들로 모인 하나의 파드는 하나의 시스템을 형성하지는 못한다. pod 는 자신의 호스트 이건 외부의 호스트이건 상관없이 각각의 컨테이너 끼리도 통신이 가능해야 한다. (이는 추후에 서비스에 관하여 설명할 때 더욱 명확히 설명한다.) 이런 과정이 어떻게 일어나는지 살펴보기 위해, 한단계씩 살펴보며 노드를 봐야 한다. 이 주제에는 사람들이 꺼려하는 주제인 네트워크 라우팅과 라우트에 대한 내용을 가져온다.

Finding a clear, brief tutorial on IP routing is difficult, but if you want a decent review wikipedia’s article on the topic isn’t horrible.

쿠버네티스 클러스터는 하나 혹은 그 이상의 노드들로 이루어져 있다. 한 노드는 물리 혹은 가상의 호스트 시스템이며, 컨테이너 런타임 및 여러 쿠버네티스 시스템 컴포턴트를 포함하고 있다. 이 시스템 컴포넌트는 다른 노드간 네트워크 들로 연결되어 있다. 두개의 노드로 이루어진 간단한 쿠버네티스 클러스터의 예시는 아래와 같다.

만약 GCP 나 AWS 같은 클라우드 플랫폼에서 클러스터를 동작하고 있다면, 위 그림이 기본 네트워크 환경과 유사한 구조라고 할 수 있다. 예시 그림에서 사설 네트워크 망을 10.000.0.0/24 로 설정하기 위해 라우터와 호스트의 IP를 위 그림과 같이 설정했다. 주어진 설정에서는 각 인스턴스는 서로 eth0 인터페이스를 통해 통신할 수 있다. 아주 좋아보이지만, 이전 글에서 본 pod 네트워크 구조는 위 그림에 포함되어 있지 않다. 조금 더 명확하게 하기 위해 아래 그림을 보자.

왼쪽의 호스트는 10.000.0.2 의 주소를 가진 eth0 인터페이스를 가지면서 기본 gateway로 10.000.0.1 를 사용하고 있다. 그리고 172.17.0.1 의 docker0 인터페이스와 연결되어 있고 이는 다시 veth0 이라는 172.17.0.2 주소의 인터페이스로 연결되어 있다. 172.17.0.2 는 pause 컨테이너에 의해 생성된 인터페이스로, 각 컨테이너들이 동일한 네트워크 스택을 공유하도록 한다. bridge 가 생성되면서 로컬 라우팅 규칙이 설정되었기 때문에, 172.17.0.2 의 목적지를 가진 패킷은 bridge 를 통해 veth0 으로 전달된다. 꽤 괜찮은거 같다. 만약 우리가 이 호스트에 172.17.0.2 주소의 파드를 가지고 있다는걸 안다면, 우리의 라우터 셋팅에 10.100.0.2 로 포워딩 하는 규칙을 추가할 수 있다. 그럼 다른 호스트를 살펴보자.

오른쪽에 10.100.0.3의 eth0 인터페이스를 가진 호스트를 보면, 똑같이 docker0 브릿지에 연결되어 있고 주소도 동일하게 설정되어 있는걸 볼 수 있다. 이건 우리가 생각한 구성이 아니다. 이제 이 호스트는 다른 호스트에 있는 bridge 와 동일한 주소를 가지지 않을 것이다. 위 구성은 처음에 docker 를 이용하여 호스트 세팅을 했을때 생길 수 있는 문제이기 때문에 일부러 동일한 주소를 갖는 문제가 생기도록 세팅했다. 패킷을 보내면서 어디로 보내야 하는지 정확히 요구한 경로로 가는지 알아야할 필요가 있다. 그렇기 때문에 위 구성이 아닌 다른 구성이 필요하다.

쿠버네티스는 이러한 문제를 해결하기 위해 크게 두가지 방법을 제시한다. 첫번째로, 각 노드에 있는 bridge 를 전체 주소 공간에 할당하고, 각 주소 공간에 따라 bridge 주소를 할당한다. 두번째로는, gateway 에 특정 패킷에 따른 다음 주소에 대한 정보를 라우팅 규칙을 추가한다. 이러한 가상 네트워크 인터페이스와, bridge 와 라우팅 규칙들을 모두 조합하여 오버레이 네트워크 라고 부른다. 보통 쿠버네티스에 대해 얘기할때 이러한 네트워크 구성을 “pod 네트워크" 라고 부른다. 왜냐하면 pod 네트워크가 오버레이 네트워크로써 서로 다른 pod 간의 통신을 제공하기 때문이다. 그 오버레이 구성은 아래 그림 같다.

하나 짚고 가야할 점은, bridge 의 이름을 cbr0 으로 바꿨다는 것이다. 쿠버네티스는 docker 에서 제공하는 기본 bridge 를 쓰지 않고, cbr 이라고 하는 커스텀 bridge 를 사용한다. 해당 bridge 에 대해서 완벽하게 알 수는 없지만, 중요한 점은 그냥 기본 세팅으로 설치했을 때와 쿠버네티스에서 생성되는 구성은 다르다는 것이다. 또 다른 점은 위 예시 처럼 bridge 가 10.0.0.0/16 의 주소 공간을 갖는 다는 것이다. 하지만 이건 올리는 클라우드 환경에 따라 다르다. 불행히도, 이를 kubectl 이용하여 외부로 노출시킬 방법은 없다. 하지만 GCP 에서는 gcloud 명령어를 이용하여 노출시킬 수 있다.

일반적으로, 네트워크 환경이 어떻게 되어있는지에 대해서 알고싶지 않을 수 있다. pod 가 다른 pod 와 통신을 할 때는 대부분 추상화된 service 를 통해 한다. 대부분의 경우에는 네트워크 환경을 알지 않아도 실행하는 데엔 문제가 없겠지만, 특정 경로로 라우팅 하거나 규칙을 정의해야 할 경우 알아야 할 수도 있다. 그런 경우에 위 포스팅이 도움이 되길 바란다.