| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- 묘공단
- KEDA
- curl
- 컨테이너
- vscode
- kubernetes
- go
- minIO
- AKS
- ubuntu
- directpv
- Azure
- HPA
- ipam
- Timeout
- AutoScaling
- windows
- calico
- WSL
- gateway api
- EKS
- upgrade
- 업그레이드
- Object Storage
- cilium
- 쿠버네티스
- aws
- VPA
- ansible
- Karpenter
- Today
- Total
a story
[5] Cilium - Encapsulation 모드 본문
지난 주에 이어서 Cilium CNI Plugin에서 파드들 통신에 대해서 살펴보겠습니다. 특히 워커 노드가 서로 다른 IP대역에 위치하는 상황에서 Encapsulation 모드의 역할을 살펴보고, Cilium LoadBalancer IPAM과 Cilium L2 Announcement 에 대해서 살펴보겠습니다.
목차
- 실습 환경 구성
- Encapsulation 모드
- Cilium LoadBalancer IPAM
- Cilium L2 Announcement
1. 실습 환경 구성
vagrant 를 통해서 실습 환경을 실행합니다.
mkdir cilium-lab && cd cilium-lab
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/cilium-study/4w/Vagrantfile
vagrant up
vagrant up 이 완료되면 4대의 VM이 생성된 상태로 확인됩니다.
PS C:\cilium-lab\w4> vagrant status
Current machine states:
k8s-ctr running (virtualbox)
k8s-w1 running (virtualbox)
router running (virtualbox)
k8s-w0 running (virtualbox)
컨트롤 플레인에 2대의 워커 노드와, 추가로 router라는 VM이 생성되어 있습니다.
해당 실습 환경에서는 서로 다른 네트워크에 워커노드를 위치하고, router라는 VM을 통해서 서로 연결되도록 구성되어 있습니다. router가 192.168.10.0/24 대역과 192.168.20.0/24에 연결된 2개의 network Interface를 가지고, 각 네트워크에 대한 라우팅을 처리해주도록 구성되어 있습니다.

이후 실습은 vagrant ssh k8s-ctr과 같이 노드에 진입한 이후에 진행하도록 하겠습니다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get no -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr Ready control-plane 59m v1.33.2 192.168.10.100 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
k8s-w0 Ready <none> 41m v1.33.2 192.168.20.100 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
k8s-w1 Ready <none> 97s v1.33.2 192.168.10.101 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
워커 노드 2대가 연결되어 있으며, k8s-ctr, k8s-w1은 192.168.10.0/24 대역을 사용하고, k8s-w0는 192.168.20.0/24대역을 사용하고 있습니다.
# 각 VM에 간단히 명령어 수행을 위해서 아래 명령 실행
sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-w0 hostname
sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-w1 hostname
sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@router hostname
# 노드 정보 : 상태, INTERNAL-IP 확인
ifconfig | grep -iEA1 'eth[0-9]:'
ip route | grep static
(⎈|HomeLab:N/A) root@k8s-ctr:~# ifconfig | grep -iEA1 'eth[0-9]:'
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.255
--
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.10.100 netmask 255.255.255.0 broadcast 192.168.10.255
(⎈|HomeLab:N/A) root@k8s-ctr:~# ip route |grep static
10.10.0.0/16 via 192.168.10.200 dev eth1 proto static
172.20.0.0/16 via 192.168.10.200 dev eth1 proto static
192.168.20.0/24 via 192.168.10.200 dev eth1 proto static # router로 향함
# router의 routing 정보를 조회해보면 192.168.10.0/24와 192.168.20.0/24에 대한 라우팅이 적용되어 있음
# 또한 dummy interface로 10.10.1.0/24와 10.10.2.0/24 네트워크에 대한 라우팅도 있음
(⎈|HomeLab:N/A) root@k8s-ctr:~# 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
# ipam 모드 확인
cilium config view | grep ^ipam
cilium config view | grep ^routing
(⎈|HomeLab:N/A) root@k8s-ctr:~# cilium config view | grep ^ipam
ipam cluster-pool
ipam-cilium-node-update-rate 15s
(⎈|HomeLab:N/A) root@k8s-ctr:~# cilium config view | grep ^routing
routing-mode native
각 노드의 라우팅 정보를 바탕으로 Cilium의 autoDirectNodeRoutes=true 설정에 대해 이해해 보겠습니다.
## --set routingMode=native --set autoDirectNodeRoutes=true 가 설정되어 있다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# cilium config view | grep ^auto
auto-direct-node-routes true
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get ciliumnode
NAME CILIUMINTERNALIP INTERNALIP AGE
k8s-ctr 172.20.0.172 192.168.10.100 121m
k8s-w0 172.20.1.16 192.168.20.100 104m
k8s-w1 172.20.2.247 192.168.10.101 64m
ip -c route
sshpass -p 'vagrant' ssh vagrant@k8s-w1 ip -c route
sshpass -p 'vagrant' ssh vagrant@k8s-w0 ip -c route
(⎈|HomeLab:N/A) root@k8s-ctr:~# 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.0.0/16 via 192.168.10.200 dev eth1 proto static
172.20.0.0/24 via 172.20.0.172 dev cilium_host proto kernel src 172.20.0.172
172.20.0.0/16 via 192.168.10.200 dev eth1 proto static
172.20.0.172 dev cilium_host proto kernel scope link
172.20.2.0/24 via 192.168.10.101 dev eth1 proto kernel # k8s-w1로 라우팅이 자동 추가됨
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.100
192.168.20.0/24 via 192.168.10.200 dev eth1 proto static
(⎈|HomeLab:N/A) root@k8s-ctr:~# sshpass -p 'vagrant' ssh vagrant@k8s-w1 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.0.0/16 via 192.168.10.200 dev eth1 proto static
172.20.0.0/24 via 192.168.10.100 dev eth1 proto kernel # k8s-ctr로 라우팅이 자동 추가됨
172.20.0.0/16 via 192.168.10.200 dev eth1 proto static
172.20.2.0/24 via 172.20.2.247 dev cilium_host proto kernel src 172.20.2.247
172.20.2.247 dev cilium_host proto kernel scope link
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.101
192.168.20.0/24 via 192.168.10.200 dev eth1 proto static
(⎈|HomeLab:N/A) root@k8s-ctr:~# sshpass -p 'vagrant' ssh vagrant@k8s-w0 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.0.0/16 via 192.168.20.200 dev eth1 proto static
172.20.0.0/16 via 192.168.20.200 dev eth1 proto static
172.20.1.0/24 via 172.20.1.16 dev cilium_host proto kernel src 172.20.1.16
172.20.1.16 dev cilium_host proto kernel scope link
192.168.10.0/24 via 192.168.20.200 dev eth1 proto static
192.168.20.0/24 dev eth1 proto kernel scope link src 192.168.20.100
# 통신 확인
ping -c 1 10.10.1.200 # router loop1
ping -c 1 192.168.20.100 # k8s-w0 eth1
# router를 통해서 k8s-w0와 통신은 성립됨
(⎈|HomeLab:N/A) root@k8s-ctr:~# ping -c 1 10.10.1.200
PING 10.10.1.200 (10.10.1.200) 56(84) bytes of data.
64 bytes from 10.10.1.200: icmp_seq=1 ttl=64 time=3.36 ms
--- 10.10.1.200 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 3.358/3.358/3.358/0.000 ms
(⎈|HomeLab:N/A) root@k8s-ctr:~# ping -c 1 192.168.20.100
PING 192.168.20.100 (192.168.20.100) 56(84) bytes of data.
64 bytes from 192.168.20.100: icmp_seq=1 ttl=63 time=3.96 ms
--- 192.168.20.100 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 3.963/3.963/3.963/0.000 ms
autoDirectNodeRoutes=true가 설정되면 같은 네트워크에 있는 노드에 대해서만 PodCidr에 대한 static routing이 추가되는 것을 알 수 있습니다.
실습 환경에서는 k8s-ctr과 k8s-w1은 같은 네트워크에 위치해 있으며, 중간에 라우터를 거쳐야 k8s-w0와 통신이 가능합니다. 이때문에 Direct Node routing이 의미가 없으며 실제로 라우터에서 라우팅을 처리해주지 않는다면, 실제로 통신이 되지 않습니다.
결론적으로는 다른 대역의 노드에 위치한 파드 간에는 통신이 불가하게 됩니다.
2. Encapsulation 모드
앞서 살펴본 바와 같이 현재는 Native Routing 모드로 구성되어 있으며, 이 상황에서 실습 환경과 같은 구성이 어떤 문제가 되는지 살펴보겠습니다.
먼저 샘플 애플리케이션 배포하겠습니다.
# 샘플 애플리케이션 배포
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
생성된 애플리케이션을 확인해보고, 실제 통신이 제대로 되는지 확인해보겠습니다.
# 배포 확인
kubectl get deploy,svc,ep webpod -owide
kubectl get endpointslices -l app=webpod
kubectl get ciliumendpoints # IP 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get deploy,svc,ep webpod -owide
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/webpod 3/3 3 3 20h webpod traefik/whoami app=webpod
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/webpod ClusterIP 10.96.234.235 <none> 80/TCP 20h app=webpod
NAME ENDPOINTS AGE
endpoints/webpod 172.20.0.74:80,172.20.1.224:80,172.20.2.63:80 20h
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get endpointslices -l app=webpod
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
webpod-9v987 IPv4 80 172.20.0.74,172.20.1.224,172.20.2.63 20h
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get ciliumendpoints
NAME SECURITY IDENTITY ENDPOINT STATE IPV4 IPV6
curl-pod 58696 ready 172.20.0.217
webpod-697b545f57-5dd9p 12564 ready 172.20.0.74
webpod-697b545f57-rdh2t 12564 ready 172.20.1.224
webpod-697b545f57-vhn6k 12564 ready 172.20.2.63
# 통신 확인 : 문제 확인
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'
# 통신이 하나씩 빠진다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- curl webpod | grep Hostname
Hostname: webpod-697b545f57-5dd9p
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- curl webpod | grep Hostname
Hostname: webpod-697b545f57-vhn6k
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- curl webpod | grep Hostname
command terminated with exit code 130
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
---
Hostname: webpod-697b545f57-5dd9p
---
---
Hostname: webpod-697b545f57-vhn6k
---
Hostname: webpod-697b545f57-5dd9p
---
Hostname: webpod-697b545f57-vhn6k
---
Hostname: webpod-697b545f57-vhn6k
---
Hostname: webpod-697b545f57-5dd9p
# k8s-w0 노드에 배포된 webpod 파드 IP 지정 (k8s-w0는 다른 네트워크에 위치함)
export WEBPOD=$(kubectl get pod -l app=webpod --field-selector spec.nodeName=k8s-w0 -o jsonpath='{.items[0].status.podIP}')
echo $WEBPOD
(⎈|HomeLab:N/A) root@k8s-ctr:~# export WEBPOD=$(kubectl get pod -l app=webpod --field-selector spec.nodeName=k8s-w0 -o jsonpath='{.items[0].status.podIP}')
echo $WEBPOD
172.20.1.224
# 신규 터미널 [router]
tcpdump -i any icmp -nn
# k8s-w0(192.168.20.100)에 있는 WEBPOD로 ping 체크를 하면 실패한다.
# 앞서 살펴본 바와 같이 k8s-w0에 위치한 PodCIDR에 대해서는 라우팅 정보가 없다.
kubectl exec -it curl-pod -- ping -c 2 -w 1 -W 1 $WEBPOD
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- ping -c 2 -w 1 -W 1 $WEBPOD
PING 172.20.1.224 (172.20.1.224) 56(84) bytes of data.
--- 172.20.1.224 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
command terminated with exit code 1
# 신규 터미널 [router] : 응답이 없다.
root@router:~# tcpdump -i any icmp -nn
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
20:37:18.313178 eth1 In IP 172.20.0.217 > 172.20.1.224: ICMP echo request, id 99, seq 1, length 64 # eth1 에서 들어옴
20:37:18.313209 eth0 Out IP 172.20.0.217 > 172.20.1.224: ICMP echo request, id 99, seq 1, length 64 # eth0 로 보낸다 : eth0는 SNAT을 위한 NIC임 -> 통신 성립 안됨
# 신규 터미널 [router]
ip -c route
ip route get 172.20.2.36
# 172.20.1.0/24에 대한 라우팅 정보가 없다(PodCIDR에 대한 라우팅 정보 없음, 노드의 IP대역에 대한 라우팅만 처리함)
# -> default 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
# 해당 IP에 대한 라우팅 정보 확인
root@router:~# ip route get 172.20.2.36
172.20.2.36 via 10.0.2.2 dev eth0 src 10.0.2.15 uid 0
cache
현재 실습 환경과 같이 노드의 네트워크가 다른 구성에서 Cilium 의 Native Routing 모드에서는 서로 다른 네트워크에 위치한 파드로의 통신이 불가하다는 것을 알 수 있습니다.
이 문제를 해결하려면 두 가지 방안이 있습니다.
- 라우팅 처리: Router에서 PodCIDR에 대한 라우팅을 Static 하게 추가할 수 있습니다. 다만 노드가 많아질 수록 관리의 복잡도가 높아지고, 노드의 PodCIDR이 변경되는 상황에서 문제가 발생할 수 있습니다. 혹은 BGP를 통해서 자동으로 라우팅을 등록해줄 수 있습니다. 이 방안은 다음 포스트에서 알아보겠습니다.
- 오버레이 네트워크: Cilium의 Encapsulation 모드를 사용할 수 있습니다. 노드 간 통신이 가능한 환경이므로 파드의 원본 패킷을 노드에서 encapsulation하여 상대 노드로 전달하는 방식입니다.
해당 포스트에서는 Cilium의 Encapsulation 모드를 통해서 문제 상황을 해결해 보겠습니다.
# [커널 구성 옵션] Requirements for Tunneling and Routing
grep -E 'CONFIG_VXLAN=y|CONFIG_VXLAN=m|CONFIG_GENEVE=y|CONFIG_GENEVE=m|CONFIG_FIB_RULES=y' /boot/config-$(uname -r)
CONFIG_FIB_RULES=y # 커널에 내장됨
CONFIG_VXLAN=m # 모듈로 컴파일됨 → 커널에 로드해서 사용
CONFIG_GENEVE=m # 모듈로 컴파일됨 → 커널에 로드해서 사용
# 커널 로드
lsmod | grep -E 'vxlan|geneve'
modprobe vxlan # modprobe geneve
lsmod | grep -E 'vxlan|geneve'
(⎈|HomeLab:N/A) root@k8s-ctr:~# lsmod | grep -E 'vxlan|geneve'
(⎈|HomeLab:N/A) root@k8s-ctr:~# modprobe vxlan # modprobe geneve
(⎈|HomeLab:N/A) root@k8s-ctr:~# lsmod | grep -E 'vxlan|geneve'
vxlan 155648 0
ip6_udp_tunnel 16384 1 vxlan
udp_tunnel 32768 1 vxlan
for i in w1 w0 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh vagrant@k8s-$i sudo modprobe vxlan ; echo; done
for i in w1 w0 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh vagrant@k8s-$i sudo lsmod | grep -E 'vxlan|geneve' ; echo; done
# k8s-w1 노드에 배포된 webpod 파드 IP 지정
export WEBPOD1=$(kubectl get pod -l app=webpod --field-selector spec.nodeName=k8s-w1 -o jsonpath='{.items[0].status.podIP}')
echo $WEBPOD1
# 반복 ping 실행해두기
kubectl exec -it curl-pod -- ping $WEBPOD1
# rollout 되는 순간 일시적으로 통신이 끊어짐
64 bytes from 172.20.2.63: icmp_seq=107 ttl=62 time=3.71 ms
64 bytes from 172.20.2.63: icmp_seq=108 ttl=62 time=2.75 ms
From 172.20.0.172 icmp_seq=143 Time to live exceeded
From 172.20.0.172 icmp_seq=144 Time to live exceeded
From 172.20.0.172 icmp_seq=145 Time to live exceeded
From 172.20.0.172 icmp_seq=146 Time to live exceeded
From 172.20.0.172 icmp_seq=147 Time to live exceeded
From 172.20.0.172 icmp_seq=148 Time to live exceeded
From 172.20.0.172 icmp_seq=149 Time to live exceeded
From 172.20.0.172 icmp_seq=150 Time to live exceeded
From 172.20.0.172 icmp_seq=151 Time to live exceeded
From 172.20.0.172 icmp_seq=152 Time to live exceeded
64 bytes from 172.20.2.63: icmp_seq=153 ttl=63 time=9.84 ms
64 bytes from 172.20.2.63: icmp_seq=154 ttl=63 time=5.50 ms
64 bytes from 172.20.2.63: icmp_seq=155 ttl=63 time=2.37 ms
# 업그레이드
helm upgrade cilium cilium/cilium --namespace kube-system --version 1.18.0 --reuse-values \
--set routingMode=tunnel --set tunnelProtocol=vxlan \
--set autoDirectNodeRoutes=false --set installNoConntrackIptablesRules=false
kubectl rollout restart -n kube-system ds/cilium
# 반복 ping 실행 결과
kubectl exec -it curl-pod -- ping $WEBPOD1
# rollout 되는 순간 일시적으로 통신이 끊어짐
64 bytes from 172.20.2.63: icmp_seq=107 ttl=62 time=3.71 ms
64 bytes from 172.20.2.63: icmp_seq=108 ttl=62 time=2.75 ms
From 172.20.0.172 icmp_seq=143 Time to live exceeded
From 172.20.0.172 icmp_seq=144 Time to live exceeded
From 172.20.0.172 icmp_seq=145 Time to live exceeded
From 172.20.0.172 icmp_seq=146 Time to live exceeded
From 172.20.0.172 icmp_seq=147 Time to live exceeded
From 172.20.0.172 icmp_seq=148 Time to live exceeded
From 172.20.0.172 icmp_seq=149 Time to live exceeded
From 172.20.0.172 icmp_seq=150 Time to live exceeded
From 172.20.0.172 icmp_seq=151 Time to live exceeded
From 172.20.0.172 icmp_seq=152 Time to live exceeded
64 bytes from 172.20.2.63: icmp_seq=153 ttl=63 time=9.84 ms
64 bytes from 172.20.2.63: icmp_seq=154 ttl=63 time=5.50 ms
64 bytes from 172.20.2.63: icmp_seq=155 ttl=63 time=2.37 ms
cilium을 encapsulation 모드로 업데이트 이후 설정값과 노드의 정보가 어떻게 변경되었는지 살펴보겠습니다.
# 설정 확인
cilium features status
cilium features status | grep datapath_network
kubectl exec -it -n kube-system ds/cilium -- cilium status | grep ^Routing
cilium config view | grep tunnel
(⎈|HomeLab:N/A) root@k8s-ctr:~# cilium features status | grep datapath_network
Yes cilium_feature_datapath_network mode=overlay-vxlan 1 1 1
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it -n kube-system ds/cilium -- cilium status | grep ^Routing
Routing: Network: Tunnel [vxlan] Host: BPF
(⎈|HomeLab:N/A) root@k8s-ctr:~# cilium config view | grep tunnel
routing-mode tunnel
tunnel-protocol vxlan
tunnel-source-port-range 0-0
# cilium_vxlan 확인
ip -c addr show dev cilium_vxlan
for i in w1 w0 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh vagrant@k8s-$i ip -c addr show dev cilium_vxlan ; echo; done
(⎈|HomeLab:N/A) root@k8s-ctr:~# ip -c addr show dev cilium_vxlan
26: cilium_vxlan: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
link/ether 16:5d:7a:ab:58:3f brd ff:ff:ff:ff:ff:ff
inet6 fe80::145d:7aff:feab:583f/64 scope link
valid_lft forever preferred_lft forever
# 라우팅 정보 확인
ip -c route | grep cilium_host
ip route get 172.20.1.10
ip route get 172.20.2.10
# k8s node 간 다른 네트워크 대역에 있더라도, 파드의 네트워크 대역 정보가 라우팅에 올라온다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# ip -c route | grep cilium_host
172.20.0.0/24 via 172.20.0.172 dev cilium_host proto kernel src 172.20.0.172
172.20.0.172 dev cilium_host proto kernel scope link
172.20.1.0/24 via 172.20.0.172 dev cilium_host proto kernel src 172.20.0.172 mtu 1450
172.20.2.0/24 via 172.20.0.172 dev cilium_host proto kernel src 172.20.0.172 mtu 1450
(⎈|HomeLab:N/A) root@k8s-ctr:~# ip route get 172.20.1.10
172.20.1.10 dev cilium_host src 172.20.0.172 uid 0
cache mtu 1450
(⎈|HomeLab:N/A) root@k8s-ctr:~# ip route get 172.20.2.10
172.20.2.10 dev cilium_host src 172.20.0.172 uid 0
cache mtu 1450
노드의 라우팅 정보에서 다른 노드의 PodCIDR에 대한 라우팅이 확인됩니다. 상대 PodCIDR에 대한 라우팅은 실제로 cilium_host 쪽으로 향하고 여기서 encapsulation 수행됩니다. 이때 노드 간은 통신이 가능한 환경이기 때문에 encapsulation된 패킷이 상대 노드로 라우팅되어 통신되게 됩니다.
라우팅 정보의 172.20.0.172 IP와 cilium_host 인터페이스에 대해서 아래와 같이 정보를 추가로 확인해보겠습니다.
# cilium 파드 이름 지정
export CILIUMPOD0=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-ctr -o jsonpath='{.items[0].metadata.name}')
export CILIUMPOD1=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w1 -o jsonpath='{.items[0].metadata.name}')
export CILIUMPOD2=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w0 -o jsonpath='{.items[0].metadata.name}')
echo $CILIUMPOD0 $CILIUMPOD1 $CILIUMPOD2
# router 역할 IP 확인
kubectl exec -it $CILIUMPOD0 -n kube-system -c cilium-agent -- cilium status --all-addresses | grep router
kubectl exec -it $CILIUMPOD1 -n kube-system -c cilium-agent -- cilium status --all-addresses | grep router
kubectl exec -it $CILIUMPOD2 -n kube-system -c cilium-agent -- cilium status --all-addresses | grep router
(⎈|HomeLab:N/A) root@k8s-ctr:~# export CILIUMPOD0=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-ctr -o jsonpath='{.items[0].metadata.name}')
export CILIUMPOD1=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w1 -o jsonpath='{.items[0].metadata.name}')
export CILIUMPOD2=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w0 -o jsonpath='{.items[0].metadata.name}')
echo $CILIUMPOD0 $CILIUMPOD1 $CILIUMPOD2
cilium-4ljkb cilium-2lgrw cilium-5s92k
# router 역할을 하는 IP를 확인해보면 172.20.0.172 IP가 확인된다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it $CILIUMPOD0 -n kube-system -c cilium-agent -- cilium status --all-addresses | grep router
172.20.0.172 (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'
# 파드 3개의 이름이 모두 확인된다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
Hostname: webpod-697b545f57-5dd9p
---
Hostname: webpod-697b545f57-vhn6k
---
Hostname: webpod-697b545f57-5dd9p
---
Hostname: webpod-697b545f57-vhn6k
---
Hostname: webpod-697b545f57-rdh2t
# 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 udp port 8472 -nn
# Ping 테스트
kubectl exec -it curl-pod -- ping -c 2 -w 1 -W 1 $WEBPOD
# 성공
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- ping -c 2 -w 1 -W 1 $WEBPOD
PING 172.20.1.224 (172.20.1.224) 56(84) bytes of data.
64 bytes from 172.20.1.224: icmp_seq=1 ttl=63 time=7.61 ms
--- 172.20.1.224 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 7.606/7.606/7.606/0.000 ms
command terminated with exit code 1
# 신규 터미널 [router] : 라우팅이 어떻게 되는가?
tcpdump -i any icmp -nn
tcpdump -i any udp port 8472 -nn # vXLAN에서 사용하는 포트
# 결과1
root@router:~# tcpdump -i any udp port 8472 -nn
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
21:52:10.609698 eth1 In IP 192.168.10.100.41429 > 192.168.20.100.8472: OTV, flags [I] (0x08), overlay 0, instance 58696
IP 172.20.0.217 > 172.20.1.224: ICMP echo request, id 194, seq 1, length 64
21:52:10.609730 eth2 Out IP 192.168.10.100.41429 > 192.168.20.100.8472: OTV, flags [I] (0x08), overlay 0, instance 58696
IP 172.20.0.217 > 172.20.1.224: ICMP echo request, id 194, seq 1, length 64
21:52:10.612113 eth2 In IP 192.168.20.100.49596 > 192.168.10.100.8472: OTV, flags [I] (0x08), overlay 0, instance 12564
IP 172.20.1.224 > 172.20.0.217: ICMP echo reply, id 194, seq 1, length 64
21:52:10.612135 eth1 Out IP 192.168.20.100.49596 > 192.168.10.100.8472: OTV, flags [I] (0x08), overlay 0, instance 12564
IP 172.20.1.224 > 172.20.0.217: ICMP echo reply, id 194, seq 1, length 64
# 결과2
root@router:~# tcpdump -i any udp port 8472 -nn
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
21:52:10.609698 eth1 In IP 192.168.10.100.41429 > 192.168.20.100.8472: OTV, flags [I] (0x08), overlay 0, instance 58696 # k8s-ctr -> k8s-w0 로 통신
IP 172.20.0.217 > 172.20.1.224: ICMP echo request, id 194, seq 1, length 64 # Inner header 정보 (pod to pod 통신)
21:52:10.609730 eth2 Out IP 192.168.10.100.41429 > 192.168.20.100.8472: OTV, flags [I] (0x08), overlay 0, instance 58696
IP 172.20.0.217 > 172.20.1.224: ICMP echo request, id 194, seq 1, length 64
21:52:10.612113 eth2 In IP 192.168.20.100.49596 > 192.168.10.100.8472: OTV, flags [I] (0x08), overlay 0, instance 12564
IP 172.20.1.224 > 172.20.0.217: ICMP echo reply, id 194, seq 1, length 64
21:52:10.612135 eth1 Out IP 192.168.20.100.49596 > 192.168.10.100.8472: OTV, flags [I] (0x08), overlay 0, instance 12564
IP 172.20.1.224 > 172.20.0.217: ICMP echo reply, id 194, seq 1, length 64
앞서 Cilium의 Routing 모드를 설명하면서 Encapsulation 모드가 가지는 단점을 말씀드렸습니다.
Encapsulation 모드에서는 encapsulation을 위한 header를 추가하므로, 페이로드에 사용할 수 있는 유효 MTU가 네이티브 라우팅(VXLAN의 경우 네트워크 패킷당 50바이트)보다 낮아지고, 결과적으로 네트워크 연결에 대한 최대 처리량이 낮아집니다.
아래 노드에서 확인해보면 eth1 인터페이스에 대해서 MTU가 1500인 것을 확인할 수 있지만, 라우팅 정보에서는 MTU가 1450으로 확인됩니다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:6b:69:c9 brd ff:ff:ff:ff:ff:ff
altname enp0s3
inet 10.0.2.15/24 metric 100 brd 10.0.2.255 scope global dynamic eth0
valid_lft 79973sec preferred_lft 79973sec
inet6 fd17:625c:f037:2:a00:27ff:fe6b:69c9/64 scope global dynamic mngtmpaddr noprefixroute
valid_lft 86322sec preferred_lft 14322sec
inet6 fe80::a00:27ff:fe6b:69c9/64 scope link
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:53:64:57 brd ff:ff:ff:ff:ff:ff
altname enp0s8
inet 192.168.10.100/24 brd 192.168.10.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe53:6457/64 scope link
valid_lft forever preferred_lft forever
...
(⎈|HomeLab:N/A) root@k8s-ctr:~# ip route | grep 172.20
172.20.0.0/24 via 172.20.0.172 dev cilium_host proto kernel src 172.20.0.172
172.20.0.0/16 via 192.168.10.200 dev eth1 proto static
172.20.0.172 dev cilium_host proto kernel scope link
172.20.1.0/24 via 172.20.0.172 dev cilium_host proto kernel src 172.20.0.172 mtu 1450
172.20.2.0/24 via 172.20.0.172 dev cilium_host proto kernel src 172.20.0.172 mtu 1450
파드간 통신에서 MTU를 테스트 하기 위해서 아래와 같이 확인해보겠습니다.
curl-pod에서 DF (Don't Fragment)플래그를 설정하고(네트워크 구간에서 MTU가 작아도 조각화하지 않는 설정) 1500 사이즈의 Ping을 수행하면 ping: sendmsg: Message too large 와 같은 에러가 발생하는 것을 알 수 있습니다.
# -M do : Don't Fragment (DF) 플래그를 설정하여 조각화 방지
# -s 1472 : 페이로드(payload) 크기, 즉 ICMP 데이터 크기
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- ping -M do -s 1500 $WEBPOD
PING 172.20.1.224 (172.20.1.224) 1500(1528) bytes of data.
ping: sendmsg: Message too large
ping: sendmsg: Message too large
^C
--- 172.20.1.224 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1019ms
command terminated with exit code 1
이때 아래와 같이 1422에서는 통신이 가능한 것을 알 수 있습니다. 이는 payload인 1422에 IP 헤더 20 바이트와 ICMP 헤더 8바이트가 추가 되고, 이후 vXLAN의 outer header인 50 바이트가 필요하기 때문입니다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- ping -M do -s 1423 $WEBPOD
PING 172.20.1.224 (172.20.1.224) 1423(1451) bytes of data.
ping: sendmsg: Message too large
ping: sendmsg: Message too large
ping: sendmsg: Message too large
ping: sendmsg: Message too large
^C
--- 172.20.1.224 ping statistics ---
4 packets transmitted, 0 received, +4 errors, 100% packet loss, time 3097ms
command terminated with exit code 1
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- ping -M do -s 1422 $WEBPOD
PING 172.20.1.224 (172.20.1.224) 1422(1450) bytes of data.
1430 bytes from 172.20.1.224: icmp_seq=1 ttl=63 time=7.45 ms
1430 bytes from 172.20.1.224: icmp_seq=2 ttl=63 time=4.37 ms
^C
--- 172.20.1.224 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 4.374/5.912/7.450/1.538 ms
마치며
Cilium 실습 환경을 통해서 서로 네트워크가 다른 노드에서 파드 통신이 가능하도록 Encapulation 모드를 살펴봤습니다. 주제가 다소 다르기 때문에 Cilium LoadBalancer IPAM와 Cilium L2 Announcement에 대해서는 다음 포스트로 나눠서 작성하도록 하겠습니다.
'Cilium' 카테고리의 다른 글
| [7] Cilium - BGP Control Plane (0) | 2025.08.14 |
|---|---|
| [6] Cilium - LoadBalancer IPAM, L2 Announcement (0) | 2025.08.08 |
| [4] Cilium에서 NodeLocalDNS 사용 (0) | 2025.08.02 |
| [3] Cilium Networking - IPAM, Routing, Masquerading (0) | 2025.08.02 |
| [2] Cilium Observability (0) | 2025.07.27 |