DevOps/Study

Cilium Study [1기] 1주차 Cilium 실습 환경 구축하기

juyeon22 2025. 7. 19. 14:49

Cilium Study [1기] 1주차 Cilium 실습 환경 구축하기

안녕하세요 이번 게시물에서는 가시다님이 운영하시는 Cilium Study [1기]에 필요한 실습 환경을 구성 해보겠습니다.

Clium과 다른 CNI를 비교하기 위해 Flannel 설치 후 Clium으로 구성 합니다.

1. 사전 유의사항

실습 시 필요한 환경과 기본지식들은 다음과 같습니다.

  • 최소 요구되는 PC Spec : CPU 4(vCPU 8) 이상, Mem 16GiB 이상
  • kubernetes 기본지식
  • 네트워크 기본지식
  • 리눅스/Git 기본지식
  • VirtualBox 와 Vagrant 설치

2. 실습 환경 구성(kubernetes 클러스터 구성, Flannel 설치)

실습 시 다음과 같은 환경으로 구성하고자 합니다.

Figure 2.1 실습 환경 개요

Figure 2.2 실습 환경 설명

eth0는 10.0.2.15 모든 노드가 동일한 IP를 사용하고 eth1로 IP를 구분하여 노드를 구성합니다.
eht0은 외부 접근 및 ssh를 사용하기 위해 eth0을 고정으로 사용합니다.

실습 환경 구성 시 vagrantfile을 사용해서 실습 환경을 구성합니다.

mkdir cilium-lab && cd cilium-lab

curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/cilium-study/1w/Vagrantfile

# 명령어 수행 시 반드시 관리자 권한으로 실행
vagrant up

스크립트가 너무 길어 수행하는 가시다님의 Vagrantfile의 MasterNode 구성 부분만 발췌 했으며, 설명은 주석으로 추가 했습니다.

# Variables
K8SV = '1.33.2-1.1' # Kubernetes Version : apt list -a kubelet , ex) 1.32.5-1.1
CONTAINERDV = '1.7.27-1' # Containerd Version : apt list -a containerd.io , ex) 1.6.33-1
N = 2 # max number of worker nodes
BOX_IMAGE = "bento/ubuntu-24.04"
BOX_VERSION = "202502.21.0"
...
Vagrant.configure("2") do |config|
#-ControlPlane Node
    config.vm.define "k8s-ctr" do |subconfig|
      subconfig.vm.box = BOX_IMAGE
      subconfig.vm.box_version = BOX_VERSION
      subconfig.vm.provider "virtualbox" do |vb|
        vb.customize ["modifyvm", :id, "--groups", "/Cilium-Lab"]
        # eth1 무작위 모드 허용을 위한 설정입니다.
        vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"] 
        vb.name = "k8s-ctr"
        vb.cpus = 2
        vb.memory = 2048
        vb.linked_clone = true
      end
      subconfig.vm.host_name = "k8s-ctr"
      # vagrant ssh접근 시 사용하는 포트포워딩 설정 및 eth1 인터페이스의 IP를 지정합니다.
      subconfig.vm.network "private_network", ip: "192.168.10.100"
      subconfig.vm.network "forwarded_port", guest: 22, host: 60000, auto_correct: true, id: "ssh" 
      # host와 guest vm간의 sync를 맞추는 설정 불필요하여 disabled 처리합니다. 
      subconfig.vm.synced_folder "./", "/vagrant", disabled: true
      # 실행 인자로 앞에서 선언한 kubernetes version과 containerd version을 받아와서 kubernetes 설치 스크립트를 수행합니다.
      subconfig.vm.provision "shell", path: "https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/cilium-study/1w/init_cfg.sh", args: [ K8SV, CONTAINERDV]
      # k8s-ctr.sh 스크립트는 kubeadm을 초기화하는 스크립트 입니다.
      subconfig.vm.provision "shell", path: "https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/cilium-study/1w/k8s-ctr.sh", args: [ N ]
    end
.....

Vargrant가 정상적으로 수행 됐는지 확인 합니다.  
# vagrant 상태 확인
 vagrant status
# Current machine states:
# k8s-ctr                   running (virtualbox)
# k8s-w1                    running (virtualbox)
# k8s-w2                    running (virtualbox)

구성된 Master Node로 접근 합니다.

vagrant ssh k8s-ctr

접근을 완료했으면 k8s-ctr 서버에 접속된 터미널에서 VM 기본 정보를 확인 하는 명령어를 이용하여 정보 들을 확인합니다.

# 추가된 HOST 확인
cat /etc/hosts

# 192.168.10.100 k8s-ctr
# 192.168.10.101 k8s-w1
# 192.168.10.102 k8s-w2

# NAT mode 확인 GATEWAY 주소 10.0.2.2
ss -tnp |grep sshd
# ESTAB 0      0           [::ffff:10.0.2.15]:22          [::ffff:10.0.2.2]:4468  users:(("sshd",pid=4982,fd=4),("sshd",pid=4936,fd=4))

# DNS 서버 정보 확인
resolvectl
# Current DNS Server: 10.0.2.3
#        DNS Servers: 10.0.2.3

kubernetes cluster 정보를 확인 하여 정상적으로 cluster가 구성되었는지 확인합니다.

# 노드 정보 확인
kubectl get node -owide
# NAME      STATUS     ROLES           AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
# k8s-ctr   NotReady   control-plane   39m   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>          37m   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>          34m   v1.33.2   10.0.2.15        <none>        Ubuntu 24.04.2 LTS   6.8.0-53-generic   containerd://1.7.27

# 파드 정보 : 상태, 파드 IP 확인 - kube-proxy 확인
kubectl get pod -A -owide
# NAMESPACE     NAME                              READY   STATUS    RESTARTS   AGE   IP          NODE      NOMINATED NODE   READINESS GATES
# kube-system   coredns-674b8bbfcf-4xknt          0/1     Pending   0          42m   <none>      <none>    <none>           <none>
# kube-system   coredns-674b8bbfcf-8bfww          0/1     Pending   0          42m   <none>      <none>    <none>           <none>
# kube-system   etcd-k8s-ctr                      1/1     Running   0          42m   10.0.2.15   k8s-ctr   <none>           <none>
# kube-system   kube-apiserver-k8s-ctr            1/1     Running   0          42m   10.0.2.15   k8s-ctr   <none>           <none>
# kube-system   kube-controller-manager-k8s-ctr   1/1     Running   0          42m   10.0.2.15   k8s-ctr   <none>           <none>
# kube-system   kube-proxy-brmmg                  1/1     Running   0          37m   10.0.2.15   k8s-w2    <none>           <none>
# kube-system   kube-proxy-g6wk8                  1/1     Running   0          40m   10.0.2.15   k8s-w1    <none>           <none>
# kube-system   kube-proxy-hzv27                  1/1     Running   0          42m   10.0.2.15   k8s-ctr   <none>           <none>
# kube-system   kube-scheduler-k8s-ctr            1/1     Running   0          42m   10.0.2.15   k8s-ctr   <none>           <none>

정보 확인 시 수정해야할 내용이 있어 수정을 진행하도록 하겠습니다.

INTERNAL-IP를 출력해보면 eth0 IP주소가 출력이 되고있는데 저희는 eth1을 사용하기 때문에 kubelet 실행 시 node IP를 eth1 IP로 지정하도록 하겠습니다.

# INTERNAL-IP 변경 설정
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

# 신규 터미널 생성 후 첫번째 워커노드 접속
vagrant ssh k8s-w1
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

# 신규 터미널 생성 후 두번째 워커노드 접속
vagrant ssh k8s-w2
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


# 마스터 노드 터미널에서 변경된 IP 확인
kubectl get node -owide
# NAME      STATUS     ROLES           AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
# k8s-ctr   NotReady   control-plane   51m   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>          48m   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>          45m   v1.33.2   192.168.10.102   <none>        Ubuntu 24.04.2 LTS   6.8.0-53-generic   containerd://1.7.27

# static Pod IP변경 확인
kubectl get pod -A -owide
# NAMESPACE     NAME                              READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
# kube-system   etcd-k8s-ctr                      1/1     Running   0          52m   192.168.10.100   k8s-ctr   <none>           <none>
# kube-system   kube-apiserver-k8s-ctr            1/1     Running   0          52m   192.168.10.100   k8s-ctr   <none>           <none>
# kube-system   kube-controller-manager-k8s-ctr   1/1     Running   0          52m   192.168.10.100   k8s-ctr   <none>           <none>
# kube-system   kube-proxy-brmmg                  1/1     Running   0          47m   192.168.10.102   k8s-w2    <none>           <none>
# kube-system   kube-proxy-g6wk8                  1/1     Running   0          50m   192.168.10.101   k8s-w1    <none>           <none>
# kube-system   kube-proxy-hzv27                  1/1     Running   0          52m   192.168.10.100   k8s-ctr   <none>           <none>
# kube-system   kube-scheduler-k8s-ctr            1/1     Running   0          52m   192.168.10.100   k8s-ctr   <none>           <none>

CNI가 설치되어있지 않아 CoreDNS의 Status가 Pending 상태에 있어, Flannel을 먼저 설치하도록 하겠습니다.

CNI 설치 전 kubernetes cluster 정보를 확인하고, 노드의 네트워크 / iptables / cni 디렉토리를 확인하여 설치 전 후를 비교합니다.

# PodCIDR 확인
kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"
# 출력결과
# "--service-cluster-ip-range=10.96.0.0/16",
# "--cluster-cidr=10.244.0.0/16",
# interface 정보 라우팅 정보 출력
ip -c link
ip -c route
brctl show

# iptables 정보 출력
iptables-save
iptables -t nat -S
iptables -t filter -S
iptables -t mangle -S

# CNI 디렉토리 출력
tree /etc/cni/net.d/

HelmChart를 이용하여 Flannel CNI를 설치합니다. Flannel 설치 시 kubectl cluster-info dump 명령어로 확인한 podCIDR 정보와 실제로 통신하는 인터페이스로 변경하여 설치합니다.

설치 후에는 노드의 네트워크 / iptables / cni 디렉토리를 확인하는 명령어를 다시 수행하여 설치 전 후를 비교합니다.

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/

# k8s 관련 트래픽 통신 동작하는 nic 지정
cat << EOF > flannel-values.yaml
podCidr: "10.244.0.0/16"

flannel:
  args:
  - "--ip-masq"
  - "--kube-subnet-mgr"
  - "--iface=eth1"  
EOF

helm install flannel --namespace kube-flannel flannel/flannel -f flannel-values.yaml


# 설치 후 interface 정보 및 라우팅 정보 확인
ip -c link
# flannel.1 interface 추가된 것 확인
#4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
#    link/ether 06:9e:00:d3:1a:2e brd ff:ff:ff:ff:ff:ff
ip -c route
# 워커노드별 인터페이스 추가된 것 확인
# worker node 1와 통신 시에는 flannel.1 를 거쳐 10.244.1.0으로 통신
# worker node 2와 통신 시에는 flannel.2 를 거쳐 10.244.2.0으로 통신
# 10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
# 10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
brctl show

# 설치 후 iptables 정보 확인
iptables-save
iptables -t nat -S
iptables -t filter -S
iptables -t mangle -S

# 설치 후 CNI 디렉토리 출력
tree /etc/cni/net.d/


kubectl get pod -A -owide

# ...
# kube-system    coredns-674b8bbfcf-4xknt          1/1     Running   0          68m     10.244.1.2       k8s-w1    <none>           <none>
# kube-system    coredns-674b8bbfcf-8bfww          1/1     Running   0          68m     10.244.1.3       k8s-w1    <none>           <none>
# ...

# k8s-w1, k8s-w2 정보 확인
for i in w1 w2 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-$i ip -c link ; echo; done
for i in w1 w2 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-$i ip -c route ; echo; done
for i in w1 w2 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-$i brctl show ; echo; done
for i in w1 w2 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-$i sudo iptables -t nat -S ; echo; done

CNI를 설치 후 Sample Application을 배포 후 각 노드 별로 추가된 네트워크 설정들을 확인해보도록 하겠습니다.

Application 배포 후 pod가 배포된 노드에서 ip -c link 명령어를 사용하여 cni0 인터페이스와 veth 인터페이스들을 확인할 수 있습니다.

cni0 인터페이스는 동일 노드에 있는 pod간 통신을 하기 위해 사용하는 인터페이스 이고, veth 인터페이스는 pod와 노드를 연결할 때 사용하는 인터페이스입니다.

 # 샘플 애플리케이션 배포
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 2
  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
EOF


# k8s-ctr 노드에 curl-pod 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
  labels:
    app: curl
spec:
  nodeName: k8s-ctr
  containers:
    - name: curl
      image: alpine/curl
      command: ["sleep", "36000"]
EOF

# 배포 확인
kubectl get deploy,svc,ep webpod -owide
# NAME                     READY   UP-TO-DATE   AVAILABLE   AGE    CONTAINERS   IMAGES           SELECTOR
# deployment.apps/webpod   2/2     2            2           111s   webpod       traefik/whoami   app=webpod

# NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE    SELECTOR
# service/webpod   ClusterIP   10.96.155.164   <none>        80/TCP    111s   app=webpod

kubectl get endpointslices -l app=webpod
# NAME           ADDRESSTYPE   PORTS   ENDPOINTS               AGE
# webpod-jrkwp   IPv4          80      10.244.2.2,10.244.1.4   2m16s

# 인터페이스 추가 확인
ip -c link
# 5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default qlen 1000
#     link/ether 36:df:53:af:29:ff brd ff:ff:ff:ff:ff:ff
# 6: veth9217cd3c@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default qlen 1000
#     link/ether 0a:8d:8f:f6:4d:25 brd ff:ff:ff:ff:ff:ff link-netns cni-baeaea1c-922f-4d62-b134-170fd8343fbf

# 추가된 네트워크 브릿지 확인
brctl show
# bridge name     bridge id               STP enabled     interfaces
# cni0            8000.36df53af29ff       no              veth9217cd3c

# 추가된 iptables 규칙 확인
iptables-save
iptables -t nat -S

# k8s-w1, k8s-w2 정보 확인
for i in w1 w2 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-$i ip -c link ; echo; done
for i in w1 w2 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-$i ip -c route ; echo; done
for i in w1 w2 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-$i brctl show ; echo; done
for i in w1 w2 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-$i sudo iptables -t nat -S ; echo; done

Sample Application 배포 후 pod간 통신을 확인 하여 Service 동작 시 iptables 규칙을 이용하는 것을 확인 해 보겠습니다.

# 통신확인할 POD IP확인
kubectl get pod -l app=webpod -owide
# NAME                      READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
# webpod-697b545f57-bhll9   1/1     Running   0          15m   10.244.2.2   k8s-w2   <none>           <none>
# webpod-697b545f57-pkrvs   1/1     Running   0          15m   10.244.1.4   k8s-w1   <none>           <none>
POD1IP=10.244.2.2
# Pod 직접 호출
kubectl exec -it curl-pod -- curl $POD1IP
# 서비스명으로 호출
kubectl exec -it curl-pod -- curl webpod | grep Hostname

# iptables 정책 사용 여부 확인
kubectl get svc webpod -o jsonpath="{.spec.clusterIP}"
SVCIP=$(kubectl get svc webpod -o jsonpath="{.spec.clusterIP}")
iptables -t nat -S | grep $SVCIP

# 10.96.155.164-A KUBE-SERVICES -d 10.96.155.164/32 -p tcp -m comment --comment "default/webpod cluster IP" -m tcp --dport 80 -j KUBE-SVC-CNZCPOCNCNOROALA
# -A KUBE-SVC-CNZCPOCNCNOROALA ! -s 10.244.0.0/16 -d 10.96.155.164/32 -p tcp -m comment --comment "default/webpod cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ

Serivce 추가 시 iptables 규칙을 사용하여 Service의 갯수만큼 iptables 규칙이 추가가되고, Service의 갯수가 엄청나게 많아지면 서버에 엄청난 부하를 줄 수 있습니다.

실제로 대규모 쿠버네티스 환경에서 iptables가 다루는 규칙 수가 방대해지면 네트워크 지연과 CPU 오버헤드가 심각해져 성능저하가 발생한 사례가 있습니다.

Figure 2.3 성능정하 사례 사진

3. 실습 환경 구성 (Flannel to Cilium)

공식문서에서 제공하는 가이드를 통해 Flannel에서 Cilum으로 Live Migration을 해보겠습니다.

Migration시 참고한 공식 문서

공식 문서에서는 Kind를 이용했지만, 저희는 Kind를 사용하지 않는 환경이기에 공식문서에서 공식적으로 제공하지 않는 환경으로 진행을 해보겠습니다.

환경 구성 시 필요사항

환경 구성 시 필요한 사항들은 다음과 같습니다.

  • Cilium이 사용할 신규 kubernetes cluster pod CIDR
  • Flannel, Calico, AWS-CNI와 같이 Linux 라우팅 스택을 사용하는 기존 네트워크 플러그인
  • 프로토콜 또는 포트가 다른 고유한 오버레이 사용

작업 진행 시 확인되지 않은 사항

작업 진행 시 확인되지 않은 사항들은 다음과 같습니다.

  • BGP 기반 라우팅
  • IPv4 to IPv6 Migration
  • 기존 kubernetes에서 사용하고 있는 Network Policy 정책 보존 여부

공식 문서에서는 Migration 진행 후 CiliumNetworkPolicy를 적용하고 있는 것을 권하는것으로 확인됩니다. 그리고 Migration 진행시 P Collisions에 발생 위험을 없애기 위해 Cilium의 Cluster-pool IPAM Allocator를 사용하는 것을 강력히 권장합니다.

Migration 절차

Migration 수행 시 다음과 같은 절차로 진행됩니다.

  • Cilum을 Secondary Mode로 설치
  • 각 Node들을 Cordon, drain, Migration 및 재시작
  • 기존 Network Povider 제거
  • 필요한 경우 각 노드 재시작

공식문서와 환경이 다르기 떄문에 Kind 클러스터 구성은 생략하고, vxlan을 사용하지 않기 때문에 8473포트를 사용하도록 하겠습니다.

저희 환경을 고려한 values-migration.yaml 파일은 이렇게 구성됩니다.

operator:
  unmanagedPodWatcher:
    restart: false # Migration: Don't restart unmigrated pods
routingMode: tunnel # Migration: Optional: default is tunneling, configure as needed
tunnelProtocol: vxlan # Migration: Optional: default is VXLAN, configure as needed
tunnelPort: 8473 # Migration: Optional, change only if both networks use the same port by default
cni:
  customConf: true # Migration: Don't install a CNI configuration file
  uninstall: false # Migration: Don't remove CNI configuration on shutdown
ipam:
  mode: "cluster-pool"
  operator:
    clusterPoolIPv4PodCIDRList: ["10.245.0.0/16"] # 기존 CNI CIDR과 겹치지 않는 IP대역
policyEnforcementMode: "never" # Migration: Disable policy enforcement
bpf:
  hostLegacyRouting: true # Migration: Allow for routing between Cilium and the existing overlay

수행 전 cilium-cli를 설치하여 설치 시 필요한 helm value의 값들을 확보합니다.

CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}

cilium-cli 설치 후 migration시 필요한 helm value를 출력합니다.

cilium install --version 1.17.6 --values values-migration.yaml --dry-run-helm-values > values-initial.yaml
cat values-initial.yaml

# bpf:
#   hostLegacyRouting: true
# cluster:
#   name: kubernetes
# cni:
#   customConf: true
#   uninstall: false
# ipam:
#   mode: cluster-pool
#   operator:
#     clusterPoolIPv4PodCIDRList:
#     - 10.245.0.0/16
# operator:
#   replicas: 1
#   unmanagedPodWatcher:
#     restart: false
# policyEnforcementMode: never
# routingMode: tunnel
# tunnelPort: 8473
# tunnelProtocol: vxlan

출력된 values를 기반으로 helm Chart를 사용해서 설치 합니다.

helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium --namespace kube-system --values values-initial.yaml

cilium status 명령어를 사용해서 Cilium에서 관리하는 Pod가 존재하고 있지 않은지 확인 합니다.

# 시간 소요가 많이 걸림
cilium status --wait
#     /¯¯\
#  /¯¯\__/¯¯\    Cilium:             OK
#  \__/¯¯\__/    Operator:           OK
#  /¯¯\__/¯¯\    Envoy DaemonSet:    OK
#  \__/¯¯\__/    Hubble Relay:       disabled
#     \__/       ClusterMesh:        disabled

# DaemonSet              cilium                   Desired: 3, Ready: 3/3, Available: 3/3
# DaemonSet              cilium-envoy             Desired: 3, Ready: 3/3, Available: 3/3
# Deployment             cilium-operator          Desired: 1, Ready: 1/1, Available: 1/1
# Containers:            cilium                   Running: 3
#                        cilium-envoy             Running: 3
#                        cilium-operator          Running: 1
#                        clustermesh-apiserver
#                        hubble-relay
# Cluster Pods:          0/2 managed by Cilium
# Helm chart version:    1.17.6
# Image versions         cilium             quay.io/cilium/cilium:v1.17.6@sha256:544de3d4fed7acba72758413812780a4972d47c39035f2a06d6145d8644a3353: 3
#                        cilium-envoy       quay.io/cilium/cilium-envoy:v1.33.4-1752151664-7c2edb0b44cf95f326d628b837fcdd845102ba68@sha256:318eff387835ca2717baab42a84f35a83a5f9e7d519253df87269f80b9ff0171: 3
#                        cilium-operator    quay.io/cilium/operator-generic:v1.17.6@sha256:91ac3bf7be7bed30e90218f219d4f3062a63377689ee7246062fa0cc3839d096: 1

per-node cofing을 생성하여 기존 CNI 정보를 Cilium이 인수하도록 합니다.

cat <<EOF | kubectl apply --server-side -f -
apiVersion: cilium.io/v2
kind: CiliumNodeConfig
metadata:
  namespace: kube-system
  name: cilium-default
spec:
  nodeSelector:
    matchLabels:
      io.cilium.migration/cilium-default: "true"
  defaults:
    write-cni-conf-when-ready: /etc/cni/net.d/05-cilium.conflist
    custom-cni-conf: "false"
    cni-chaining-mode: "none"
    cni-exclusive: "true"
EOF

workernode들을 cordon 수행하고 Migration 작업을 수행해보겠습니다.

NODE1="k8s-w1"
kubectl cordon $NODE1
# node/k8s-w1 cordoned

kubectl drain --ignore-daemonsets $NODE1
# Warning: ignoring DaemonSet-managed Pods: kube-flannel/kube-flannel-ds-mlzjz, kube-system/cilium-2rlvw, kube-system/cilium-envoy-xbh2z, kube-system/kube-proxy-8jtss
# evicting pod kube-system/coredns-674b8bbfcf-5swv5
# evicting pod kube-system/coredns-674b8bbfcf-2f7lz
# pod/coredns-674b8bbfcf-5swv5 evicted
# pod/coredns-674b8bbfcf-2f7lz evicted
# node/k8s-w1 drained

kubectl label node $NODE1 --overwrite "io.cilium.migration/cilium-default=true"
# node/k8s-w1 labeled

kubectl -n kube-system delete pod --field-selector spec.nodeName=$NODE1 -l k8s-app=cilium
# pod "cilium-2rlvw" deleted

kubectl -n kube-system rollout status ds/cilium -w
# Waiting for daemon set "cilium" rollout to finish: 2 of 3 updated pods are available...
# daemon set "cilium" successfully rolled out
NODE2="k8s-w2"
kubectl cordon $NODE2
# node/k8s-w2 cordoned

kubectl drain --ignore-daemonsets $NODE2
# Warning: ignoring DaemonSet-managed Pods: kube-flannel/kube-flannel-ds-f6jmr, kube-system/cilium-envoy-pcgb6, kube-system/cilium-qffp6, kube-system/kube-proxy-7l57s
# evicting pod kube-system/coredns-674b8bbfcf-xgjf8
# evicting pod kube-system/cilium-operator-787c6d8b85-9zzqx
# pod/cilium-operator-787c6d8b85-9zzqx evicted
# pod/coredns-674b8bbfcf-xgjf8 evicted
# node/k8s-w2 drained

kubectl label node $NODE2 --overwrite "io.cilium.migration/cilium-default=true"
# node/k8s-w2 labeled

kubectl -n kube-system delete pod --field-selector spec.nodeName=$NODE2 -l k8s-app=cilium
# pod "cilium-qffp6" deleted

kubectl -n kube-system rollout status ds/cilium -w
# Waiting for daemon set "cilium" rollout to finish: 2 of 3 updated pods are available...
# daemon set "cilium" successfully rolled out

해당 명령어들을 모두 수행 후 node를 재시작 해줍니다.

node 재시작 이후 명령어를 통해 정상적으로 Migration이 됐는지 확인 합니다.

# 재부팅 한 Cilum CNI 가 정상적으로 배포됐는지 확인
for i in w1 w2 ; do echo ">> node : k8s-$i <<"; sshpass -p 'vagrant' ssh -o StrictHostKeyChecking=no vagrant@k8s-$i ls -alh /etc/cni/net.d/ ; echo; done
# >> node : k8s-w1 <<
# total 16K
# drwxrwxr-x 2 root root 4.0K Jul 19 01:11 .
# drwxrwxr-x 3 root root 4.0K Jul 18 23:53 ..
# -rw-r--r-- 1 root root  191 Jul 19 01:08 05-cilium.conflist
# -rw-r--r-- 1 root root  292 Jul 19 01:08 10-flannel.conflist.cilium_bak
# -rw-r--r-- 1 root root    0 Dec 12  2024 .kubernetes-cni-keep

# >> node : k8s-w2 <<
# total 16K
# drwxrwxr-x 2 root root 4.0K Jul 19 01:21 .
# drwxrwxr-x 3 root root 4.0K Jul 18 23:56 ..
# -rw-r--r-- 1 root root  191 Jul 19 01:21 05-cilium.conflist
# -rw-r--r-- 1 root root  292 Jul 19 01:20 10-flannel.conflist.cilium_bak


cilium status --wait

#     /¯¯\
#  /¯¯\__/¯¯\    Cilium:             OK
#  \__/¯¯\__/    Operator:           OK
#  /¯¯\__/¯¯\    Envoy DaemonSet:    OK
#  \__/¯¯\__/    Hubble Relay:       disabled
#     \__/       ClusterMesh:        disabled

kubectl get -o wide node $NODE1

# NAME     STATUS                     ROLES    AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
# k8s-w1   Ready,SchedulingDisabled   <none>   3d1h   v1.33.2   192.168.10.101   <none>        Ubuntu 24.04.2 LTS   6.8.0-53-generic   containerd://1.7.27

kubectl get -o wide node $NODE2

# NAME     STATUS                     ROLES    AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
# k8s-w2   Ready,SchedulingDisabled   <none>   3d1h   v1.33.2   192.168.10.102   <none>        Ubuntu 24.04.2 LTS   6.8.0-53-generic   containerd://1.7.27

kubectl -n kube-system run --attach --rm --restart=Never verify-network   --overrides='{"spec": {"nodeName": "'$NODE1'", "tolerations": [{"operator": "Exists"}]}}'   --image ghcr.io/nicolaka/netshoot:v0.8 -- /bin/bash -c 'ip -br addr && curl -s -k https://$KUBERNETES_SERVICE_HOST/healthz && echo'
# lo               UNKNOWN        127.0.0.1/8 ::1/128
# eth0@if15        UP             10.245.0.205/32 fe80::54cc:10ff:feca:354c/64
# ok
# pod "verify-network" deleted

kubectl -n kube-system run --attach --rm --restart=Never verify-network   --overrides='{"spec": {"nodeName": "'$NODE2'", "tolerations": [{"operator": "Exists"}]}}'   --image ghcr.io/nicolaka/netshoot:v0.8 -- /bin/bash -c 'ip -br addr && curl -s -k https://$KUBERNETES_SERVICE_HOST/hea
lthz && echo'
# lo               UNKNOWN        127.0.0.1/8 ::1/128
# eth0@if13        UP             10.245.1.58/32 fe80::30ca:fff:fe87:59cc/64
# ok
# pod "verify-network" deleted

kubectl uncordon $NODE1
# node/k8s-w1 uncordoned

kubectl uncordon $NODE2
# node/k8s-w2 uncordoned

Migation 이후 Cilim의 상태를 확인 후 Cilum의 설정 값을 업데이트 합니다.

업데이트 시 eBPF Host-Routing을 사용하면 네트워크 향상이 됩니다.

하지만 Daemonset이 재시작 되면서 노드간 연결에 짧은 중단이 발생합니다.

공식문서에서는 eBPF Hosting-Routing을 사용하는 것을 권장하여 진행해보도록 하겠습니다.

cilium status
#     /¯¯\
#  /¯¯\__/¯¯\    Cilium:             OK
#  \__/¯¯\__/    Operator:           OK
#  /¯¯\__/¯¯\    Envoy DaemonSet:    OK
#  \__/¯¯\__/    Hubble Relay:       disabled
#     \__/       ClusterMesh:        disabled

cilium install --version 1.17.6 --values values-initial.yaml --dry-run-helm-values   --set operator.unmanagedPodWatcher.restart=true --set cni.customConf=false   --set policyEnforcementMode=default   --set bpf.hostLegacyRouting=false > values-final.yaml 


# 변경된 Value값을 확인 합니다.  
diff values-initial.yaml values-final.yaml
# 2c2
# <   hostLegacyRouting: true
# ---
# >   hostLegacyRouting: false
# 6c6
# <   customConf: true
# ---
# >   customConf: false
# 16,17c16,17
# <     restart: false
# < policyEnforcementMode: never
# ---
# >     restart: true
# > policyEnforcementMode: default


# Cilum 설정값 업데이트
helm upgrade --namespace kube-system cilium cilium/cilium --values values-final.yaml


# Cilium Daemonset Restart
kubectl -n kube-system rollout restart daemonset cilium




# 모두 다 OK 될 때 까지 확인
cilium status --wait
#     /¯¯\
#  /¯¯\__/¯¯\    Cilium:             OK
#  \__/¯¯\__/    Operator:           OK
#  /¯¯\__/¯¯\    Envoy DaemonSet:    OK
#  \__/¯¯\__/    Hubble Relay:       disabled
#     \__/       ClusterMesh:        disabled

설정값을 모두 업데이트 진행한 뒤에는 config와 기존에 설치한 CNI(Flannel)을 삭제 합니다.

# 초기 cilium per node config 삭제
kubectl delete -n kube-system ciliumnodeconfig cilium-default
# ciliumnodeconfig.cilium.io "cilium-default" deleted

# 초기에 설치한 Flannel 제거
helm uninstall flannel --namespace kube-flannel
# release "flannel" uninstalled

# Flannel NameSpace 제거
kubectl delete ns kube-flannel
# namespace "kube-flannel" deleted

기존 CNI를 제거 후에는 신규 Pod를 생성하여 pod간 통신이 가능한지 확인 합니다.

 # 샘플 애플리케이션 배포
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 2
  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
EOF


# k8s-ctr 노드에 curl-pod 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
  labels:
    app: curl
spec:
  nodeName: k8s-ctr
  containers:
    - name: curl
      image: alpine/curl
      command: ["sleep", "36000"]
EOF

# 통신확인할 POD IP확인
kubectl get pod -l app=webpod -owide
# NAME                      READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
# webpod-697b545f57-psbnn   1/1     Running   0          47s   10.245.0.4    k8s-w1   <none>           <none>
# webpod-697b545f57-vdfv5   1/1     Running   0          47s   10.245.1.67   k8s-w2   <none>           <none>


# Pod 직접 호출
POD1IP=10.245.0.4 
kubectl exec -it curl-pod -- curl $POD1IP
# Hostname: webpod-697b545f57-psbnn
# IP: 127.0.0.1
# IP: ::1
# IP: 10.245.0.4
# IP: fe80::7805:3eff:fec3:c805
# RemoteAddr: 10.245.2.211:48426
# GET / HTTP/1.1
# Host: 10.245.0.4
# User-Agent: curl/8.14.1
# Accept: */*



# 서비스명으로 호출
kubectl exec -it curl-pod -- curl webpod | grep Hostname
# Hostname: webpod-697b545f57-psbnn

Cilium 실습 환경 구축하기 내용을 끝내며 Cilium CNI에 대한 소개의 내용으로 다시 찾아 뵙겠습니다.