[Study 2주차] 쿠버네티스 Flannel CNI
- -
CloudNet@ 팀의 가시다님께서 리딩하시는 KANS Study (Kubernetes Advanced Networking Study) 2주차 스터디 내용 정리
이번에는 Flannel 을 공부하면서, 쿠버네티스 CNI 에 대해서 이해해보겠습니다.
1. CNI (Container Network Interface)
CNI 는 컨테이너 간 네트워크 제어 플러그인을 작성하기 위한 표준입니다.
- 쿠버네티스 네트워크 모델 요구사항 4가지
- 파드와 파드 간 통신 시 NAT 없이 통신 가능
- 노드 에이전트는 파드와 통신 가능
- 호스트 네트워크 파드는 NAT 없이 파드와 통신 가능
- 서비스 클러스터 IP 대역과 파드 IP 대역 간 중복 방지
- CNI 가 해결해야 하는 쿠버네티스 네트워크 통신 문제
- 파드 내부 컨테이너는 루프백 (Loopback) 을 통한 통신 가능
- 파드 간 통신 가능
- 클러스터 내부에서 쿠버네티스 서비스를 통한 통신 가능
- 클러스터 외부에서 쿠버네티스 서비스를 통한 통신 가능
이러한 요구사항을 충족하는 CNI 는 종류가 다양하지만, 이번에는 가볍고 간단하게 동작할 수 있는 Flannel 을 알아보겠습니다.
1.1. Flannel CNI 소개 및 설치
Flannel 은 단일 바이너리 에이전트인 flanneld 가 각 노드에서 동작하여, 작은 규모의 클러스터 환경에서 파드 간 통신 환경을 구성해주는 CNI 입니다.
- kind 를 통한 실습환경 구축
kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane labels: mynode: control-plane extraPortMappings: - containerPort: 30000 hostPort: 30000 - containerPort: 30001 hostPort: 30001 - containerPort: 30002 hostPort: 30002 kubeadmConfigPatches: - | kind: ClusterConfiguration controllerManager: extraArgs: bind-address: 0.0.0.0 etcd: local: extraArgs: listen-metrics-urls: http://0.0.0.0:2381 scheduler: extraArgs: bind-address: 0.0.0.0 - | kind: KubeProxyConfiguration metricsBindAddress: 0.0.0.0 - role: worker labels: mynode: worker - role: worker labels: mynode: worker2 networking: disableDefaultCNI: true
kind 는 kindnet 이라는 기본 CNI 가 같이 배포되는데, Flannel CNI 설치를 위해 배포하지 않겠다는 설정을 하는 것입니다.
networking: disableDefaultCNI: true
# 클러스터 배포 kind create cluster --config kind-cni.yaml --name myk8s --image kindest/node:v1.30.4 # Node 와 Pods 상태 확인 kubectl get nodes -o wide kubectl get pods -A -o wide
CNI 가 배포되지 않았기 때문에, Node 가 NotReady 상태이며 Static Pod 를 제외한 모든 파드가 배포되지 않았습니다.

# 실습을 위한 네트워크 도구 설치 docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree jq psmisc lsof wget bridge-utils tcpdump iputils-ping htop git nano -y' docker exec -it myk8s-worker sh -c 'apt update && apt install tree jq psmisc lsof wget bridge-utils tcpdump iputils-ping -y' docker exec -it myk8s-worker2 sh -c 'apt update && apt install tree jq psmisc lsof wget bridge-utils tcpdump iputils-ping -y'
- Flannel CNI 설치
# Flannel 설치 kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml # Flannel 확인 kubectl get ds,pod,cm -n kube-flannel
1.2. Bridge 에러 해결
Flannel 을 설치하더라도, CoreDNS 가 배포되지 않습니다.
kubectl describe pod -n kube-system -l k8s-app=kube-dns
'/opt/cni/bin' 안에 'bridge' 파일이 없어서 발생한 오류인 것을 알 수 있습니다.

아래와 같은 방법으로 bridge 실행 파일을 생성하여 컨테이너에 주입하면 해결할 수 있습니다.
# docker exec -it myk8s-control-plane bash --------------------------------------- apt install golang -y git clone https://github.com/containernetworking/plugins cd plugins chmod +x build_linux.sh # ./build_linux.sh Building plugins bandwidth firewall portmap sbr tuning vrf bridge host-device ipvlan loopback macvlan ptp vlan dhcp host-local static # 파일 권한 확인 755 ls -l bin -rwxr-xr-x 1 root root 4559683 Sep 3 04:54 bridge ... exit --------------------------------------- # 자신의 PC에 복사 : -a 권한 보존하여 복사(755) docker cp -a myk8s-control-plane:/plugins/bin/bridge . ls -l bridge
# 로컬에 복사한 bridge 파일을 kind 노드에 배포 docker cp bridge myk8s-control-plane:/opt/cni/bin/bridge docker cp bridge myk8s-worker:/opt/cni/bin/bridge docker cp bridge myk8s-worker2:/opt/cni/bin/bridge
문제가 해결되어 파드가 배포되었습니다.

1.3. Flannel 정보 확인
# 각 노드의 파드 서브넷 대역 for i in myk8s-control-plane myk8s-worker myk8s-worker2; do echo ">> node $i <<"; docker exec -it $i cat /run/flannel/subnet.env ; echo; done # 파드의 Public IP kubectl describe node | grep -A3 Annotations


- 노드 내부에서 Flannel 프로세스 확인
docker exec -it myk8s-worker bash lsns -p $(pgrep flanneld) lsns -p $(pgrep kube-proxy)

# 노드의 라우팅 정보 확인 ip -c route
라우팅 정보를 확인해보면, CNI 가 자동으로 내부 네트워크 라우팅을 잡아주는 것을 확인할 수 있습니다.

1.4. 쿠버네티스 파드 통신
파드 간 통신 테스트를 위해 2개의 파드를 배포합니다.
cat <<EOF | kubectl create -f - apiVersion: v1 kind: Pod metadata: name: pod-1 labels: app: pod spec: nodeSelector: kubernetes.io/hostname: myk8s-worker containers: - name: netshoot-pod image: nicolaka/netshoot command: ["tail"] args: ["-f", "/dev/null"] terminationGracePeriodSeconds: 0 --- apiVersion: v1 kind: Pod metadata: name: pod-2 labels: app: pod spec: nodeSelector: kubernetes.io/hostname: myk8s-worker2 containers: - name: netshoot-pod image: nicolaka/netshoot command: ["tail"] args: ["-f", "/dev/null"] terminationGracePeriodSeconds: 0 EOF
- 파드의 네트워크 통신 흐름 파악
스터디 멤버 중 한 분이신 광순님께서 테스트 흐름도를 너무 이해하기 쉽게 만들어 주셨습니다.

kubectl exec -it pod-1 -- zsh # 호스트 GW IP 호출 (동일 노드 간 내부 통신) ping 10.244.1.1 # Pod2 IP 호출 (다른 노드의 파드 간 통신) ping 10.244.2.2 # 외부 인터넷 IP 호출 ping 8.8.8.8 # 외부 인터넷 도메인 ghcnf curl -s wttr.in/Seoul

- Node 의 eth0 인터페이스에서 패킷 덤프
## Terminal 1번 # Node 1번 접속 docker exec -it myk8s-worker bash # eth0 에서 UDP 패킷 덤프 tcpdump -i eth0 -nn udp port 8472 -w /root/vxlan.pcap ## Terminal 2번 # Pod 접근 kubectl exec -it pod-1 -- zsh # 다른 노드의 파드로 Ping 전송 ping 10.244.2.2
성공적으로 ping 을 전송했다면, Wireshark 를 통해 덤프를 확인해봅니다.
# Node 에 덤프 뜬 패킷을 로컬로 복사 docker cp myk8s-worker:/root/vxlan.pcap . # Wireshark 로 패킷 확인 wireshark vxlan.pcap
Flannel 은 다른 노드의 파드와 통신할 때, UDP 8472 를 사용하여 전송합니다.
만약 Wireshark 에서 Source / Destination IP 정보가 제대로 안보인다면,
Preference 설정에서 VXLAN 프로토콜을 8472로 설정해주면 됩니다.

이번 주차 스터디를 통해서 쿠버네티스의 파드 간 통신과 CNI 에 대해 잘 이해할 수 있었습니다.
처음에는 하나도 몰라서 멘붕이 왔는데, 반복해서 학습을 하다보니 내것이 되어가고 있는 느낌이었습니다.
다음은 어떤 쿠버네티스 네트워크를 배울 지 기대가 되네요!