iamjjanga blog

Cilium Study Networking Bgp Control Plane

Introduction

Kubernetes 클러스터에서 외부 네트워크로 통신을 위한 방법으로 이전 포스팅에서는 1. Native Routing 모드에서 직접 Static Routing을 추가하는 방법과 2. 캡슐화를 통한 OverLay Network를 이용하는 방법에 대해서 알아보았다.

이번에는 대규모 네트워크 환경에서 BGP 기반 네트워크가 구성된 경우 Cilium의 BGP Control Plane을 통해 Routing을 제어하는 부분에 대해서 알아보려고한다.

실습환경구성

alt text

FRRouting (frr)

FRRouting

alt text

FRRouting은 Quagga 프로젝트에서 포크된 이후 커뮤니티 주도로 발전해 온 고성능 라우팅 데몬 모음이다.

활용사례

BGP

alt text
https://www.noction.com/wp-content/uploads/2012/03/560xNxbgp.jpg.pagespeed.ic.CbKBD-wMzH.jpg

**BGP(Border Gateway Protocol)**는 인터넷에서 사용되는 핵심 라우팅 프로토콜로, 자율 시스템(AS, Autonomous System) 간에 라우팅 정보를 교환하는 데 사용된다. 또한 BGP는 AS 간뿐만 아니라 대규모 기업 네트워크에서도 외부와 내부 라우팅을 위해 활용

특징

구성요소

Cilium BGP Control Plane

BGP Control Plane Resources — Cilium 1.18.0 documentation

alt text
https://docs.cilium.io/en/stable/network/bgp-control-plane/bgp-control-plane-v2/

Cilium BGP Control Plane은 쿠버네티스 클러스터 내 Pod 네트워크 및 서비스(IP)를 외부 네트워크(예: 데이터센터 라우터, 온프레미스 라우터 등)와 직접 연결·라우팅하기 위해 BGP(Border Gateway Protocol)를 활용하는 네트워크 제어 기능이다.

CNI 계층에서 네이티브 라우팅/로드밸런싱과 인프라 통합을 목적으로 고안되었다.

역할

구성

Custom Resources로 선언적 정책 사용

Cilium에서 Custom Resources를 통해서 BGP 설정을 선언적으로 관리하는게 가능해 GitOps/자동화와 결합 용이하다.

GoBGP 통합

내부적으로 GoBGP 엔진을 활용해 BGP 피어 세션 관리, Prefix 광고, 정책 적용 등 프로그램 유연성을 제공한다.

ECMP

Equal Cost Multi Path(ECMP) 설정 시 여러 노드에서 같은 서비스 VIP를 광고하여 상위 라우터에서 여러 노드로 트래픽을 분산, 고가용 및 로드밸런싱 구현이 가능하다.

ECMP?

ECMP (equal cost multipath) is a method to utilize multiple same-cost paths to route a packet to a destination

alt text

위의 예시에서 [ R1 ] → [ R6 ]로 가기위해서는 2가지 방법이 있는데,

ECMP를 적용하면 동일한 목적지까지 비용(예: hop 수, bandwidth, delay 등)이 같은 여러 경로가 있을 때, 그 경로들을 동시에 활용하여 트래픽을 분산된다.

eBGP Multi Hop 지원

복잡하고 다층적인 네트워크 토폴로지를 처리하는 기업에 더욱 정교하고 유용한 기능을 제공

alt text

BGP Graceful Restart: 고가용성 지원

BGP 세션이 중단이나 시스템 장애 발생 시 데이터 전달에 즉각적인 영향을 미치지 않고 원활하게 복구될 수 있도록하는 기능을 제공한다.

alt text

Cilium BGP Daemon과 Router-A간 BGP Session이 구성된 상황에서

alt text

alt text

실습 1 : BGP 설정 후 통신 확인

실습환경 네트워크 확인

현재 실습환경의 네트워크 정보를 확인한다.

→ 현재 ‘Native Routing’ 모드, autoDirecNodeRoute 는 비활성화 되어있고 따라서 각 노드들은 PodCIDR의 Routing 경로에 대해서 없는상태이다.

→ router도 역시 k8s Cluster의 PodCIDR에 대해서는 모르는 상태이다.

현재 통신상태에 문제가 있는부분을 확인한다.

BGP 설정 확인

[ router ] 에서 frr의 데몬이 실행되고 있다. zebra는 TCP(2601)으로 오픈되어있고, bgpd는 내부통신을 위해 2605 포트와 다른 AS간 BGP통신을 위한 TCP(179) 포트를 오픈한다.

1# zebra (2601), bgpd(2605, 179) 포트 오픈
2**ss -tnlp | grep -iE 'zebra|bgpd'**
3LISTEN 0      3          127.0.0.1:2605      0.0.0.0:*    users:(("bgpd",pid=4221,fd=18))                                                      
4LISTEN 0      3          127.0.0.1:2601      0.0.0.0:*    users:(("zebra",pid=4216,fd=23))                                                     
5LISTEN 0      4096         0.0.0.0:179       0.0.0.0:*    users:(("bgpd",pid=4221,fd=22))                                                      
6LISTEN 0      4096            [::]:179          [::]:*    users:(("bgpd",pid=4221,fd=23))         
1# frr 데몬 정보 (zebra,bgpd, watchfrr, staticd)
2**ps -ef |grep frr**
3root        4203       1  0 17:58 ?        00:00:01 /usr/lib/frr/watchfrr -d -F traditional zebra bgpd staticd
4frr         4216       1  0 17:58 ?        00:00:00 /usr/lib/frr/zebra -d -F traditional -A 127.0.0.1 -s 90000000
5frr         4221       1  0 17:58 ?        00:00:00 /usr/lib/frr/bgpd -d -F traditional -A 127.0.0.1
6frr         4228       1  0 17:58 ?        00:00:00 /usr/lib/frr/staticd -d -F traditional -A 127.0.0.1

그리고 현재 BGP에 대해서 등록된 이웃(neighbor)은 없는 상황이다.

 1# 현재 frr 설정 확인
 2**vtysh -c 'show running' # 혹은 cat /etc/frr/frr.conf**
 3Current configuration:
 4...
 5!
 6router bgp 65000
 7 bgp router-id 192.168.10.200
 8 no bgp ebgp-requires-policy
 9 bgp graceful-restart
10 bgp bestpath as-path multipath-relax
11 !
12 address-family ipv4 unicast
13  network 10.10.1.0/24
14  maximum-paths 4
15...
16!
17end
18
19# 현재 Neighbor이 등록되어 있지 않음
20**vtysh -c 'show ip bgp summary'**
21% No BGP neighbors found in VRF default
22
23# BGP 정보 확인
24**vtysh -c 'show ip bgp'**
25BGP table version is 1, local router ID is 192.168.10.200, vrf id 0
26Default local pref 100, local AS 65000
27Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
28               i internal, r RIB-failure, S Stale, R Removed
29Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
30Origin codes:  i - IGP, e - EGP, ? - incomplete
31RPKI validation codes: V valid, I invalid, N Not found
32
33   Network          Next Hop            Metric LocPrf Weight Path
34*> 10.10.1.0/24     0.0.0.0                  0         32768 i
35
36# 라우팅 테이블과 BGP 라우팅
37**ip -c route
38...**
39192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.200 
40192.168.20.0/24 dev eth2 proto kernel scope link src 192.168.20.200 
41**vtysh -c 'show ip route'**
42K>* 0.0.0.0/0 [0/100] via 10.0.2.2, eth0, src 10.0.2.15, 01:16:53
43...
44C>* 192.168.10.0/24 is directly connected, eth1, 01:16:53
45C>* 192.168.20.0/24 is directly connected, eth2, 01:16:53

BGP 설정 후 통신확인 (통신 문제 있음)

FRR 설정에 ‘k8s cluster’의 노드들을 CILIUM 그룹명으로 추가한다. 연동 방법은 2가지로 설정이 가능하다.

▶ 방법 1 : /etc/frr/frr.conf 에 추가
 1**cat << EOF >> /etc/frr/frr.conf**
 2    neighbor CILIUM peer-group
 3    neighbor CILIUM remote-as external
 4    neighbor 192.168.10.100 peer-group CILIUM
 5    neighbor 192.168.10.101 peer-group CILIUM
 6    neighbor 192.168.20.100 peer-group CILIUM 
 7EOF
 8
 9# frr 데몬 재시작
10**systemctl daemon-reexec && systemctl restart frr**
11systemctl status frr --no-pager --full
12● frr.service - FRRouting
13        Loaded: loaded (/usr/lib/systemd/system/frr.service; enabled; preset: enabled)
14        Active: active (running) since Fri 2025-08-15 19:40:57 KST; 10s ago
15        Docs: https://frrouting.readthedocs.io/en/latest/setup.html
16    Process: 4739 ExecStart=/usr/lib/frr/frrinit.sh start (code=exited, status=0/SUCCESS)
17    Main PID: 4749 (watchfrr)
18        Status: "FRR Operational"
19        Tasks: 13 (limit: 553)
20        Memory: 19.2M (peak: 26.7M)
21        CPU: 176ms
22        CGroup: /system.slice/frr.service
23                ├─4749 /usr/lib/frr/watchfrr -d -F traditional zebra bgpd staticd
24                ├─4762 /usr/lib/frr/zebra -d -F traditional -A 127.0.0.1 -s 90000000
25                ├─4767 /usr/lib/frr/bgpd -d -F traditional -A 127.0.0.1
26                └─4774 /usr/lib/frr/staticd -d -F traditional -A 127.0.0.1
27
28Aug 15 19:40:57 router watchfrr.sh[4760]: Cannot stop zebra: pid file not found
29Aug 15 19:40:57 router zebra[4762]: [VTVCM-Y2NW3] Configuration Read in Took: 00:00:00
30Aug 15 19:40:57 router frrinit.sh[4739]:  * Started watchfrr
31Aug 15 19:40:57 router bgpd[4767]: [VTVCM-Y2NW3] Configuration Read in Took: 00:00:00
32Aug 15 19:40:57 router staticd[4774]: [VTVCM-Y2NW3] Configuration Read in Took: 00:00:00
33Aug 15 19:40:57 router watchfrr[4749]: [QDG3Y-BY5TN] zebra state -> up : connect succeeded
34Aug 15 19:40:57 router watchfrr[4749]: [QDG3Y-BY5TN] bgpd state -> up : connect succeeded
35Aug 15 19:40:57 router watchfrr[4749]: [QDG3Y-BY5TN] staticd state -> up : connect succeeded
36Aug 15 19:40:57 router watchfrr[4749]: [KWE5Q-QNGFC] all daemons up, doing startup-complete notify
37Aug 15 19:40:57 router systemd[1]: Started frr.service - FRRouting.
▶ 방법 2 : vtysh 이용
 1**vtysh**
 2---------------------------
 3?
 4show ?
 5show running
 6show ip route
 7
 8# config 모드 진입
 9**conf**
10?
11
12## bgp 65000 설정 진입
13**router bgp 65000**
14?
15neighbor CILIUM peer-group
16neighbor CILIUM remote-as external
17neighbor 192.168.10.100 peer-group CILIUM
18neighbor 192.168.10.101 peer-group CILIUM
19neighbor 192.168.20.100 peer-group CILIUM 
20end
21
22# 설정파일에 쓰기
23**write memory
24
25exit**

BGP 설정 변경사항에 대해서 한번 더 확인한다.

 1# 현재 frr 설정 확인
 2vtysh -c 'show running' # 혹은 cat /etc/frr/frr.conf
 3...
 4router bgp 65000
 5 bgp router-id 192.168.10.200
 6 no bgp ebgp-requires-policy
 7 bgp graceful-restart
 8 bgp bestpath as-path multipath-relax
 9 neighbor CILIUM peer-group # <-
10 neighbor CILIUM remote-as external # <-
11 neighbor 192.168.10.100 peer-group CILIUM # <-
12 neighbor 192.168.10.101 peer-group CILIUM # <-
13 neighbor 192.168.20.100 peer-group CILIUM # <-
14 !
15 address-family ipv4 unicast
16  network 10.10.1.0/24
17  maximum-paths 4
18 exit-address-family
19exit
20!
21
22# neighbor이 추가됨
23vtysh -c 'show ip bgp summary'
24...
25Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt Desc
26192.168.10.100  4          0         0         0        0    0    0    never       Active        0 N/A
27192.168.10.101  4          0         0         0        0    0    0    never       Active        0 N/A
28192.168.20.100  4          0         0         0        0    0    0    never       Active        0 N/A
29
30# neighbor은 추가되었지만, 라우팅은 추가되지 않음
31vtysh -c 'show ip route'
32K>* 0.0.0.0/0 [0/100] via 10.0.2.2, eth0, src 10.0.2.15, 00:06:47
33...
34C>* 192.168.10.0/24 is directly connected, eth1, 00:06:47
35C>* 192.168.20.0/24 is directly connected, eth2, 00:06:47

Cilium에 BGP를 설정한다. 먼저 CiliumBGPClusterConfig 에서 Selector로 지정이 되어야하기때문에 Node에 레이블(Label)을 추가한다. 그리고 Cilium BGP에 필요한 리소스를 배포한다.

1# BGP 동작할 노드를 위한 label 설정
2kubectl label nodes k8s-ctr k8s-w0 k8s-w1 enable-bgp=true
3# 확인
4kubectl get node -l enable-bgp=true
5NAME      STATUS   ROLES           AGE    VERSION
6k8s-ctr   Ready    control-plane   120m   v1.33.2
7k8s-w0    Ready    <none>          113m   v1.33.2
8k8s-w1    Ready    <none>          117m   v1.33.2
  graph TD
    A[CiliumBGPClusterConfig<br>cilium-bgp] --> B(CiliumBGPPeerConfig<br>cilium-peer)
    B --> C(CiliumBGPAdvertisement<br>bgp-advertisements)

각 리소스는 아래와 같은 의존성 관계를 가진다.

  1. CiliumBGPClusterConfig의 peer에 대한 정보는 CiliumBGPPeerConfig 에 명세되어 있고
  2. CiliumBGPPeerConfig 의 advertise에 대한 정보든 CiliumBGPAdvertisement 에 명세되어 있어서 상호 참조(Ref)한다.
▶ CiliumBGPClusterConfig , CiliumBGPPeerConfig , CiliumBGPAdvertisement 리소스 배포
 1# Config Cilium BGP
 2cat << EOF | kubectl apply -f -
 3apiVersion: cilium.io/v2
 4kind: CiliumBGPAdvertisement
 5metadata:
 6    name: bgp-advertisements
 7    labels:
 8    advertise: bgp
 9spec:
10    advertisements:
11    - advertisementType: "PodCIDR"
12---
13apiVersion: cilium.io/v2
14kind: CiliumBGPPeerConfig
15metadata:
16    name: cilium-peer
17spec:
18    timers:
19    holdTimeSeconds: 9
20    keepAliveTimeSeconds: 3
21    ebgpMultihop: 2
22    gracefulRestart:
23    enabled: true
24    restartTimeSeconds: 15
25    families:
26    - afi: ipv4
27        safi: unicast
28        advertisements:
29        matchLabels:
30            advertise: "bgp"
31---
32apiVersion: cilium.io/v2
33kind: CiliumBGPClusterConfig
34metadata:
35    name: cilium-bgp
36spec:
37    nodeSelector:
38    matchLabels:
39        enable-bgp: true
40    bgpInstances:
41    - name: "instance-65001"
42    localASN: 65001
43    peers:
44    - name: "tor-switch"
45        peerASN: 65000
46        peerAddress: 192.168.10.200  # router ip address
47        peerConfigRef:
48        name: "cilium-peer"
49EOF

Cilium BGP 리소스 배포 후 확인

Cilium BGP 설정 후 다시 통신요청을 해보아도 정상적으로 동작하지 않는다.

alt text

이유는?

통신 문제 해결 후 확인

alt text

결론적으로 Cilium BGP Control Plane으로 BGP 이용 시, 2개 이상의 NIC를 사용하는 경우 Node에 직접 라우팅 설정 및 관리가 필요하다.

 1# k8s 파드 사용 대역 통신 전체는 eth1을 통해서 라우팅 설정
 2ip route add 172.20.0.0/16 via 192.168.10.200
 3# Worker Node (w1, w0)에도 설정
 4sshpass -p 'vagrant' ssh vagrant@k8s-w1 sudo ip route add 172.20.0.0/16 via 192.168.10.200
 5sshpass -p 'vagrant' ssh vagrant@k8s-w0 sudo ip route add 172.20.0.0/16 via 192.168.20.200
 6
 7# 통신 확인
 8kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
 9Hostname: webpod-697b545f57-gqmg8
10---
11Hostname: webpod-697b545f57-nj2pc
12---
13Hostname: webpod-697b545f57-hb9th
14---
15...

실습 2 : 노드 유지보수

Document

Cilium BGP Control Plane 모드 사용중 Node에 대한 유지보수가 필요한 상황을 시나리오로 가정한다. 아래의 시나리오 단계별로 진행시 패킷 손실을 최대한으로 방지할 수 있다.

  1. Node의 Workload를 제거하기위해 Drain 진행한다.

    1**kubectl drain k8s-w0 --ignore-daemonsets**
    
  2. Node에 CiliumBGPPeerConfig 혹은 CiliumBGPClusterConfig 의 NodeSelector로 선택하지 못하게 레이블을 수정한다. 수정 후 Node의 BGP 세션은 종료된다.

    1**kubectl label nodes k8s-w0 enable-bgp=false --overwrite**
    
  3. BGP 피어가 제거될때까지 기다린다.

     1# Drain 확인
     2**kubectl get node**
     3NAME      STATUS                     ROLES           AGE   VERSION
     4k8s-ctr   Ready                      control-plane   21h   v1.33.2
     5k8s-w0    Ready,SchedulingDisabled   <none>          21h   v1.33.2
     6k8s-w1    Ready                      <none>          21h   v1.33.2
     7
     8# Cilium BGP Node에서 제거 확인
     9**kubectl get ciliumbgpnodeconfigs**
    10NAME      AGE
    11k8s-ctr   118m
    12k8s-w1    118m
    13
    14# Cilium BGP Route에서 제거 확인
    15**cilium bgp routes**
    16Node      VRouter   Prefix          NextHop   Age       Attrs
    17k8s-ctr   65001     172.20.0.0/24   0.0.0.0   1h59m9s   [{Origin: i} {Nexthop: 0.0.0.0}]   
    18k8s-w1    65001     172.20.1.0/24   0.0.0.0   1h59m9s   [{Origin: i} {Nexthop: 0.0.0.0}]   
    19
    20# Cilium BGP Peer에서 제거 확인
    21**cilium bgp peers**
    22Node      Local AS   Peer AS   Peer Address     Session State   Uptime    Family         Received   Advertised
    23k8s-ctr   65001      65000     192.168.10.200   established     1h35m6s   ipv4/unicast   3          2    
    24k8s-w1    65001      65000     192.168.10.200   established     1h35m7s   ipv4/unicast   3          2  
    25
    26# [ router ] k8s-w0으로의 라우팅 제거 확인
    27**ip -c route | grep bgp**
    28172.20.0.0/24 nhid 96 via 192.168.10.100 dev eth1 proto bgp metric 20 
    29172.20.1.0/24 nhid 94 via 192.168.10.101 dev eth1 proto bgp metric 20 
    

유지보수가 끝나고 다시 [k8s-w0] 노드를 피어에 등록하기 위해서는 진행했던 부분을 반대로 진행하면된다.

 1# 원복 설정
 2**kubectl label nodes k8s-w0 enable-bgp=true --overwrite**
 3**kubectl uncordon k8s-w0
 4
 5# 확인**
 6kubectl get node
 7kubectl get ciliumbgpnodeconfigs
 8cilium bgp routes
 9**cilium bgp peers**
10
11# [ router ] k8s-w0으로의 라우팅 확인
12**ip -c route | grep bgp**
13
14# 노드별 파드 분배 실행
15kubectl get pod -owide
16kubectl scale deployment webpod --replicas 0
17kubectl scale deployment webpod --replicas 3

Disabling CRD Status Report

CRD Status Reporting은 트러블슈팅(TroubleShooting)에 매우 유용하다. (기본값 enable ) 하지만, 노드가 많이 운용되는 대규모 클러스터의 경우나 BGP Policy가 많은 경우 CRD Status Reporting은 API Server에 많은 부하를 발생시킨다.

 1# 현재 status Report로 출력되고 있음.
 2**kubectl get ciliumbgpnodeconfigs k8s-ctr -o yaml | grep -A10 status:**
 3status:
 4  bgpInstances:
 5  - localASN: 65001
 6    name: instance-65001
 7    peers:
 8    - establishedTime: "2025-08-16T06:55:05Z"
 9      name: tor-switch
10      peerASN: 65000
11      peerAddress: 192.168.10.200
12      peeringState: established
13      routeCount:
14      
15# Helm Chart Upgrade
16helm upgrade cilium cilium/cilium --version 1.18.0 --namespace kube-system --reuse-values \
17  **--set bgpControlPlane.statusReport.enabled=false
18  
19kubectl -n kube-system rollout restart ds/cilium**
20
21# 확인 -> Status 정보가 없어짐
22**kubectl get ciliumbgpnodeconfigs k8s-ctr -o yaml | grep -A10 status:**
23status: {}

실습 3 : Service Virtual IPs (LoadBalancer, External IP) 를 BGP로 광고

alt text

BGP Control Plane은 BGP 피어의 Virtual IP 주소를 BGP로 광고할 수 있다. 이렇게 설정을 한다면 K8S 클러스터 외부에서 Service로 직접접근이 가능하다.

광고가능한 종류 (advertisementType)으로는 아래의 종류가 있다.

✅ Cilium BGP Control Plane VIPs에 대해서 개별 주소에 대해서 라우팅만 광고된다. (Subnetting)

LoadBalancerIP

LoadBalancer IP를 BGP로 광고하기 이전에 LB IPAM를 설정한다.

{{ details “Cilium LoadBalancer IPAM 설정” }}

 1cat << EOF | kubectl apply -f -
 2apiVersion: "cilium.io/v2"
 3kind: CiliumLoadBalancerIPPool
 4metadata:
 5    name: "cilium-pool"
 6spec:
 7    allowFirstLastIPs: "No"
 8    blocks:
 9    - cidr: "172.16.1.0/24" # 노드의 네트워크 대역이 아니여도 괜찮다.
10EOF
1# 확인
2kubectl get ippool
3NAME          DISABLED   CONFLICTING   IPS AVAILABLE   AGE
4cilium-pool   false      False         254             5s

webpod의 Service를 LoadBalancer 타입으로 변경한다.

 1kubectl patch svc webpod -p '{"spec": {"type": "LoadBalancer"}}'
 2# External IP 할당됨
 3kubectl get svc webpod 
 4NAME     TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
 5webpod   LoadBalancer   10.96.255.83   172.16.1.1    80:30681/TCP   21h
 6
 7# ippool을 조회해보면 `IPS AVAILABLE` 값이 1 줄어든것을 확인
 8kubectl get ippool
 9NAME          DISABLED   CONFLICTING   IPS AVAILABLE   AGE
10cilium-pool   false      False         253             93s
11
12# Ciliu의 서비스 목록 조회
13kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg service list
14ID   Frontend               Service Type   Backend                                 
15...        
1617   172.16.1.1:80/TCP      LoadBalancer   1 => 172.20.0.170:80/TCP (active)       
17                                            2 => 172.20.1.2:80/TCP (active)         
18                                            3 => 172.20.1.68:80/TCP (active)      

통신 확인

 1# [k8s-ctr] -> 정상
 2kubectl get svc webpod -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
 3LBIP=$(kubectl get svc webpod -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
 4**curl -s $LBIP**
 5Hostname: webpod-697b545f57-tc9wb
 6IP: 127.0.0.1
 7IP: ::1
 8IP: 172.20.1.68
 9IP: fe80::7cbc:baff:fe8b:5b50
10RemoteAddr: 192.168.10.100:40958
11GET / HTTP/1.1
12Host: 172.16.1.1
13User-Agent: curl/8.5.0
14Accept: */*
15
16# [router] -> 외부요청은 동작 X
17LBIP=172.16.1.1
18curl -s $LBIP

{{ /details}}

IPAM 설정 후 LB의 External IP를 BGP를 통해 광고한다.

 1cat << EOF | kubectl apply -f -
 2apiVersion: cilium.io/v2
 3kind: **CiliumBGPAdvertisement**
 4metadata:
 5  name: **bgp-advertisements-lb-exip-webpod**
 6  labels:
 7    advertise: bgp
 8spec:
 9  advertisements:
10    - advertisementType: "**Service**"
11      service:
12        addresses:
13          - **LoadBalancerIP
14      selector:             
15        matchExpressions:
16          - { key: app, operator: In, values: [ webpod ] }**
17EOF

확인

LB의 External IP를 BGP를 통해서 광고 후 외부에서 호출되는지 한번 더 확인

 1# [router] -> 정상적으로 동작
 2LBIP=172.16.1.1
 3curl -s $LBIP
 4Hostname: webpod-697b545f57-ggqwg
 5IP: 127.0.0.1
 6IP: ::1
 7IP: 172.20.1.114
 8IP: fe80::80c0:5aff:febc:74c
 9RemoteAddr: 192.168.10.100:52812
10GET / HTTP/1.1
11Host: 172.16.1.1
12User-Agent: curl/8.5.0
13Accept: */*
14
15# 반복호출로 로드밸런서 동작 확인 -> 분산되는것처럼 보임
16for i in {1..100};  do curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr
17     34 Hostname: webpod-697b545f57-tsvkg
18     34 Hostname: webpod-697b545f57-ggqwg
19     32 Hostname: webpod-697b545f57-5dgz7

광고된 LB의 External IP에 대한 요청의 부하분산은 정상적인가?

위의 시나리오에서는 각 Node마다 webpod가 배포된 상태(repilicas=3)에서 부하 분산되는 것처럼 보였다.

External Traffic Policy와 ECMP 설정

curl-pod에서 webpod의 (BGP Announced) LB의 External IP로 요청 보내 요청을 받은 RemoteAddr 값을 확인해본다. 현재 webpod는 [ k8s-w1 ]과 [ k8s-w0 ]에 각각 Pod로 배포되어있는데 요청을 응답받은 RemoteAddr 의 값이 이상하다.

1# RemoteAddr이 192.168.10.100으로 출력
2while true; do curl -s $LBIP | egrep 'Hostname|RemoteAddr' ; sleep 0.1; done
3Hostname: webpod-697b545f57-p59p6
4RemoteAddr: 192.168.10.100:54810
5...
6Hostname: webpod-697b545f57-5mfm6
7RemoteAddr: 192.168.10.100:54838
1# [k8s-w1], [k8s-w0]에 tcpdump를 통해 확인 -> Source IP가 192.168.10.100
2tcpdump -i eth1 -A -s 0 -nn 'tcp port 80'
3...
421:43:29.928248 IP 192.168.10.100.35696 > 172.20.2.171.80: Flags [F.], seq 74, ack 328, win 1002, options [nop,nop,TS val 1735956835 ecr 1103763912], length 0
5E..4..@.>.....
6d.....p.P.....].A....j4.....
7gx.cA...

External Traffic Policy = Local

기본적으로 Service는 externalTrafficPolicy 값이 Cluster 로 설정되어 있는데 이는 Cilium BGP Control Plane에서 해당 노드에 Endpoint가 없는 경우에도 Routing이 살아있어 추가 Network Hop을 가지게 된다.

externalTrafficPolicy: Local 로 설정한 경우 노드 Local에서 해당 서비스의 엔드포인트를 추적하고 없으면 광고를 중단하게 된다.

1kubectl patch service webpod -p '{"spec":{"externalTrafficPolicy":"Local"}}'
2
3kubectl patch service webpod -p '{"spec":{"externalTrafficPolicy":"Cluster"}}'

externalTrafficPolicy: Local 설정을 하면 다음 2가지 변경사항이 발생한다.

  1. 리눅스 커널 라우팅에서 캐싱되면서 한쪽 노드로 몰리는 현상이 발생한다.

    • 기존 externalTrafficPolicy: Cluster 에서는 k8s-ctr 으로 캐싱되면서 다른 노드로 라우팅되었지만, 엔드포인트가 제거되면서 첫번째로 라우팅 되는 노드 ([ k8s-w1 ] or [ k8s-w0 ])에 리눅스 커널 라우팅 캐시가 설정된다.

       1# [ router ]
       2LBIP=172.16.1.1
       3curl -s $LBIP
       4for i in {1..100};  do curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr
       5# 한쪽 Pod로 요청이 몰림
       6    100 Hostname: webpod-697b545f57-5mfm6
       7
       8# 리눅스 커널 라우팅 캐싱 상태
       9ip route get 172.16.1.1
      10172.16.1.1 via 192.168.10.101 dev eth1 src 192.168.10.200 uid 0 
      11    cache # <-
      
  2. 기존 엔드포인트가 없던 노드에서 다시 라우팅되는 경우 SNAT이 발생하면서 Source IP가 변경되었지만, 현재 엔드포인트가 활성화된 노드로 직접 통신을 하기때문에 최초의 요청이 바로 해당 Pod가 실행중인 노드로 요청된다.

ECMP Hash Policy

externalTrafficPolicy: Local 를 설정했을때의 문제점 ‘리눅스 커널 라우팅 캐싱으로 한쪽 노드에 몰리는 문제’를 해결할 수 있는 방법은?

이전에도 언급했던 **ECMP(Equal Cost MultiPath)**를 통해서 동일 비용의 경로에 Hashing으로 요청을 분산시킬 수 있다.

alt text

리눅스 커널은 기본적으로 L3(목적지 IP 기반) 해시를 사용한다. 보다 정교한 부하분산을 원하면 L4 해시 (IP + 포트) 기반으로 설정한다.

ExternalTrafficPolicy(Local) 설정 시, Router 의 ECMP 에서 Hash Policy 경로 결정과 요청 트래픽 환경(소스 IP, 포트 등)으로 특정 동일 노드로만 라우팅 될 수 있음 → 대체로 Hash Policy 를 L4 수준 설정 권장.

1# [ router ]
2sudo sysctl -w net.ipv4.fib_multipath_hash_policy=1
3# echo "net.ipv4.fib_multipath_hash_policy=1" >> /etc/sysctl.conf

요청이후 ‘한쪽 노드로 요청이 몰리는 현상없이’ 정상적으로 부하분산되는걸 확인할 수 있다.

1for i in {1..100};  do curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr
2     54 Hostname: webpod-697b545f57-5mfm6
3     46 Hostname: webpod-697b545f57-p59p6

다시 replicas=3 으로 원복하고 요청을 하면

1# [ k8s-ctr ]
2kubectl scale deployment webpod --replicas 3
3kubectl get pod -owide
4
5# 유사한 비중으로 부하분산
6for i in {1..100};  do curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr
7     35 Hostname: webpod-697b545f57-p59p6
8     34 Hostname: webpod-697b545f57-5mfm6
9     31 Hostname: webpod-697b545f57-vdc4j

Ref