들어가며
이번 포스팅은 CloudNet@ 커뮤니티에서 주최하는 KANS 스터디 2주 차 주제인 "K8S Flannel CNI & PAUSE"에 대해서 정리한 내용입니다.
(스터디 내용이 많아 "K8S Pause container(1)"와 "K8S Flannel CNI(2)"로 포스팅을 나누어 작성합니다.)
CNI(Container Network Interface)
간단하게 CNI에 대해 먼저 살펴보겠습니다. CNI는 Kubernetes에서 네트워크 인터페이스를 생성하고, IP 주소를 할당하며, 네트워크 정책을 적용하는 역할을 합니다.
CNI의 주요 기능
- 네트워크 인터페이스 생성 : CNI 플러그인은 컨테이너에 네트워크 인터페이스(예: 가상 이더넷 장치)를 생성하고 컨테이너 네임스페이스에 이를 연결
- IP 주소 할당: 플러그인은 네트워크 인터페이스에 IP 주소를 할당하고, 필요에 따라 IP 주소 관리를 처리
- 네트워크 정책 적용: 네트워크 정책을 통해 트래픽을 제어할 수 있으며, CNI 플러그인은 이를 구현하는 데 사용
- 다양한 네트워크 모드 지원: 다양한 네트워크 토폴로지와 요구 사항을 지원하기 위해 여러 네트워크 모드(예: 브리지, VLAN, 오버레이 네트워크 등)를 지원
CNI의 작동 방식
CNI는 기본적으로 플러그인 기반 구조를 따릅니다. 오케스트레이션 툴은 특정 이벤트가 발생할 때 CNI 플러그인을 호출하여 필요한 네트워크 설정을 수행합니다. 각 CNI 플러그인은 JSON 형식의 구성 파일로 정의되며, 이를 통해 플러그인의 동작을 제어할 수 있습니다.
CNI 플러그인의 종류
CNI 플러그인은 다양한 종류가 있으며, 각기 다른 네트워킹 요구 사항을 충족시킵니다. 대표적인 CNI 플러그인은 다음과 같습니다.
- Flannel: 간단하고 사용하기 쉬운 오버레이 네트워크 플러그인입니다.
- Calico: 네트워크 정책과 보안을 강조하는 플러그인으로, 네트워크 격리 및 정책 적용에 강점이 있습니다.
- Weave: 자동 메쉬 네트워크와 서비스 디스커버리를 제공하는 플러그인입니다.
- Cilium: 고성능 BPF 기반 네트워킹 및 보안을 제공하는 플러그인입니다.
- Multus: 여러 CNI 플러그인을 사용하여 컨테이너에 여러 네트워크 인터페이스를 지원하는 플러그인입니다.
이 중에서 이번 주차 스터디에서 다룬 CNI는 Flannel입니다.
쿠버네티스 네트워크모델은 4가지 요구사항을 만족하며 4가지 문제를 해결해야 합니다.
요구사항
- 파드와 파드 간 통신 시 NAT(Network Address Translation) 없이 통신이 가능해야 함
- 노드의 에이전트(kubelet, 시스템 데몬)는 Pod와 통신이 가능해야 함
- 호스트 네트워크를 사용하는 파드는 NAT 없이 파드와 통신이 가능해야 함
- 서비스 클러스터 IP 대역과 파드가 사용하는 IP 대역은 중복되지 않아야 함
해결해야 하는 문제
1. 파드 내 컨테이너는 Loopback을 통한 통신을 할 수 있도록 해야 함
2. 파드 간 통신을 할 수 있어야 함
3. 클러스터 내부에서 Service를 통한 통신을 할 수 있어야 함
4. 클러스터 외부에서 Service를 통한 통신을 할 수 있어야 함
쿠버네티스에서는 다음과 같은 요구사항으로 네트워크 통신을 위해 CNI(Container Network Interface)를 정의했습니다. 위에서 설명한 플러그인들은 이러한 요구사항들을 기반으로 만들어졌습니다.
Kubelet을 통해 파드가 신규 생성될 때 네트워크 관련 설정 추가 필요합니다. CNI 플러그인은 전달되는 설정 정의서를 보고 실제 파드가 통신하기 위한 네트워크 설정들을 실행하게 됩니다. 또한 CNI 플러그인은 IPAM(IP Address Management), 즉 IP 할당 관리를 수행해야 하며, 파드 간 통신을 위한 라우팅 설정을 처리해야 합니다.
Flannel
Flannel은 Kubernetes 클러스터 내에서 네트워크 연결을 제공하기 위해 설계된 간단하고 가벼운 네트워크 플러그인입니다. Flannel은 기본적으로 Kubernetes의 클러스터 네트워킹 요구 사항을 충족시키며, 각 Pod가 클러스터 내의 다른 모든 Pod와 통신할 수 있도록 하는 오버레이 네트워크를 생성합니다.
이 오버레이 네트워크를 생성하기 위한 다양한 방법이 있지만, 대표적으로 VXLAN(Virtual eXtensible Local Area Network)를 이용할 수 있습니다. VXLAN은 물리적인 네트워크 환경에서 논리적인 가상의 네트워크 환경을 만들어 주는 것으로, UDP 8472 포트를 통해 노드 간 터널링 기법으로 통신하는 기술입니다.
그림과 같이 파드의 eth0 네트워크 인터페이스는 호스트 네임스페이스의 vethY 인터페이스와 쌍으로 연결되고,
vethY는 cni0와 쌍으로 연결됩니다. (동일 노드 내에서는 cni0를 이용하여 통신)
flannel.1은 패킷을 감싸고 제거하는 VTEP(Vxlan Tunnel End Point)라고 하며, flannel.1 은 host의 eth0을 통해 클러스터의 노드 간 통신을 가능하게 해 줍니다.
각 노드마다 파드에 IP를 할당해 줄 수 있는 IP 네트워크 대역이 있고, flannel을 통하여 ETCD 또는 Kubernetes API에 전달되고, 모든 노드는 해당 정보를 자신의 라우팅 테이블에 업데이트합니다.
kind & Flannel 설치
kind에 flannel을 이용하여 네트워크를 구성하고, 실습을 진행했습니다. kind는 기본적으로 kindnet을 통해 CNI를 구현함으로 default networking을 끄고 설치합니다.
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 create cluster --config kind-cni.yaml --name myk8s --image kindest/node:v1.30.4
# 배포 확인
kind get clusters
kind get nodes --name myk8s
kubectl cluster-info
# 네트워크 확인
kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"
# 노드 확인 : CRI
kubectl get nodes -o wide
# 노드 라벨 확인
kubectl get nodes myk8s-control-plane -o jsonpath={.metadata.labels} | jq
...
"mynode": "control-plane",
...
kubectl get nodes myk8s-worker -o jsonpath={.metadata.labels} | jq
kubectl get nodes myk8s-worker2 -o jsonpath={.metadata.labels} | jq
# 컨테이너 확인 : 컨테이너 갯수, 컨테이너 이름 확인
docker ps
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
#
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을 설치하려면 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
마지막으로, flannel을 설치합니다.
watch -d kubectl get pod -A -owide
#
kubectl describe pod -n kube-system -l k8s-app=kube-dns | grep Events: -A 6
# Flannel cni 설치
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
# namespace 에 pod-security.kubernetes.io/enforce=privileged Label 확인
kubectl get ns --show-labels
kubectl get ds,pod,cm -n kube-flannel
kubectl describe cm -n kube-flannel kube-flannel-cfg
kubectl describe ds -n kube-flannel kube-flannel-ds
kubectl exec -it ds/kube-flannel-ds -n kube-flannel -c kube-flannel -- ls -l /etc/kube-flannel
# failed to find plugin "bridge" in path [/opt/cni/bin]
kubectl get pod -A -owide
kubectl describe pod -n kube-system -l k8s-app=kube-dns
Warning FailedCreatePodSandBox 35s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "786e9caec9c312a0b8af70e14865535575601d024ec02dbb581a1f5ac0b8bb06": plugin type="flannel" failed (add): loadFlannelSubnetEnv failed: open /run/flannel/subnet.env: no such file or directory
Warning FailedCreatePodSandBox 23s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "6ccd4607d32cbb95be9ff40b97a436a07e5902e6c24d1e12aa68fefc2f8b548a": plugin type="flannel" failed (add): failed to delegate add: failed to find plugin "bridge" in path [/opt/cni/bin]
#
kubectl get pod -A -owide
다음으로, kube-dns 파드의 상태를 살펴보면 FailedCreatePodSandBox 문제로 STATUS가 ContainerCreating 상태에 머무르는 것을 확인할 수 있습니다. 이는 네트워크 이슈로 인해 pouse 컨테이너가 생성되지 못했다는 것입니다.
이 현상을 해결하기 위해 앞서 생성한 bridge를 각각의 인스턴스의 /opt/cni/bin에 복사합니다.
#
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
docker exec -it myk8s-control-plane chmod 755 /opt/cni/bin/bridge
docker exec -it myk8s-worker chmod 755 /opt/cni/bin/bridge
docker exec -it myk8s-worker2 chmod 755 /opt/cni/bin/bridge
#
docker exec -it myk8s-control-plane ls -l /opt/cni/bin/
docker exec -it myk8s-worker ls -l /opt/cni/bin/
docker exec -it myk8s-worker2 ls -l /opt/cni/bin/
for i in myk8s-control-plane myk8s-worker myk8s-worker2; do echo ">> node $i <<"; docker exec -it $i ls /opt/cni/bin/; echo; done
bridge flannel host-local loopback portmap ptp
#
kubectl get pod -A -owide
이제 coredns pod가 정상적으로 배포된 것을 확인할 수 있습니다.
Flannel 정보 확인
생성된 Flannel 네트워크에서 설정 정보들을 확인해 봅니다. flannel.1 은 10.244.X.0으로 구성되어 있습니다.
kubectl get ds,pod,cm -n kube-flannel -owide
kubectl describe cm -n kube-flannel kube-flannel-cfg
# iptables 정보 확인
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-control-plane iptables -t $i -S ; echo; done
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-worker iptables -t $i -S ; echo; done
for i in filter nat mangle raw ; do echo ">> IPTables Type : $i <<"; docker exec -it myk8s-worker2 iptables -t $i -S ; echo; done
# flannel 정보 확인 : 대역, 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
>> node myk8s-control-plane <<
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24 # 해당 노드에 파드가 배포 시 할당 할 수 있는 네트워크 대역
FLANNEL_MTU=65485 # MTU 지정
FLANNEL_IPMASQ=true # 파드가 외부(인터넷) 통신 시 해당 노드의 마스커레이딩을 사용
...
# 노드마다 할당된 dedicated subnet (podCIDR) 확인
kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' ;echo
# 노드 정보 중 flannel 관련 정보 확인 : VXLAN 모드 정보와, VTEP 정보(노드 IP, VtepMac) 를 확인
kubectl describe node | grep -A3 Annotations
# 각 노드(?) 마다 bash 진입 후 아래 기본 정보 확인 : 먼저 worker 부터 bash 진입 후 확인하자
docker exec -it myk8s-worker bash
docker exec -it myk8s-worker2 bash
docker exec -it myk8s-control-plane bash
----------------------------------------
# 호스트 네트워크 NS와 flannel, kube-proxy 컨테이너의 네트워크 NS 비교 : 파드의 IP와 호스트(서버)의 IP를 비교해보자!
lsns -p 1
lsns -p $(pgrep flanneld)
lsns -p $(pgrep kube-proxy)
# 기본 네트워크 정보 확인
ip -c -br addr
ip -c link | grep -E 'flannel|cni|veth' -A1
ip -c addr
ip -c -d addr show cni0 # 네트워크 네임스페이스 격리 파드가 1개 이상 배치 시 확인됨
ip -c -d addr show flannel.1
brctl show
# 라우팅 정보 확인 : 다른 노드의 파드 대역(podCIDR)의 라우팅 정보가 업데이트되어 있음을 확인
ip -c route
# flannel.1 인터페이스를 통한 ARP 테이블 정보 확인 : 다른 노드의 flannel.1 IP와 MAC 정보를 확인
ip -c neigh show dev flannel.1
# 브리지 fdb 정보에서 해당 MAC 주소와 통신 시 각 노드의 enp0s8
bridge fdb show dev flannel.1
# 다른 노드의 flannel.1 인터페이스로 ping 통신 : VXLAN 오버레이를 통해서 통신
ping -c 1 10.244.0.0
ping -c 1 10.244.1.0
ping -c 1 10.244.2.0
# iptables 필터 테이블 정보 확인 : 파드의 10.244.0.0/16 대역 끼리는 모든 노드에서 전달이 가능
iptables -t filter -S | grep 10.244.0.0
# iptables NAT 테이블 정보 확인 : 10.244.0.0/16 대역 끼리 통신은 마스커레이딩 없이 통신을 하며,
# 10.244.0.0/16 대역에서 동일 대역(10.244.0.0/16)과 멀티캐스트 대역(224.0.0.0/4) 를 제외한 나머지 (외부) 통신 시에는 마스커레이딩을 수행
iptables -t nat -S | grep 'flanneld masq' | grep -v '! -s'
Pod on the Flannel의 통신 흐름 이해 #1
워커노드에 pod가 생성되면 cni0 bridge, veth 등이 자동으로 생성됩니다. 또한 사전에 할당했던 IP 대역대로 각 워커노드에서 IP를 할당받습니다.
# [터미널1,2] 워커 노드1,2 - 모니터링
docker exec -it myk8s-worker bash
docker exec -it myk8s-worker2 bash
-----------------------------
watch -d "ip link | egrep 'cni|veth' ;echo; brctl show cni0"
-----------------------------
# [터미널3] cat & here document 명령 조합으로 즉석(?) 리소스 생성
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
# 파드 확인 : IP 확인
kubectl get pod -o wide
Pod 생성 전
Pod 생성 후
Pod on the Flannel의 통신 흐름 이해 #2
Flannel 기반으로 네트워크를 구축할 때는 다음과 같이 세가지 정도의 통신 시나리오가 생길 수 있습니다.
1. 단일 노드 내부에서 Pod 간 통신을 하는 경우
2. 파드에서 외부와 통신 하는 경우
3. 서로다른 노드에서 Pod간 통신 하는 경우
Pod 간 통신, 서로다른 node의 Pod간 통신, 외부 통신 여부에 대해 살펴보고, 통신이 일어나는 상황의 패킷을 캡처하면서 Flannel network에 대해 이해해 보았습니다.
kubectl exec -it pod-1 -- zsh
-----------------------------
ip -c addr show eth0
# GW IP는 어떤 인터페이스인가?cni0
ip -c route
route -n
ping -c 1 <GW IP>
ping -c 1 <pod-2 IP> # 다른 노드에 배포된 파드 통신 확인
ping -c 8.8.8.8 # 외부 인터넷 IP 접속 확인
curl -s wttr.in/Seoul # 외부 인터넷 도메인 접속 확인
ip -c neigh
exit
모두 정상적으로 통신이 잘 됩니다.
이번에는 각 노드의 cni0에서 패킷 캡처를 진행해 보았습니다.
# [터미널1,2] 워커 노드1,2
docker exec -it myk8s-worker bash
docker exec -it myk8s-worker2 bash
-----------------------------
tcpdump -i cni0 -nn icmp
tcpdump -i flannel.1 -nn icmp
tcpdump -i eth0 -nn icmp
tcpdump -i eth0 -nn udp port 8472 -w /root/vxlan.pcap
# CTRL+C 취소 후 확인 : ls -l /root/vxlan.pcap
conntrack -L | grep -i icmp
-----------------------------
# [터미널3]
docker cp myk8s-worker:/root/vxlan.pcap .
wireshark vxlan.pcap
Pod 1 -> Pod 2 (cni0 관점)
Pod 1 -> 8.8.8.8(외부) (cni0 관점)
Pod 1 -> Pod 2 (flannel.1 관점)
Pod 1 -> 8.8.8.8(외부) (flannel.1 관점)
Pod 1 -> Pod 2 (eth0 관점)
Pod 1 -> 8.8.8.8(외부) (eth0 관점)
Pod 1 -> Pod 2 (eth0 관점, udp port 8472 덤프)
vxlan 기반으로 캡슐화되어 Pod 간 ICMP 통신이 일어난 것을 확인했습니다 ♣
마치며
스터디를 통해 Pause container와 Flannel CNI plugin에 대해 이해했습니다. 처음에는 긴가민가 했는데, 반복적으로 보고 가시다님께서 정리해 놓은 자료를 보다 보니, 이제 Kubernetes 통신 관련 트러블슈팅을 조금씩 할 수 있는 자신감이 드네요!
이상으로 KANS 스터디 2주 차 관련 포스팅을 마치도록 하겠습니다.
감사합니다.
'클라우드 컴퓨팅 & NoSQL > [KANS] 쿠버네티스 네트워크 심화 스터디' 카테고리의 다른 글
[3주차(2/2) - k8s Calico CNI mode & 운영] KANS 스터디 (24.09.08) (0) | 2024.09.18 |
---|---|
[3주차(1/2) - k8s Calico CNI] KANS 스터디 (24.09.08) (1) | 2024.09.18 |
[2주차(1/2) - K8S Pause container] KANS 스터디 (24.09.01) (6) | 2024.09.04 |
[1주차(2/2) - 컨테이너 네트워크 & IPTables] KANS 스터디 (24.08.25) (6) | 2024.09.01 |
[1주차(1/2) - 도커 컨테이너 격리] KANS 스터디 (24.08.25) (0) | 2024.09.01 |