지난 글 말미에 Kubernetes
에서 실제 통신은 Service
는 추상화된 layer를 통해 이루어진다는 말씀을 드렸습니다.
Service
는 일종의 softeware-defined-proxy
역할을 수행하며 Kubernetes Cluster
에서 Pod
이나 Node
가 변경되더라도 동일한 기능을 제공하는 애플리케이션에 접근할 수 있도록 해줍니다.
이번 글에서는 Pod
이 Service
와 어떤 방식으로 통신하는지에 대해 알아보겠습니다.
Service
란 어떤 Pod
에 접근하기 위해 추상화된 layer입니다.
Kubernetes
환경에서는 문제가 발생했을 때 Node
나 Pod
을 쉽게 교체할 수 있습니다.
그렇기 때문에 외부 사용자 또는 내부 Pod
들간의 통신에서 Pod
의 IP를 이용해 통신할 경우 문제가 생겨 Pod
이나 Node
가 교체되면 동일한 Pod
에 접근할 수 없게 됩니다.
그렇기 때문에 Kubernetes
에서는 어떤 상황에서라도 동일한 기능을 제공하는 Pod
에 접근하기 위해 Pod
의 IP가 아닌 Service
라는 추상화된 접근 포인트를 이용하여 Pod
에 접근합니다.
이전 환경의 마지막 구성도는 여러개의 Container
를 가진 Pod
이 어떻게 구성되어 있는지를 보여줍니다.
위의 구성도는 하나의 Node
에 하나의 Pod
이 위치하고, Pod
은 여러개의 Container
를 가지고 있는 구성도였습니다.
하지만, 대부분의 경우 Pod
는 하나의 책임을 가지는 것이 권장되기 때문에 단일 Container
로 구성하고 추가적인 Container
는 메인 Container
의 기능을 강화하거나, 보조하는 방식으로 사용합니다(참고).
따라서, 이번 Service
에 대한 글에서 참고할 구성도는 위와 같습니다.
하나의 Pod
은 하나의 Contaienr
만을 가지고, 2대의 Worker Node
에 각각 2대와 1대의 Pod
이 배포되어 있으며 네트워킹 정보는 아래와 같습니다.
Worker Node 1
IP : 10.100.0.2
Container Runtime : 10.0.1.0/24Worker Node2
IP : 10.100.0.3
Contaienr Runtime : 10.0.2.0/24
위 구성도의 왼쪽 Worker Node
에 위치한 client pod
에서 오른쪽 Worker Node
의 server pod1
로 패킷을 전달하려고 한다고 생각해보겠습니다.
Destination IP는 10.3.241.152라는 Cluster IP
입니다.
구성도를 보면 아시겠지만 어떤 인터페이스에도 10.3.241.152라는 IP는 할당되어 있지 않습니다.
veth1
과 cbr0
, eth0
은 자신이 모르는 IP이기 때문에 자신의 Default Gateway
로 패킷을 전달할 것입니다.
veth1
은 cbr0
으로, cbr0
은 eth0
으로 eth0
은 10.100.0.1로 패킷을 전달할 것입니다.
그런데 위의 그림을 보면 eth0
에서 router/gateway
로 패킷이 전달될 때 Destiantion IP가 변경된 것을 확인할 수 있습니다.
과연 Worker Node
는 어떻게 10.0.2.2라는 IP를 알게 되었을까요?
Kubernetes
에서 Service
를 하나 생성하게 되면 많은 컴포넌트에 영향을 미칩니다.
단순히 생각해도 etcd
에 Cluster IP
와 Endpoint
에 대한 정보를 저장해야 하며,
Cluster IP
와의 통신을 위한 라우팅 테이블의 설정 등 많은 일들이 이루어질 것입니다.
그럼 이 모든 일들을 관리자가 직접 수행해주어야 할까요?
정말 다행스럽게도 Kubernetes
에는 이런 Cluster
내부의 네트워킹에 대한 설정을 자동으로 관리해주는 kube-proxy
라는 컴포넌트가 존재합니다.
kube-proxy
는 proxy
라는 이름에서 알 수 있는 것 처럼 Client
와 Server
간의 트래픽을 전달하는 것이 주된 목적입니다.
kube-proxy
는 Kubernetes
오브젝트들이 생성될 때, 관리자가 별도의 작업을 수행하지 않아도 오브젝트들간에 통신이 이루어질 수 있도록 네트워크 룰을 설정하고, NodePort
와 같이 외부로 노출되는 port
들을 오픈합니다.
또한, kernel space
의 netfilter
와 user space
의 iptables
룰을 통해 Cluster IP
에 대한 네트워크 정책을 생성합니다.
iptables
는 리눅스상에서 방화벽을 설정하는 도구로 user space
에 위치합니다.
netfilter
패킷 필터링 기능의 리눅스 커널 방화벽이라고도 정의하는데, iptables
는 체인이라는 규칙을 생성하여 들어오는 패킷들을 체인에 따라 관리합니다.
iptables
에서 룰을 정의하면 netfilter
에도 룰이 생성됩니다.
netfilter
란 kernel space
에 위치하며 패킷의 생명주기를 관리하는 툴인데, netfilter
에 설정된 규칙에 매칭되는 패킷을 발견하면 미리 정의된 action을 수행합니다.
iptables
은 단지 netfilter
의 룰을 쉽게 세워주기 위한 도구입니다.
iptables
은 패킷필터링을 수행하지 않으며, 실제 패킷 필터링은 커널에 탑제된 netfilter
가 수행합니다.
즉, kube-proxy
는 iptables
룰을 정의하고, iptables
에 정의한 룰은 netfilter
에도 생성되며, 실제로 패킷은 netfilter
에 의해 특정 경로로 전달됩니다.
kube-proxy
는 크게 3가지 모드가 존재합니다.
- userspace mode
- iptables mode
- ipvs mode
그러면 Kubernetes
환경에서 kube-proxy
가 어떤 모드로 동작하고 있는지 알아보겠습니다.
만약, Kubernetes Cluster
에서 직접 확인해보고 싶다면 Minikube 설치 글을 참고해 Minikube
를 설치하거나, 다른 방법으로 Kubernetes Cluster
를 구축해주세요.
# Minikube가 설치된 환경에서 사용중인 포트 정보 확인
$ netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:6010 0.0.0.0:* LISTEN 374438/sshd: root@p
tcp 0 0 127.0.0.1:34365 0.0.0.0:* LISTEN 481/containerd
tcp 0 0 127.0.0.1:35523 0.0.0.0:* LISTEN 2174/kubelet
tcp 0 0 127.0.0.1:10248 0.0.0.0:* LISTEN 2174/kubelet
tcp 0 0 127.0.0.1:2379 0.0.0.0:* LISTEN 2785/etcd
tcp 0 0 10.0.1.50:2379 0.0.0.0:* LISTEN 2785/etcd
tcp 0 0 10.0.1.50:2380 0.0.0.0:* LISTEN 2785/etcd
tcp 0 0 127.0.0.1:2381 0.0.0.0:* LISTEN 2785/etcd
tcp 0 0 127.0.0.1:10257 0.0.0.0:* LISTEN 2783/kube-controlle
tcp 0 0 127.0.0.1:10259 0.0.0.0:* LISTEN 2724/kube-scheduler
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN 399/systemd-resolve
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 62350/sshd: /usr/sb
tcp6 0 0 ::1:6010 :::* LISTEN 374438/sshd: root@p
tcp6 0 0 :::8443 :::* LISTEN 2717/kube-apiserver
tcp6 0 0 :::10249 :::* LISTEN 3526/kube-proxy
tcp6 0 0 :::10250 :::* LISTEN 2174/kubelet
tcp6 0 0 :::8080 :::* LISTEN 20779/java
tcp6 0 0 :::10256 :::* LISTEN 3526/kube-proxy
tcp6 0 0 :::22 :::* LISTEN 62350/sshd: /usr/sb
포트 조회 결과를 보면 각 Kubernetes Componenet
들이 어떤 포트를 사용하고 있는지 알 수 있습니다.
kube-proxy
는 10249, 10256 포트를 사용하고 있습니다.
이 중 10249번 포트를 이용해 kube-proxy
가 어떤 모드로 동작하고 있는지 확인할 수 있습니다.
# kube-proxy 동작 모드 조회
$ curl localhost:10249/proxyMode
iptables
조회 결과 Minikube
를 설치하면 기본적으로 iptables mode
로 동작하는 것을 확인할 수 있습니다.
먼저 가장 legacy한 모드인 userspace mode
를 기반으로 설명하고자 합니다.
userspace mode
에서는 kube-proxy
가 상당히 많은 역할을 수행합니다.
위의 그림은 userspcae mode
의 동작방식입니다.
Client
가 Service IP
로 요청을 하면, iptables
룰에 의해 kube-proxy
로 전달이 되고, kube-proxy
가 대상 Pod
들로 패킷을 전송해줍니다.
Pod
및 Service
의 등록부터 패킷의 전달까지의 과정을 자세히 나열하면 아래와 같습니다.
kube-proxy userspace mode
kube-proxy
는api-server
와 통신하면서Cluster
의Pod
과Service
CRUD를 모니터링Service
오브젝트가 생성되면,kube-proxy
는 자신이 현재 동작중인Node
에 랜덤한port
를 오픈하고api-server
와의 통신을 통해 알게된Service
와Pod
정보에 따라Node
에 오픈한port
와Pod
을 연결시킴kube-proxy
는 위에서 설정한Service
에 대한 트래픽을 자신이 받기 위해Service
로 가는 트래픽을 전부 자신이 받도록iptables
룰을 설정Client
는Cluster IP
로 request 패킷 전달- 3번에서 설정한
iptables
룰에 의해kube-proxy
로 패킷 전달kube-proxy
가 해당Service
에 매칭된Pod
중 하나로 패킷 전달 (Round-Robin)
이 과정을 패킷 전송 부분의 구성도에 적용시켜보면 아래와 같이 동작합니다.
먼저 kube-proxy
는 10.100.0.2라는 IP를 가진 Worker Node
에서 동작하고 있습니다.
kube-proxy
가 10400번 포트로 동작하고 있다고 가정하겠습니다.
Client process
(Pod
내부의Container
)에서Cluster IP
로 요청Pod
는 10.3.241.152:80이라는Cluster IP
정보를 모르므로Worker Node
로 트래픽 전달Cluster IP
가 생성될 때kube-proxy
가iptables
룰을 설정함. 이때,netfilter
의 룰이 생성되며, 해당Cluster IP
로 가려는 패킷은kube-proxy
로 가라는 정책이 적용됨Worker Node
는kube-proxy
로 패킷 전달kube-proxy
는 해당Cluster IP
에 해당하는Pod
들의 정보를 알고 있기때문에Cluster IP
에 매칭된Pod
들 중에서 적절한Pod
의 IP와 포트 정보를 전달
userspace mode
의 kube-proxy
는 로드밸런싱, 패킷 규칙 설정 등 대부분의 네트워크킹 작업을 대부분 process
인 kube-proxy
자체에서 컨트롤 하기 때문에 userspace와 kernel 간 엑세스 해야하는 일이 굉장히 많아 성능이 떨어집니다.
위에서 설명한 것 처럼 kube-proxy
의 userspace mode
는 process
인 kube-proxy
가 대부분의 네트워킹 작업을 주도하기 때문에 성능이 떨어진다는 단점이 있습니다.
이를 해결하기 위해 나온 모드가 iptables mode
입니다.
위의 구성도를 보면 기존에는 Cluster IP
로 접근시 kube-proxy
로 패킷이 전달되었지만, 이제는 iptables
룰에 의해 바로 패킷이 전달되는 것을 확인할 수 있습니다.
iptables mode
에서 kube-proxy
는 iptables
에 룰을 적용하는 것만을 담당하고, Pod
의 정보를 전달하는 역할은 수행하지 않습니다.
구성도에서의 실제 과정은 다음과 같습니다.
Client process
(Pod
내부의Container
)에서Cluster IP
로 요청Pod
는 10.3.241.152:80이라는Cluster IP
정보를 모르므로Worker Node
로 트래픽 전달Cluster IP
가 생성될 때kube-proxy
가iptables
룰을 설정함. 이때,netfilter
의 룰이 생성되며, 해당Cluster IP
가selector
로 선택한Pod
들에 대한 정보까지netfilter
에 적용Worker Node
의netfilter
에 의해 해당Pod
의 정보를 획득하고 전달
요청 과정은 userspace mode
와 동일하지만 kube-proxy
가 전달 과정에 관여하지 않는 것을 확인할 수 있으며, 이를 통해 네트워크의 성능이 향상되었습니다.
하지만, iptables mode
의 경우 Cluster IP
가 여러대의 Pod
을 셀렉트 하고 있을 때, 하나의 Pod
에 연결이 실패했을 경우 다른 Pod
으로의 연결을 재시도하지 않습니다.
이는 Pod
하나에 연결이 실패했을 때 다른 Pod
또는 동일한 Pod
에 재시도를 하는 userspace mode
와 비교했을때 단점이 됩니다.
또한, iptables
자체가 방화벽 목적으로 설계 되었는데 kube-proxy
가 담당하고 있던 역할들까지 수행하다 보니 iptables
레코드가 과도하게 생성되어 병목현상이 발생할 수 있습니다.
IPVS Mode
는 리눅스 커널의 L4 Loadbalnacer인 IPVS
기술을 사용합니다.
IPVS Mode
에서 kube-proxy
는 생성 및 삭제되는 Service
와 Pod
을 파악하고 파악한 정보에 따라 IPVS
규칙을 설정하는 것입니다.
Client
에서 전달되는 request 패킷은 netfilter
를 통해 IPVS
로 전달되며, IPVS
가 모든 네트워킹 작업을 처리해서 request 패킷을 해당하는 Pod
으로 전달합니다.
IPVS
는 Pod
에 대한 접근 실패 시 재시도 기능을 제공하며, 커널 영역에서 동작하는 hash table 방식을 활용하여 iptables
와 비교했을 때 효율적인 데이터 구조를 통해 거의 무제한적인 확장이 가능합니다.
하지만, IPVS Mode
를 사용하기 위해서는 모든 Node
들이 IPVS
를 사용할 수 있도록 미리 환경을 구성해 주어야 하며, IPVS
자체가 로드밸런싱은 가능하지만 패킷 필터링이나 hairpin-masquerade, SNAT 등 기존 iptables
이 제공하던 기능들을 제공하지 않아 iptables
를 함께 사용해야 합니다.
IPVS Mode
는 iptables
의 사용을 최소화 하기 위해 ipset
기술을 사용해 문제를 해결했습니다.
IPVS Mode
에서는 iptables
와 유사하게 kube-proxy
가 직접적인 패킷 전달을 담당하지 않습니다.
iptables Mode
와의 차이점은 iptables Mode
에서는 kube-proxy
가 iptables
룰을 추가하지만, IPVS Mode
에서는 IPVS Hash Table
에 Service
와 그에 매칭되는 Pod
의 정보를 기록하고, iptables
에 cluster IP
, NodePort
, LoadBalancer
Type의 패킷을 IPVS
로 전달하는 체인을 생성합니다.
그렇다면 Cluster IP
를 통해 패킷이 전송될 때 Pod
의 어떤 Port로 패킷이 전송될까요?
Kubernetes
공식 문서에서 Service
를 생성하는 예시로 아래와 같은 yaml 파일이 있습니다.
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
여기를 보면 spec.ports에 port
와 targetPort
가 존재합니다.
이때 port
는 Cluster IP
의 포트이고, targetPort
는 Pod
의 포트입니다.
예를 들어 위의 그림에서 Cluster IP
의 80 포트로 전달된 패킷이 10.0.2.2의 8080 포트로 전달되었습니다.
이 때 해당 Cluster IP
를 describe 명령어를 통해 확인하면
Cluster IP : 10.3.241.152
port : 80
targetPort : 8080
위와 같은 설정을 확인할 수 있을 것이고, 패킷 전달 대상에 10.0.2.2라는 IP를 가진 Pod
를 확인할 수 있을 것입니다.
https://ssup2.github.io/theory_analysis/Kubernetes_Service_Proxy/
https://coffeewhale.com/k8s/network/2019/05/11/k8s-network-02/