이번 주차에서는 드디어 BGP 광고하는 방법에 대해서 알아보려고 한다.
cilium native mode로 운영을 할 때 라우팅 테이블을 주입하기 위한 방법이다.
실습 환경 구성

라우팅 관련된 주차에서는 비슷한 실습 환경이다.
하지만 이번 주차에는 라우터 노드에 BGP 업데이트를 위한 frr 에이전트가 추가로 설치되어 있다.
- FRR은 라우팅 서비스를 제공하는 오픈소스이다. 라우터를 구입할 수 없기 때문에 오픈소스로 활용한다.
cilium은 cluster-pool IPAM, native 모드로 설치하였고 kube-proxy 대체모드이다.
그리고 BGP 실습을 위해서 bgpControlPlane을 true 그리고 autoDirectNodeRoutes는 false로 하였다.
그렇다는것은 다른 노드의 pod과는 통신을 할 수 없다는 것을 의미한다.(내가 static 라우팅 설정을 하지 않는다면)
helm install cilium cilium/cilium --version $2 --namespace kube-system \
--set k8sServiceHost=192.168.10.100 --set k8sServicePort=6443 \
--set ipam.mode="cluster-pool" --set ipam.operator.clusterPoolIPv4PodCIDRList={"172.20.0.0/16"} --set ipv4NativeRoutingCIDR=172.20.0.0/16 \
--set routingMode=native --set autoDirectNodeRoutes=false --set bgpControlPlane.enabled=true \
--set kubeProxyReplacement=true --set bpf.masquerade=true --set installNoConntrackIptablesRules=true \
--set endpointHealthChecking.enabled=false --set healthChecking=false \
--set hubble.enabled=true --set hubble.relay.enabled=true --set hubble.ui.enabled=true \
--set hubble.ui.service.type=NodePort --set hubble.ui.service.nodePort=30003 \
--set prometheus.enabled=true --set operator.prometheus.enabled=true --set hubble.metrics.enableOpenMetrics=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,icmp,httpV2:exemplars=true;labelsContext=source_ip\,source_namespace\,source_workload\,destination_ip\,destination_namespace\,destination_workload\,traffic_direction}" \
--set operator.replicas=1 --set debug.enabled=true >/dev/null 2>&1
---
(⎈|HomeLab:N/A) root@k8s-ctr:~# cilium config view | grep -i bgp
bgp-router-id-allocation-ip-pool
bgp-router-id-allocation-mode default
bgp-secrets-namespace kube-system
enable-bgp-control-plane true
enable-bgp-control-plane-status-report true
autoDirectnodeRoutes를 false로 했기 때문에 라우팅테이블을 보면 podCIDR에 대해 노드로 설정된 라우팅테이블이 없다.
--- k8s-ctr 라우팅 테이블 ---
172.20.0.0/24 via 172.20.0.103 dev cilium_host proto kernel src 172.20.0.103
172.20.0.103 dev cilium_host proto kernel scope link
통신 테스트를 위한 샘플앱을 배포한다.
# 샘플 애플리케이션 배포
cat << EOF | kubectl apply -f -
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
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: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
Cilium BGP Control Plane
Cilium에서는 크게 native 또는 tunnel 모드를 사용해서 노드간 통신을 제어하고 있다.
이때 natvie 모드는 podCIDR를 기반으로 통신을 하기 때문에 클러스터 범주를 벗어나면 네트워크 장비에서는 라우팅을 할 수 없다.
그리고 이를 지원하기 위한것이 bgp control plane 기능이며, 외부 라우터 장비와 BGP 피어링을 맺고
podCIDR, service IP, loadbalancer IP 등 라우팅에 필요한 정보를 서로 주고 받으며 라우팅 테이블을 지정한다.

그림에서 알 수 있듯이 BGP 광고를 위해서는 총 4개의 객체를 사용할 수 있다.
- CiliumBGPClusterConfig : 클러스터 단위로 BGP 데몬과 피어를 맺을 때 사용하는 객체이다.
- CiliumBGPNoneConfigOverride : 이는 클러스터 단위 + 특정 노드에만 적용되어 피어를 맺을 때 사용하는 객체이다.
- CiliumBGPPeerConfig : 여러 BGP 데몬과 피어를 맺어야 할 떄 공통 설정을 지정하는 객체이다.
- CiliumBGPAdvertisement : 어떤 객체를 BGP 광고할지 선택하게 된다. PodCIDR or Service IP 등등
노드 통신 테스트, Non BGP
실습 환경에서 autoDirectNodeRoutes를 false로 했기 때문에, 다른 노드와 통신이 되지 않을것이라고 예상했다.
그리고 테스트해보면 k8s-w0에 배포된 샘플에서는 응답이 오지 않는다.
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'

노드 노드 podCIDR에 대해서 라우팅테이블이 안잡혀있기 때문에 당연한 결과이다.
이번 주차에서는 이처럼 라우팅테이블이 없을 때 BGP 를 통해서 어떻게 광고를 하는지에 대한 실습이다.
Router BGP 설정 방법
먼저 기본적으로 router에는 FSS 에이전트가 설치되어 있다.
실습 환경을 구성할 때 FSS 에이전트를 설치하고, 이를 위한 config는 추가했지만 보면 neighbor가 없는 상태이다.
그러면 실제로 BGP 교환할 대상이 없기 때문에 neighbor를 추가해줘야 한다.
- 물론 이 방법 말고, vtysh 커맨드를 통해서 각 단계를 들어가서 설정하는것도 가능하다.
- 일반적으로 네트워크 장비에서 하는 방법인데, 이 방법보다는 config를 직접 수정하는게 더 수월한것 같다.
cat /etc/frr/frr.conf
log syslog informational
!
router bgp 65000
bgp router-id 192.168.10.200
bgp graceful-restart
no bgp ebgp-requires-policy
bgp bestpath as-path multipath-relax
maximum-paths 4
network 10.10.1.0/24
--- K8s node neighbor 추가
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
--- 그리고 재시작
systemctl daemon-reexec && systemctl restart frr
k8s 각 노드의 neighbor를 추가했기 때문에, 이제 남은건 cilium에서 설정하면 된다.
Cilium BGP 설정 방법
Cilium BGP Control Plane을 소개할 때, 4개의 객체가 필요하다고 설명했는데 이중에서 3개 객체를 배포한다.
- CiliumBGPNodeConfigOverride는 각 노드별로 다른 설정을 주입하기 위한 객체인데, 이번에는 사용하지 않는다.
객체에 대해서 짧게 설명하면
- node labels에서 advertise: bgp가 있는 대상으로 BGP 광고를 한다.
- 광고할 대상은 podCIDR 정보이다.
- BGPCluster는 router에서 확인한 bgp 65000 그리고 router 주소를 등록한다.
- BGP 연결을 맺을때는 cilium-peer 객체를 참고해서 설정한다.
--- bgp nodelabel 추가
kubectl label nodes k8s-ctr k8s-w0 k8s-w1 enable-bgp=true
--- cilium bgp 객체 반영
cat << EOF | kubectl apply -f -
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
name: bgp-advertisements
labels:
advertise: bgp
spec:
advertisements:
- advertisementType: "PodCIDR"
---
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"
---
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"
EOF
cilium에서 BGP 관련된 객체를 반영하였고, router 에서 라우팅테이블 정보를 보면 각 노드별 podCIDR가 등록되어 있다.
그러면 예상되는 결과는 이제 다른 노드에 있는 서비스와 통신이 되야 하는데, 실제로는 되지 않는다.
왜냐? 각 노드에서 podCIDR에 대해서 router로 가는 라우팅테이블이 등록이 안되어 있다.
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'

스터디 선생님 왈,
cilium bgp는 speaker로 역할을 하고, 수신한 경로에 대해서는 라우팅테이블에 따로 등록하지 않는다고 한다.
cilium에서는 네트워크 경로를 eBPF로 처리를 처리하기 때문에 라우팅테이블을 참조하는 경우가 적기 때문이라고 한다.
- 그리고 기본적으로는 각 서버에서는 보통 기본 라우팅테이블로 네트워크 장비로 잡기 떄문에
- 위와 같이 라우팅테이블을 수정할 일이 없다고 한다.
- 하지만 실습 환경에서는 기본 라우팅테이블이 eth0으로 되어 있기 떄문에 수정이 필요하다.
그러면 실습 환경에서 라우팅 테이블에서 기본 게이트웨이를 router로 잡고나면 이제 통신이 잘된다!
각 k8s 노드에서는 다른 노드에 대한 podCIDR에 대해서 기본 게이트웨이인 router로 보내고
router에서는 각 노드로부터 BGP 광고를 받아서 라우팅테이블에 등록되어 있기 때문에 서로 다른 노드로 라우팅이 된다.
--- k8s-ctr에서 진행
ip route add 172.20.0.0/16 via 192.168.10.200
sshpass -p 'vagrant' ssh vagrant@k8s-w1 sudo ip route add 172.20.0.0/16 via 192.168.10.200
sshpass -p 'vagrant' ssh vagrant@k8s-w0 sudo ip route add 172.20.0.0/16 via 192.168.20.200
만약 특정 노드에 대해 작업을 할 때는 노드에 대한 lables을 제거해서 BGP 라우팅테이블을 제거하면 된다.
(⎈|HomeLab:kube-system) root@k8s-ctr:~# kubectl label nodes k8s-w0 enable-bgp=false --overwrite
node/k8s-w0 labeled

근데 생각해보면 BGP 라우팅에서 제외해도 되지만, 별도 작업을 하지 않아도 될것 같다.
왜 그럴까 생각해보면?
- 각 노드별로 podCIDR는 고정되어 있고, 해당 노드가 drain 되어 있다면 라우팅테이블이 있더라도
- 해당 노드에 대한 pod는 없기 때문에 라우팅테이블이 매칭되는 pod가 없다.
- 그래서 라우팅테이블의 존재 유/무가 서비스에 영향을 끼치지 않는다.
(⎈|HomeLab:kube-system) root@k8s-ctr:~# k get ciliumnode k8s-w0 -oyaml |grep podCIDR -A2
podCIDRs:
- 172.20.2.0/24
노드 labels을 추가하면 다시 BGP 라우팅테이블에 반영이 된다.
(⎈|HomeLab:kube-system) root@k8s-ctr:~# kubectl label nodes k8s-w0 enable-bgp=true --overwrite
node/k8s-w0 labeled

cilium docs를 보면 대규모 노드를 운영하는 클러스터의 경우 ciliumbpgnodeconfigs에 상태 업데이트를 하는데
업데이트 하는 과정에서 kube-apiserver의 부하가 발생하기 때문에 상태 업데이트 비활성화를 권장한다.
helm upgrade cilium cilium/cilium --version 1.18.0 --namespace kube-system --reuse-values \
--set bgpControlPlane.statusReport.enabled=false
실제로 얼마나 많은 부하가 발생하는지 테스트해보지는 않았으나, apiserver는 생각보다 많은 컴포넌트의 요청을 처리한다.
최근에 회사에서도 특정 상황이 발생하여 apiserver OOM이 발생했었는데, 각 컴포넌트에 대해서 모두 이런 제한을 할 수 없기 때문에
apiserver에 APF를 적용하는것도 하나의 방법이 될것 같다.
- 쉽게 말하면 특정 user, group에 대해서 API 요청에 대한 queue / 할당 리소스를 제한하는것이다.
API Priority and Fairness
FEATURE STATE: Kubernetes v1.29 [stable] Controlling the behavior of the Kubernetes API server in an overload situation is a key task for cluster administrators. The kube-apiserver has some controls available (i.e. the --max-requests-inflight and --max-mut
kubernetes.io
BGP 광고 객체 추가(LoadBalancer)
위 테스트를 할때는 podCIDR에 대해서 BGP 광고를 했으나, pod뿐만 아니라 service IP에 대해서도 가능하다.
이전에 공부했던 CiliumLoadBalancerIPpool 객체를 통해서 service lb IP를 할당받도록 한다.
cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2"
kind: CiliumLoadBalancerIPPool
metadata:
name: "cilium-pool"
spec:
allowFirstLastIPs: "No"
blocks:
- cidr: "172.16.1.0/24"
EOF
--- service 타입 변경
kubectl patch svc webpod -p '{"spec": {"type": "LoadBalancer"}}'
(⎈|HomeLab:test) root@k8s-ctr:/# k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
webpod LoadBalancer 10.96.206.199 172.16.1.1 80:31826/TCP 71m
--- service LoadBalancer를 광고하기 위한 객체
cat << EOF | kubectl apply -f -
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
name: bgp-advertisements-lb-exip-webpod
labels:
advertise: bgp
spec:
advertisements:
- advertisementType: "Service"
service:
addresses:
- LoadBalancerIP
selector:
matchExpressions:
- { key: app, operator: In, values: [ webpod ] }
EOF
--- service 타입 변경
kubectl patch svc webpod -p '{"spec": {"type": "LoadBalancer"}}'
객체를 배포하고 나면, svc에 대한 external-ip는 할당된다. external-ip는 이름은 external이지만 라우팅을 잡아줘야한다.
그래서 cilium BGP 객체에서 Loadbalancer를 광고하도록 객체를 배포하고 나면, router에 신기한 라우팅테이블이 설정된다.
이전에 없는 LoadBalancer IP 에 대해서 해당 service endpoint로 잡혀있는 3개의 노드 IP가 라우팅으로 잡혀있다.

실제로 BGP status를 보면 새로 배포한 객체에 대해서 알 수 있다.
- 어떤 router와 BGP 광고를 하는지 각 Policy별로 어떤 IP Range를 광고하는지도 알 수 있다.
(⎈|HomeLab:test) root@k8s-ctr:/# kubectl exec -it -n kube-system ds/cilium -- cilium-dbg bgp route-policies
VRouter Policy Name Type Match Peers Match Families Match Prefixes (Min..Max Len) RIB Action Path Actions
65001 allow-local import accept
65001 tor-switch-ipv4-PodCIDR export 192.168.10.200/32 172.20.1.0/24 (24..24) accept
65001 tor-switch-ipv4-Service-webpod-test-LoadBalancerIP export 192.168.10.200/32 172.16.1.1/32 (32..32) accept
BGP 광고 객체 추가(ClusterIP)
clusterip type을 추가하는것도 간단하다. BGPPeerConfig에서 설정한 advertisement label에 맞춰서 svc를 생성한다.
--- CiliumBGPPeerConfig
spec:
ebgpMultihop: 2
families:
- advertisements:
matchLabels:
advertise: bgp
---
apiVersion: v1
kind: Service
metadata:
name: webpodclusterip
labels:
app: webpod
spec:
selector:
app: webpod
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
그리고 sepc.advertisements.service.address를 ClusterIP로 변경해서 추가 배포하면 된다.
apiVersion: cilium.io/v2
kind: CiliumBGPAdvertisement
metadata:
name: bgp-adv-webpod-clusterip
labels:
advertise: bgp
spec:
advertisements:
- advertisementType: Service
service:
addresses:
- ClusterIP
selector:
matchExpressions:
- { key: app, operator: In, values: [ webpod ] }
router ip route를 보면, 이전에는 없었던 svc cluster ip에 대해서 라우팅 테이블이 잡혀있는것을 볼 수 있다.

하지만!! 중요한것은 cilium service list를 보면 clusrer ip에 대해서 endpoint가 잡혀있고, 실제로 router로 트래픽이 흐르지 않는다.
- 생각해보면.. 굳이 clsuter ip에 대해서 BGP 광고를 할 필요가 있을까? 싶다.
(⎈|HomeLab:test) root@k8s-ctr:~# k exec -it ds/cilium -n kube-system -- cilium-dbg service list
ID Frontend Service Type Backend
'''
21 10.96.62.173:80/TCP ClusterIP 1 => 172.20.0.120:80/TCP (active)
2 => 172.20.1.121:80/TCP (active)
3 => 172.20.2.121:80/TCP (active)
'''
--- tcpdump
17:07:32.119131 eth1 Out IP k8s-ctr.37916 > 172.20.1.121.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 2890472426 ecr 3819390684], length 0
17:07:32.119990 eth1 Out IP k8s-ctr.37916 > 172.20.1.121.http: Flags [P.], seq 1:76, ack 1, win 502, options [nop,nop,TS val 2890472427 ecr 3819390684], length 75: HTTP: GET / HTTP/1.1
17:07:32.121893 eth1 In IP 172.20.1.121.http > k8s-ctr.37916: Flags [.], ack 76, win 509, options [nop,nop,TS val 3819390687 ecr 2890472427], length 0
17:07:32.126710 eth1 In IP 172.20.1.121.http > k8s-ctr.37916: Flags [P.], seq 1:330, ack 76, win 509, options [nop,nop,TS val 3819390690 ecr 2890472427], length 329: HTTP: HTTP/1.1 200 OK
17:07:32.126858 eth1 Out IP k8s-ctr.37916 > 172.20.1.121.http: Flags [.], ack 330, win 501, options [nop,nop,TS val 2890472434 ecr 3819390690], length 0
17:07:32.127070 eth1 Out IP k8s-ctr.37916 > 172.20.1.121.http: Flags [F.], seq 76, ack 330, win 501, options [nop,nop,TS val 2890472434 ecr 3819390690], length 0
Service Traffic Policy 에 따른 라우팅 테이블 반영
Traffic Policy = Cluster
현재 service에서는 traffic policy가 모두 cluster로 되어 있다.
그렇기 때문에 service는 모든 노드에 대해서 service ip를 받게 되고, 이를 다시 match pod으로 전달을 하게 된다.
apiVersion: v1
kind: Service
metadata:
labels:
app: webpod
name: webpod
namespace: test
spec:
allocateLoadBalancerNodePorts: true
clusterIP: 10.96.206.199
clusterIPs:
- 10.96.206.199
externalTrafficPolicy: Cluster
internalTrafficPolicy: Cluster
'''
여기에서 맹점은 match pod이 없더라도, 해당 노드로 트래픽이 라우팅 될 수 있고 다시 다른 노드로 라우팅되어 홉이 많아진다.

router에는 match pod이 없는 k8s-ctr로 라우팅 테이블이 되어 있기 때문에, 해당 노드로 전달을 하지만
실제로 k8s-ctr에는 match pod이 없어서 다시 service lit에 있는 ip로 가기 위해서 다시 router를 거쳐서 간다.

cluster로 설정하면 전체 노드에 부하를 분산한다는 장점은 있지만, 이처럼 불필요한 네트워크 홉이 생길 수 있다.
그리고 네트워크 지연이 민감한 서비스라면 홉을 최소한으로 하고 싶고, 이런 흐름은 트러블슈팅에서도 어려움을 유발한다.
Traffic Policy = Local
그렇다면 servivce traffic local로 변경하면 어떻게 될까?
가장 큰 차이점은 router에서 match pod이 없는 노드에 대해서는 라우팅테이블이 없어진다.
그리고 match pod이 존재하는 노드로만 전달이 되고, 해당 노드에 배포된 pod으로 바로 전달이 된다.


실제로 tcpdump를 해서 보면 eth1으로 들어온 packet이 match pod의 lxc 인터페이스로 바로 전달이 된다.
만약 다른 노드의 pod에서 처리를 하기 된다면, 다시 eth1 out으로 나가야하는데 그렇지 않다.

이번 주차에서는 BGP podCIDR, SVC IP에 대해서 광고를 하고 이를 통해서 네트워크 통신이 잘 되는지 알아보았다.
이전에는 신경조차 쓰지 않았던 라우팅 테이블.. 그리고 cilium tunnel 모드까지 cilium 스터디를 통해서 많은걸 배우고 있다.
특이 이번 주차에서는 natvie 모드를 운영하기 위해서는 BGP 광고가 필요하다고만 이야기 들었지, 실제로 실습한건 처음이였다.
BGP 광고를 어떻게 하지? 네트워크 장비와 어떻게 동기화를 해야 하는지 몰랐는제 새롭게 알게되서 좋았다.
- 사실 BGP도 아주아주 예전에 테스트정도만 해보고, 실제로 구현하고 동작과정을 확인한건.. 손에 꼽는다.

'기술 토론장 > [K8s] Kubernetes' 카테고리의 다른 글
| [Cilium][6주차] Service Mesh Ingress Support (1) | 2025.08.23 |
|---|---|
| [Cilium][5주차] Cilium Clsuter Mesh (5) | 2025.08.17 |
| [Cilium][4주차] Node & Pod 네트워크 통신, Service External-IP 활용 (1) | 2025.08.10 |
| [Cilium][4주차] Node & Pod 네트워크 통신, Native vs VXLAN (6) | 2025.08.09 |
| [Cilium][3주차] Node & Pod 네트워크 통신 상세 - Routing, DNS (0) | 2025.08.03 |