컨테이너 네트워킹 (3) - Kubernetes 네트워킹 - soo:bak
작성일 :
Kubernetes는 네트워킹을 어떻게 추상화하는가
Part 2에서 오버레이 네트워크와 CNI를 통해 멀티호스트 컨테이너 통신이 어떻게 가능한지 살펴보았습니다.
Kubernetes는 이런 저수준 기술 위에 추상화 계층을 제공하여, 개발자가 네트워크 세부사항을 몰라도 애플리케이션을 배포할 수 있게 합니다.
Kubernetes 네트워크 모델
Kubernetes는 CNI 플러그인에게 다음 네 가지 조건을 만족시킬 것을 요구합니다:
- 모든 Pod는 고유 IP 주소를 가진다
- 모든 Pod는 NAT 없이 다른 모든 Pod와 통신할 수 있다
- 모든 노드는 NAT 없이 모든 Pod와 통신할 수 있다
- Pod가 보는 자신의 IP는 다른 Pod가 보는 IP와 같다
1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes 클러스터 │
│ │
│ 노드 A (10.0.0.1) 노드 B (10.0.0.2) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Pod 1 │ │ Pod 3 │ │
│ │ 10.244.1.5 │◄──────────►│ 10.244.2.3 │ │
│ │ │ 직접 │ │ │
│ │ Pod 2 │ 통신 │ Pod 4 │ │
│ │ 10.244.1.6 │ (NAT X) │ 10.244.2.4 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
이 모델은 포트 충돌 걱정 없이 각 Pod가 전체 포트 공간을 사용할 수 있게 하고, 컨테이너에서 VM으로의 이식성을 높이며, 네트워크 정책 설정을 단순하게 만듭니다.
Pod 네트워킹
Pod 내부의 네트워킹은 어떻게 동작할까요? 이를 이해하려면 먼저 pause 컨테이너를 알아야 합니다.
pause 컨테이너
모든 Pod에는 사용자가 직접 정의하지 않았지만 자동으로 생성되는 숨겨진 pause 컨테이너가 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Pod
┌─────────────────────────────────────────────────────────────┐
│ │
│ ┌───────────────┐ │
│ │ pause │ ← 네트워크 네임스페이스 소유 │
│ │ 컨테이너 │ │
│ └───────┬───────┘ │
│ │ │
│ ┌──────┴──────────────────────┐ │
│ │ │ │
│ ┌─┴──────────┐ ┌──────────────┐│ │
│ │ 앱 컨테이너 │ │ 사이드카 ││ ← 네임스페이스 공유 │
│ │ │ │ 컨테이너 ││ │
│ └────────────┘ └──────────────┘│ │
│ │
│ IP: 10.244.1.5 │
│ 모든 컨테이너가 같은 IP, localhost로 통신 │
│ │
└─────────────────────────────────────────────────────────────┘
pause 컨테이너는 네트워크 네임스페이스를 생성하고 유지하는 역할을 합니다. 앱 컨테이너가 재시작되어도 네트워크가 유지되며, 매우 작아서 실질적으로 아무 작업도 하지 않습니다.
Pod 내부 통신
같은 Pod의 컨테이너들은 네트워크 네임스페이스를 공유하므로 localhost로 통신할 수 있습니다. 포트만 다르게 사용하면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: two-containers
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
- name: sidecar
image: busybox
command: ['wget', '-O-', 'localhost:80'] # localhost로 nginx 접근
Pod 간 통신
다른 Pod와는 Pod IP로 직접 통신합니다. CNI 플러그인이 라우팅을 처리하며, Kubernetes 네트워크 모델에 따라 NAT 없이 직접 통신합니다.
1
Pod A (10.244.1.5) → 10.244.2.3:80 → Pod B (10.244.2.3)
Service 추상화
Pod IP를 직접 사용하면 몇 가지 문제가 있습니다. Pod는 일시적이어서 재시작되면 새 IP를 받고, 여러 Pod에 부하를 분산해야 하며, 동적으로 변하는 Pod IP를 하드코딩할 수 없습니다.
Service가 이 문제를 해결합니다. Service는 Pod 집합에 대한 안정적인 엔드포인트(IP와 DNS 이름)를 제공합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────────────────────┐
│ Service │
│ (my-service.default) │
│ ClusterIP: 10.96.0.10 │
└──────────────────────────┬──────────────────────────────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ Pod 1 │ │ Pod 2 │ │ Pod 3 │
│10.244 │ │10.244 │ │10.244 │
│.1.5 │ │.1.6 │ │.2.3 │
└────────┘ └────────┘ └────────┘
▲ ▲ ▲
│ │ │
label: app=myapp (셀렉터로 Pod 선택)
Service 유형
Service는 네 가지 유형이 있으며, 각각 다른 접근 범위와 사용 사례를 가집니다.
ClusterIP (기본)
클러스터 내부에서만 접근 가능한 가상 IP를 생성합니다. 마이크로서비스 간 내부 통신에 적합합니다.
1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: ClusterIP # 기본값
selector:
app: myapp
ports:
- port: 80 # Service 포트
targetPort: 8080 # Pod 포트
NodePort
모든 노드의 특정 포트(30000-32767 범위)를 열어 외부 접근을 허용합니다. 개발 환경이나 간단한 외부 노출에 적합합니다.
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
nodePort: 30080 # 30000-32767 범위
1
외부 → 노드IP:30080 → Service → Pod
LoadBalancer
클라우드 환경(AWS, GCP, Azure 등)에서 외부 로드 밸런서를 자동으로 프로비저닝합니다. 프로덕션 환경에서 외부 트래픽을 받을 때 주로 사용합니다.
1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: LoadBalancer
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
1
인터넷 → 클라우드 LB → NodePort → Service → Pod
어떤 Service 유형을 선택해야 할까?
| 사용 사례 | 권장 유형 |
|---|---|
| 클러스터 내 마이크로서비스 간 통신 | ClusterIP |
| 개발/테스트 환경에서 외부 접근 | NodePort |
| 프로덕션 환경 외부 노출 (L4) | LoadBalancer |
| HTTP/HTTPS 라우팅이 필요한 경우 | Ingress + ClusterIP |
| 여러 서비스를 하나의 IP로 노출 | Ingress + ClusterIP |
일반적인 패턴은 내부 서비스에는 ClusterIP, 외부 노출이 필요한 HTTP 서비스에는 Ingress, 비HTTP 서비스(DB, 메시징 등)의 외부 노출에는 LoadBalancer를 사용하는 것입니다.
kube-proxy의 역할
Service는 가상의 개념이며, 실제 트래픽 라우팅은 kube-proxy가 담당합니다. kube-proxy는 각 노드에서 실행되면서 Service의 동작을 구현합니다.
iptables 모드
기본 모드로, Service IP로 가는 트래픽을 iptables 규칙으로 Pod로 리다이렉트합니다.
1
2
3
4
5
6
7
8
# kube-proxy가 생성하는 iptables 규칙 (단순화)
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp --dport 80 -j KUBE-SVC-XXX
-A KUBE-SVC-XXX -m statistic --mode random --probability 0.333 -j KUBE-SEP-AAA
-A KUBE-SVC-XXX -m statistic --mode random --probability 0.500 -j KUBE-SEP-BBB
-A KUBE-SVC-XXX -j KUBE-SEP-CCC
-A KUBE-SEP-AAA -p tcp -j DNAT --to-destination 10.244.1.5:8080
-A KUBE-SEP-BBB -p tcp -j DNAT --to-destination 10.244.1.6:8080
-A KUBE-SEP-CCC -p tcp -j DNAT --to-destination 10.244.2.3:8080
IPVS 모드
수천 개의 Service가 있는 대규모 클러스터에서는 IPVS 모드가 더 나은 성능을 제공합니다.
1
2
3
4
5
6
# IPVS 가상 서버
IP Virtual Server version 1.2.1
-> 10.96.0.10:80 rr
-> 10.244.1.5:8080 Masq 1
-> 10.244.1.6:8080 Masq 1
-> 10.244.2.3:8080 Masq 1
IPVS는 해시 테이블을 사용하여 O(1) 복잡도로 동작하며(iptables는 O(n)), round-robin, least-connection 등 다양한 로드 밸런싱 알고리즘을 지원합니다.
Ingress
Service는 L4(TCP/UDP)에서 동작하여 IP와 포트 기반으로만 라우팅합니다. 반면 Ingress는 L7(HTTP/HTTPS) 라우팅을 제공하여 URL 경로나 호스트명 기반으로 트래픽을 분배할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────────────────────┐
│ Ingress │
│ │
│ /api/* → api-service │
│ /web/* → web-service │
│ /*.js → static-service │
│ │
└──────────────────────────┬──────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│api-service │ │web-service │ │static-svc │
└────────────┘ └────────────┘ └────────────┘
Ingress 리소스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
tls:
- hosts:
- example.com
secretName: tls-secret
Ingress Controller
Ingress 리소스는 “이런 라우팅을 원한다”는 선언이며, 실제 라우팅은 Ingress Controller가 구현합니다. Kubernetes에는 기본 Ingress Controller가 없으므로 별도로 설치해야 합니다.
주요 Ingress Controller:
- NGINX Ingress: 가장 널리 사용되며 검증됨
- Traefik: 동적 설정과 자동 인증서 갱신에 적합
- HAProxy Ingress: 고성능이 필요한 환경
- AWS ALB Ingress Controller: AWS 환경에서 ALB와 통합
Network Policy
기본적으로 Kubernetes에서 모든 Pod는 서로 제한 없이 통신할 수 있습니다. 보안을 위해 Network Policy로 Pod 간 트래픽을 제어할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-policy
namespace: default
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
이 정책은 app: api 레이블이 있는 Pod에 적용되며, 인바운드 트래픽은 app: web Pod에서 8080 포트로만, 아웃바운드 트래픽은 app: database Pod의 5432 포트로만 허용합니다.
기본 거부 정책
1
2
3
4
5
6
7
8
9
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {} # 모든 Pod
policyTypes:
- Ingress
- Egress
모든 트래픽을 기본 차단하고 필요한 것만 명시적으로 허용하는 Zero Trust 접근법입니다. Network Policy는 CNI 플러그인이 지원해야 동작하며, Flannel은 지원하지 않고 Calico, Cilium 등은 지원합니다.
서비스 메시 개요
마이크로서비스가 많아지면 Service와 Network Policy만으로는 해결하기 어려운 문제들이 생깁니다. 서비스 간 mTLS 암호화, 트래픽 관찰(메트릭, 분산 트레이싱), 고급 트래픽 관리(A/B 테스트, 카나리 배포), 재시도/타임아웃/서킷 브레이커 같은 복원력 패턴 등이 그 예입니다.
서비스 메시가 이런 문제를 해결합니다.
Sidecar 패턴
서비스 메시의 핵심 아이디어는 각 Pod에 프록시 사이드카를 주입하는 것입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────────────────────────┐
│ Pod │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 앱 컨테이너 │ ◄──── localhost ──► │ 사이드카 │ │
│ │ │ │ (Envoy) │ │
│ └─────────────┘ └──────┬──────┘ │
│ │ │
└─────────────────────────────────────────────┼───────────────┘
│
mTLS │
│
┌─────────────────────────────────────────────┼───────────────┐
│ Pod │ │
│ ┌─────────────┐ ┌──────┴──────┐ │
│ │ 앱 컨테이너 │ ◄──── localhost ──► │ 사이드카 │ │
│ └─────────────┘ │ (Envoy) │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
앱은 localhost로 통신한다고 생각하지만, 실제로는 사이드카 프록시가 모든 네트워크 처리를 담당합니다. 이를 통해 애플리케이션 코드 수정 없이 mTLS, 관찰성, 트래픽 제어 기능을 추가할 수 있습니다.
주요 서비스 메시
Istio는 Google, IBM, Lyft가 개발했으며 Envoy 프록시를 사용합니다. 풍부한 기능을 제공하지만 그만큼 복잡성도 높습니다.
Linkerd는 CNCF 프로젝트로, Rust로 작성된 경량 프록시를 사용합니다. 단순함과 낮은 리소스 사용이 특징입니다.
정리
Kubernetes는 여러 계층의 추상화를 통해 컨테이너 네트워킹의 복잡성을 숨깁니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
높음
│
│ Service Mesh (Istio, Linkerd)
│ ↓
│ Ingress (L7 라우팅)
│ ↓
│ Service (L4 로드밸런싱, 서비스 디스커버리)
│ ↓
│ Network Policy (트래픽 제어)
│ ↓
│ Pod 네트워킹 (CNI)
│ ↓
│ 오버레이 네트워크 (VXLAN 등)
│ ↓
낮음 │ Linux 네트워킹 (veth, bridge, iptables)
각 계층은 아래 계층의 복잡성을 숨기므로, 애플리케이션 개발자는 Service와 Ingress 수준만 이해하면 됩니다. 반면 플랫폼 엔지니어나 인프라 담당자는 CNI, 오버레이 네트워크 등 하위 계층까지 이해해야 문제를 진단하고 해결할 수 있습니다.
관련 글