네트워킹 — 서비스부터 Gateway API까지
파드 IP 는 계속 바뀐다. 그런데 서로를 찾아야 한다. 이 편은 "어떻게 안정된 주소를 만들고, 밖에서 어떻게 들어오게 하나" 다.
1. 네트워크 모델의 전제
쿠버네티스가 요구하는 것은 단 세 가지다
- ① 모든 파드는 NAT 없이 서로 통신할 수 있다
- ② 모든 노드는 NAT 없이 모든 파드와 통신할 수 있다
- ③ 파드가 보는 자기 IP 와 남이 보는 그 파드의 IP 가 같다
즉 '평평한 네트워크' 를 전제한다 이것을 어떻게 구현할지는 CNI 플러그인의 몫이다
- Calico · Cilium · Flannel · AWS VPC CNI …
클러스터에 세 종류의 IP 대역이 있다
노드 IP 실제 머신 주소
파드 CIDR 파드에 주는 주소 (CNI 가 관리)
서비스 CIDR 서비스 가상 IP (실재하지 않는 주소다)
2. 서비스 — 바뀌지 않는 주소
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web # 이 라벨을 가진 파드로 보낸다
ports:
- port: 80 # 서비스 포트
targetPort: 8080 # 파드 포트
서비스는 '라벨 셀렉터로 파드를 모아 하나의 주소를 주는 것' 이다 파드가 죽고 새로 떠도 라벨만 맞으면 자동으로 대상에 들어간다
두 개의 객체가 함께 움직인다
Service 가상 IP 와 포트
EndpointSlice 실제로 지금 Ready 인 파드 IP 목록
(엔드포인트 컨트롤러가 관리한다)
kubectl get endpointslices -l kubernetes.io/service-name=web
서비스가 있는데 트래픽이 안 가면 엔드포인트를 먼저 본다. 비어 있으면 라벨이 안 맞거나 파드가 Ready 가 아닌 것이다. 서비스 설정 문제가 아니다.
타입 네 가지
- ClusterIP — 클러스터 내부 전용 (기본값)
- NodePort — 모든 노드의 특정 포트를 연다 (30000-32767)
- ClusterIP 를 포함한다
- LoadBalancer — 클라우드 로드밸런서를 만든다
- NodePort 를 포함한다 (즉 세 겹으로 쌓인 구조다)
- ExternalName — 외부 DNS 이름으로 CNAME 을 준다 (프록시하지 않는다)
헤드리스 서비스 — clusterIP: None
- 가상 IP 를 만들지 않고 DNS 가 파드 IP 들을 직접 돌려준다
- StatefulSet 에서 web-0.web.default.svc 같은 개별 주소를 만들 때 쓴다
- 클라이언트가 직접 로드밸런싱하고 싶을 때도 (gRPC 클라이언트 등)
3. kube-proxy — 가상 IP 는 어떻게 도나
서비스의 ClusterIP 는 어느 인터페이스에도 없는 '가짜 주소' 다 패킷이 그 주소로 나가면 커널 규칙이 실제 파드 IP 로 바꿔치기한다(DNAT)
-
iptables 모드 — 규칙을 iptables 체인으로 만든다
- 서비스가 많아지면 규칙이 선형으로 늘어 갱신이 느려진다
-
IPVS 모드 — 커널의 로드밸런서를 쓴다. 해시 기반이라 규모에 유리하다
- 여러 분배 알고리즘 지원 (rr · lc · sh …)
-
nftables 모드 — 최신 대안. iptables 의 확장성 문제를 개선한다
-
확인 — kubectl -n kube-system logs ds/kube-proxy | head
- 또는 kube-proxy 설정의 mode 필드
중요 — kube-proxy 는 L4 다
- HTTP 경로·헤더를 못 본다. 그건 인그레스/게이트웨이의 일이다
- 그리고 연결 단위 분배라 gRPC 처럼 한 연결을 오래 쓰면
- 한 파드로 쏠린다 → 이 경우 클라이언트 LB 나 서비스 메시가 필요하다
세션 어피니티와 트래픽 정책
spec:
sessionAffinity: ClientIP # 같은 클라이언트를 같은 파드로
internalTrafficPolicy: Local # 같은 노드의 파드로만
externalTrafficPolicy: Local # 외부 트래픽도 로컬 노드 파드로만
externalTrafficPolicy: Local 의 효과
얻는 것 클라이언트 원본 IP 가 보존된다 (SNAT 를 안 한다)
불필요한 노드 간 홉이 사라진다
잃는 것 그 노드에 파드가 없으면 트래픽이 버려진다
→ 파드가 고르게 퍼져 있어야 한다 (topologySpread 와 함께)
4. DNS — 이름으로 찾기
서비스가 생기면 CoreDNS 에 레코드가 만들어진다
web.default.svc.cluster.local
│ │ │ └ 클러스터 도메인
│ │ └ 리소스 종류
│ └ 네임스페이스
└ 서비스 이름
같은 네임스페이스면 web
다른 네임스페이스면 web.other
완전한 이름 web.other.svc.cluster.local
파드의 /etc/resolv.conf
- nameserver 10.96.0.10
- search default.svc.cluster.local svc.cluster.local cluster.local
- options ndots:5
ndots:5 가 만드는 지연
ndots:5 는 "점이 5개 미만이면 search 도메인을 먼저 붙여 본다" 는 뜻이다
- api.example.com (점 2개) 을 조회하면
- api.example.com.default.svc.cluster.local ← 실패
- api.example.com.svc.cluster.local ← 실패
- api.example.com.cluster.local ← 실패
- api.example.com ← 성공
외부 도메인 하나 조회에 4번의 왕복이 든다 트래픽이 많으면 CoreDNS 부하와 지연이 눈에 띄게 늘어난다
대응
- 외부 주소 끝에 점을 붙인다 (api.example.com.) — 완전한 이름이 된다
- 파드의 dnsConfig 로 ndots 를 낮춘다
- NodeLocal DNSCache 를 둔다 (노드마다 캐시)
5. 밖에서 들어오기 — Ingress 에서 Gateway API 로
-
Service(LoadBalancer)만 쓰면
- 서비스마다 클라우드 LB 가 하나씩 생긴다 → 비용과 관리 부담
-
L7 라우터를 하나 두고 경로·호스트로 나눈다
apiVersion: networking.k8s.io/v1
kind: Ingress
spec:
ingressClassName: nginx
rules:
- host: shop.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service: { name: api, port: { number: 80 } }
지금 상황을 정확히 알아야 한다
-
셋을 구분한다
-
① Ingress API (networking.k8s.io/v1)
- 폐기되지 않았다. GA 로 남아 있다
- 다만 feature-frozen — 새 기능은 추가되지 않는다
-
② Ingress NGINX (kubernetes/ingress-nginx)
- 커뮤니티 관리 컨트롤러. 2026년 3월 은퇴했다
- 릴리스·버그픽스·보안 패치가 모두 중단됐다
- 클라우드 네이티브 환경의 약 절반이 쓰던 것이다
-
③ NGINX Ingress Controller (nginxinc/kubernetes-ingress)
- F5/NGINX 가 관리하는 '별개' 코드베이스. 영향받지 않는다
-
② 와 ③은 이름이 거의 같고 코드가 다르다
-
"nginx ingress 가 없어졌다" 는 서술은 틀렸다
Gateway API — 후속 표준
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: prod-gw
spec:
gatewayClassName: envoy
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
certificateRefs: [{ name: shop-tls }]
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
spec:
parentRefs: [{ name: prod-gw }]
hostnames: ["shop.example.com"]
rules:
- matches: [{ path: { type: PathPrefix, value: /api } }]
backendRefs: [{ name: api, port: 80 }]
-
Ingress 와 무엇이 다른가
-
역할 분리 — GatewayClass(인프라 제공자) / Gateway(플랫폼 팀) / Route(앱 팀)
- RBAC 로 책임을 나눌 수 있다
- Ingress 는 하나의 객체에 전부 있어 권한을 쪼갤 수 없었다
-
표현력 — 헤더·쿼리 매칭 · 트래픽 분할(가중치) · 재시도 · 타임아웃이
- 표준 필드다
- Ingress 는 이 전부를 애너테이션으로 우회했고,
- 컨트롤러마다 애너테이션이 달라 이식성이 없었다
-
프로토콜 — HTTPRoute · GRPCRoute · TCPRoute · TLSRoute
CKA v1.35 커리큘럼에도 "Use the Gateway API to manage Ingress traffic" 이 명시적으로 들어갔다. 이제 기본 경로로 봐야 한다
주의 — 드롭인 교체가 아니다. 계획과 엔지니어링 시간이 필요하다
6. NetworkPolicy — 기본은 전부 열려 있다
기본 상태는 '모든 파드가 모든 파드와 통신 가능' 이다 네임스페이스는 보안 경계가 아니다
NetworkPolicy 를 하나라도 적용하면 그 파드는 '정책에 명시된 것만' 허용하는 상태가 된다 (기본 거부로 전환)
# 이 네임스페이스의 모든 파드에 대해 인그레스를 기본 거부
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {} # 전체
policyTypes: [Ingress]
---
# api 는 web 에서만 8080 을 받는다
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-from-web
spec:
podSelector:
matchLabels: { app: api }
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels: { app: web }
ports:
- protocol: TCP
port: 8080
알아 둘 것
- 정책은 '허용' 만 쓴다. 거부 규칙이 없다 (허용 목록 방식)
- 여러 정책이 있으면 합집합이 허용된다
- CNI 가 지원해야 동작한다
- Flannel 은 기본적으로 지원하지 않는다 → 정책을 써도 아무 일이 없다
- 적용 전에 CNI 지원 여부를 반드시 확인한다
- egress 도 따로 막아야 한다. ingress 만 막으면 나가는 것은 자유다
- DNS(53/UDP)를 막으면 이름 해석이 안 돼 전부 깨진다
- egress 정책을 쓸 때 가장 자주 겪는 사고다
7. 통신이 안 될 때 — 좁혀 가는 순서
① 파드가 Ready 인가
kubectl get pods -o wide
② 서비스의 엔드포인트가 있는가 ← 여기서 대부분 끝난다
kubectl get endpointslices -l kubernetes.io/service-name=web
비어 있으면 → 라벨 불일치 또는 readiness 실패
③ 이름이 풀리는가
kubectl run tmp --rm -it --image=nicolaka/netshoot -- nslookup web
④ 포트가 열려 있는가
kubectl run tmp --rm -it --image=nicolaka/netshoot -- nc -zv web 80
⑤ targetPort 가 컨테이너 포트와 맞는가
kubectl get svc web -o yaml | grep -A3 ports
⑥ NetworkPolicy 가 막고 있는가
kubectl get networkpolicy -A
⑦ 밖에서 안 되면 인그레스/게이트웨이 컨트롤러 로그
②에서 끝나는 경우가 압도적으로 많다
selector 의 라벨과 파드의 라벨을 눈으로 대조하는 것이 가장 빠르다
kubectl get pods --show-labels
kubectl get svc web -o jsonpath='{.spec.selector}'
한눈에 정리
- 네트워크 모델 — NAT 없는 평평한 네트워크. 구현은 CNI 의 몫
- 서비스 — 라벨로 파드를 모아 안정된 주소를 준다
- 트래픽이 안 가면 EndpointSlice 부터 본다
- 타입 — ClusterIP ⊂ NodePort ⊂ LoadBalancer. 헤드리스는 DNS 만
- kube-proxy — L4 다. iptables/IPVS/nftables. 연결 단위라 gRPC 는 쏠린다
- DNS — ndots:5 로 외부 도메인 조회에 왕복이 늘어난다
- Ingress — API 는 살아 있지만 feature-frozen
- Ingress NGINX 컨트롤러는 2026-03 은퇴 (F5 판과 다른 것)
- Gateway API — 역할 분리 + 표준 필드. CKA 커리큘럼에도 들어갔다
- NetworkPolicy 기본은 전부 열림. 하나 적용하면 그 파드는 기본 거부로 전환
- CNI 지원 필수 · DNS 를 막으면 전부 깨진다
- 진단 순서 — Ready → 엔드포인트 → DNS → 포트 → targetPort → 정책 → 컨트롤러
출처 — Kubernetes Docs: "Service" · "EndpointSlices" · "DNS for Services and Pods" · "Ingress" · "Gateway API" · "Network Policies" · "Cluster Networking" · Kubernetes Blog "Ingress NGINX Retirement: Statement from the Steering and Security Response Committees" (2026-01-29) · CKA Curriculum v1.35 (Services and Networking 20%)