Cloudnet Cilium 1주차 스터디를 진행하며 정리한 글입니다.
이번 포스팅은 사전 실습 환경 설정 및 Flannel CNI 설치에 대해 포스팅 해보겠습니다.
실습 환경 소개
현재 사전 준비 과정으로 vagrant를 통해, 기본 배포 가상 머신 (k8s-ctr, k8s-w1, k8s-w2)을 설치하였습니다.
- eth0 : 10.0.2.15 모든 노드가 동일
- eth1 : 192.168.10.100, 101, 102
초기 프로비저닝으로 kubeadm init 과 join 실행되었고, CNI 미설치 상태로 배포 완료되었습니다. 이 때, 가상 머신의 eth0는 10.0.2.15로 모두 동일하며, 외부 인터넷 연결 역할을 합니다.
vagrant ssh 접속 시 호스트에 127.0.0.1(2222)를 목적지로 접속하고, 이후 포트포워딩(S/DNAT)을 통해서 내부에 VM로 SSH 연결됩니다.
NAT Mode 에 10.0.2.2(GateWay), 10.0.2.3(DNS Server), 10.0.2.4(TFTP Server) 용도로 IP가 예약됩니다.


k8s-ctr 노드에 접근하여 기본 정보를 확인해보겠습니다.
# vagrant를 이용하여 k8s-ctr 노드 접속
> vagrant ssh k8s-ctr ✘ INT 12s 21:56:10
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-53-generic aarch64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Sat Jul 19 09:56:08 PM KST 2025
System load: 0.32
Usage of /: 22.3% of 29.82GB
Memory usage: 32%
Swap usage: 0%
Processes: 116
Users logged in: 0
IPv4 address for eth0: 10.0.2.15
IPv6 address for eth0: fd17:625c:f037:2:a00:27ff:fe71:19d8
This system is built by the Bento project by Chef Software
More information can be found at https://github.com/chef/bento
Use of this system is acceptance of the OS vendor EULA and License Agreements.
Last login: Sat Jul 19 21:56:08 2025 from 10.0.2.2
# k8s-ctr 호스트 정보 확인
root@k8s-ctr:~# cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 vagrant
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
127.0.2.1 k8s-ctr k8s-ctr
192.168.10.100 k8s-ctr
192.168.10.101 k8s-w1
192.168.10.102 k8s-w2
# 정보 확인 및 워커 노드 Ping
root@k8s-ctr:~# whoami
pwd
hostnamectl
htop
root
/root
Static hostname: k8s-ctr
Icon name: computer-vm
Chassis: vm 🖴
Machine ID: f6b1bc8ed2f948ee945d4aed72f83374
Boot ID: 5c423912391b4ff5931b1651317afa98
Virtualization: qemu
Operating System: Ubuntu 24.04.2 LTS
Kernel: Linux 6.8.0-53-generic
Architecture: arm64
root@k8s-ctr:~# ping -c 1 k8s-w2
root@k8s-ctr:~# ping -c 1 k8s-w2
PING k8s-w1 (192.168.10.101) 56(84) bytes of data.
64 bytes from k8s-w1 (192.168.10.101): icmp_seq=1 ttl=64 time=1.21 ms
--- k8s-w1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.213/1.213/1.213/0.000 ms
PING k8s-w2 (192.168.10.102) 56(84) bytes of data.
64 bytes from k8s-w2 (192.168.10.102): icmp_seq=1 ttl=64 time=1.04 ms
--- k8s-w2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.042/1.042/1.042/0.000 ms
# 클러스터 상태 확인
root@k8s-ctr:~# kubectl cluster-info
Kubernetes control plane is running at https://192.168.10.100:6443
CoreDNS is running at https://192.168.10.100:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
# 클러스터 노드, 파드 상태 확인
# 현재 cni가 없어서 노드가 NotReady 상태
root@k8s-ctr:~# kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr NotReady control-plane 20m v1.33.2 192.168.10.100 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
k8s-w1 NotReady <none> 18m v1.33.2 10.0.2.15 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
k8s-w2 NotReady <none> 16m v1.33.2 10.0.2.15 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
root@k8s-ctr:~# kubectl get pod -A -owide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-674b8bbfcf-f9glh 0/1 Pending 0 20m <none> <none> <none> <none>
kube-system coredns-674b8bbfcf-pjcrm 0/1 Pending 0 20m <none> <none> <none> <none>
kube-system etcd-k8s-ctr 1/1 Running 0 20m 10.0.2.15 k8s-ctr <none> <none>
kube-system kube-apiserver-k8s-ctr 1/1 Running 0 20m 10.0.2.15 k8s-ctr <none> <none>
kube-system kube-controller-manager-k8s-ctr 1/1 Running 0 20m 10.0.2.15 k8s-ctr <none> <none>
kube-system kube-proxy-5d8w9 1/1 Running 0 20m 10.0.2.15 k8s-ctr <none> <none>
kube-system kube-proxy-fwmm9 1/1 Running 0 16m 10.0.2.15 k8s-w2 <none> <none>
kube-system kube-proxy-l8sgh 1/1 Running 0 18m 10.0.2.15 k8s-w1 <none> <none>
kube-system kube-scheduler-k8s-ctr 1/1 Running 0 20m 10.0.2.15 k8s-ctr <none> <none>
kube init 할때 api server ip를 지정해서 internal ip가 들어왔기 때문에 static 하게 고정하겠습니다. (k8s-ctr, k8s-w1, k8s-w2)
root@k8s-ctr:~# cat /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--container-runtime-endpoint=unix:///run/containerd/containerd.sock --pod-infra-container-image=registry.k8s.io/pause:3.10"
root@k8s-ctr:~# NODEIP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
sed -i "s/^\(KUBELET_KUBEADM_ARGS=\"\)/\1--node-ip=${NODEIP} /" /var/lib/kubelet/kubeadm-flags.env
systemctl daemon-reexec && systemctl restart kubelet
root@k8s-ctr:~# cat /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--node-ip=192.168.10.100 --container-runtime-endpoint=unix:///run/containerd/containerd.sock --pod-infra-container-image=registry.k8s.io/pause:3.10"
root@k8s-ctr:~# kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr NotReady control-plane 52m v1.33.2 192.168.10.100 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
k8s-w1 NotReady <none> 50m v1.33.2 10.0.2.15 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
k8s-w2 NotReady <none> 48m v1.33.2 10.0.2.15 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
# k8s-w1, w2도 동일
root@k8s-ctr:~# kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr NotReady control-plane 56m v1.33.2 192.168.10.100 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
k8s-w1 NotReady <none> 54m v1.33.2 192.168.10.101 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
k8s-w2 NotReady <none> 52m v1.33.2 192.168.10.102 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.27
root@k8s-ctr:~# kubectl get pod -A -owide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-674b8bbfcf-f9glh 0/1 Pending 0 56m <none> <none> <none> <none>
kube-system coredns-674b8bbfcf-pjcrm 0/1 Pending 0 56m <none> <none> <none> <none>
kube-system etcd-k8s-ctr 1/1 Running 0 57m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-apiserver-k8s-ctr 1/1 Running 0 57m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-controller-manager-k8s-ctr 1/1 Running 0 57m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-proxy-5d8w9 1/1 Running 0 56m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-proxy-fwmm9 1/1 Running 0 53m 192.168.10.102 k8s-w2 <none> <none>
kube-system kube-proxy-l8sgh 1/1 Running 0 55m 192.168.10.101 k8s-w1 <none> <none>
kube-system kube-scheduler-k8s-ctr 1/1 Running 0 57m 192.168.10.100 k8s-ctr <none> <none>
참고로, 이렇게 수동으로 번거롭게 설정할 필요없이 kubeadm configuration 기능이 beta 버전으로 나와서, 해당 설정만으로 가능하다고 합니다.
https://kubernetes.io/docs/reference/config-api/kubeadm-config.v1beta3/
kubeadm Configuration (v1beta3)
Overview Package v1beta3 defines the v1beta3 version of the kubeadm configuration file format. This version improves on the v1beta2 format by fixing some minor issues and adding a few new fields. A list of changes since v1beta2: The deprecated "ClusterConf
kubernetes.io
# 예시
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-ip: "192.168.10.101"
Flannel CNI 설치하기
Cilium CNI를 사용하기전, 대표적으로 많이 사용되는 Flannel CNI에 대해 알아보고 설치해보도록 하겠습니다.
CNI
CNI는 Container Network Interface의 약자로, 컨테이너 네트워크 설정을 위한 표준 인터페이스입니다. Kubernetes는 Pod를 실행할 때 각 Pod에 네트워크를 자동으로 할당해주는데, 이때 CNI 플러그인이 그 역할을 수행합니다.
CNI는 Kubernetes의 네트워크를 구성할 때 아래와 같은 역할을 합니다:
- Pod에 IP 주소 할당
- Pod 간 통신 경로 설정
- 네트워크 정책(NetworkPolicy) 적용
- Pod가 종료되었을 때 네트워크 리소스 정리
즉, CNI는 Pod ↔ Pod, Pod ↔ 외부 간의 네트워크가 가능하도록 해주는 핵심 구성 요소입니다.
CNI는 다음과 같은 구조로 동작합니다.

- Kubelet이 Pod를 생성할 때 CNI를 호출
- CNI 플러그인은 /opt/cni/bin 경로에 있는 바이너리를 실행
- /etc/cni/net.d의 설정 파일을 읽어 네트워크 세팅 수행
- Pod에 veth(가상 이더넷 페어)를 생성하고 하나는 Pod에, 하나는 호스트 브릿지에 연결
- IPAM을 통해 IP 할당
- Pod가 삭제되면 CNI 플러그인이 이를 감지하고 네트워크 자원 해제
Flannel CNI
Flannel은 다음과 같은 방식으로 동작합니다.
- 클러스터 전체의 큰 주소 공간(예: 10.244.0.0/16)을 노드 단위의 서브넷(예: 10.244.1.0/24, 10.244.2.0/24)으로 나눔
- 각 노드에 flanneld 데몬이 실행되며, 해당 노드에 서브넷을 할당하고, 다른 노드들과 라우팅 정보를 주고받음
- Pod가 생성되면, 해당 노드의 cni0 브릿지를 통해 IP를 할당받고, flannel.1 인터페이스를 통해 다른 노드와 통신
Flannel은 노드 간 Overlay 네트워크를 구성하기 위해 다음과 같은 3가지 백엔드를 지원합니다.
1) VXLAN (권장)
- 방식 : 패킷을 VXLAN 헤더로 캡슐화하여 다른 노드로 전달
- 포트 : UDP 8472 사용
- 특징 :
- 현재 가장 많이 쓰이는 방식으로, 퍼블릭 클라우드 환경에서도 안정적으로 동작
- DirectRouting : 같은 서브넷 상의 노드 간에는 VXLAN 없이 host-gw 방식처럼 통신하여 성능 향상
- "L2 확장"과는 다름: 전통적인 Layer 2 브로드캐스트 영역을 확장하는 방식이 아니라, 각 노드는 고유한 서브넷을 가지고 있고, NAT 없이 라우팅만 수행
- 대부분의 커널에서 잘 작동
- 클러스터 외부 네트워크에 의존하지 않음
추가)
VXLAN 터널이란? VXLAN (Virtual Extensible LAN) 은 IP 네트워크 위에 가상 네트워크를 캡슐화하여 전송하는 기술입니다.
"인터넷 위에 가상의 전용선(터널)을 만들어서, 마치 같은 네트워크에 있는 것처럼 만드는 기술"
노드 A의 Pod가 노드 B의 Pod에게 패킷을 보냄이 패킷은 일반적인 L2 패킷 (MAC/IP 기반)인데,Flannel은 이걸 VXLAN 헤더로 감싸서 UDP 패킷으로 만들고노드 B의 flanneld가 이 패킷을 받아 껍질을 벗기고 다시 Pod에게 전달
즉, 패킷을 하나 더 감싸서 보내는 일종의 네트워크 인셉션(캡슐화)입니다.
[Pod A] --- cni0 --- flannel.1 --[UDP 8472]--> flannel.1 --- cni0 --- [Pod B]
(VXLAN 캡슐화) (디캡슐화)
2) host-gw
- 방식 : 네트워크 오버레이 기법을 사용하지 않고, IP 레벨에서 직접 호스트 게이트웨이(Route Table)를 구성해 통신
- 특징
- 노드 간 통신은 L2 또는 L3 라우팅으로 직접 전달
- 캡슐화가 없기 때문에 VXLAN보다 성능이 좋음
- 단, 노드 간 직접 라우팅이 가능해야 함 (같은 VPC, 같은 서브넷이거나, 네트워크 장비가 허용해야 함)
- 퍼블릭 클라우드 환경에서는 노드 간 직접 라우팅이 어렵기 때문에 권장되지 않음
3) UDP (비권장)
- 방식 : VXLAN처럼 캡슐화하지만, 자체 UDP 터널(포트 8285)을 사용
- 포트 : UDP 8285
- 특징 :
- 오래된 커널이나 VXLAN 미지원 환경에서 사용
- 모든 트래픽을 터널링하므로 성능이 떨어짐
- 성능 저하, 커널 호환성 문제, 실무에서는 거의 사용되지 않음
Flannel CNI 설치
이 때, "--iface=eth1" 옵션을 통해 Flannel이 노드 간 통신에 사용할 인터페이스를 강제 지정하였습니다.
Flannel은 Overlay 네트워크(VXLAN 등)를 구성할 때, 노드 간 터널을 열기 위해 사용할 노드의 외부 통신 가능 IP를 알아야 하는데, 이 IP를 eth1 인터페이스에서 가져올지를 정하기 위함입니다.
- eth0: 10.0.2.15 — 일반적으로 NAT로 연결된 외부 통신용 인터페이스 (ex. VirtualBox NAT)
- eth1: 192.168.10.100 — 내부 네트워크 (Host-Only, VM 간 통신용)

# 사전 네임스페이스 생성
kubectl create ns kube-flannel
kubectl label --overwrite ns kube-flannel pod-security.kubernetes.io/enforce=privileged
helm repo add flannel https://flannel-io.github.io/flannel/
helm repo list
helm search repo flannel
helm show values flannel/flannel
# k8s 관련 트래픽 통신 동작하는 nic 지정
cat << EOF > flannel-values.yaml
podCidr: "10.244.0.0/16"
flannel:
args:
- "--ip-masq"
- "--kube-subnet-mgr"
- "--iface=eth1"
EOF
# helm 설치
helm install flannel --namespace kube-flannel flannel/flannel -f flannel-values.yaml
helm list -A
NAME: flannel
LAST DEPLOYED: Sat Jul 19 23:09:30 2025
NAMESPACE: kube-flannel
STATUS: deployed
REVISION: 1
TEST SUITE: None
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
flannel kube-flannel 1 2025-07-19 23:09:30.602318804 +0900 KST deployed flannel-v0.27.1 v0.27.1
# 기본 flannel 세팅 정보 확인
# CNI 바이너리 플러그인들이 위치하는 경로
root@k8s-ctr:~# tree /opt/cni/bin/
/opt/cni/bin/
├── bandwidth
├── bridge
├── dhcp
├── dummy
├── firewall
├── flannel
├── host-device
├── host-local
├── ipvlan
├── LICENSE
├── loopback
├── macvlan
├── portmap
├── ptp
├── README.md
├── sbr
├── static
├── tap
├── tuning
├── vlan
└── vrf
# CNI 설정 파일이 저장되는 위치
root@k8s-ctr:~# tree /etc/cni/net.d/
/etc/cni/net.d/
└── 10-flannel.conflist
1 directory, 1 file
root@k8s-ctr:~# cat /etc/cni/net.d/10-flannel.conflist | jq
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
# ip 링크 확인
root@k8s-ctr:~# ip -c link
ip -c route | grep 10.244.
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 08:00:27:71:19:d8 brd ff:ff:ff:ff:ff:ff
altname enp0s8
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 08:00:27:7e:57:7a brd ff:ff:ff:ff:ff:ff
altname enp0s9
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/ether de:ef:c5:28:c0:94 brd ff:ff:ff:ff:ff:ff
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
# ping 확인
ping -c 1 10.244.1.0
ping -c 1 10.244.2.0
PING 10.244.1.0 (10.244.1.0) 56(84) bytes of data.
64 bytes from 10.244.1.0: icmp_seq=1 ttl=64 time=2.03 ms
--- 10.244.1.0 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 2.027/2.027/2.027/0.000 ms
PING 10.244.2.0 (10.244.2.0) 56(84) bytes of data.
64 bytes from 10.244.2.0: icmp_seq=1 ttl=64 time=1.87 ms
--- 10.244.2.0 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.868/1.868/1.868/0.000 ms
샘플 어플리케이션 배포하여 통신 되는 것을 확인해보겠습니다.
kubectl get deploy,svc,ep webpod -owide
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/webpod 2/2 2 2 11s webpod traefik/whoami app=webpod
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/webpod ClusterIP 10.96.55.34 <none> 80/TCP 11s app=webpod
NAME ENDPOINTS AGE
endpoints/webpod 10.244.1.4:80,10.244.2.2:80 11s
kubectl get pod -l app=webpod -owide
POD1IP=10.244.1.4
kubectl exec -it curl-pod -- curl $POD1IP
Hostname: webpod-697b545f57-49wg4
IP: 127.0.0.1
IP: ::1
IP: 10.244.1.4
IP: fe80::c88e:31ff:fe14:3942
RemoteAddr: 10.244.0.2:54992
GET / HTTP/1.1
Host: 10.244.1.4
User-Agent: curl/8.14.1
Accept: */*
Flannel은 자체적으로 iptables를 이용해 다음과 같은 네트워크 처리를 합니다:
- Pod IP 라우팅
- SNAT (source NAT) 처리
- 포트 포워딩
- Overlay 네트워크(VXLAN 등) 터널에 대한 라우팅
즉, Flannel은 노드 간 통신을 VXLAN 등으로 연결하고, 실제 트래픽 흐름을 iptables 룰로 제어합니다.
실제로 대규모 환경에서 iptables는 치명적인 병목 요인이 됩니다. KubeCon China 2025 발표 자료에서도 아래와 같은 문제가 지적되었습니다. 즉, 단순하고 작을 때는 문제가 없지만, 노드 수나 파드 수가 많아질수록 심각한 비효율이 발생합니다.
| 네트워크 지연 | 연결 설정 시 1.2ms 추가 지연 |
| 룰 적용 속도 | 24,000개 룰을 갱신하는 데 5분 이상 소요 |
| CPU 오버헤드 | 53% 이상의 CPU 사용량 발생 |

Cilium은 iptables 없이도 커널 수준에서 네트워크 흐름을 처리할 수 있는 eBPF 기반이기 때문에, 성능과 확장성, 보안 측면에서 훨씬 우수합니다. 다음 실습에서 Cilium CNI에 대한 포스팅을 이어서 작성하도록 하겠습니다.
'스터디 > Cilium' 카테고리의 다른 글
| [Cilium] IPAM (3) | 2025.08.03 |
|---|---|
| [Cilium] Prometheus, Grafana을 활용한 Cilium 모니터링 (6) | 2025.07.27 |
| [Cilium] Cilium Observability - Hubble 설치 및 CiliumNetworkPolicy 적용 (3) | 2025.07.27 |
| [Cilium] Cilium CNI 설치 및 통신 확인 (7) | 2025.07.20 |
| [Cilium] Cilium CNI과 eBPF 소개 (5) | 2025.07.20 |