들어가기전 읽어보기 좋은 개념 : 01_OSI 7계층 모델,02_DNS

🎯 핵심 3가지 네트워킹 요구사항

쿠버네티스는 어떻게 구현할지는 자유, 하지만 이 3가지는 반드시 지켜라!

모든 파드는 고유 IP를 가진다

전통적 Docker 방식 (문제):
Host IP: 192.168.1.100
  ├─ Container1 → 192.168.1.100:8080 (포트 매핑)
  └─ Container2 → 192.168.1.100:8081 (포트 충돌 관리 필요)
  
❌ 문제점:
- 포트 충돌 관리 복잡
- 컨테이너가 외부 노출 IP/포트를 알 수 없음
- 동적 스케일링 어려움

쿠버네티스 방식 (해결):
Node IP: 192.168.1.100
  ├─ Pod1: 10.244.1.5 (독립된 IP!)
  └─ Pod2: 10.244.1.6 (독립된 IP!)
  
✅ 장점:
- 포트 충돌 없음 (각자 80포트 사용 가능)
- VM처럼 독립적인 네트워크 환경
- 개발자는 포트 매핑 신경 안 써도 됨

CNI가 이를 구현하는 방법

파드 생성 플로우:

1. 사용자: kubectl run nginx --image=nginx
   ↓
2. 스케줄러: "이 파드는 node-1로!"
   ↓
3. node-1의 kubelet: "새 파드 할당됨 감지"
   ↓
4. kubelet → containerd: 
   "컨테이너 생성하고 network namespace 만들어줘"
   
   (네트워크 네임스페이스 = 격리된 네트워크 공간)
   ├─ 독립된 IP 주소
   ├─ 독립된 라우팅 테이블
   └─ 독립된 방화벽 규칙
   ↓
5. kubelet → CNI 플러그인 호출:
   /opt/cni/bin/calico ADD \
     --containerid=abc123 \
     --netns=/var/run/netns/pod-abc123
   ↓
6. CNI 플러그인 작업:
   
   a) IPAM(IP 주소 관리) 호출
      "이 노드의 파드 대역: 10.244.1.0/24"
      → 사용 가능한 IP: 10.244.1.5 할당!
   
   b) veth pair 생성 (가상 케이블)
      ┌─────────────────────────────┐
      │   Pod Namespace             │  Node Root Namespace
      │                             │
      │   eth0 (10.244.1.5) ◄─────► vethXXX ─┐
      └─────────────────────────────┘         │
                                              │
                                         ┌────▼────┐
                                         │  cni0   │ (브릿지)
                                         │ bridge  │
                                         └─────────┘
   
   c) IP/Gateway 설정
      Pod 내부 eth0:
      - IP: 10.244.1.5/24
      - Gateway: 10.244.1.1 (cni0 브릿지)
   
   d) 라우팅 규칙 추가
      Node의 라우팅 테이블:
      10.244.1.5 → vethXXX (이 파드로!)
   ↓
7. kubelet: "성공!" → 파드 Running 상태

2️⃣ 모든 파드는 NAT 없이 서로 통신 가능

NAT가 뭔가요?

집 (사설 IP: 192.168.0.10) → 공유기 (공인 IP: 203.0.113.5)
출발지 IP를 바꿔서 전송하는 것!

외부에서 보면: "어? 203.0.113.5에서 온 패킷이네"
실제 출발지: 192.168.0.10 (숨겨짐!)

쿠버네티스는 왜 NAT 없는 통신을 요구하나?

NAT가 있으면 (문제):

Pod A (10.244.1.5) → NAT → Pod B
                      ↓
Pod B가 받은 패킷:
출발지: 192.168.1.100 (Node IP로 변환됨)

❌ 문제:
- 실제 출발지(Pod A)를 알 수 없음
- 로그에 Node IP만 찍힘
- 보안 정책 적용 불가 (어떤 파드인지 모름)
- 디버깅 어려움


NAT 없는 통신 (쿠버네티스):

Pod A (10.244.1.5) → 직접 → Pod B (10.244.2.10)

Pod B가 받은 패킷:
출발지: 10.244.1.5 (Pod A의 실제 IP)

✅ 장점:
- 투명한 통신 (출발지 명확)
- 로그/모니터링 정확
- 보안 정책 적용 가능
- 서비스 메시 구현 용이

같은 노드 vs 다른 노드 통신

🏠 Case 1: 같은 노드 내 파드 통신 (L2)

node-1:
  Pod A (10.244.1.5) → cni0 bridge → Pod B (10.244.1.6)
  
  단순히 브릿지만 거침! (매우 빠름)


🌐 Case 2: 다른 노드의 파드 통신 (L3)

방법 1️⃣: Overlay Network (Flannel VXLAN)

Pod A                              Pod B
10.244.1.5                        10.244.2.10
(node-1)                          (node-2)
   ↓
1. Pod A: "10.244.2.10으로 보낼게"
   ↓
2. node-1의 flanneld:
   "어? 다른 노드 파드네. 캡슐화 필요!"
   
   [원본 패킷]
   Src: 10.244.1.5
   Dst: 10.244.2.10
   
   ↓ VXLAN 캡슐화
   
   [VXLAN 헤더 + 원본 패킷]
   Outer Src: 192.168.1.100 (node-1 IP)
   Outer Dst: 192.168.1.101 (node-2 IP)
   
3. 물리 네트워크를 통해 node-2로 전송
   ↓
4. node-2의 flanneld:
   "VXLAN 패킷이네. 캡슐 벗겨야지"
   
   ↓ 역캡슐화
   
   [원본 패킷]
   Src: 10.244.1.5
   Dst: 10.244.2.10
   
5. Pod B에 전달!

💡 Pod B 입장: NAT 없이 Pod A의 진짜 IP 보임!


방법 2️⃣: Direct Routing (Calico BGP)

Pod A                              Pod B
10.244.1.5                        10.244.2.10
(node-1)                          (node-2)

1. node-1: "10.244.2.0/24는 node-2로 보내!"
   node-2: "10.244.1.0/24는 node-1로 보내!"
   
   ↓ BGP로 서로 경로 광고
   
2. Pod A → 10.244.2.10으로 패킷 전송
   ↓
3. node-1 라우팅 테이블:
   "10.244.2.0/24 → 192.168.1.101 (node-2)"
   
4. 직접 라우팅! (캡슐화 없음)
   [원본 패킷 그대로]
   Src: 10.244.1.5
   Dst: 10.244.2.10
   
5. node-2로 도착 → Pod B에 전달

💡 오버헤드 없어서 더 빠름!

3️⃣ 노드와 파드는 양방향 NAT 없이 통신

왜 필요한가?

1. Node → Pod 통신 필요 상황:

kubelet이 파드 상태 체크:
curl http://10.244.1.5:8080/healthz

Prometheus가 메트릭 수집:
curl http://10.244.1.5:9090/metrics


2. Pod → Node 통신 필요 상황:

파드가 kubelet API 호출:
curl https://192.168.1.100:10250/stats

DaemonSet의 node-exporter 접근:
curl http://192.168.1.100:9100/metrics

실제 동작

노드의 프로세스들:

┌─────────────────────────────────────────┐
│           Node (192.168.1.100)          │
│                                         │
│  kubelet (10250)                        │
│  node-exporter (9100)                   │
│                                         │
│  ┌──────────────┐  ┌──────────────┐   │
│  │ Pod A        │  │ Pod B        │   │
│  │ 10.244.1.5   │  │ 10.244.1.6   │   │
│  └──────────────┘  └──────────────┘   │
└─────────────────────────────────────────┘

Node → Pod:
kubelet이 10.244.1.5:8080 호출
→ cni0 브릿지 거쳐 Pod A 도달
→ Pod A는 출발지 192.168.1.100 확인 (Node IP)

Pod → Node:
Pod A가 192.168.1.100:10250 호출
→ 기본 게이트웨이(cni0) 거쳐 Node로
→ kubelet은 출발지 10.244.1.5 확인 (Pod IP)

✅ 양쪽 다 실제 IP 그대로!

📦 4가지 IP 주소 종류

계층별 IP 정리

외부 세계
   ↓
[External IP] 203.0.113.5 (로드밸런서)
   ↓
클러스터 경계
   ↓
[Cluster IP] 10.96.0.10 (Service)
   ↓ kube-proxy가 로드밸런싱
   ├─ [Pod IP] 10.244.1.5
   ├─ [Pod IP] 10.244.1.6
   └─ [Pod IP] 10.244.2.10
   
이 모든 파드들이 실행되는 곳:
[Node IP] 192.168.1.100 (물리/가상 머신)

1️⃣ Pod IP

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
```
```
생성되면:
 
$ kubectl get pod nginx -o wide
NAME    READY   STATUS    IP            NODE
nginx   1/1     Running   10.244.1.5    node-1
 
특징:
✅ 클러스터 내부에서만 접근 가능
✅ 파드 재시작 시 IP 변경됨 (비영속적)
✅ CNI가 할당 (10.244.0.0/16 같은 대역)
✅ 파드 내 모든 컨테이너가 공유
 
실제 통신:
$ kubectl exec pod-a -- curl http://10.244.1.5
→ 가능 (같은 클러스터)
 
$ curl http://10.244.1.5
→ 불가능 (클러스터 외부에서)
```
 
### 2️⃣ Node IP
```
물리/가상 머신의 IP:
 
$ kubectl get nodes -o wide
NAME     STATUS   INTERNAL-IP      EXTERNAL-IP
node-1   Ready    192.168.1.100    34.123.45.67
node-2   Ready    192.168.1.101    34.123.45.68
 
Internal-IP: 사설 네트워크 IP (VPC 내부)
External-IP: 공인 IP (있을 수도, 없을 수도)
 
특징:
✅ 인프라 레벨 IP
✅ 노드가 살아있는 동안 안정적
✅ kubelet API 접근용

3️⃣ Cluster IP (Service IP)

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: ClusterIP  # 기본값
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 8080
```
```
생성되면:
 
$ kubectl get svc my-service
NAME         TYPE        CLUSTER-IP    PORT(S)
my-service   ClusterIP   10.96.0.10    80/TCP
 
특징:
✅ 가상 IP (실제 인터페이스 없음!)
✅ 클러스터 내부에서만 접근 가능
✅ 서비스가 살아있는 동안 불변
✅ kube-proxy가 iptables/IPVS로 구현
 
동작 원리:
 
클라이언트 파드: curl http://10.96.0.10:80

kube-proxy의 iptables 규칙:
   "10.96.0.10:80 → 랜덤으로 하나 선택"
   ├─ 10.244.1.5:8080 (33%)
   ├─ 10.244.1.6:8080 (33%)
   └─ 10.244.2.10:8080 (33%)
   
실제로는 Cluster IP로 가는 게 아니라
바로 Pod IP로 전달됨!

4️⃣ External IP

apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  type: LoadBalancer  # 외부 노출
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080
```
```
클라우드에서 생성되면:
 
$ kubectl get svc web-service
NAME          TYPE           CLUSTER-IP    EXTERNAL-IP
web-service   LoadBalancer   10.96.0.20    34.123.45.100
 
특징:
✅ 인터넷에서 접근 가능한 IP
✅ 클라우드 제공자가 로드밸런서 생성
✅ 비용 발생 (AWS ELB, GCP LB 등)
 
통신 흐름:
 
인터넷 사용자

External IP: 34.123.45.100:80
   ↓ (클라우드 로드밸런서)
NodePort: 192.168.1.100:30080
   ↓ (kube-proxy)
Cluster IP: 10.96.0.20:80
   ↓ (로드밸런싱)
Pod IP: 10.244.1.5:8080
```
 
---
 
## 🔧 CNI 플러그인 실전 비교
 
### 시나리오별 선택 가이드
```
📚 학습/개발 환경:
→ Flannel (K3s 기본)
  - 설정 0개
  - 바로 사용 가능
  - 네트워크 정책 필요 없음
 
🏢 프로덕션 (보안 중요):
→ Calico
  - 강력한 네트워크 정책
  - BGP로 고성능
  - 대규모 검증됨
 
🚀 최신 기술 + 최고 성능:
→ Cilium
  - eBPF 기반
  - L7 정책 가능
  - Hubble로 가시성
 
☁️ 클라우드 환경:
→ 해당 클라우드 CNI
  - AWS VPC CNI
  - Azure CNI
  - GKE CNI
```
 
### 실제 동작 차이
```
Pod A (node-1) → Pod B (node-2) 통신 시:
 
Flannel (VXLAN):
1. 패킷 캡슐화 필요
2. UDP 오버헤드 있음
3. 지연시간: ~10% 증가
4. 네트워크 정책: 
 
Calico (BGP):
1. 직접 라우팅
2. 오버헤드 거의 없음
3. 지연시간: 최소
4. 네트워크 정책: ✅ 강력
 
Cilium (eBPF):
1. 커널 레벨 처리
2. iptables 우회
3. 지연시간: 최소
4. 네트워크 정책: ✅ L7까지
5. 추가: HTTP 경로 제어 가능!
```
 
---
 
## 💡 핵심 정리
 
### 레이어별 역할
 
| 레이어 | 컴포넌트 | 역할 |
|--------|---------|------|
| **애플리케이션** | 컨테이너 | 비즈니스 로직 실행 |
| **파드** | Pod (고유 IP) | 컨테이너 그룹 + 네트워크 격리 |
| **서비스** | Service (Cluster IP) | 안정적인 엔드포인트 제공 |
| **노드** | kubelet + CNI | 파드 네트워킹 구현 |
| **물리/가상** | Node (Node IP) | 인프라 레벨 통신 |
 
### 통신 플로우 전체 그림
```
1. 개발자: kubectl apply -f deployment.yaml

2. 스케줄러: "pod-1은 node-1로, pod-2는 node-2로"

3. 각 노드의 kubelet: CNI 호출

4. CNI: veth pair 생성 + IP 할당
   ├─ pod-1: 10.244.1.5
   └─ pod-2: 10.244.2.10

5. 서비스 생성:
   ClusterIP: 10.96.0.10 → {pod-1, pod-2}

6. kube-proxy: iptables 규칙 생성

7. 다른 파드: curl http://my-service
   ↓ DNS로 Cluster IP 조회
   ↓ kube-proxy가 Pod IP로 변환
   ↓ CNI가 라우팅
   ↓ 목적지 파드 도달!

💡 핵심 개념 정리

개념설명
CNI네트워킹 플러그인 표준 인터페이스
VXLAN오버레이 네트워크 (캡슐화)
BGPL3 라우팅 프로토콜
eBPF커널 프로그래밍 기술
CoreDNS클러스터 내부 DNS 서버
FQDN완전한 도메인 이름
Default Allow기본 허용 모드 (위험)
Default Deny기본 거부 모드 (안전)

01. CNI(Container Network Interface)의 역할과 원리

🎯 핵심 개념

CNI는 쿠버네티스가 네트워킹 책임을 외부 플러그인에 위임하기 위한 표준 명세

동작 플로우

1. 파드 스케줄링
   ↓
2. Kubelet이 파드 할당 인지
   ↓
3. 컨테이너 런타임 호출 (containerd)
   → 네트워크 네임스페이스 생성 (격리된 네트워크 공간)
   ↓
4. CNI 플러그인 호출 ⭐️
   (Kubelet이 /opt/cni/bin/calico 등 실행)
   → 네임스페이스 경로, 컨테이너 ID 전달
   ↓
5. CNI 플러그인의 실제 작업:
   ├─ IPAM 플러그인으로 IP 주소 할당받음
   ├─ veth pair 생성 (가상 이더넷 쌍)
   │  ├─ 한쪽: 호스트의 루트 네임스페이스
   │  └─ 다른쪽: 컨테이너의 네임스페이스
   ├─ 컨테이너 인터페이스에 IP/게이트웨이 설정
   └─ 호스트 라우팅 테이블 업데이트

핵심 포인트

  • Kubelet은 세부사항을 모름 - 단지 표준 인터페이스로 호출만 함
  • 플러그인 아키텍처 - 다양한 CNI 구현체 선택 가능
  • 책임 분리 - 쿠버네티스는 오케스트레이션, CNI는 네트워킹

02. CNI 플러그인 비교 분석

1️⃣ Flannel - 단순함의 미학

핵심 기술: VXLAN 오버레이 네트워크

작동 원리

파드A(node-1) → 파드B(node-2) 통신 시:

1. flanneld가 패킷 가로챔
2. VXLAN 헤더로 캡슐화 (Encapsulation)
3. node-2 IP로 전송
4. node-2의 flanneld가 역캡슐화 (Decapsulation)
5. 목적지 파드에 전달
항목평가
✅ 장점• 설정 매우 간단
• 기존 네트워크 변경 불필요
• 학습 곡선 낮음
❌ 단점• 캡슐화 오버헤드
네트워크 정책 미지원 (치명적)
🎯 추천개발/테스트 환경, 소규모 클러스터

2️⃣ Calico - 보안과 확장성의 강자

핵심 기술: BGP 기반 순수 L3 라우팅

작동 원리

각 노드 = BGP 라우터 역할

node-1: "10.244.1.0/26 대역은 나한테 보내!"
node-2: "10.244.2.0/26 대역은 나한테 보내!"
         ↓ BGP로 서로 광고
캡슐화 없이 네이티브 라우팅으로 통신
항목평가
✅ 장점강력한 네트워크 정책
• 오버레이 없어 고성능
• 대규모 검증됨 (수천 노드)
• 글로벌 정책, deny 규칙 지원
❌ 단점• BGP 설정 복잡할 수 있음
• 환경에 따라 네트워크팀 협력 필요
🎯 추천프로덕션 표준, 보안 중요 환경, 대규모 클러스터

3️⃣ Cilium - eBPF 기반 차세대 혁신

핵심 기술: eBPF (커널 프로그래밍)

eBPF란?

  • 커널 소스 수정 없이 커널 동작을 프로그래밍
  • 커널 네트워킹 스택 초기 단계에서 패킷 처리
  • iptables/IPVS 우회

혁신적 기능

1. L7(API 수준) 네트워크 정책:
   "A 서비스는 B 서비스의 /api/v1/read에만 
    GET 요청 가능" ← HTTP 경로까지 제어!

2. Hubble 가시성:
   서비스 간 모든 통신 실시간 시각화

3. 서비스 메시 기능:
   사이드카 없이 eBPF로 L7 라우팅
항목평가
✅ 장점압도적 성능
• API-aware 보안 (L7)
• 뛰어난 관측성 (Hubble)
• 내장 서비스 메시
❌ 단점• 최신 커널 필요 (4.9+)
• 상대적으로 덜 성숙한 커뮤니티
🎯 추천최고 성능+보안+가시성, 최신 기술 수용 가능한 조직

📊 비교 도표

기능FlannelCalicoCilium
핵심 기술VXLANBGPeBPF
성능낮음높음매우 높음
네트워크 정책✅ 강력✅ L7
확장성중간높음매우 높음
관측성기본기본뛰어남
설정 난이도매우 쉬움중간중간

03. 쿠버네티스 DNS와 서비스 디스커버리

🎯 핵심 문제

IP 주소 대신 이름으로 통신하기

  • ClusterIP도 변할 수 있음
  • IP는 인간 친화적이지 않음
  • 서비스 디스커버리 필요

CoreDNS 동작 플로우

1. 서비스 생성
   kubectl create service user-service
   ↓
2. API 서버가 이벤트 발생
   ↓
3. CoreDNS가 watch하다가 감지
   ↓
4. 자동으로 DNS 레코드 생성
   ├─ user-service.production (짧은 이름)
   └─ user-service.production.svc.cluster.local (FQDN)
   ↓
5. 파드의 /etc/resolv.conf에 자동 설정
   ├─ nameserver: CoreDNS ClusterIP
   └─ search: production.svc.cluster.local

DNS 이름 규칙

<service-name>.<namespace>.svc.cluster.local
     ↓              ↓        ↓        ↓
  서비스명      네임스페이스  서비스  클러스터도메인

예시:

  • 같은 네임스페이스: user-service
  • 다른 네임스페이스: user-service.production
  • 완전한 이름: user-service.production.svc.cluster.local

위치 투명성 (Location Transparency)

개발자 코드:
USER_SERVICE_HOST=user-service
http://{USER_SERVICE_HOST}/api/users

→ CoreDNS가 ClusterIP로 변환
→ 서비스가 실제 파드 IP로 로드밸런싱
→ 파드가 어느 노드에 있든 상관없음!

장점:

  • IP 주소 관리 불필요
  • 동적 환경에서 안정적
  • 마이크로서비스 간 느슨한 결합

04. 네트워크 정책 - 제로 트러스트

🚨 문제: 기본적으로 허용 (Default Allow)

현재 상태: 모든 파드 ↔ 모든 파드 통신 가능

위험:
frontend → database 직접 접속 가능
침해된 파드 → 측면 이동(Lateral Movement) 가능

제로 트러스트 원칙

“Never Trust, Always Verify”

  • 내부 네트워크도 믿지 않음
  • 모든 통신은 명시적 허가 필요
  • 최소 권한의 원칙

네트워크 정책 구조

yaml

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-allow-backend
spec:
  podSelector:              # 1️⃣ 누구에게 적용?
    matchLabels:
      role: database
  
  policyTypes:             # 2️⃣ 방향
  - Ingress                # 인바운드 제어
  
  ingress:                 # 3️⃣ 규칙
  - from:                  # 출발지
    - podSelector:
        matchLabels:
          app: backend
    ports:                 # 포트
    - protocol: TCP
      port: 6379
```
 
### 작동 원리
```
정책 적용 전 (Default Allow):
모든 파드 → database:6379 ✅
 
정책 적용 후 (Default Deny + 명시적 허용):
app: backend → database:6379 ✅
app: frontend → database:* ❌
app: backend → database:3306 ❌
```
 
### 핵심 포인트
 
1. **정책 하나라도 적용되면 Default Deny로 전환**
2. **레이블 셀렉터 기반** - 동적으로 파드 선택
3. **CNI가 실제 구현** - iptables, eBPF 등으로 변환
4. **L3/L4 수준 제어** - IP, 포트, 프로토콜
 
### 선택 기준
- **podSelector**: 파드 레이블로 선택
- **namespaceSelector**: 네임스페이스로 선택
- **ipBlock**: IP 대역으로 선택
 
---
 
## 🔄 전체 플로우 요약
```
1. CNI가 파드에 IP 할당
   └─ Kubelet → CNI 플러그인 → IP/라우팅 설정
 
2. 서비스가 안정적인 진입점 제공
   └─ ClusterIP로 파드 집합 추상화
 
3. DNS가 이름 기반 디스커버리 제공
   └─ CoreDNS가 서비스명 → ClusterIP 변환
 
4. 네트워크 정책이 보안 제공
   └─ 명시적 허용만 통신 가능 (Zero Trust)

작성일: 2025-11-05