Service 심화 — ClusterIP, NodePort, LoadBalancer가 트래픽을 라우팅하는 방식
Pod IP는 재시작될 때마다 바뀌는데, 다른 서비스는 어떻게 안정적으로 Pod에 접근할 수 있을까요?
Kubernetes의 Service는 변하지 않는 엔드포인트를 제공해서 이 문제를 해결합니다. 하지만 "가상 IP"라는 것이 실제로 어떻게 트래픽을 Pod까지 전달하는지, ClusterIP/NodePort/LoadBalancer가 각각 어떤 계층에서 동작하는지를 알아야 네트워크 문제를 제대로 디버깅할 수 있습니다.
Service의 기본 동작
Service는 레이블 셀렉터 로 대상 Pod을 선택하고, 해당 Pod들의 IP를 Endpoints 오브젝트로 관리합니다.
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app # 이 레이블을 가진 Pod들이 대상
ports:
- port: 80 # Service 포트
targetPort: 8080 # Pod의 컨테이너 포트
type: ClusterIP # 기본값
# Endpoints 확인 — Service가 트래픽을 보내는 Pod IP 목록
kubectl get endpoints my-service
# NAME ENDPOINTS AGE
# my-service 10.244.1.5:8080,10.244.2.3:8080 5m
Pod이 추가되거나 삭제되면 Endpoints가 자동으로 업데이트됩니다. readinessProbe가 실패한 Pod은 Endpoints에서 제외됩니다.
ClusterIP — 클러스터 내부 통신
가장 기본적인 Service 타입입니다. 클러스터 내부에서만 접근 가능한 가상 IP를 할당합니다.
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
type: ClusterIP # 기본값이라 생략 가능
selector:
app: backend
ports:
- port: 80
targetPort: 3000
클러스터 내부의 다른 Pod에서 backend-service:80이나 backend-service.default.svc.cluster.local:80으로 접근할 수 있습니다.
NodePort — 노드 IP로 외부 접근
각 노드에 고정 포트를 열어서 외부에서 접근할 수 있게 합니다.
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
type: NodePort
selector:
app: frontend
ports:
- port: 80
targetPort: 8080
nodePort: 31000 # 30000-32767 범위, 생략 시 자동 할당
<노드IP>:31000으로 접근하면 Service를 거쳐 Pod에 도달합니다. 어떤 노드로 접근하든 동일하게 동작합니다 (해당 노드에 Pod이 없어도).
외부 클라이언트 → 노드IP:31000 → kube-proxy → Pod:8080
LoadBalancer — 클라우드 로드밸런서 연동
클라우드 환경에서 외부 로드밸런서를 자동 생성합니다.
apiVersion: v1
kind: Service
metadata:
name: web-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb" # AWS NLB 사용
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 8080
# 외부 IP 확인
kubectl get svc web-service
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# web-service LoadBalancer 10.96.45.12 a1b2c3.elb... 80:31234/TCP
LoadBalancer는 내부적으로 NodePort를 포함합니다. 구조는 다음과 같습니다.
클라이언트 → 클라우드 LB → 노드IP:NodePort → kube-proxy → Pod
Service 타입 비교
| 타입 | 접근 범위 | 사용 사례 |
|---|---|---|
| ClusterIP | 클러스터 내부만 | 마이크로서비스 간 통신 |
| NodePort | 노드 IP + 포트 | 개발/테스트 환경 |
| LoadBalancer | 외부 LB IP | 프로덕션 외부 서비스 |
| ExternalName | DNS CNAME | 외부 서비스 추상화 |
kube-proxy — 실제 트래픽 라우팅의 주역
Service의 가상 IP는 실제로 어떤 네트워크 인터페이스에도 바인딩되지 않습니다. kube-proxy가 각 노드에서 트래픽 규칙을 관리하여 가상 IP로 향하는 트래픽을 실제 Pod IP로 전달합니다.
iptables 모드 (기본)
# iptables 규칙 확인
sudo iptables -t nat -L KUBE-SERVICES -n
# Service IP(10.96.45.12)로 향하는 트래픽을
# Pod IP(10.244.1.5, 10.244.2.3) 중 하나로 DNAT
iptables 모드는 커널 레벨에서 동작해서 빠르지만, Service와 Endpoint가 많아지면 규칙 수가 증가하여 성능이 저하될 수 있습니다.
IPVS 모드
# IPVS 모드 활성화 (kube-proxy 설정)
# --proxy-mode=ipvs
# IPVS 가상 서버 확인
sudo ipvsadm -Ln
IPVS는 해시 테이블 기반이라 Service 수가 수천 개로 늘어나도 성능이 안정적입니다. 추가로 다양한 로드밸런싱 알고리즘을 지원합니다.
| 알고리즘 | 설명 |
|---|---|
| rr (Round Robin) | 순서대로 분배 |
| lc (Least Connection) | 연결 수가 적은 Pod에 분배 |
| sh (Source Hash) | 같은 클라이언트는 같은 Pod에 분배 |
Headless Service
가상 IP 없이 DNS로 직접 Pod IP를 반환하는 Service입니다. StatefulSet과 함께 사용하여 각 Pod에 개별적으로 접근할 때 사용합니다.
apiVersion: v1
kind: Service
metadata:
name: db-headless
spec:
clusterIP: None # Headless Service
selector:
app: database
ports:
- port: 5432
# DNS 조회 시 Pod IP들이 직접 반환됨
nslookup db-headless.default.svc.cluster.local
# Address: 10.244.1.5
# Address: 10.244.2.3
# StatefulSet과 함께 사용 시 개별 Pod DNS
# db-0.db-headless.default.svc.cluster.local
# db-1.db-headless.default.svc.cluster.local
ExternalName Service
클러스터 외부 서비스를 내부 DNS 이름으로 추상화합니다.
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: db.production.example.com
Pod에서 external-db로 접근하면 db.production.example.com으로 CNAME 리다이렉트됩니다. 나중에 외부 DB를 클러스터 내부로 마이그레이션할 때 Service만 수정하면 됩니다.
sessionAffinity
같은 클라이언트의 요청을 같은 Pod에 보내고 싶을 때 사용합니다.
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3시간
실무에서 자주 만나는 문제
- **Service에 Endpoints가 없음 **: 레이블 셀렉터가 Pod과 일치하지 않거나, readinessProbe 실패
- **ClusterIP로 접근 불가 **: kube-proxy가 정상 동작하는지, Pod 네트워크가 정상인지 확인
- LoadBalancer EXTERNAL-IP가 Pending: 클라우드 컨트롤러가 설치되지 않았거나 권한 부족
# 디버깅 순서
kubectl get svc my-service # Service 확인
kubectl get endpoints my-service # Endpoints 확인
kubectl get pods -l app=my-app -o wide # Pod 상태/IP 확인
kubectl describe svc my-service # 이벤트 확인
정리
Service는 "변하지 않는 엔드포인트"를 제공하는 추상화 계층입니다. kube-proxy가 iptables 또는 IPVS 규칙으로 가상 IP를 실제 Pod IP에 매핑하고, Service 타입에 따라 접근 범위가 결정됩니다. Headless Service로 직접 Pod에 접근하거나, ExternalName으로 외부 서비스를 추상화하는 패턴도 알아두면 실무에서 유용합니다.