들어가며
Kubernetes 클러스터 내에서 Pod 간 통신은 CNI 플러그인에 의해 자동으로 설정되지만, 클러스터 외부와의 통신 경로를 동적으로 구성하려면 추가적인 네트워크 연동이 필요합니다. 특히 BGP(Border Gateway Protocol)를 활용하여 외부에서 클러스터 내 Pod/Service 네트워크로의 접근을 가능하게 만들 수 있습니다.
Cilium은 BGP Control Plane 기능을 통해 Kubernetes 노드의 Pod CIDR 등을 외부로 광고(advertise)할 수 있는 기능을 제공합니다. 그러나 Cilium 자체는 BGP 피어로서 동작할 수 있지만, 클러스터 외부에 존재하는 router의 역할까지는 수행하지 않습니다.
Cilium BGP Control Plane의 구성 요소를 간략하게 알아보고, 설정 방법과 외부 라우터인 FRRouting과 연동하여 통신 경로를 완성하는 과정을 실습해보겠습니다.
Cilium BGP Control Plane
Cilium BGP Control Plane은 Kubernetes Clsuter 내의 Cilium Node가 BGP를 통해 외부 네트워크와 동적으로 라우팅 정보를 교환할 수 있도록 하는 기능입니다.
이때, CRD를 통해 어떤 노드에서 BGP를 구동할지(CiliumBGPClusterConfig), 어떤 피어와 어떤 방식으로 세션을 맺을지(CiliumBGPPeerConfig), 그리고 어떤 네트워크 정보를 광고할지(CiliumBGPAdvertisement)를 설정할 수 있습니다.
즉, Cluster 내부의 Pod/Service CIDR을 외부 라우터에 광고하여, 외부 네트워크에서 해당 CIDR로 접근할 수 있도록 합니다.
CiliumBGPClusterConfig
apiVersion: cilium.io/v2
kind: CiliumBGPClusterConfig
metadata:
name: cilium-bgp
spec:
nodeSelector:
matchLabels:
"enable-bgp": "true"
bgpInstances:
- name: "instance-65001"
localASN: 65001
peers:
- name: "tor-switch"
peerASN: 65000
peerAddress: 192.168.10.200 # router ip address
peerConfigRef:
name: "cilium-peer"
CiliumBGPClusterConfig은 어떤 노드에서 어떤 BGP 인스턴스를 띄우고, 어떤 외부 피어와 세션을 맺을지 정의하는 최상위 설정입니다.
CiliumBGPPeerConfig
apiVersion: cilium.io/v2
kind: CiliumBGPPeerConfig
metadata:
name: cilium-peer
spec:
timers:
holdTimeSeconds: 9
keepAliveTimeSeconds: 3
ebgpMultihop: 2
gracefulRestart:
enabled: true
restartTimeSeconds: 15
families:
- afi: ipv4
safi: unicast
advertisements:
matchLabels:
advertise: "bgp"
CiliumBGPPeerConfig은 BGP 피어링에 필요한 세부 파라미터를 정의하고, 어떤 advertise policy를 사용할지 지정합니다.
CiliumBGPAdvertisement
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
name: bgp-advertisements
labels:
advertise: bgp
spec:
advertisements:
- advertisementType: "PodCIDR"
CiliumBGPAdvertisement은 어떤 정보를 BGP를 통해 외부 라우터에 광고(advertise)할지를 정의하는 리소스입니다.
Cilium BGP Control Plane의 한계
Cilium BGP Control Plane은 Kubernetes 노드의 PodCIDR이나 서비스 경로를 외부 네트워크로 광고(advertise)할 수 있도록 설계된 구성 도구입니다. 그러나 Cilium 자체는 BGP 경로를 수신하거나 커널 라우팅 테이블에 반영하는 기능을 제공하지 않기 때문에, 외부 BGP 피어로부터 이 경로들을 수신하고 실제 네트워크 경로에 반영할 수 있는 FRRouting(FRR)과 같은 별도의 BGP 도구가 필요합니다.
FRRouting
FRRouting이란?
Linux 및 Unix 계열 플랫폼에서 실행되는 오픈 소스 IP 라우팅 프로토콜(BGP, OSPF, RIP, IS‑IS, PIM, LDP, BFD, EVPN 등 다양한 프로토콜)입니다. OS 커널에 설치하는 Control Plane이며 네트워크 스택에서 라우팅 정보를 교환하고, 어떻게 라우팅 할 것인지 정책을 결정합니다. 또한, 동적 라우팅 외에도 정적 라우팅, address 설정, router advertisements(라우터 광고)를 설정합니다.
+----+ +----+ +-----+ +----+ +----+ +----+ +-----+
|bgpd| |ripd| |ospfd| |ldpd| |pbrd| |pimd| |.....|
+----+ +----+ +-----+ +----+ +----+ +----+ +-----+
| | | | | | |
+----v-------v--------v-------v-------v-------v--------v
| |
| Zebra |
| |
+------------------------------------------------------+
| | |
| | |
+------v------+ +---------v--------+ +------v------+
| | | | | |
| *NIX Kernel | | Remote dataplane | | ........... |
| | | | | |
+-------------+ +------------------+ +-------------+
위와같이 아키텍처가 모듈화되어 있으며, 프로토콜별로 bgpd, ospfd등과 같은 데몬이 중앙 데몬인 zebra와 통신하여 라우팅 테이블을 구성하고, zebra가 os 커널 등 실제 data plane과 통신합니다.
또한, vtysh 라는 별도의 CLI을 제공합니다.
실습
이번 실습은 Cilium BGP Control Plane이 노드에서 PodCIDR 등을 BGP로 광고하고, Router의 FRRouting(FRR)에서 그 경로를 수신하여 통신을 확인하는 구조입니다.
실습 환경
k8s controlplane 역할의 k8s-ctr, workernode의 k8s-w1, k8s-w0 이 있고, router가 존재합니다.
이때, router는 192.168.10.0/24 ↔ 192.168.20.0/24 대역의 라우팅 역할을 하며, k8s에서 관리하지 않은 서버이며 BGP 동작을 위해 frr이 설치되어있습니다. 또한, k8s-w0은 k8s-ctr, k8s-w1와 다른 네트워크 대역에 배치되어 있습니다.
Node별 Pod CIDR 확인
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
위 명령어를 통해 K8s가 Node에 지정한 Pod 기본 CIDR을 확인해보겠습니다.
kubectl get ciliumnode -o json | grep podCIDRs -A2
이번엔 Cilium이 실제로 사용하는 Pod CIDR을 확인해보겠습니다.
샘플 애플리케이션 배포 및 통신 문제 확인
샘플 애플리케이션 배포
webpod
apiVersion: apps/v1
kind: Deployment
metadata:
name: webpod
spec:
replicas: 3
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
curl pod
apiVersion: v1
kind: Pod
metadata:
name: curl-pod
labels:
app: curl
spec:
nodeName: k8s-ctr
containers:
- name: curl
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
Pod 확인
kubectl get po -o wide
위 명령어를 통해 pod의 상세정보를 확인해보겠습니다.
현재 webpod는 k8s-ctr, k8s-w0, k8s-w1에 각각 하나씩 배포되어 있으며, curl pod는 k8s-ctr에 배포되어 있는 것을 확인 할 수 있습니다.
통신 문제 확인
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
위 명령어를 통해 curl-pod에서 webpod에 각각 curl 요청을 보내보겠습니다.
현재 같은 node인 k8s-ctr에 배포된 webpod로만 통신이 되는 것을 확인할 수 있습니다.
BGP 설정 실습
[router] FRR 데몬 확인
ss -tnlp | grep -iE 'zebra|bgpd'
먼저 위 명령어를 통해 FRR의 zebra 데몬과 bgpd 데몬이 어떤 포트에서 LISTEN 중인지 확인해보겠습니다.
현재 2601, 2605 port에서 각각 LISTEN 중인것을 알 수 있습니다.
ps -ef |grep frr
FRR 관련 프로세스(zebra, bgpd, ospfd 등)가 실행 중인지 확인해보겠습니다.
정상적으로 프로세스가 실행중임을 확인할 수 있습니다.
[router] Cilium node 연동 설정
cat << EOF >> /etc/frr/frr.conf
neighbor CILIUM peer-group
neighbor CILIUM remote-as external
neighbor 192.168.10.100 peer-group CILIUM
neighbor 192.168.10.101 peer-group CILIUM
neighbor 192.168.20.100 peer-group CILIUM
EOF
FRR에서 CILIUM이라는 공통 peer group을 정의하고, 여러 Cilium 노드(192.168.10.100, 192.168.10.101, 192.168.20.100)와 eBGP 세션을 맺도록 하는 BGP neighbor 설정을 진행합니다.
[router] frr 데몬 재시작
systemctl daemon-reexec && systemctl restart frr
위 명령어를 통해 systemd 자체를 재실행하여 최신 상태로 동기화한 뒤, FRR 서비스를 재시작하여 새로운 BGP/라우팅 설정을 적용해보겠습니다.
systemctl status frr --no-pager --full
재시작 후 systemctl status 명령어로 frr.service의 상태를 확인해보겠습니다.
정상적으로 동작중임을 알 수 있습니다.
통신 테스트 - 1
[router] frr 모니터링 설정
journalctl -u frr -f
현재 router node에 접속한 상태이고, 해당 node에서 frr 설정이 되어있어 frr.service의 log를 모니터링해보겠습니다.
[k8s-ctr] 통신 테스트
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
이제 새로운 터미널에서 k8s-ctr node에 접속한 후 위 명령어를 통해 k8s-ctr에 배포된 curl-pod에서 각 node에 배포된 webpod로 통신을 해보곘습니다.
여전히 통신이 되지 않고 있고, router node의 frr도 어떤한 로그도 쌓이지 않고 있습니다.
[k8s-ctr] BGP 동작할 노드를 위한 label 설정
kubectl label nodes k8s-ctr k8s-w0 k8s-w1 enable-bgp=true
BGP 기능이 동작할 대상 노드를 지정하기 위해 enable-bgp라는 label을 설정합니다.
CiliumBGP 설정 변경
CiliumBGPAdvertisement
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
name: bgp-advertisements
labels:
advertise: bgp
spec:
advertisements:
- advertisementType: "PodCIDR"
위의 CiliumBGPAdvertisement 설정은 각 노드에 할당된 Pod CIDR 대역을 BGP를 통해 외부 peer에 advertise하도록 설정합니다.
CiliumBGPPeerConfig
apiVersion: cilium.io/v2
kind: CiliumBGPPeerConfig
metadata:
name: cilium-peer
spec:
timers:
holdTimeSeconds: 9
keepAliveTimeSeconds: 3
ebgpMultihop: 2
gracefulRestart:
enabled: true
restartTimeSeconds: 15
families:
- afi: ipv4
safi: unicast
advertisements:
matchLabels:
advertise: "bgp"
위 설정을 상세히 봐보겠습니다.
- timers.holdTimeSeconds: 9 : BGP 세션이 끊겼다고 판단하는 시간 (9초)
- timers.keepAliveTimeSeconds: 3 : Keepalive 패킷 전송 주기 (3초)
- ebgpMultihop: 2 : eBGP 피어가 직접 연결되지 않고 2홉 이상 떨어져 있어도 세션을 형성할 수 있도록 허용
- gracefulRestart.enabled: true : Graceful Restart 기능 활성화 → 피어 장애 시 일정 시간(15초) 동안 기존 라우팅 유지
- families.afi: ipv4, families.safi: unicast : IPv4 유니캐스트 주소 패밀리 사용
- families.advertisements.matchLabels.advertise: "bgp" : CiliumBGPAdvertisement 리소스 중 라벨이 advertise=bgp 인 것을 매칭해서 광고 정책으로 사용.
CiliumBGPClusterConfig
apiVersion: cilium.io/v2
kind: CiliumBGPClusterConfig
metadata:
name: cilium-bgp
spec:
nodeSelector:
matchLabels:
"enable-bgp": "true"
bgpInstances:
- name: "instance-65001"
localASN: 65001
peers:
- name: "tor-switch"
peerASN: 65000
peerAddress: 192.168.10.200 # router ip address
peerConfigRef:
name: "cilium-peer"
nodeSelector.matchLabels.enable-bgp=true
- enable-bgp=true 레이블이 붙은 노드에서만 이 설정이 적용됨
bgpInstances
- instance-65001 이라는 이름의 BGP 인스턴스를 생성
- localASN: 65001 → Cilium 노드들이 사용할 자율 시스템 번호(AS)
peers
- tor-switch 이라는 이름의 peer
- peerASN: 65000 : 외부 라우터(TOR 스위치)의 AS 번호
- peerAddress: 192.168.10.200 : 외부 라우터의 BGP 세션 IP
- peerConfigRef.name: "cilium-peer" : 앞서 정의한 CiliumBGPPeerConfig를 참조해 설정 적용
이 CRD는 쿠버네티스 노드(Cilium 노드)를 AS 65001로 묶고, 외부 라우터(AS 65000, 192.168.10.200)와 BGP 피어링을 형성하는 CRD입니다.
enable-bgp=true 노드에서 Cilium BGP 인스턴스가 뜨고, CiliumBGPPeerConfig + CiliumBGPAdvertisement와 연계해 PodCIDR을 외부 라우터에 광고하게 됩니다.
cilium bgp 정보 확인
cilium bgp peers
cilium bgp routes available ipv4 unicast
통신 테스트 - 2
[router] frr 모니터링
journalctl -u frr -f
위의 통신테스트 -1과 같이 router node에서 frr.service의 로그를 실시간으로 확인해보겠습니다.
아까 위에서는 안보였던 로그가 수집되었습니다.
Aug 16 16:26:05 router bgpd[4571]: [M59KS-A3ZXZ] bgp_update_receive: rcvd End-of-RIB for IPv4 Unicast from 192.168.20.100 in vrf default
Aug 16 16:26:05 router bgpd[4571]: [M59KS-A3ZXZ] bgp_update_receive: rcvd End-of-RIB for IPv4 Unicast from 192.168.10.101 in vrf default
Aug 16 16:26:05 router bgpd[4571]: [M59KS-A3ZXZ] bgp_update_receive: rcvd End-of-RIB for IPv4 Unicast from 192.168.10.100 in vrf default
FRR의 bgpd 로그로, 각 피어(192.168.20.100 / 192.168.10.101 / 192.168.10.100)로부터 IPv4 Unicast에 대한 End-of-RIB(EoR) 메시지를 받았다는 내용의 로그입니다.
EoR(End-of-RIB)은 BGP 초기 동기화(또는 라우트 리프레시/Graceful Restart) 이후 “해당 AFI/SAFI에 더 보낼 초기 라우트 없음”을 알리는 신호로, 초기 라우팅 정보 교환이 완료되어 수렴 단계에 들어갔음을 나타냅니다.
- GPT
[k8s-ctr] 통신 테스트
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
마찬가지로 새로운 터미널에서 k8s-ctr node에 접속한 후 위 명령어를 통해 k8s-ctr에 배포된 curl-pod에서 각 node에 배포된 webpod로 통신을 해보곘습니다.
여전히 통신이 되고 있지 않습니다...!
문제 상황 확인
현재 실습 환경은 2개의 NIC(eth0, eth1)을 사용하고 있는 상황으로, default GW가 eth0 경로로 설정 되어 있고, eth1은 k8s 통신 용도로 사용 중입니다. 그렇기 때문에 현재 k8s 파드 사용 대역 통신 전체는 eth1을 통해서 라우팅 설정하면 됩니다.
즉, Cilium 으로 BGP 사용 시, 2개 이상의 NIC 사용할 경우에는 Node에 직접 라우팅 설정 및 관리가 필요합니다.
라우팅 설정
# k8s-ctr
ip route add 172.20.0.0/16 via 192.168.10.100
# k8s-w1
ip route add 172.20.0.0/16 via 192.168.10.200
# k8s-w0
ip route add 172.20.0.0/16 via 192.168.20.200
각 node에 접근하여 위 명령어를 통해 eth1의 라우팅을 설정합니다.
통신 테스트 - 3
[router] frr 모니터링
journalctl -u frr -f
[k8s-ctr] 통신 테스트
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
다시 한번 k8s-ctr node의 curl-pod에서 각 node에 배포된 webpod로 curl 요청 테스트를 진행해보겠습니다.
이제 정상적으로 통신이 이루어진것을 확인할 수 있습니다.
노드 유지보수 실습
만약 운영중에 하나의 node에 작업을 해야 해서 scheduling을 꺼야 한다면 어떻게 해야 할까요? 위에서 설정한 대로 node의 label을 수정하면 됩니다. (비교적 간단하죠?)
k8s-w0의 node에 작업이 필요하다고 가상의 시나리오를 만들어 실습을 진행해보겠습니다.
유지보수를 위한 설정
kubectl drain k8s-w0 --ignore-daemonsets
k8s-w0를 cordon 하고, daemonset/static-pod를 제외한 나머지 파드를 축출(evict)합니다.
kubectl label nodes k8s-w0 enable-bgp=false --overwrite
k8s-w0 node의 label을 enable-bgp=false 로 설정합니다.
node 확인
kubectl get node
k8s-w0 node가 SchedulingDisabled 상태로 변경된 것을 확인할 수 있습니다.
cilium bpg peer 확인
cilium bgp peers
k8s-w0 node는 bgp peer도 제외된 것을 확인할 수 있습니다.
이렇게해서 k8s-w0의 scheduling을 disabled 했고, Cilium BGP Peering도 제외되었습니다. 이후 k8s-w0 node 작업을 진행했다고 가정하고 다시 원복 시키도록 하겠습니다.
원복 설정
kubectl label nodes k8s-w0 enable-bgp=true --overwrite
kubectl uncordon k8s-w0
node 확인
kubectl get node
k8s-w0 node가 다시 Ready 상태로 변경된 것을 확인할 수 있습니다.
cilium bpg peer 확인
cilium bgp peers
Cilium bgp peering에서도 k8s-w0가 할당된 것을 확인할 수 있습니다.
참고
https://docs.cilium.io/en/stable/network/bgp-control-plane/bgp-control-plane-v2/
이 내용은 CloudNet@에서 진행하는 Cilium 스터디를 참여하면서 공부하는 내용을 기록하며, CloudNet@에서 제공해주는 자료들을 바탕으로 작성되었습니다.
'스터디 이야기 > 25' Cilium' 카테고리의 다른 글
Cilium Overlay Network 실습 (4) | 2025.08.06 |
---|---|
Cilium 환경에서 NodeLocal DNSCache 적용하기 (2) | 2025.07.30 |
Cilium IPAM Mode와 IPAM 모드 변경 실습 (3) | 2025.07.28 |
Cilium Hubble을 활용한 L7 네트워크 가시성 및 메트릭 수집 실습 (1) | 2025.07.26 |