본문 바로가기

컨테이너/쿠버네티스 네트워크

[Cilium] (10) 실리움 네트워킹 _ Native Routing

Native Routing

mkdir cilium-lab && cd cilium-lab

curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/cilium-study/4w/Vagrantfile

vagrant up

 

✅ Native Routing 이란? 

📦 상황
Pod가 10.244.1.5라는 사설 IP를 가지고 있어요.
이 Pod는 외부 시스템(예: 내부 데이터센터에 있는 DB 서버 192.0.2.20)에 접근하려고 합니다.
이 환경에서는 클러스터 네트워크와 외부 네트워크 간에 직접 라우팅이 가능하도록 구성되어 있습니다.

💡 즉, 클러스터 외부 네트워크 장비가 10.244.0.0/16 같은 Pod CIDR을 직접 라우팅할 수 있음.


🚫 Masquerading이 필요한가요?
→ ❌ 필요 없습니다. Pod의 IP는 외부에서 인식 가능하고, 외부 장비가 해당 IP로 직접 응답할 수 있기 때문입니다.


🎯 Native Routing 흐름

  1. Pod (10.244.1.5)가 내부 시스템(192.0.2.20)으로 패킷을 전송
  2. 이 패킷은 노드를 통해 외부 네트워크로 전송됨 (출발지 IP는 그대로 10.244.1.5)
  3. 외부 라우터는 10.244.0.0/16 대역을 라우팅할 수 있으므로 패킷을 정상적으로 전달
  4. 응답 패킷은 192.0.2.20 → 10.244.1.5로 클러스터 내 Pod로 직접 돌아옴

 

autoDirectNodeRoutes=true는 Cilium에서 사용하는 중요한 설정 중 하나입니다. 이 옵션은 Pod 간 통신을 최적화하고, 불필요한 NAT(Masquerading)를 피하기 위한 노드 간 라우팅 자동화 기능입니다.


🔧 autoDirectNodeRoutes=true란?

Cilium이 각 노드의 Pod CIDR에 대해 직접 라우팅 경로(direct routes)를 자동으로 설정해주는 기능입니다.

✅ 기능 요약

  • 클러스터 내 노드 간 Pod 통신 시,
    → 패킷을 kube-proxy나 NAT를 거치지 않고 직접 목적지 노드로 보냄.
  • Pod IP 대역(Pod CIDR) 에 대한 라우팅 테이블을 자동으로 관리함.
  • 결과적으로 masquerading 없이도 Pod 간 통신이 가능함 (출발지 IP가 유지됨).
# vagrant ssh router
# router 네트워크 인터페이스 정보 확인
root@router:~# ip -br -c -4 addr
lo               UNKNOWN        127.0.0.1/8
eth0             UP             10.0.2.15/24 metric 100
eth1             UP             192.168.10.200/24
eth2             UP             192.168.20.200/24
loop1            UNKNOWN        10.10.1.200/24
loop2            UNKNOWN        10.10.2.200/24

# k8s node 네트워크 인터페이스 정보 확인
ip -c -4 addr show dev eth1
sshpass -p 'vagrant' ssh vagrant@k8s-w1 ip -c -4 addr show dev eth1
sshpass -p 'vagrant' ssh vagrant@k8s-w0 ip -c -4 addr show dev eth1


# 라우팅 정보 확인
sshpass -p 'vagrant' ssh vagrant@router ip -c route

default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.0.2.2 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.0.2.3 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.10.1.0/24 dev loop1 proto kernel scope link src 10.10.1.200
10.10.2.0/24 dev loop2 proto kernel scope link src 10.10.2.200
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.200
192.168.20.0/24 dev eth2 proto kernel scope link src 192.168.20.200


## --set routingMode=native --set autoDirectNodeRoutes=true 동작을 정확히 이해해보자!
ip -c route
sshpass -p 'vagrant' ssh vagrant@k8s-w1 ip -c route
sshpass -p 'vagrant' ssh vagrant@k8s-w0 ip -c route


# 통신 확인
ping -c 1 10.10.1.200     # router loop1 
ping -c 1 192.168.20.100  # k8s-w0 eth1

# 목적지까지 경우하는 라우팅 정보 제공
## Path MTU (pmtu): 출발지에서 목적지까지 모든 네트워크 경로 상에서 통과할 수 있는 최대 패킷 크기(Byte), IP fragmentation 없이 전송 가능한 가장 큰 패킷 크기를 의미.
## pmtu 1500: 전체 경로의 최소 MTU는 1500 , hops 2: 총 2단계 라우터/노드를 거쳐서 도달 , back 2: 응답도 동일한 hop 수로 돌아옴 


(⎈|HomeLab:N/A) root@k8s-ctr:~# tracepath -n 192.168.20.100
 1?: [LOCALHOST]                      pmtu 1500
 1:  192.168.10.200                                        0.317ms
 1:  192.168.10.200                                        0.288ms
 2:  192.168.20.100                                        0.549ms reached
     Resume: pmtu 1500 hops 2 back 2

 

샘플어플리케이션 배포

# 샘플 애플리케이션 배포
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webpod
  template:
    metadata:
      labels:
        app: webpod
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - sample-app
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: webpod
        image: traefik/whoami
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webpod
  labels:
    app: webpod
spec:
  selector:
    app: webpod
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
EOF


# k8s-ctr 노드에 curl-pod 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
  labels:
    app: curl
spec:
  nodeName: k8s-ctr
  containers:
  - name: curl
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

확인 :cilium-dbg

# 배포 확인
kubectl get deploy,svc,ep webpod -owide
kubectl get endpointslices -l app=webpod
kubectl get ciliumendpoints # IP 확인

#
kubectl exec -n kube-system ds/cilium -- cilium-dbg ip list
kubectl exec -n kube-system ds/cilium -- cilium-dbg endpoint list

kubectl exec -n kube-system ds/cilium -- cilium-dbg service list
20   10.96.32.212:80/TCP      ClusterIP      1 => 172.20.0.140:80/TCP (active)       
                                             2 => 172.20.1.213:80/TCP (active)       
                                             3 => 172.20.2.43:80/TCP (active) 

kubectl exec -n kube-system ds/cilium -- cilium-dbg bpf lb list
kubectl exec -n kube-system ds/cilium -- cilium-dbg bpf lb list | grep 10.96.32.212
10.96.32.212:80/TCP (0)        0.0.0.0:0 (20) (0) [ClusterIP, non-routable]   
10.96.32.212:80/TCP (3)        172.20.2.43:80/TCP (20) (3)                                   
10.96.32.212:80/TCP (2)        172.20.1.213:80/TCP (20) (2)                                                 
10.96.32.212:80/TCP (1)        172.20.0.140:80/TCP (20) (1) 

kubectl exec -n kube-system ds/cilium -- cilium-dbg bpf nat list

# map
kubectl exec -n kube-system ds/cilium -- cilium-dbg map list | grep -v '0             0'
Name                           Num entries   Num errors   Cache enabled
cilium_lb4_backends_v3         16            0            true
cilium_lb4_reverse_nat         20            0            true
cilium_runtime_config          256           0            true
cilium_policy_v2_01061         3             0            true
cilium_lb4_services_v2         45            0            true
cilium_ipcache_v2              22            0            true
cilium_lxc                     1             0            true
cilium_policy_v2_01990         2             0            true

kubectl exec -n kube-system ds/cilium -- cilium-dbg map get cilium_lb4_services_v2
kubectl exec -n kube-system ds/cilium -- cilium-dbg map get cilium_lb4_services_v2 | grep 10.96.32.212
Key                            Value                     State   Error
10.96.32.212:80/TCP (0)        0 3[0] (20) [0x0 0x0]             
10.96.32.212:80/TCP (1)        16 0[0] (20) [0x0 0x0]            
10.96.32.212:80/TCP (2)        14 0[0] (20) [0x0 0x0]            
10.96.32.212:80/TCP (3)        15 0[0] (20) [0x0 0x0]   

kubectl exec -n kube-system ds/cilium -- cilium-dbg map get cilium_lb4_backends_v3
kubectl exec -n kube-system ds/cilium -- cilium-dbg map get cilium_lb4_reverse_nat
kubectl exec -n kube-system ds/cilium -- cilium-dbg map get cilium_ipcache_v2

kubectl exec -n kube-system ds/cilium -- cilium-dbg service list | grep -3 10.96.187.60

20   10.96.187.60:80/TCP      ClusterIP      1 => 172.20.0.62:80/TCP (active)
                                             2 => 172.20.1.214:80/TCP (active)
                                             3 => 172.20.2.159:80/TCP (active)

🛠️ cilium-dbg란?

  • Cilium의 모니터링/디버깅 전용 CLI 도구
  • kubectl exec로 Cilium Pod 안에 들어가서 실행
  • cilium-dbg = 디버그 심볼(debug symbols)이 포함된 cilium 명령어 버전
    • 더 자세한 내부 상태 확인 가능
    • BPF map, endpoint, service, policy 상태 등을 볼 수 있음

📦 명령어 예시 설명

kubectl exec -n kube-system ds/cilium -- cilium-dbg service list | grep -3 10.96.187.60
 

✅ 이 명령어의 의미

  1. kubectl exec -n kube-system ds/cilium --
    → Cilium DaemonSet의 Pod 중 하나에 명령어 실행
  2. cilium-dbg service list
    → Cilium이 관리하는 서비스 (k8s Service) 목록을 출력
    → ClusterIP, NodePort, LoadBalancer 등 모두 포함

📦 명령어 예시 설명

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -n kube-system ds/cilium -- cilium-dbg bpf lb list | grep 10.96.187.60

10.96.187.60:80/TCP (2)        172.20.1.214:80/TCP (20) (2)
10.96.187.60:80/TCP (3)        172.20.2.159:80/TCP (20) (3)
10.96.187.60:80/TCP (0)        0.0.0.0:0 (20) (0) [ClusterIP, non-routable]
10.96.187.60:80/TCP (1)        172.20.0.62:80/TCP (20) (1)

이 명령어는 Cilium의 BPF LoadBalancer(BPF lb) 에서 10.96.187.60:80/TCP 이란 ClusterIP에 매핑된 백엔드들을 출력합니다.

❗ 이 라인:

10.96.187.60:80/TCP (0)        0.0.0.0:0 (20) (0) [ClusterIP, non-routable]
 
  • 0.0.0.0:0는 null backend로, 유효하지 않은 경우를 나타냅니다.
  • Cilium은 이 슬롯을 fallback 용도로 예약하기도 합니다.
  • 실제로 사용되지 않고, ClusterIP가 외부로 라우팅되지 않는다는 의미.

router과 라우팅 통신 확인

# 통신 확인 : 문제 확인
kubectl exec -it curl-pod -- curl webpod | grep Hostname
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'


# k8s-w0 노드에 배포된 webpod 파드 IP 지정
export WEBPOD=$(kubectl get pod -l app=webpod --field-selector spec.nodeName=k8s-w0 -o jsonpath='{.items[0].status.podIP}')
echo $WEBPOD

# 신규 터미널 [router]
tcpdump -i any icmp -nn

# 
kubectl exec -it curl-pod -- ping -c 2 -w 1 -W 1 $WEBPOD

# 신규 터미널 [router] : 라우팅이 어떻게 되는가?
tcpdump -i any icmp -nn

08:47:33.305585 eth1  In  IP 172.20.0.77 > 172.20.2.159: ICMP echo request, id 27, seq 1, length 64
08:47:33.305601 eth0  Out IP 172.20.0.77 > 172.20.2.159: ICMP echo request, id 27, seq 1, length 64
^C

# 신규 터미널 [router]
ip -c route
root@router:~# ip -c route
default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.0.2.2 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.0.2.3 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.10.1.0/24 dev loop1 proto kernel scope link src 10.10.1.200
10.10.2.0/24 dev loop2 proto kernel scope link src 10.10.2.200
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.200
192.168.20.0/24 dev eth2 proto kernel scope link src 192.168.20.200

root@router:~# ip route get 172.20.2.159
172.20.2.159 via 10.0.2.2 dev eth0 src 10.0.2.15 uid 0
    cache

 

🎯 Native Routing 라우터의 routing table을 보면 172.20.2.159의 라우팅 테이블에는 172 대역이 없다.

  • 172.20.2.36에 대한 정확한 경로가 없기 때문에
  • 리눅스는 default route (기본 경로)인 default via 10.0.2.2를 사용합니다.
  • 그래서 ip route get 172.20.2.36 결과는:
    → 172.20.2.36 via 10.0.2.2 dev eth0

📘 라우팅 동작 원리 (핵심 개념)

리눅스는 목적지 IP를 결정할 때 아래와 같은 방식으로 라우팅 테이블을 위에서 아래로 순차적으로 탐색합니다:

  1. 정확한 경로 (most specific match) 있는지 찾음
  2. 없으면 상위 prefix (예: /24, /16) 를 점점 완화해가며 탐색
  3. 그래도 없으면 마지막으로 default 경로 사용
root@router:~# ip -c route
default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
10.0.2.2 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.0.2.3 dev eth0 proto dhcp scope link src 10.0.2.15 metric 100
10.10.1.0/24 dev loop1 proto kernel scope link src 10.10.1.200
10.10.2.0/24 dev loop2 proto kernel scope link src 10.10.2.200
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.200
192.168.20.0/24 dev eth2 proto kernel scope link src 192.168.20.200