스터디 이야기/25' Cilium

Cilium Overlay Network 실습

lakescript 2025. 8. 6. 17:27
728x90

 

들어가며

Kubernetes 환경에서는 기본적으로 모든 노드가 같은 네트워크 대역에 있을 때, 별다른 추가 설정 없이도 Pod 간 통신이 가능합니다.

왜냐하면 Kubernetes가 각 Pod에 고유한 IP인 PodIP를 부여하고, 해당 IP들 간의 라우팅을 자동으로 구성해주기 때문입니다.

 

이때 Pod 간 통신은 ClusterIP(Service)를 거치지 않고 직접 Pod IP로 이루어집니다. 

 

하지만 실제로 운영을 하다보면 모든 노드들을 같은 subnet에 배치하기 어렵습니다.

AWS EKS는 VPC CNI를 사용해 예외적인 구조를 가지므로 여기서는 논외로 합니다. 

 

이러한 경우 기본적인 CNI로는 Pod간 통신이 불가능합니다. 

 

이번에 해본 실습은 Control Plane 노드(k8s-ctr)와 Worker 노드(k8s-w0, k8s-w1)를 서로 다른 서브넷에 배치하여 Pod 간 통신을 테스트해보고, Cilium의 Overlay Network 기능인 VXLAN 모드를 활성화하여 다른 서브넷에 있는 Node들 간에 Pod 통신에 대해 실습해보겠습니다.

 

kubernetes 환경에서는 기본적으로 모든 노드가 같은 네트워크 대역에 있을 경우, Kubernetes가 각 Pod에 고유한 IP인 Pod IP를 부여하고, CNI를 통해 해당 IP들 간의 라우팅을 자동으로 구성해주기 때문에 별다른 추가 설정 없이도 Pod간 통신이 가능합니다.

 

이때 Pod 간 통신은 ClusterIP(Service) 를 거치지 않고, Pod IP를 이용한 직접 통신으로 이루어집니다.

 

하지만 실제 운영 환경에서는 모든 노드를 동일한 서브넷에 배치하기 어려운 경우가 많습니다. 이러한 상황에서는 기본적인 CNI만으로는 서브넷이 다른 노드 간에 Pod 통신이 되지 않습니다.

AWS EKS는 VPC CNI를 사용해 예외적인 구조를 가지므로 여기서는 논외로 합니다.

 

 

Control Plane 노드(k8s-ctr)와 Worker 노드(k8s-w0, k8s-w1)를 서로 다른 서브넷에 배치하여, 초기에는 통신이 되지 않는 상태를 확인한 후, Cilium의 Overlay Network 기능(VXLAN 모드) 를 활성화하여 서로 다른 서브넷에 위치한 노드들 간에도 Pod 통신이 가능해지는 과정에 대해 실습을 진행해보았습니다.

 

 

실습 환경

실습 환경 구성

 

 

 

control plane 역할을 하는 k8s-ctr과 worker node의 k8s-w1, k8s-w0, 외부 망의 역할을 하는 router가 구성되어있는 환경입니다.

router는 192.168.10.0/24, 192.168.20.0/24 대역의 라우팅 역할을 하며 k8s에서 관리하지 않는 외부 서버입니다. 

 

이번 실습을 위해 k8s-w0은 k8s-ctr과 k8s-w1 노드와 다른 네트워크 대역에 배치되어 구성되어 있습니다.

 

샘플 애플리케이션 배포

더보기

webpod 배포

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

 

curl-pod 배포

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

 

 

실습 환경 확인 및 설정

node별 상세 정보 확인

kubectl get node -o wide

 

 

k8s-ctr의 IP는 위에서 설명한대로 192.168.10.100, k8s-w1은 같은 대역인 192.168.10.101 입니다. k8s-w0은 다른네트워크 대역인 192.168.20.100 인 것을 알 수 있습니다.

 

Pod CIDR 확인

kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'

 

위 명령어를 통해 Pod CIDR을 확인해보겠습니다.

 

 

k8s-ctr node의 Pod CIDR은 10.244.0.0/24, k8s-w1은 10.244.1.0/24, k8s-0은 10.244.2.0/24 인 것을 확인할 수 있습니다.

 

Cilium IPAM Mode 확인

cilium config view | grep ^ipam

 

IPAM 모드도 확인해보겠습니다.

 

현재는 cluster-pool 모드로 설정되어 있는 것을 확인할 수 있습니다.

 

router 네트워크 인터페이스 정보 확인

ip -br -c -4 addr

 

router에 접근하여 위 명령어를 통해 IP 주소 정보를 출력해보겠습니다.

 

 

eth0은 첫 번째 이더넷 인터페이스로, 일반적으로 NAT 또는 외부 네트워크와 연결되며 10.0.2.15/24 대역이 할당되어 있습니다.

eth1은 두 번째 이더넷 인터페이스로, 내부 네트워크 192.168.10.0/24에 연결되어 있으며 192.168.10.200/24 대역이 할당되어 있습니다.
eth2내부 네트워크 192.168.20.0/24에 연결된 세 번째 인터페이스로, 192.168.20.200/24 대역이 할당되어 있습니다.

 

k8s node 네트워크 인터페이스 정보 확인

# k8s-ctr
ip -c -4 addr show dev eth1

 

먼저, k8s-ctr의 IP 주소 정보를 확인해보겠습니다.

 

 

k8s-ctr node의 eth1 인터페이스는 192.168.10.100/24 주소를 가지고 있는 것을 확인할 수 있습니다.

 

# k8s-w1
ip -c -4 addr show dev eth1

 

마찬가지로 k8s-w1의 ip 주소 정보를 확인해보겠습니다.

 

k8s-w1 노드의 eth1 인터페이스는 192.168.10.101/24 주소를 가지고 있습니다.

 

# k8s-w0
ip -c -4 addr show dev eth1

 

마지막으로 k8s-w0의 IP 주소를 확인해보겠습니다.

 

k8s-w0 노드의 eth1 인터페이스는 192.168.20.100/24 주소를 갖고 있습니다.

 

배포된 Pod 확인

kubectl get deploy,svc,ep webpod -owide

 

위 명령어를 통해 배포된 webpod의 deploy와 service, endpoint를 확인해보겠습니다.

 

 

현재 webpod는 10.96.107.107의 내부 IP를 가지며, 세 개의 파드 IP (172.20.0.178, 172.20.1.245, 172.20.2.250)로 endpoint가 설정되어 있는 것을 확인할 수 있습니다.

 

kubectl get ciliumendpoints

 

cilium endpoint도 확인해보겠습니다.

 

 

마찬가지로 pod의 IP를 확인할 수 있습니다.

 

kubectl exec -n kube-system ds/cilium -- cilium-dbg service list

 

위 명령어를 통해 Cilium이 인식한 전체 Kubernetes 서비스 목록을 확인해보겠습니다.

 

 

 

요청이 10.96.107.107:80의 clusterIP로 들어오면 172.20.0.178:80, 172.20.1.245:80, 172.20.2.250:80으로 트래픽을 전달하는 것을 알 수 있습니다.

 

통신 확인

현재 구성된 환경에서는 k8s-ctr이나 k8s-w1에서 k8s-w0으로 통신하기 위해서는 router를 경유해야 합니다. 

 

하지만 네트워크 대역이 다르기 때문에 통신이 불가능합니다. 그 상황을 확인해보겠습니다.

 

pod 통신 확인

kubectl exec -it curl-pod -- curl webpod | grep Hostname

 

위 명령어를 통해 webpod에 접근해보겠습니다.

 

통신이 안되고 있는 것을 확인할 수 있습니다.

 

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

 

다른 네트워크 대역에 구성되어 있는 k8s-w0에 배포된 webpod를 환경변수로 지정해보겠습니다.

 

tcpdemp 실행

tcpdump -i any icmp -nn

 

통신시 패킷을 확인해보기 위해 새로운 터미널을 실행하고, router 노드에 접근해서 tcpdump를 실행합니다.

 

접속 테스트

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

 

기존에 k8s-ctr 노드에 접근해있는 터미널에서 위 명령어를 실행하여 k8s-w0에 배포된 webpod에 요청을 보냅니다.

 

tcpdump 확인

 

수집된 내용을 확인해보면 아래와 같습니다.

16:29:58.862575 eth1  In  IP 172.20.0.63 > 172.20.2.250: ICMP echo request, id 12, seq 1, length 64
16:29:58.862587 eth0  Out IP 172.20.0.63 > 172.20.2.250: ICMP echo request, id 12, seq 1, length 64

 

172.20.0.63 에서 172.20.2.250으로 요청을 보냈습니다.

 


현재 배포되어 있는 pod을 확인해보면 172.20.0.63은 curl-pod이고, 요청을 받은 172.20.2.250은 k8s-w0에 배포되어 있는 webpod인 것을 확인할 수 있습니다.

 

16:29:58.862575 eth1  In  IP 172.20.0.63 > 172.20.2.250: ICMP echo request, id 12, seq 1, length 64
16:29:58.862587 eth0  Out IP 172.20.0.63 > 172.20.2.250: ICMP echo request, id 12, seq 1, length 64

 

다시 돌아와서 eth1을 통해 요청을 받은 후 정상적이라면 eth2으로 트래픽을 보내야 하는데, eth0으로 보낸 것을 알 수 있습니다.

 

router node의 라우팅 테이블 확인

ip -c route

 

 

위에서 왜 eth0으로 보냈을까요? 바로 router node의 라우팅 테이블에 172.20 대역에 해당하는 라우팅 정보가 없기 때문에 default router인 10.0.2.2로 보내어 eth0으로 보내게 되었습니다.

 

그렇다면 위 문제를 해결하기 위해서는 어떻게 해야 할까요?

 

router에 해당 pod의 CIDR을 static routing으로 설정해놓으면 될까요?  지금과 같이 node의 갯수가 적다면 수동으로 static routing을 설정하면 되지만, 만약 node가 100대, 200대라면 불가능합니다. 또한, pod는 stateful하지 않기 때문에 배포될 때마다 CIDR이 바뀔 수 있는데, 그럴 때마다 또 수동으로 설정을 하기엔 어려움이 있습니다.

 

 

이를 해결하기 위해 node별로 BGP를 구성하여 자동으로 CIDR을 설정하게 끔 하는 방법이 있고, overlay network를 통해 구성하는 방법이 존재합니다.

 

이번 실습에서는 overlay network를 통해 구성해보겠습니다. (BGP는 다음에..)

 

 

Overlay Network (Encapsulation) mode로 환경 구성

overlay network 모드인 Encapsulation에 대한 프로토콜인 VXLAN에 대한 내용은 블로그 를 참고해주세요.

 

현재 설정 확인

grep -E 'CONFIG_VXLAN=y|CONFIG_VXLAN=m|CONFIG_GENEVE=y|CONFIG_GENEVE=m|CONFIG_FIB_RULES=y' /boot/config-$(uname -r)

 

위 명령어를 통해 현재 구성된 환경을 확인해보겠습니다.

 

VXLAN과 GENEVE가 현재 활성화되어 있는 상태는 아니고 kurnel에서 활성화하면 사용할 수 있는 플래그인 m인 것을 확인할 수 있습니다.

 

VXLAN 활성화

modprobe vxlan

 

위 명령어를 통해 간단하게 VXLAN 모드를 활성화 할 수 있습니다.

 

lsmod | grep -E 'vxlan|geneve'

 

위 명령어를 통해 현재 모드 중 VXLAN과 GENEVE를 확인해보겠습니다.

 

 

 

정상적으로 활성화 된 것을 확인할 수 있습니다.

 

 

CIlium 업그레이드

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

 

turnal protocol을 VXLAN으로 구성하여 cilium을 재배포해보겠습니다.

 

이때, 통신 단절이 발생하니 운영에 적용하실 때는 유의해주세요!

 

 

cilium agent 재시작

kubectl rollout restart -n kube-system ds/cilium

 

수정된 cilium을 적용하기 위해 cilum agent를 재시작해줍니다.

 

cilium 기능 확인

cilium features status | grep datapath_network

 

features status 를 통해 Cilium이 데이터 경로에서 네트워크 관련 기능을 확인하는데, 이때 datapath_network 항목만 필터해서 확인해보겠습니다.

 

 

현재 mode가 overlay-vxlan으로 설정된 것을 확인하실 수 있습니다.

 

cilium_vxlan 확인

ip -c addr

 

이제 네트워크 인터페이스 정보를 확인해보면 cilium_vxlan 인터페이스가 생성된 것을 확인할 수 있습니다.

 

 

for i in w1 w0 ; do echo ">> node : k8s-$i <<"; k8s-$i ip -c addr show dev cilium_vxlan ; echo; done

 

위 명령어를 통해 k8s-w0과 k8s-w1 노드에서도 확인해보겠습니다.

 

k8s-w0, k8s-w1 노드에 각각 cilium_vxlan 인터페이스가 생성된 것을 확인할 수 있습니다.

 

통신 확인

위에서 진행했던 pod 통신 실습에서는 node의 대역이 다르기 때문에 Pod CIDR의 정보가 없어 통신이 실패했는데요.

 

VXLAN 모드로 변경한뒤는 어떻게 되는지 확인해보겠습니다.

 

pod 통신 확인

kubectl exec -it curl-pod -- curl webpod | grep Hostname

 

위 명령어를 통해 webpod에 접속해보겠습니다.

 

 

위에서 했던 실습과는 다르게 이번엔 정상적으로 통신이 되고 있는 것을 확인할 수 있습니다.

 

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

 

tcpdemp 실행

tcpdump -i any udp port 8472 -nn

 

통신시 패킷을 확인해보기 위해 새로운 터미널을 실행하고, router 노드에 접근해서 tcpdump를 실행합니다.

 

이전과 다른 점은 VXLAN이 사용하는 port는 udp의 port인 8472이기 때문에 해당 옵션을 지정해서 실행해야 합니다.

 

접속 테스트

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

 

기존에 k8s-ctr 노드에 접근해있는 터미널에서 위 명령어를 실행하여 k8s-w0에 배포된 webpod에 요청을 보냅니다.

 

tcpdump 확인

 

수집된 내용을 확인해보면 아래와 같습니다.

 

 

 

17:07:49.333315 eth1  In  IP 192.168.10.100.48391 > 192.168.20.100.8472: OTV, flags [I] (0x08), overlay 0, instance 414
IP 172.20.0.63 > 172.20.2.250: ICMP echo request, id 24, seq 1, length 64

 

 

router 노드에서 192.168.10.100으로부터 192.168.20.100의 eth0으로 요청을 받았고, 해당 요청을 내부 VXLAN 페이로드인 172.20.0.63에서 172.20.2.250으로 전달했다는 것을 확인할 수 있습니다.

 

728x90