이전 글에서 Pod 네트워킹을 다뤘습니다.
- 노드마다 브리지(또는 오버레이)가 구성되고
- Pod가 생성될 때 네트워크 네임스페이스가 만들어지며
- veth가 붙고
- 노드별 Pod CIDR에서 IP가 할당되고
- 라우팅(또는 오버레이)으로 모든 Pod가 서로 통신 가능한 큰 가상 네트워크가 된다
여기까지 이해했다면 다음 질문이 자연스럽게 나옵니다.
“그럼 Pod끼리 직접 IP로 통신하면 되지 않나?”
실무에서는 거의 그렇게 하지 않습니다. Pod는 수시로 죽고/재생성되고/스케일링되며 IP가 바뀔 수 있기 때문입니다.
그래서 Kubernetes는 Pod 앞단에 Service라는 “안정적인 접근 지점”을 둡니다.
1) Service 빠른 복습: ClusterIP vs NodePort
예시로 DB Pod(오렌지)가 있고, 이를 사용하는 앱 Pod(파랑)가 있다고 합시다.
1.1 ClusterIP
- Service는 클러스터 내부에서만 접근 가능
- 파랑 Pod는 오렌지 Pod에 직접 가지 않고, Service의 IP(ClusterIP) 또는 DNS 이름으로 접근
즉,
- 파랑 Pod →
orange-svc(ClusterIP) → 오렌지 Pod
1.2 NodePort
- ClusterIP 성질은 그대로 가지면서
- 추가로 모든 노드의 특정 포트(노드포트)를 열어 외부에서 접근 가능하게 함
즉,
- 외부 사용자 →
any-node-ip:nodePort→ Service → 백엔드 Pod
여기서 핵심 질문:
Service는 “노드에 뜨는 프로세스”가 아니다.
그럼 Service IP로 접속하면 누가 받아서 Pod로 보내주는가?
2) kubelet은 Pod를 만들고, kube-proxy는 Service를 “성립”시킨다
Kubernetes의 두 에이전트를 대비해서 보면 이해가 쉬워집니다.
2.1 kubelet
- 각 노드에 존재
- API Server를 watch하다가 “이 노드에 Pod가 배치됨”을 감지
- 컨테이너 런타임(containerd/CRI-O 등)을 통해 컨테이너를 만들고
- CNI를 호출해 Pod 네트워크를 구성
2.2 kube-proxy
- 각 노드에 존재
- API Server를 watch하다가 “Service가 생성/변경/삭제됨”을 감지
- 각 노드에 트래픽 포워딩 규칙을 설치해서 Service가 동작하도록 만듦
3) Service는 “실체가 없다”: IP에 바인딩된 서버도, 네임스페이스도 없다
Pod는 실체가 명확합니다.
- 컨테이너 프로세스 존재
- 네트워크 네임스페이스 존재
- 인터페이스/IP 존재
하지만 Service는 가상 오브젝트입니다.
- Service IP에 실제로
listen()하고 있는 서버 프로세스가 있는 게 아닙니다. - Service 네임스페이스/인터페이스 같은 것도 없습니다.
그런데도 왜 Service IP로 접속하면 Pod로 연결될까요?
정답은:
kube-proxy가 모든 노드에 L4 포워딩 규칙(iptables/ipvs 등) 을 깔아두기 때문입니다.
4) Service IP는 어디서 나오나? (Service CIDR)
Service를 만들면 ClusterIP가 할당됩니다. 이 IP는 미리 정해둔 Service CIDR에서 나옵니다.
- 이 값은 보통 API Server 옵션인
--service-cluster-ip-range에서 설정됩니다. - 설치 방식에 따라 다르지만, kubeadm 환경에서 흔히
10.96.0.0/12를 많이 봅니다.
확인 방법(클러스터마다 다름):
# kube-apiserver static pod(manifest)에서 확인하는 경우 (kubeadm 흔함)
sudo cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep service-cluster-ip-range
# 또는 kube-apiserver 프로세스 인자에서 확인
ps -ef | grep kube-apiserver | grep service-cluster-ip-range
중요한 운영 포인트 하나:
Pod CIDR(예:
10.244.0.0/16)와 Service CIDR(예:10.96.0.0/12)은 절대 겹치면 안 됩니다.
Pod와 Service가 같은 IP를 받을 수 있는 위험한 구성이 되기 때문입니다.
5) kube-proxy가 Service를 구현하는 방식: userspace / iptables / ipvs
kube-proxy는 “Service → Pod(Endpoint)” 포워딩을 여러 방식으로 구현할 수 있습니다.
5.1 userspace (구식/비권장)
- kube-proxy가 유저 스페이스에서 포트를 열고 프록시처럼 중계
- 성능/확장성 측면에서 요즘은 잘 안 씀
5.2 iptables (기본으로 많이 쓰이는 모드)
- 커널의 netfilter(iptables)를 이용해 DNAT 규칙으로 목적지를 바꿈
- “Service IP:Port로 들어온 트래픽을 Pod IP:Port로 바꿔치기”
5.3 ipvs
- 커널의 IPVS(로드밸런싱)를 이용
- 대규모 서비스에서 iptables보다 효율적일 수 있음
현재 모드 확인:
# kube-proxy 로그에서 확인
kubectl -n kube-system logs ds/kube-proxy | head -n 50
# 또는 kube-proxy ConfigMap에서 확인 (많이 쓰는 방식)
kubectl -n kube-system get cm kube-proxy -o yaml | grep -i mode
6) iptables 모드에서 “Service가 실제로 어떻게 보이나?” + 왜 ‘바꿔치기(DNAT)’가 필요한가
예시로 DB Pod가 node1에 있고 IP가 10.244.1.2라고 합시다.
그리고 이를 노출하는 ClusterIP Service를 만들었더니 Service IP가 10.103.132.104가 됐다고 해봅시다(예시).
이때 kube-proxy는 모든 노드에 다음 의미의 규칙을 설치합니다.
- 목적지:
10.103.132.104:3306(Service IP/Port) - 변경:
10.244.1.2:3306(Pod IP/Port) 또는 Endpoint 후보 중 하나
여기서 중요한 질문이 나옵니다.
“왜 굳이 Service IP를 Pod IP로 바꿔치기(DNAT)해야 하지?
그냥 Service IP로 받는 서버를 만들면 안 되나?”
6.1 Service는 ‘가상 IP(VIP)’라서, 그대로 두면 받을 주체가 없다
Pod는 실제 인터페이스에 IP가 붙어 있고, 해당 IP:Port로 들어오면 컨테이너 프로세스가 받아 처리합니다.
반면 ClusterIP는 어떤 네임스페이스/인터페이스에도 바인딩되어 있지 않고, 그 IP:Port에서 listen하는 프로세스도 없습니다.
즉, dst=ServiceIP:Port로 온 패킷을 그대로 두면 “받아줄 프로세스”가 없으니 통신이 성립하지 않습니다.
그래서 커널 레벨에서 목적지를 실제 존재하는 엔드포인트(Pod) 로 바꿔서 통신이 되도록 만드는 게 DNAT입니다.
6.2 Pod는 바뀌지만 Service는 고정이어야 한다
Pod는 재시작/스케일/롤링 업데이트로 IP가 바뀝니다.
클라이언트가 Pod IP를 직접 들고 있으면 배포 한 번에 깨질 수 있습니다.
Service는 “고정된 접점”입니다.
- 클라이언트는 항상 Service IP/DNS만 사용
- 뒤쪽 Pod가 바뀌면 kube-proxy가 규칙을 갱신해서 DNAT 대상만 바꿔준다
그래서 “앞단은 안정적, 뒤는 유연”이 가능해집니다.
6.3 로드밸런싱을 ‘커널’에서 빠르게 하기 위한 가장 단순한 방식
Service 뒤에는 보통 여러 Pod(Endpoints)가 있습니다.
svc→pod1/pod2/pod3
kube-proxy는 iptables/ipvs 규칙으로 “이 연결은 pod2로” 같은 결정을 하고,
그 결과를 적용하는 가장 직접적인 방법이 목적지(IP:Port)를 선택된 Pod로 바꾸는 것(DNAT) 입니다.
(유저 스페이스 프록시로도 가능하지만, 성능/확장성이 떨어져 요즘은 iptables/ipvs가 주류입니다.)
6.4 “Pod 네트워크는 NAT 없이”와 “Service는 DNAT”는 충돌이 아니다
Pod 네트워킹에서 말하는 “NAT 없이”는 보통 Pod-to-Pod 통신을 Pod IP로 직접 성립시키라는 의미입니다.
반면 Service는 “실체 없는 VIP를 실체 있는 Pod로 연결해야 한다”는 다른 문제를 풀고 있고, 그 구현이 DNAT입니다.
정리하면:
- Pod 네트워크: Pod IP를 end-to-end로 쓰게 하자
- Service: 가상 VIP(ClusterIP)를 실제 Pod로 매핑하자(DNAT)
이 둘은 층위가 다릅니다.
6.5 누가 실제로 패킷을 처리하나? (중요한 오해 포인트)
iptables 모드에서 “프록시가 라우팅해주는 것처럼” 느껴지지만,
- kube-proxy는 규칙을 설치하는 역할(컨트롤 플레인) 이고
- 실제 패킷 DNAT/포워딩은 리눅스 커널(netfilter) 이 합니다.
userspace 모드일 때만 kube-proxy가 진짜 프록시처럼 트래픽을 받아 중계합니다.
6.6 확인 명령 (iptables 모드 기준)
# NAT 테이블에서 kube-proxy 체인 확인
sudo iptables -t nat -L -n --line-numbers | head
# kube-proxy가 만드는 핵심 체인들에서 서비스 체인 탐색
sudo iptables -t nat -S | grep KUBE-SVC | head
sudo iptables -t nat -S | grep KUBE-NODEPORTS | head
# 특정 서비스 이름(코멘트 등)으로 단서 찾기
sudo iptables -t nat -S | grep -i "db"
iptables 모드에서 자주 보이는 체인들:
KUBE-SERVICES: Service로 들어오는 트래픽의 엔트리KUBE-SVC-XXXXX: 특정 Service에 대한 로드밸런싱/분기 체인KUBE-SEP-XXXXX: 특정 Endpoint(Pod)로 DNAT 하는 체인KUBE-NODEPORTS: NodePort 트래픽 처리 체인
7) NodePort는 어떻게 외부에 포트를 여나?
NodePort Service를 만들면 다음이 추가됩니다.
- 모든 노드에서
nodeIP:nodePort로 들어오는 트래픽을 - 내부적으로
clusterIP:servicePort또는 바로 endpoint로 포워딩
iptables 모드에서는 KUBE-NODEPORTS 체인에 규칙이 생깁니다.
확인:
kubectl get svc -o wide
sudo iptables -t nat -S | grep KUBE-NODEPORTS
동작 흐름을 한 줄로 정리하면:
- 외부 →
nodeIP:nodePort→ (kube-proxy 규칙) →podIP:targetPort
8) Service와 Endpoint의 관계(실전에서 꼭 같이 봐야 함)
Service는 “트래픽을 어디로 보낼지”를 Endpoint로부터 얻습니다.
디버깅할 때는 Service만 보지 말고, 항상 Endpoint/EndpointSlice도 같이 봅니다.
kubectl get svc
kubectl get endpoints
kubectl get endpointslice -A | head
- Service는 있는데 Endpoint가 비어 있으면?
- selector가 잘못됐거나
- 백엔드 Pod가 Ready가 아니거나
- 포트 이름/타겟포트 매핑이 어긋났을 수 있습니다.
9) kube-proxy 로그로도 확인 가능
kube-proxy는 Service가 생길 때 규칙을 추가/삭제합니다.
로그 verbosity(로그 레벨)에 따라 더 상세하게 보일 수도 있습니다.
kubectl -n kube-system logs ds/kube-proxy --tail=200
설치 방식에 따라 kube-proxy 로그 위치/형식이 다를 수 있으니, 가장 확실한 건 위처럼 Pod 로그를 보는 것입니다.
10) 정리
- Pod 네트워크는 “Pod가 IP를 갖고 서로 통신 가능한 기반”을 만든다.
- 하지만 실무에서 Pod끼리 직접 붙는 대신 Service를 사용한다.
- Service는 프로세스/네임스페이스/인터페이스가 있는 실체가 아니다.
- Service IP는 Service CIDR에서 할당되며, Pod CIDR과 겹치면 안 된다.
- Service가 “동작하는 것처럼 보이는 이유”는 kube-proxy가 iptables/ipvs 규칙을 모든 노드에 설치하기 때문이다.
- 여기서 “바꿔치기(DNAT)”는 실체 없는 VIP를 실체 있는 Pod로 연결하고, 고정 접점 + 유연한 백엔드 + 커널 레벨 로드밸런싱을 가능하게 만드는 핵심 메커니즘이다.
- NodePort는 여기에 “노드 포트 오픈 + 포워딩 규칙”이 추가된 형태다.
- 디버깅은
svc + endpoints/endpointslice + kube-proxy 모드/규칙을 같이 봐야 빠르다.
실습 체크리스트(바로 따라하기)
# 1) Service/Endpoint 확인
kubectl get svc -o wide
kubectl get endpoints
kubectl get endpointslice -A | head
# 2) kube-proxy 모드 확인
kubectl -n kube-system logs ds/kube-proxy | head -n 50
kubectl -n kube-system get cm kube-proxy -o yaml | grep -i mode
# 3) iptables 규칙 확인(iptables 모드일 때)
sudo iptables -t nat -L -n --line-numbers | head
sudo iptables -t nat -S | grep KUBE-SVC | head
sudo iptables -t nat -S | grep KUBE-NODEPORTS | head
Practice Test: https://uklabs.kodekloud.com/topic/practice-test-service-networking-2/
'CKA' 카테고리의 다른 글
| Network - Gateway API (0) | 2026.01.05 |
|---|---|
| Network - Ingress (1) | 2026.01.05 |
| Network - IPAM(IP Address Management) (0) | 2026.01.05 |
| Network - Pod Networking & CNI (1) | 2026.01.05 |
| Network - 정리(1) (1) | 2026.01.05 |