Back to Notes

Notes

Kubernetes Service, NodePort, kube-proxy, NAT 흐름 정리

Pod IP와 ClusterIP의 차이, NodePort 트래픽이 iptables 기반 DNAT으로 실제 Pod까지 전달되는 과정을 정리한다.

Published
Updated
Area
Cloud Infrastructure
Type
concept
Category
Notes
KubernetesServiceNodePortkube-proxyiptablesNATDNATClusterIPPod IP

핵심 요약

Kubernetes에서 Service는 여러 Pod를 안정적으로 접근하기 위한 L4 추상화다. Pod IP는 실제 Pod에 붙는 주소이고, ClusterIP는 Service에 붙는 가상 IP다.

kube-proxy는 트래픽을 직접 받아서 전달하는 프록시라기보다, ServiceEndpointSlice 정보를 기반으로 각 Node의 iptables 또는 IPVS rule을 관리하는 agent에 가깝다. 실제 패킷 rewrite와 forwarding은 Linux kernel networking stack이 수행한다.

kube-proxy = Service/EndpointSlice 상태를 보고 iptables/IPVS rule을 관리하는 agent
iptables/netfilter = 실제 패킷의 IP/Port를 rewrite하는 data plane
Service = Pod 집합에 대한 안정적인 L4 접근 지점

Pod IP와 ClusterIP의 차이

구분붙는 대상성격예시
Pod IPPod실제 endpoint 주소10.42.1.7
ClusterIPServiceService용 virtual IP10.43.0.15
NodePortNode의 특정 port외부에서 Service로 들어오는 entrypointNodeIP:30080

Pod IP는 실제 Pod에 붙는 주소다. Pod가 재생성되면 바뀔 수 있다.

Pod A -> 10.42.1.7
Pod B -> 10.42.2.3

반면 ClusterIP는 Service에 붙는 virtual IP다.

Service web-svc
ClusterIP = 10.43.0.15

주의할 점은 ClusterIP가 특정 Pod에 직접 붙는 IP가 아니라는 것이다. ClusterIPiptables 또는 IPVS가 가로채기 위한 Service용 virtual destination에 가깝다.


Service는 ReplicaSet을 직접 묶는가?

정확히는 Service가 ReplicaSet 자체를 묶는 것이 아니라, selector로 특정 label을 가진 Pod들을 선택한다.

apiVersion: v1
kind: Service
metadata:
  name: web-svc
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 8080

위 Service는 app=nginx label을 가진 Pod들을 backend endpoint로 사용한다.

보통 Deployment 또는 ReplicaSet이 같은 label을 가진 Pod replica들을 만들기 때문에, 결과적으로는 Service가 ReplicaSet의 여러 Pod를 묶는 것처럼 보인다.

Deployment/ReplicaSet
  -> app=nginx Pod 여러 개 생성

Service selector
  -> app=nginx Pod들을 backend로 선택

kube-proxy의 역할

kube-proxy는 각 Node에서 동작하면서 Kubernetes API Server를 watch한다.

주로 관찰하는 정보는 다음과 같다.

대상의미
Service어떤 virtual IP/port가 존재하는지
EndpointSlice해당 Service 뒤에 어떤 Pod IP들이 있는지
Pod readinessbackend로 보낼 수 있는 Ready Pod인지

kube-proxy는 이 정보를 바탕으로 iptables rule을 생성, 수정, 삭제한다.

Service/EndpointSlice 상태 변경

kube-proxy가 감지

각 Node의 iptables/IPVS rule 업데이트

Linux kernel이 실제 패킷 처리

따라서 다음 표현은 약간 부정확하다.

kube-proxy가 트래픽을 Pod replica 중 하나로 전달한다.

더 정확한 표현은 다음과 같다.

kube-proxy가 Service/EndpointSlice 정보를 기반으로 iptables rule을 관리하고,
실제 패킷은 Linux kernel의 netfilter/iptables rule에 의해 Pod IP로 DNAT된다.

NAT 개념

NATNetwork Address Translation의 약자다. 중간 장비나 kernel network stack이 패킷의 주소 정보를 rewrite하는 기술이다.

NAT는 보통 L3의 IP 주소와 L4의 port 정보를 함께 다룬다.

src IP
src Port
dst IP
dst Port

대표적으로 다음 두 종류를 구분한다.

종류바꾸는 대상대표 용도
SNATSource IP/Port사설망에서 외부로 나갈 때 출발지 주소 변경
DNATDestination IP/Port외부 요청을 내부 서버나 Pod로 전달

DNAT

DNATDestination NAT이다. 패킷의 목적지 주소를 바꾼다.

예를 들어 외부 클라이언트가 다음 주소로 접속했다고 하자.

NodeIP:30080

실제 애플리케이션 Pod는 다음 주소에서 실행 중일 수 있다.

10.42.1.7:8080

이때 iptables rule은 패킷의 destination을 다음처럼 변경한다.

before:
dst = NodeIP:30080

after:
dst = 10.42.1.7:8080

이것이 DNAT이다.

Kubernetes Service 관점에서는 다음과 같이 볼 수 있다.

ClusterIP:ServicePort
        ↓ DNAT
PodIP:TargetPort

NodePort도 같은 원리를 사용한다.

NodeIP:NodePort
        ↓ DNAT
PodIP:TargetPort

NodePort 트래픽 흐름

NodePort는 외부에서 NodeIP:NodePort로 들어온 트래픽을 Service backend Pod 중 하나로 전달하기 위한 Service type이다.

예시 상황은 다음과 같다.

Service: web-svc
ClusterIP: 10.43.0.15
Service Port: 80
NodePort: 30080
TargetPort: 8080

Ready Pods:
10.42.1.7:8080
10.42.2.3:8080

외부 클라이언트가 다음으로 요청한다.

NodeIP:30080

Node의 kernel network stack은 iptables rule에 따라 Service chain으로 이동한 뒤, backend endpoint 중 하나를 선택하고 destination을 rewrite한다.

Client
  -> NodeIP:30080
  -> iptables PREROUTING
  -> KUBE-NODEPORTS
  -> KUBE-SVC-xxxxx
  -> KUBE-SEP-yyyyy
  -> DNAT to 10.42.1.7:8080
  -> Pod

개념적으로는 다음과 같다.

외부 클라이언트

NodeIP:NodePort

iptables rule

Service backend 중 하나 선택

DNAT

PodIP:TargetPort

ClusterIP 트래픽 흐름

Cluster 내부의 다른 Pod가 Service의 ClusterIP로 접근하는 경우도 유사하다.

Client Pod
  -> 10.43.0.15:80
  -> iptables OUTPUT 또는 PREROUTING
  -> KUBE-SVC-xxxxx
  -> KUBE-SEP-yyyyy
  -> DNAT to 10.42.2.3:8080
  -> Backend Pod

ClusterIP는 실제 Pod IP가 아니라, Service backend 중 하나로 DNAT되기 위한 virtual destination이다.


iptables mode에서의 mental model

iptables mode의 kube-proxy는 다음 mental model로 이해하면 좋다.

1. kube-proxy가 Service와 EndpointSlice를 watch한다.
2. Ready Pod 목록을 기반으로 iptables rule을 만든다.
3. 패킷이 Node에 들어오거나 로컬에서 생성된다.
4. netfilter hook에서 iptables rule이 적용된다.
5. destination IP/Port가 PodIP:TargetPort로 DNAT된다.
6. Linux kernel이 routing table을 보고 실제 Pod로 forwarding한다.

kube-proxy는 rule을 관리하고, packet path에서 실제 rewrite는 kernel이 수행한다.


netfilter hook과 NAT 위치

Linux kernel의 netfilter에는 패킷 흐름 중간에 hook point가 있다.

Hook대표 역할
PREROUTING외부에서 들어온 패킷의 routing 결정 전 처리
OUTPUT로컬 프로세스가 만든 패킷 처리
POSTROUTING패킷이 나가기 직전 처리

Kubernetes Service의 DNAT는 주로 PREROUTING 또는 OUTPUT 경로에서 적용된다.

외부에서 들어온 NodePort traffic
  -> PREROUTING
  -> DNAT
  -> routing
  -> Pod

Node 내부에서 ClusterIP로 접근
  -> OUTPUT
  -> DNAT
  -> routing
  -> Pod

SNAT과 MASQUERADE

Kubernetes에서는 DNAT뿐 아니라 SNAT 또는 MASQUERADE도 사용될 수 있다.

SNAT은 source address를 바꾸는 NAT다.

before:
src = 10.42.1.7

after:
src = NodeIP

대표적인 사용 맥락은 다음과 같다.

상황목적
Pod가 cluster 외부로 나갈 때외부 대상이 Pod CIDR을 모르는 경우 응답 경로 보장
NodePort에서 특정 traffic policy 사용 시return path를 맞추기 위해 source address 변경
CNI 구성에 따라 필요한 경우Pod network routing 조건에 맞춤

주의할 점은 SNAT이 적용되면 backend Pod 입장에서는 원래 client IP가 보존되지 않을 수 있다는 것이다.

확인 필요 항목은 다음과 같다.

  • externalTrafficPolicy 설정
  • CNI plugin 동작 방식
  • Pod CIDR routing 가능 여부
  • NodePort 접근 경로
  • MASQUERADE rule 적용 여부

자칫 실수하기 쉬운 표현

표현판단더 정확한 표현
kube-proxy가 직접 트래픽을 전달한다부정확kube-proxy가 iptables/IPVS rule을 관리하고 kernel이 전달한다
ClusterIP는 Pod 여러 개를 묶은 IP다부분적으로만 맞음ClusterIP는 Service에 붙는 virtual IP이고, selector가 Pod 집합을 backend로 연결한다
Service는 ReplicaSet을 바라본다부정확Service는 label selector로 Pod를 선택한다
NodePort는 특정 Node의 Pod로만 보낸다부정확NodePort로 들어온 Node에서 iptables rule에 따라 Service backend Pod 중 하나로 DNAT될 수 있다
NAT는 IP만 바꾼다부정확보통 IP와 port를 함께 바꾼다

구분 기준

Kubernetes Service networking을 볼 때는 다음 기준으로 구분하면 명확하다.

질문확인할 대상
이 IP는 누구에게 붙는가?Pod IP인지 ClusterIP인지
Service가 어떤 Pod를 선택하는가?spec.selector, Pod label
실제 backend 목록은 무엇인가?EndpointSlice
트래픽 rewrite는 누가 하는가?Linux kernel netfilter
rewrite rule은 누가 관리하는가?kube-proxy
목적지를 바꾸는가?DNAT
출발지를 바꾸는가?SNAT 또는 MASQUERADE

검증 포인트

실제 cluster에서 확인할 때는 다음 항목을 점검하면 된다.

kubectl get svc
kubectl get endpointslice
kubectl get pod -o wide

Service와 Pod IP를 비교한다.

kubectl get svc web-svc
kubectl get endpointslice -l kubernetes.io/service-name=web-svc
kubectl get pod -l app=nginx -o wide

Node의 iptables rule을 확인할 수 있는 환경이라면 다음을 확인한다.

iptables-save | grep KUBE-SVC
iptables-save | grep KUBE-SEP
iptables-save | grep KUBE-NODEPORTS

확인할 핵심은 다음과 같다.

KUBE-NODEPORTS
  -> NodePort traffic 처리

KUBE-SVC-xxxxx
  -> Service 단위 load balancing chain

KUBE-SEP-yyyyy
  -> 특정 endpoint Pod로 DNAT

정리

Kubernetes Service의 핵심은 Pod IP를 직접 노출하지 않고, 안정적인 virtual endpoint를 제공하는 것이다.

ClusterIP = Service에 붙는 virtual IP
Pod IP = 실제 Pod에 붙는 endpoint IP
NodePort = Node의 특정 port를 통해 Service로 들어오는 외부 entrypoint

iptables mode에서 kube-proxy는 packet forwarding 자체를 수행하지 않는다. 대신 Service와 EndpointSlice 상태를 보고 iptables rule을 관리한다.

실제 트래픽은 Linux kernel의 netfilter rule에 의해 처리되며, Service 또는 NodePort로 들어온 패킷은 backend Pod 중 하나의 PodIP:TargetPortDNAT된다.

Service abstraction
  -> kube-proxy가 iptables rule로 구현
  -> Linux kernel이 DNAT 수행
  -> Ready Pod 중 하나로 도달