Network - Service Networking

2026. 1. 5. 22:11·CKA

이전 글에서 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
'CKA' 카테고리의 다른 글
  • Network - Gateway API
  • Network - Ingress
  • Network - IPAM(IP Address Management)
  • Network - Pod Networking & CNI
5jyan5
5jyan5
  • 5jyan5
    jyan
    5jyan5
  • 전체
    오늘
    어제
    • 분류 전체보기 (243)
      • 김영한의 스프링 핵심 원리(기본편) (8)
      • 김영한의 스프링 핵심 원리 - 고급편 (11)
      • 김영한의 스프링 MVC 1편 (1)
      • 김영한의 스프링 DB 1편 (3)
      • 김영한의 스프링 MVC 2편 (3)
      • 김영한의 ORM 표준 JPA 프로그래밍(기본편) (9)
      • 김영한의 스프링 부트와 JPA 활용2 (2)
      • 김영한의 실전 자바 - 중급 1편 (1)
      • 김영한의 실전 자바 - 고급 1편 (9)
      • 김영한의 실전 자바 - 고급 2편 (9)
      • Readable Code: 읽기 좋은 코드를 작성.. (2)
      • 김영한의 실전 자바 - 고급 3편 (9)
      • CKA (119)
      • 개발 (37)
      • 경제 (4)
      • 리뷰 (1)
      • 정보 (2)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      JPQL
      락
      고급
      Thread
      조회 성능 최적화
      jdk 동적 프록시
      reentarantlock
      프록시
      양방향 맵핑
      페치 조인
      단방향 맵핑
      프록시 팩토리
      Target
      @within
      hibernate5module
      자바
      WAS
      빈 후처리기
      @discriminatorvalue
      log trace
      스레드
      typequery
      @args
      버퍼
      김영한
      @discriminatorcolumn
      cglib
      gesingleresult
      jpq
      requset scope
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.2
    5jyan5
    Network - Service Networking
    상단으로

    티스토리툴바