본문 바로가기
스터디 이야기/Kubernetes Advanced Networking Study

[KANS] Kubernetes Flannel CNI

by lakescript 2024. 9. 8.
728x90
더보기

이 스터디는 CloudNet@에서 진행하는 KANS 스터디를 참여하면서 공부하는 내용을 기록하는 블로그 포스팅입니다.

CloudNet@에서 제공해주는 자료들을 바탕으로 작성되었습니다.

Flannel CNI

 

쿠버네티스 네트워킹

쿠버네티스 네트워크 모델의 요구 사항

쿠버네티스 네트워크 모델은 4가지의 요구 사항이 있습니다. 

1. Pod와 Pod간 통신 시 NAT없이 통신이 가능해야 합니다.

2. Node의 Agent(kubelet, system daemon등)는 Pod와 통신이 가능해야 합니다.

3. Host 네트워크를 사용하는 Pod는 Nat없이 Pod와 통신이 가능해야 합니다.

4. 서비스 Cluster IP 대역과 Pod가 사용하는 IP 대역은 중복되지 않아야 합니다.

 

쿠버네티스 네트워크 모델의 해결 사항

또한, 아래의 4가지 문제를 해결해야 합니다.

1. Pod 내 Container는 Loopback을 통한 통신을 할 수 있습니다.

2. Pod간 통신을 할 수 있습니다.

 

 

3. Cluster 내부에서 service를 이용한 통신을 할 수 있습니다.

 

4. Cluster 외부에서 service를 이용한 통신을 할 수 있습니다.

 

위와 같은 통신을 위해 쿠버네티스에서는 CNI(Container Network Interface)를 정의했습니다. 

 

 

CNI 동작

 

kubelet을 통해 신규 Pod가 생성될 때 네트워크 관련 설정을 추가해줘야 합니다. 여기서 CNI 플러그인은 실제 Pod가 통신하기 위한 네트워크 설정들을 실행하게 됩니다. 또한, CNI 플러그인은 IPAM(IP Address Management/IP 할당 관리)를 수행해야 하며, Pod간 통신을 위한 Routing 설정을 처리해야 합니다.

 

CNI 플러그인에는 Flannel, Calico, Cilium등이 있고, 이번 포스팅에는 FlannelCNI에 대해 작성해보겠습니다.

 

Flannel CNI란

Flannel은 단일 바이너리 에이전트인 flanneld이 각 노드에서 동작하며, 작은 규모의 클러스터 환경에서 Pod들 간 통신 환경을 구성해주는 CNI 입니다. 

VXLAN(Virtual eXtensible LAN

네트워크 오버레이 기술을 구현해주는 대표적인 방법으로 물리적인 네트워크 환경 위에서 논리적인 가상의 네트워크를 만들어준 후 터널링(Turnnel) 기법을 사용하여 동작합니다. 그 후 파드의 패킷을 감싸서 노드를 빠져나가고, 목적지 노드에 도착하여 해당 패킷에 감싸진 부분을 제거한 후 목적지 파드에 전달하게 됩니다. 이때, 패킷을 감싸고 제거하는 지점을 VTEP(Vxlan Tunnel End Point)라고 하며, flannel.1 네트워크 인터페이스가 해당 역할을 합니다. (참고로 Flannel의 VXLAN은 UDP 8472 port를 사용합니다.)

또한, Pod의 eth0 네트워크 인터페이스는 호스트 네트워크 네임스페이스의 vethY 인터페이스와 쌍으로 연결되고, vethY 인터페이스는 cni0 네트워크 브리지와 쌍으로 연결됩니다. 즉, 동일한 노드 내에 파드들간 통신은 cni0 네트워크 브리지를 통하여 통신하게 됩니다.

 

보통 각 노드마다 파드에 할당할 수 있는 IP 네트워크 대역이 있고 해당 네트워크 대역을 flannel을 통하여 ETCD나 Kubernetes API에 전달되고, 모든 노드는 해당 네트워크 대역 정보를 획득하여 자신의 라우팅 테이블에 업데이트하게 됩니다. 이와 같은 과정으로 인해 모든 노드는 각 노드에 할당된 파드이 네트워크 대역으로 통신이 가능하게 됩니다.

 

 

Pod간 통신(내부)

Pod간 통신(외부)

실습

실습환경 구성

kind 설치 파일 구성

cat <<EOF> kind-cni.yaml
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
EOF

 

위의 값으로 kind cluster yaml 파일을 생성합니다. 이때 networking.disableDefaultCNI를 ture로 설정하여 kind에서 default로 생성되어 설정되는 kindnetd을 생성하지 않습니다. (공식문서)

 

kind 설치

kind create cluster --config kind-cni.yaml --name myk8s --image kindest/node:v1.30.4

 

Cluster 생성 확인

kind get nodes --name myk8s

 

kubectl cluster-info

 

네트워크 확인

kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"

dump 옵션을 주어 Kubernetes 클러스터의 네트워크 설정을 확인해보겠습니다.  grep을 통해 cluster 네트워크의 CIDR 범위(cluster-cidr)와 서비스 클러스터 IP 범위(service-cluster-ip-range)를 확인합니다.

 

Node의 라벨 확인

kubectl get nodes myk8s-control-plane -o jsonpath={.metadata.labels} | jq
kubectl get nodes myk8s-worker -o jsonpath={.metadata.labels} | jq
kubectl get nodes myk8s-worker2 -o jsonpath={.metadata.labels} | jq

node의 label을 통해 어떤 역할을 하는 node인지 확인해보겠습니다.

 

컨테이너 확인

docker ps

docker ps 명령어를 통해 현재 실행되고 있는 container process 목록을 확인합니다.

 

docker port myk8s-control-plane

docker port myk8s-worker
docker port myk8s-worker2

 

컨테이너 내부 정보 확인

docker exec -it myk8s-control-plane ip -br -c -4 addr

 

docker exec -it myk8s-worker  ip -br -c -4 addr

 

docker exec -it myk8s-worker2  ip -br -c -4 addr

 

 

node 목록 확인

kubectl get node

노드의 상태 및 목록을 확인해보면 아래와 같이 NotReady인 것을 확인하실 수 있습니다.

왜 NotReady일까요? 왜냐하면 CNI를 설정하지 않았기 때문입니다. 실습에서 사용할 CNI는 flannelCNI인데 해당 CNI를 설치해보겠습니다.

 

flannel CNI 설치

kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

위의 명령어로 github repo에서 flannelCNI의 yaml 파일을 바로 cluster에 apply 합니다.

 

flannel CNI 리소스 확인

kubectl get ds,pod,cm -n kube-flannel

kube-flannel이라는 namespace에 배포된 deamonset, pod, configmap 목록을 출력해보겠습니다.

core-dns 확인

kubectl get po -n kube-system

위의 명령어로 kube-system namespace내에 배포되어있는 pod들의 목록을 확인해보겠습니다.

위의 사진과 같이 core-dns가 생성이 되지 않고 ContainerCreating 상태인 것이 보여집니다.

 

kubectl describe pod -n kube-system ${core-dns-pod-name}

 

describe명령어로 core-dns Pod의 자세한 정보를 확인해보겠습니다.

 

failed to find plugin "bridge" in path [/opt/cni/bin]

 

Event 로그를 살펴보면 PodSandBox라고 하는 pause container가 공유할 namespace를 할당해줘야 하는데 bridge plugin이 없다는 내용을 확인하실 수 있습니다.

for i in myk8s-control-plane myk8s-worker myk8s-worker2; do echo ">> node $i <<"; docker exec -it $i ls /opt/cni/bin/; echo; done

위의 명령줄을 이용하여 지금 생성되어있는 docker container에 접근하여 /opt/cni/bin 경로의 목록을 출력해보겠습니다.

실제로 bridge plugin이 없는 것을 확인하실 수 있습니다. 이 문제는 아마 kind로 구성했기 때문에 발생한 문제인 것 같아서,, 아래의 접은 글로 bridge를 docker ps로 옮기는 방법을 적어놓겠습니다.

더보기
docker exec -it myk8s-control-plane bash

 호스트에서 myk8s-control-plane container에 bash 쉘로 접근합니다.

 

apt install golang -y
git clone https://github.com/containernetworking/plugins
cd plugins
chmod +x build_linux.sh
./build_linux.sh

위의 순서대로 진행해주시면 설치가 완료 후 실행까지 하게 되어 plugins을 사용하실 수 있습니다.

exit

 쉘을 닫습니다.

docker cp -a myk8s-control-plane:/plugins/bin/bridge .

docker cp 명령어를 통해 myk8s-control-plane container에 설치되어있던 bridge 파일을 host로 옮겨옵니다.

 

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

 그 후 host에서 해당 bridge 파일을 container 각각의 /opt/cni/bin 경로에 옮겨줍니다.

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

그 후 다시 각 docker container에 /opt/cni/bin의 목록을 출력해보겠습니다.

 

bridge plugin이 출력되는 것을 확인하실 수 있습니다.

 

kubectl get po -A -o wide

그 후 전체 pod 목록을 출력해보겠습니다.

 

core-dns 파드 상태 확인

kubectl describe pod -n kube-system -l k8s-app=kube-dns

위에서 defaultCNI를 사용하지 않고 cluster를 생성했기 때문에 CNI가 없습니다. 그렇기 때문에 어떤 상태인지 확인을 하기 위해 k8s-app=kube-dns label이 붙은 pod의 상세 정보를 확인해보겠습니다.

현재 scheduling이 이루어 지지 않고 있는 것을 확인하실 수 있습니다. 그렇기 때문에 이번 실습에서 사용할 CNI인 flannelCNI를 설치해보겠습니다.

 

flannel cm 확인

kubectl describe cm -n kube-flannel kube-flannel-cfg

먼저 flannel의 configmap을 확인해보겠습니다.

 

kubectl exec -it ds/kube-flannel-ds -n kube-flannel -c kube-flannel -- ls -l /etc/kube-flannel

Kubernetes 클러스터에서 Flannel 네트워크 플러그인에 대한 특정 디렉토리의 내용을 확인합니다.

 

flannel CNI 대역, MTU 확인

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

각 컨테이너의 /run/flannel/subnet.env 파일을 확인해보겠습니다.

control-plane, worker, worker2의 FLANNEL_NETWORK는 10.244.0.0/16으로 모두 똑같고, FLANNEL_SUBNET은 모두 다른 것을 확인하실 수 있습니다. AWS EC2로 VPC CNI에서 Pod들에게 할당할 IPAM정보의 네트워크 갯수 만큼 대역폭에서 Pod에 할당해주는 것 처럼 FLANNEL_SUBNET은 해당 노드에 Pod가 배포 시 할당할 수 있는 네트워크 대역을 의미합니다. 

 

worker 정보 확인

worker 접근

docker exec -it myk8s-worker bash

여러가지 정보를 확인하기 위해서 myk8s-worker에 bash로 접근해보겠습니다.

 

기본 네트워크 정보 확인

ip -c addr

네트워크 인터페이스에 대한 정보를 색상으로 구분하여 출력해보겠습니다.

 

eth0 값을 확인해보면 172.18.0.3/16인 것을 확인하실 수 있는데, 실제로는 docker로 띄어진 kind contaier의 network bridge의 정보입니다.

 

ip -c -d addr show cni0

cni0의 정보를 확인해보겠습니다.

현재는 안보이지만 네트워크 네임스페이스 격리 파드가 1개 이상 배치 시 확인이 가능합니다.

 

ip -c -d addr show flannel.1

flannel interface를 확인해보겠습니다.

친숙한 vxlan을 사용하는 것이 확인이 됩니다.

 

brctl show

bridge 정보도 확인해보겠습니다.

현재는 pod가 하나도 없기 때문에 생성되지 않았습니다.

 

라우팅 정보 확인

ip -c route

route 정보를 확인해보겠습니다.

특이하게 현재 자신의 node외에 다른 node에 할당된 IP routing 정보가 이미 설정되어있는 것을 확인하실 수 있습니다.

 

Pod를 생성하여 network 정보 확인

모니터링 설정

docker exec -it myk8s-worker  bash
docker exec -it myk8s-worker2 bash

worker, worker2에 bash shell로 접근합니다.

watch -d "ip link | egrep 'cni|veth' ;echo; brctl show cni0"

cni와 veth와 관련된 네트워크 인터페이스 상태와 브리지 정보(cni0 브리지)등의 네트워크 인터페이스를 지속적으로 모니터링하면서 pod가 생성될 때 어떤것이 생성되는지 확인해보겠습니다.

 

worker

 

worker2

 

pod 생성

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

 

 

 

worker

worker2

 

두 node 역할을 하는 contaienr에 새로운 cni와 veth와 관련된 네트워크 인터페이스 상태와 브리지 정보(cni0 브리지)등의 네트워크 인터페이스가 생성된 것을 확인하실 수 있습니다.

 

728x90

'스터디 이야기 > Kubernetes Advanced Networking Study' 카테고리의 다른 글

[KANS] Calico CNI  (1) 2024.09.19
[KANS] 컨테이너 네트워크  (0) 2024.09.13
[KANS] Pod - pause container  (4) 2024.09.05
[KANS] Docker In Docker - Kind  (0) 2024.09.05