tech/k8s

쿠버네티스 이해해보기 [kube-proxy]

eumdengs 2025. 3. 27. 23:52

 

서로 다른 노드에서 파드간 통신

쿠버네티스에서 서로 다른 노드 간의 통신에는 일반적으로 L3 라우팅이 사용됩니다.
또한 서로 다른 노드 간의 통신을 위해 물리 네트워크는 브릿지가 어떤 형태로 든 연결되어 있어야 합니다.
 

Network에 양 노드의 물리 인터페이스가 연결되어 있습니다.

 
 
 
이러한 컨테이너 네트워크 연결을 위해 컨테이너 네트워크 인터페이스(CNI) 프로젝트가 시작되었습니다.
 
CNI 종류에는 아래와 같은 것이 있습니다.
- Calico
- Clilium
- Flannel
- Romana
- AWS CNI / Azure CNI (public cloud)
 
이전 포스팅에서 보았던 파드 내 네트워크 할당과 더불어 노드 간 연결에서도 CNI는 역할을 합니다.
 
CNI는 브리지 별로 서로 같은 대역 내에서 겹치지 않게 네트워크를 할당하고 라우팅 테이블을 설정합니다.
이 덕분에 서로 다른 노드 간의 파드는 IP 주소를 변경하지 않고 서로 간의 통신할 수 있습니다.
CNI의 네트워크 할당, 라우팅 정보 설정, 노드 간 설정 등을 통해 컨테이너는 NAT없이 다른 컨테이너와 통신합니다.
 
또한 CNI는 노드 간 통신에서 다양한 방식으로 트래픽을 전달합니다.
CNI 별로 다르게 동작하므로 개별 CNI를 둘러볼 때 확인해보겠습니다.
 
 
그런데 쿠버네티스는 애플리케이션이 직접 파드 IP를 통신하는 경우는 많이 없습니다.
Service 라는 리소스를 사용하게 됩니다.
 
 
 

Service

Service는 가상IP (Virtual IP, VIP)로 오는 트래픽을 특정 파드로 전달해주는 endpoint 입니다.
또한 Service의 이름은 쿠버네티스에서 DNS Name 역할도 수행하죠.
Service 아래 여러 파드들이 묶여있으므로 자연스럽게 부하 분산 역할도 수행합니다.
 
이 서비스의 VIP를 파드의 IP로 매핑 해주는 작업을 각 노드의 kube-proxy 가 수행합니다.
kube-proxy는 iptables 또는 IPVS를 사용해서 매핑을 진행합니다. 
 

서비스의 가상IP는 실제 네트워크 인터페이스와 같은 곳에 존재하는 것이 아닌 iptables에 존재합니다.

 
 
 

kube-proxy

쿠버네티스에서 Service를 구현하는 컴포넌트는 바로 kube-proxy입니다.
모든 노드에서 실행되고 파드와 Service 통신에 필요한 복잡한 필터링과 NAT를 수행합니다.
 
 
iptables는 리눅스에서 커널영역의 netfilter 인터페이스 역할로, 여러 작업을 수행합니다.
특히 패킷에 대한 조작이 가능하죠.
 
iptables는 체인을 가집니다. (자세한 내용은 참조링크의 커피고래님 포스팅을 확인해보시길 바랍니다.)
 
PREROUTING : 패킷이 네트워크 인터페이스 도착할때 수행, NAT 시 사용
INPUT : 노드로 들어오는 패킷을 처리, 특정 포트 차단 및 허용 여부 결정
OUTPUT : 노드에서 나가는 패킷을 처리, 특정 IP 차단/허용
POSTROUTING : 패킷이 마지막으로 노드를 떠나기 전에 실행, NAT 시 사용
FORWARD : 다른 노드로 갈때 처리 라우터 기능(ip_forward) 활성화 시 동작
 
 
이전 포스팅에서 ens3 인터페이스에서 변환되는 정책을 추가하였을 때 시스템에 적용한 문법입니다.

iptables -t nat -A POSTROUTING -s 172.30.1.0/24 -o ens3 -j MASQUERADE

 
172.30.1.0 대역의 출발지가 ens3 인터페이스의 IP로 SNAT 처리하라는 의미입니다.
외부 인터넷 통신이 가능한 인터페이스로 변경하기 위해 수행하였습니다.
 
이처럼 iptables는 패킷을 조작하여 원하는 작업을 수행하며,
kube-proxy는 이러한 iptables를 통해 Service 통신을 구현합니다.
 
 
 

서비스 전달 흐름 이해해보기

아래 쿠버네티스 인 액션에서 발췌한 그림을 보면서 내용을 정리해보겠습니다.

 
서비스가 B가 생성되고 172.30.0.1 이라는 가상 IP 주소가 바로 할당됩니다 (실제 네트워크 인터페이스는 아닙니다)
172.30.0.1:80 이라는 IP:포트 쌍을 kube-proxy는 실행 중인 노드 iptables에 기록합니다
이제 NodeA 파드A (10.1.1.1) 에서 노드B의 파드 B1-3 으로 접근할때 서비스 즉 엔드포인트B(172.30.0.1:80)으로 전달합니다.
 
이때 노드A에서 커널은 네트워크 패킷이 전송되기전에 iptables 규칙에서 일치하는게 있는지 확인합니다.
아까 kube-proxy는 iptables에 172.30.0.1:80 목적지인 패킷은 Pod B1-3 IP 중 하나로 교체되어야 한다고 기록해두었습니다.
커널은 패킷의 목적지를 10.1.2.1:8080 으로 변경하고 네트워크에 전달합니다.
파드A는 10.1.2.1 파드B2 와 통신하게 됩니다.
 
파드A는 엔드포인트(172.30.0.1:80)를 목적지로 통신하였지만 네트워크로 전달될 때는 10.1.2.1:8080 으로 전달되었습니다.
즉, 클라이언트는 10.1.2.1이라는 IP와 8080 포트 (파드B) 로 직접 패킷을 보낸 것처럼 동작하게 됩니다.
 
 
 

컨테이너 런타임 구성

공식 Docs 중 컨테이너 런타임 항목을 보면 아래와 같이 구성하는 페이지가 나옵니다.
https://kubernetes.io/ko/docs/setup/production-environment/container-runtimes/#ipv4%EB%A5%BC-%ED%8F%AC%EC%9B%8C%EB%94%A9%ED%95%98%EC%97%AC-iptables%EA%B0%80-%EB%B8%8C%EB%A6%AC%EC%A7%80%EB%90%9C-%ED%8A%B8%EB%9E%98%ED%94%BD%EC%9D%84-%EB%B3%B4%EA%B2%8C-%ED%95%98%EA%B8%B0

 

컨테이너 런타임

참고: Dockershim은 쿠버네티스 릴리스 1.24부터 쿠버네티스 프로젝트에서 제거되었다. 더 자세한 내용은 Dockershim 제거 FAQ를 참고한다. 파드가 노드에서 실행될 수 있도록 클러스터의 각 노드에 컨

kubernetes.io

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# 필요한 sysctl 파라미터를 설정하면, 재부팅 후에도 값이 유지된다.
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 재부팅하지 않고 sysctl 파라미터 적용하기
sudo sysctl --system

 
여기서 br_netfilter는 커널 모듈입니다.브릿지 네트워크 인터페이스를 통과하는 패킷은 리눅스에서 기본적으로 감지하지 못하게 되어있습니다.
 
CNI 구성을 떠올려 보시면 브리지 네트워크를 통과하는 패킷은 iptables에서 필터링 할 수 있어야합니다. 해당 설정을 통해 파드 간 통신 시 브릿지 네트워크 트래픽을 iptables가 감지할 수 있게 해주죠.

컨테이너는 생성되면 브릿지를 통해 호스트와 통신한다는 것을 이전에 확인했습니다. 이제 컨테이너 런타임 구성에서 해당 설정값이 필요한지 알 수 있습니다.
 
이처럼 쿠버네티스 네트워크는 커널 단의 여러 기능을 통해서 수행된다는 것을 확인해보았습니다.
앞으로 CNI를 몇 개 확인해보면서 CNI별 구성은 어떻게 다른지 확인해보겠습니다.
 
 
 
 
 

참고링크

https://coffeewhale.com/packet-network3

 

[번역]쿠버네티스 패킷의 삶 - #3

쿠버네티스 패킷의 삶 #3에서는 Service 네트워크가 동작하는 방법에 대해 심층적으로 알아 봅니다.

coffeewhale.com

+ 쿠버네티스 인 액션 11장
 
 
 

매일 업데이트하는 식으로 포스팅합니다.