投稿

[Cilium Study 1기 by Gasida] Networking 요약-2 + 도전과제2 GENEVE 모드 확인

[Cilium Study 1기 by Gasida] Networking 요약-2 + 도전과제2 GENEVE 모드 확인

실습환경

img.png

먼저 위와 같이 k8s-ctr(control-plane), k8s-w1(worker-node 1), k8s-w0(worker-node 2), router를 가상 머신으로 배포한다. k8s-w0(192.168.20.0/24)는 보는 바와 같이 컨트롤 플레인(192.168.10.0/24)과 다른 네트워크 대역에 배치된다.

eth1eth2는 각각 게이트웨이 역할을 하며 서로 다른 대역의 네트워크와 통신하기 위해 존재한다.

배포파일 및 배포

1
2
3
4
5
mkdir cilium-lab && cd cilium-lab

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

vagrant up

배포 후 확인

1
2
3
4
5
6
# k9s 설치되어 있음
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
wget https://github.com/derailed/k9s/releases/latest/download/k9s_linux_${CLI_ARCH}.deb -O /tmp/k9s_linux_${CLI_ARCH}.deb
apt install /tmp/k9s_linux_${CLI_ARCH}.deb
k9s # node/pod 정보 확인 - metrics-server 설치되어 있어서, cpu/mem 확인 가능

img_1.png

podCIDR 확인

1
kubectl get ciliumnode -o json | grep podCIDRs -A2

img_2.png

Router 인터페이스 정보 확인

1
ip -br -c -4 addr

img_3.png

k8s-ctr(192.168.10.100)k8s-w1(192.168.10.101)과 같이 192.168.10.0/24 대역이 192.168.20.0/24와 통신하기 위해서는 eth1(192.168.10.200)의 static routing으로 통신한다. img_4.png

-set routingMode=native –set autoDirectNodeRoutes=true 동작이해

--set autoDirectNodeRoutes=true의 의미는 같은 네트워크 대역에 있는 podCIDRstatic으로 추가된다.

노드별 podCIDR

  • 172.20.0.0/24 (k8s-ctr) - 192.168.10.0/24 대역
  • 172.20.1.0/24 (k8s-w1) - 192.168.10.0/24 대역
  • 172.20.2.0/24 (k8s-w0) - 192.168.20.0/24 대역

k8s-ctr

img_5.png

k8s-w1

img_6.png

k8s-ctr과 k8s-w1의 라우팅 정보를 보면 192.168.10.0/24 대역이 아닌 k8s-w0(192.168.20.0/24)노드의 podCIDR(172.20.2.0/24)는 찾아볼 수 없다.

그렇다면 통신을 확인해보자.

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

img_7.png

당연히 k8s-w0의 노드 IP(192.168.20.100)로는 통신이 가능하다. 하지만 노드 상의 파드간 통신은 서로 다른 대역끼리 불가능한 상태다.

Native Routing Mode

네이티브 라우팅 모드 실습을 위해 샘플애플리케이션 배포한다.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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


배포 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 배포 확인
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

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
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'

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

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


통신확인을 해보자. 서로 다른 대역에 있는 파드간 통신을 해결하기 위해서는 어떻게 해야할까?

문제 확인

아래의 명령어로 반복접근을 실시해보자.

1
2
3
# 통신 확인 : 문제 확인
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'

아래와 같이 서로 다른 네트워크 대역의 파드는 통신이 되지 않는것 을 볼 수 있다.(되는 파드는 Hostname에 정확히 파드이름이 뜨는 것을 확인가능)

img_9.png

그러면 이제 k8s-w0노드에 배포된 webpod 파드로 curl요청을 날려보자.

1
2
3
4
5
6
7
8
9
10
# 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

k8s-ctr의 파드는 우측 상단의 빨간 네모에서 보는 것처럼 IP주소는 172.20.0.106이며 k8s-w0 파드의 172.20.2.26로 요청을 보내고 있다. eth1로 요청이 들어왔는데 eth2로 보내야하지만 eth0(10.0.0.2)으로 보내고 있다. img_10.png

img_11.png

img_12.png

결국 router 머신의 라우트 정보에 그 어디에도 172.20.2.26을 알 수 있는 정보가 없다. 따라서 default 라우트에 매치가 됨으로 eth0으로 빠져 나가는 것을 확인할 수 있다. 당연히 라우터는 podCIDR의 정보를 알리가 없다.

아래 명령어로 트래픽 경로를 유추 할 수 있다. (eth0으로 빠져나가는 것을 확인)

1
ip route get 172.20.2.26

img_13.png

허블에서도 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'

# 신규 터미널 [router]
tcpdump -i any tcp port 80 -nn

# hubble 확인
# hubble ui 웹 접속 주소 확인 : default 네임스페이스 확인
NODEIP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
echo -e "http://$NODEIP:30003"

# hubble relay 포트 포워딩 실행
cilium hubble port-forward&
hubble status

# flow log 모니터링
hubble observe -f --protocol tcp --pod curl-pod

hubble에서 확인해보면 k8s-w0의 경우 TCP Flags: SYN이외에 아무런 응답이 없는 것을 확인 할 수 있다. img_14.png (허블을 관측하는 순간 아래와 같이 k8s-w0노드가 이용할 수 없다고 뜨기도 함) img_15.png



결국 해결 방안은 두가지 방법이 있음.

  • 라우팅 설정 - 수동 또는 자동 BGP
  • Overlay Network

Overlay Network (Encapsulation) mode

Overlay Network에는 두가지 모드가 있음

  • VXLAN(default) : 8472/UDP
  • Geneve : 6081/UDP

기본적으로는 위 두가지 모드는 활성화 되어 있지는 않다. 별도로 세팅을 해보자.

1
2
3
4
5
# [커널 구성 옵션] 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 # 모듈로 컴파일됨 → 커널에 로드해서 사용

사용하기 위해 커널 로드

1
2
3
4
#  커널 로드
lsmod | grep -E 'vxlan|geneve'
modprobe vxlan # modprobe geneve
lsmod | grep -E 'vxlan|geneve'
1
2
3
4
5
6
# 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
1
2
3
4
5
6
# 업그레이드
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
1
2
3
4
5
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
1
2
3
4
5
6
# 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

# 라우팅 정보 확인 : k8s node 간 다른 네트워크 대역에 있더라도, 파드의 네트워크 대역 정보가 라우팅에 올라왔다!
ip -c route | grep cilium_host

다른 네트워크 대역에 있는 노드와 통신할때는 오버레이 하기 위한 새로운 인터페이스가 생성되었다. img_16.png

라우팅 정보를 보면 172.20.0.0/24, 172.20.1.0/24, 172.20.2.0/24가 모두 올라온 것을 확인 할 수 있다. img_17.png

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
ip route get 172.20.1.10
ip route get 172.20.2.10

for i in w1 w0 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh vagrant@k8s-$i ip -c route | grep cilium_host ; echo; done


# 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

#
kubectl exec -n kube-system ds/cilium -- cilium-dbg bpf ipcache list
kubectl exec -n kube-system $CILIUMPOD0 -- cilium-dbg bpf ipcache list
kubectl exec -n kube-system $CILIUMPOD1 -- cilium-dbg bpf ipcache list
kubectl exec -n kube-system $CILIUMPOD2 -- cilium-dbg bpf ipcache list

#
kubectl exec -n kube-system ds/cilium -- cilium-dbg bpf socknat list
kubectl exec -n kube-system $CILIUMPOD0 -- cilium-dbg bpf socknat list
kubectl exec -n kube-system $CILIUMPOD1 -- cilium-dbg bpf socknat list
kubectl exec -n kube-system $CILIUMPOD2 -- cilium-dbg bpf socknat list

via뒤에 있는 172.20.0.12의 정체가 궁금할 텐데 이는 router의 IP임을 확인 할 수 있다. img_18.png

파드간 통신 확인

curl파드로 부터 반복접근을 시도 해보자.

1
2
3
# 통신 확인
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개의 파드가 모두 뜨는 것을 확인 할 수 있다.
img_19.png

1
2
3
# 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

Overlay Network는 router를 경유하는데 vxlan의 포트번호 8472로 설정해준다.

1
2
# 신규 터미널 [router]
tcpdump -i any udp port 8472 -nn
1
kubectl exec -it curl-pod -- ping -c 2 -w 1 -W 1 $WEBPOD

먼저 eth1 In 과 eth2 Out이 보인다. k8s-ctr(192.168.10.100)에서 k8s-w0(192.168.20.100)으로 요청을 보내고 있는데 eth1로 들어와서 eth2로 나간다는 것을 알 수있다. img_21.png

두번째로 172.20.0.158이 보이는데 Inner Header의 정보를 볼 수 있다. img_22.png

실제로 Router에서 tcpdump를 확인해보자.

1
2
3
4
5
# 신규 터미널 [router]
tcpdump -i any udp port 8472 -w /tmp/vxlan.pcap
tshark -r /tmp/vxlan.pcap -d udp.port==8472,vxlan
termshark -r /tmp/vxlan.pcap
termshark -r /tmp/vxlan.pcap -d udp.port==8472,vxlan

termshark -r /tmp/vxlan.pcap -d udp.port==8472,vxlan 로 확인, Filter에 http를 입력한다. 특정 트래픽을 선택하면 Outer Header에 SrcIP : 192.168.10.100 (k8s-ctr), DstIP: 192.168.20.100(k8s-w0) 로 되어 있는 것을 확인할 수 있다. Router는 여기까지만 확인하며 (L2/L3까지만 확인 후) 뒷단의 페이로드는 확인하지 않는다. 따라서 7번째 줄의 Src: 172.20.0.158, Dst: 172.20.2.115가 있더라도 무시할 수 있는 것이다. img_23.png

VXLAN 모드임도 확인할 수 있다. 디켑슐레이션하는 포트를 8472로 해야 확인 할 수 있다. img_24.png

Inner Header에 파드 to 파드 IP를 확인할 수 있다. img_25.png

Hubble

1
2
3
4
5
6
# hubble relay 포트 포워딩 실행
cilium hubble port-forward&
hubble status

# flow log 모니터링
hubble observe -f --protocol tcp --pod curl-pod

허블에서도 아래와 같이 to-overlay로 Overlay Network임을 확인 할 수 있다. img_26.png

지금까지의 과정이 Datapath의 중 Encapsulate, Route에 해당한다. img_27.png

GENEVE MODE 확인

VXLAN 대신 GENEVE 모드로 변경 설정 후 설정 상태와 통신 트래픽을 확인한다. 결과적으로 설정과 트래픽 확인측면에서는 VXLAN과 차이는 보이지 않는다.

커널 구성 옵션 확인

1
2
3
4
5
# [커널 구성 옵션] 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 # 모듈로 컴파일됨 → 커널에 로드해서 사용


GENEVE 모드로 커널 로드한다. k8s-w0, k8s-w1 노드에도 동일하게 적용해준다.

1
2
modprobe geneve # modprobe geneve
lsmod | grep -E 'vxlan|geneve'


트래픽 반복접근 해두기

1
2
3
4
5
6
# 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


cilium 업그레이드를 진행하자. vxlan 대신 --set tunnelProtocol=geneve로 바꾸어 아래 명령어를 실행한다.

1
2
3
4
5
6
7
# 업그레이드
helm upgrade cilium cilium/cilium --namespace kube-system --version 1.18.0 --reuse-values \
  --set routingMode=tunnel --set tunnelProtocol=geneve \
  --set autoDirectNodeRoutes=false --set installNoConntrackIptablesRules=false

# 데몬셋 재실행
kubectl rollout restart -n kube-system ds/cilium


설정을 확인하자.

1
2
3
# 설정 확인
cilium features status
cilium features status | grep datapath_network


아래와 같이 cilium_feature_datapath_network mode=overlay-geneveYES로 되어있고 각 3개의 노드가 모두 활성화 되어있다. img_28.png

인터페이스 확인

1
ip -c addr show dev cilium_geneve

img_29.png


라우팅 정보 확인 : k8s node 간 다른 네트워크 대역에 있더라도, 파드의 네트워크 대역 정보가 라우팅에 올라왔다!

1
ip -c route | grep cilium_host

img_30.png

이전과 마찬가지로 via 뒤에 있는 172.20.0.133은 라우터 ip주소이다. img_31.png

이제 통신을 확인하자.

1
2
3
4
5
6
7
8
# 통신 확인
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

vxlan는 포트번호 8472를 geneve의 경우 6081로 설정

1
2
3
4
5
6
7
8
# 신규 터미널 [router]
tcpdump -i any udp port 6081 -nn

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

# 신규 터미널 [router] : 라우팅이 어떻게 되는가?
tcpdump -i any icmp -nn
1
2
3
4
5
6
7
8
9
10
11
12
# 반복 접속
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'

# 신규 터미널 [router]
tcpdump -i any udp port 6081 -w /tmp/geneve.pcap
tshark -r /tmp/vxlan.pcap -d udp.port==6081,geneve
termshark -r /tmp/geneve.pcap
termshark -r /tmp/geneve.pcap -d udp.port==6081,geneve

# 신규 터미널 [k8s-ctr] hubble flow log 모니터링 : overlay 통신 모드 확인!
hubble observe -f --protocol tcp --pod curl-pod

vxlan과 큰 차이는 없이 eth1 인입 eth2로 아웃되는 것이 보이며 inner header도 확인가능하다. img_33.png

GENEVE모드이므로 아래와 같이 Generic Network Virtualization Encapsulation, VNI: 0x000cba가 확인이 가능하다. img_34.png

hubble flow log 모니터링을 진행하자. to-overlay가 확인되며 overlay 통신모드 확인이 가능하다. img_35.png

この投稿は投稿者によって CC BY 4.0 の下でライセンスされています。