25๋ ๋ Cilium Study 1๊ธฐ ์ ๋ฆฌ ๊ธ์ ๋๋ค.
์ค์ต ์ธํ
์ํ ์ฑ ๋ฐฐํฌ
(โ|HomeLab:N/A) root@k8s-ctr:~# 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:
EOFype: ClusterIP0rPort: 80miernetes.io/hostname"
deployment.apps/webpod created
service/webpod created
(โ|HomeLab:N/A) root@k8s-ctr:~# 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
pod/curl-pod created
(โ|HomeLab:N/A) root@k8s-ctr:~# 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 3/3 3 3 19s webpod traefik/whoami app=webpod
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/webpod ClusterIP 10.96.180.250 <none> 80/TCP 19s app=webpod
NAME ENDPOINTS AGE
endpoints/webpod 172.20.0.205:80,172.20.1.135:80,172.20.1.157:80 19s
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl get endpointslices -l app=webpod
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
webpod-8kmfd IPv4 80 172.20.0.205,172.20.1.135,172.20.1.157 26s
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl get ciliumendpoints
NAME SECURITY IDENTITY ENDPOINT STATE IPV4 IPV6
curl-pod 15614 ready 172.20.0.187
webpod-697b545f57-42lfb 31003 ready 172.20.1.135
webpod-697b545f57-8r5sl 31003 ready 172.20.1.157
webpod-697b545f57-fnz6w 31003 ready 172.20.0.205
๋ผ์ฐํฐ ์ค์ ํ์ธ
root@router:~# ss -tnlp | grep -iE 'zebra|bgpd'
LISTEN 0 3 127.0.0.1:2601 0.0.0.0:* users:(("zebra",pid=4467,fd=25))
LISTEN 0 3 127.0.0.1:2605 0.0.0.0:* users:(("bgpd",pid=4472,fd=18))
LISTEN 0 4096 0.0.0.0:179 0.0.0.0:* users:(("bgpd",pid=4472,fd=22))
LISTEN 0 4096 [::]:179 [::]:* users:(("bgpd",pid=4472,fd=23))
root@router:~# ps -ef |grep frr
root 4454 1 0 21:50 ? 00:00:01 /usr/lib/frr/watchfrr -d -F traditional zebra bgpd staticd
frr 4467 1 0 21:50 ? 00:00:00 /usr/lib/frr/zebra -d -F traditional -A 127.0.0.1 -s 90000000
frr 4472 1 0 21:50 ? 00:00:00 /usr/lib/frr/bgpd -d -F traditional -A 127.0.0.1
frr 4479 1 0 21:50 ? 00:00:00 /usr/lib/frr/staticd -d -F traditional -A 127.0.0.1
root 4795 4776 0 22:33 pts/1 00:00:00 grep --color=auto frr
ํ์ฌ ์คํ ์ค์ธ ํ๋ก์ธ์ค
- zebra: ์ปค๋ ๋ผ์ฐํ ํ ์ด๋ธ๊ณผ FRR ๋ผ์ฐํ ์ ๋ณด๋ฅผ ๋๊ธฐํ
- bgpd: BGP ํผ์ด์ ์ธ์ ์ ๋งบ๊ณ ๊ฒฝ๋ก๋ฅผ ๊ตํ
- staticd: ์ค์ ๋ ์ ์ ๋ผ์ฐํธ ๊ด๋ฆฌ
- watchfrr: ์ ๋ฐ๋ชฌ๋ค ์ฃฝ์ผ๋ฉด ์๋ ์ฌ์์
root@router:~# vtysh -c 'show running'
Building configuration...
Current configuration:
!
frr version 8.4.4
frr defaults traditional
hostname router
log syslog informational
no ipv6 forwarding
service integrated-vtysh-config
!
router bgp 65000
bgp router-id 192.168.10.200 # BGP์์ ์๋ณ์๋ก ์ฐ๋ ๊ณ ์ IP
no bgp ebgp-requires-policy # eBGP์์ route-map ์ ์ฑ
์์ด๋ ๊ฒฝ๋ก ๊ด๊ณ ํ์ฉ
bgp graceful-restart # ์ธ์
์ด ์ ์ ๋๊ฒจ๋ ๊ธฐ์กด ๋ผ์ฐํ
์ ๋ณด ์ ์ง
bgp bestpath as-path multipath-relax # AS ๊ฒฝ๋ก ๊ธธ์ด๊ฐ ๋ฌ๋ผ๋ multipath ๋ก๋๋ฐธ๋ฐ์ฑ ํ์ฉ
!
address-family ipv4 unicast
network 10.10.1.0/24
maximum-paths 4
exit-address-family
exit
!
end
root@router:~# cat /etc/frr/frr.conf
# default to using syslog. /etc/rsyslog.d/45-frr.conf places the log in
# /var/log/frr/frr.log
#
# Note:
# FRR's configuration shell, vtysh, dynamically edits the live, in-memory
# configuration while FRR is running. When instructed, vtysh will persist the
# live configuration to this file, overwriting its contents. If you want to
# avoid this, you can edit this file manually before starting FRR, or instruct
# vtysh to write configuration to a different file.
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
root@router:~# ip -c route
default via 192.168.163.2 dev eth0 proto dhcp src 192.168.163.168 metric 100
10.10.1.0/24 dev loop1 proto kernel scope link src 10.10.1.200
10.10.2.0/24 dev loop2 proto kernel scope link src 10.10.2.200
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.200
192.168.20.0/24 dev eth2 proto kernel scope link src 192.168.20.200
192.168.163.0/24 dev eth0 proto kernel scope link src 192.168.163.168 metric 100
192.168.163.2 dev eth0 proto dhcp scope link src 192.168.163.168 metric 100
root@router:~# vtysh -c 'show ip route'
Codes: K - kernel route, C - connected, S - static, R - RIP, # ์ปค๋์์ ๊ฐ์ ธ์จ ๊ธฐ๋ณธ ๊ฒฝ๋ก / ์ง์ ์ฐ๊ฒฐ๋จ / ์ ์ / ๋ฆฌ์ผ Ip
O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR,
f - OpenFabric,
> - selected route, * - FIB route, q - queued, r - rejected, b - backup
t - trapped, o - offload failure
K>* 0.0.0.0/0 [0/100] via 192.168.163.2, eth0, src 192.168.163.168, 01:05:55
C>* 10.10.1.0/24 is directly connected, loop1, 01:05:55
C>* 10.10.2.0/24 is directly connected, loop2, 01:05:55
C>* 192.168.10.0/24 is directly connected, eth1, 01:05:55
C>* 192.168.20.0/24 is directly connected, eth2, 01:05:55
C>* 192.168.163.0/24 [0/100] is directly connected, eth0, 01:05:55
K>* 192.168.163.2/32 [0/100] is directly connected, eth0, 01:05:55
Cilium BGP ํผ์ด ๋ง๋ค๊ธฐ
root@router:~# 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
root@router:~# cat /etc/frr/frr.conf
# default to using syslog. /etc/rsyslog.d/45-frr.conf places the log in
# /var/log/frr/frr.log
#
# Note:
# FRR's configuration shell, vtysh, dynamically edits the live, in-memory
# configuration while FRR is running. When instructed, vtysh will persist the
# live configuration to this file, overwriting its contents. If you want to
# avoid this, you can edit this file manually before starting FRR, or instruct
# vtysh to write configuration to a different file.
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
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
systemctl daemon-reexec && systemctl restart frr
systemctl status frr --no-pager --full
์ปจํธ๋กค ํ๋ ์ธ BGP ๋์ํ ๋ ธ๋ ์ค์
# ๋ผ๋ฒจ์ด ๋ฌ๋ฆฐ ๋
ธ๋์์๋ง BGP ๊ธฐ๋ฅ์ ํด
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl label nodes k8s-ctr k8s-w0 k8s-w1 enable-bgp=true
node/k8s-ctr labeled
node/k8s-w0 labeled
node/k8s-w1 labeled
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl get node -l enable-bgp=true
NAME STATUS ROLES AGE VERSION
k8s-ctr Ready control-plane 77m v1.33.2
k8s-w0 NotReady <none> 70m v1.33.2
k8s-w1 Ready <none> 74m v1.33.2
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
์ปจํธ๋กค ํ๋ ์ธ์์ ๋ผ๋ฒจ ๋ถ์ ๋ ธ๋๋ค์์ BGP ์คํผ์ปค๋ฅผ ์ผ๊ณ ,
๋ก์ปฌ AS๋ 65001๋ก, ๋ผ์ฐํฐ(AS65000, 192.168.10.200)์ ํผ์ด๋งํ๊ณ , ๊ฐ ๋ ธ๋์ PodCIDR๋ฅผ ๊ด๊ณ ํ๋ CRD๋ฅผ ์ ์ฉํ๋ค.
๋ผ์ฐํฐ์์
journalctl -u frr -f

์ปจํธ๋กค ํ๋ ์ธ์ BGP ํผ์ด ์ฐ๊ฒฐ์ด ์ฑ๊ณตํ๊ณ , ์ด๊ธฐ ๋ผ์ฐํธ ๊ตํ์ด ์๋ฃ๋๋ค
- 192.168.10.101 (Cilium BGP ๋ ธ๋1) → IPv4 Unicast
- 192.168.10.100 (Cilium BGP ๋ ธ๋2) → IPv4 Unicast
- 192.168.20.100 (Cilium BGP ๋ ธ๋3) → IPv4 Unicast
์ ์ฉ๋ ๋ด์ฉ ํ์ธ
(โ|HomeLab:N/A) root@k8s-ctr:~# cilium bgp peers
Node Local AS Peer AS Peer Address Session State Uptime Family Received Advertised
k8s-ctr 65001 65000 192.168.10.200 established 15m0s ipv4/unicast 4 2
k8s-w0 65001 65000 192.168.10.200 established 13m51s ipv4/unicast 4 2
k8s-w1 65001 65000 192.168.10.200 established 15m8s ipv4/unicast 4 2
(โ|HomeLab:N/A) root@k8s-ctr:~# cilium bgp routes available ipv4 unicast
Node VRouter Prefix NextHop Age Attrs
k8s-ctr 65001 172.20.0.0/24 0.0.0.0 15m13s [{Origin: i} {Nexthop: 0.0.0.0}]
k8s-w0 65001 172.20.2.0/24 0.0.0.0 13m58s [{Origin: i} {Nexthop: 0.0.0.0}]
k8s-w1 65001 172.20.1.0/24 0.0.0.0 15m13s [{Origin: i} {Nexthop: 0.0.0.0}]
3๊ฐ์ ๋ ธ๋(k8s-ctr, k8s-w0, k8s-w1)๊ฐ ๋ชจ๋ ๋ผ์ฐํฐ์ BGP ์ธ์ ์ ๋งบ๊ณ ์์ผ๋ฉฐ IPv4 ๊ฒฝ๋ก๋ฅผ ์๋ก ์ฃผ๊ณ ๋ฐ๊ณ ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl get ciliumbgpadvertisements,ciliumbgppeerconfigs,ciliumbgpclusterconfigs
NAME AGE
ciliumbgpadvertisement.cilium.io/bgp-advertisements 19m # ๋ฌด์์ ๊ด๊ณ ํ ์ง ์ ์
NAME AGE
ciliumbgppeerconfig.cilium.io/cilium-peer 19m # BGP ํผ์ด(๋ผ์ฐํฐ)์์ ์ธ์
ํ๋ผ๋ฏธํฐ ์ ์
NAME AGE
ciliumbgpclusterconfig.cilium.io/cilium-bgp 19m # ์ด๋ค ๋
ธ๋์์ BGP๋ฅผ ์ผ๊ณ ์ด๋ค AS/ํผ์ด ์ค์ ์ ์ธ์ง ๊ฒฐ์ ํ๋ ๋ฆฌ์์ค
root@router:~# ip -c route | grep bgp
172.20.0.0/24 nhid 28 via 192.168.10.100 dev eth1 proto bgp metric 20
172.20.1.0/24 nhid 26 via 192.168.10.101 dev eth1 proto bgp metric 20
172.20.2.0/24 nhid 30 via 192.168.20.100 dev eth2 proto bgp metric 20
# bgp ํผ์ด ์ํ
root@router:~# vtysh -c 'show ip bgp summary'
IPv4 Unicast Summary (VRF default):
BGP router identifier 192.168.10.200, local AS number 65000 vrf-id 0
BGP table version 4
RIB entries 7, using 1344 bytes of memory
Peers 3, using 2172 KiB of memory
Peer groups 1, using 64 bytes of memory
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc
192.168.10.100 4 65001 467 470 0 0 0 00:23:12 1 4 N/A
192.168.10.101 4 65001 470 473 0 0 0 00:23:20 1 4 N/A
192.168.20.100 4 65001 445 447 0 0 0 00:22:04 1 4 N/A
Total number of neighbors 3
root@router:~# vtysh -c 'show ip bgp'
BGP table version is 4, local router ID is 192.168.10.200, vrf id 0
Default local pref 100, local AS 65000
Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes: i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found
Network Next Hop Metric LocPrf Weight Path
*> 10.10.1.0/24 0.0.0.0 0 32768 i # ๋ผ์ฐํฐ ๋ก์ปฌ์ ์๋ ๋คํธ์ํฌ (BGP๋ก๋ ๊ด๊ณ ์ค)
*> 172.20.0.0/24 192.168.10.100 0 65001 i # k8s-ctr ๋
ธ๋์ PodCIDR
*> 172.20.1.0/24 192.168.10.101 0 65001 i # k8s-w1 ๋
ธ๋์ PodCIDR
*> 172.20.2.0/24 192.168.20.100 0 65001 i # k8s-w0 ๋
ธ๋์ PodCIDR
Displayed 4 routes and 4 total paths

๋ผ์ฐํฐ(FRR, 192.168.10.200)์ Cilium ๋ ธ๋(192.168.10.100) ๊ฐ BGP ์ธ์ ์ด Established๊ฐ ๋ ์ํ์ด๋ฉฐ, ๋ผ์ฐํฐ๊ฐ ํผ์ด์๊ฒ ์ฌ๋ฌ ๊ฐ์ ๊ฒฝ๋ก๋ฅผ ํ ๋ฒ์ UPDATE ๋ฉ์์ง๋ก ์ ์กํ๊ฒ๋๋ค.
๋ ธ๋์ ๋ผ์ฐํ ์ค์ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ
(โ|HomeLab:N/A) root@k8s-ctr:~# ip route add 172.20.0.0/16 via 192.168.10.200
(โ|HomeLab:N/A) root@k8s-ctr:~# sshpass -p 'vagrant' ssh vagrant@k8s-w1 sudo ip route add 172.20.0.0/16 via 192.168.10.200
(โ|HomeLab:N/A) root@k8s-ctr:~# sshpass -p 'vagrant' ssh vagrant@k8s-w0 sudo ip route add 172.20.0.0/16 via 192.168.20.200
์์ปค ๋ ธ๋ ์ปค๋ ๋ผ์ฐํ ํ ์ด๋ธ์ 172.20.0.0/16 ๊ฒฝ๋ก๋ฅผ ๋ช ์์ ์ผ๋ก ์ถ๊ฐํ ํ ๋ชจ๋ Pod CIDR๋ก ๊ฐ๋ ํธ๋ํฝ์ด NIC(eth1)์ ํตํด ๋ผ์ฐํฐ๋ก ๋๊ฐ๊ฒ ๋๋ค.
root@router:~# ip -c route | grep bgp
172.20.0.0/24 nhid 60 via 192.168.10.100 dev eth1 proto bgp metric 20
172.20.1.0/24 nhid 59 via 192.168.10.101 dev eth1 proto bgp metric 20
172.20.2.0/24 nhid 56 via 192.168.20.100 dev eth2 proto bgp metric 20

์ค์ ์ฃผ์ ์ดํ ์ ์์ ์ผ๋ก ์ปฌ์ ๋ชจ๋ ์ํํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
Disabling CRD Status Report
(โ|HomeLab:N/A) root@k8s-ctr:~# helm upgrade cilium cilium/cilium --version 1.18.0 --namespace kube-system --reuse-values \
--set bgpControlPlane.statusReport.enabled=false
Release "cilium" has been upgraded. Happy Helming!
NAME: cilium
LAST DEPLOYED: Fri Aug 15 00:40:19 2025
NAMESPACE: kube-system
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
You have successfully installed Cilium with Hubble Relay and Hubble UI.
Your release version is 1.18.0.
For any further help, visit https://docs.cilium.io/en/v1.18/gettinghelp
bgpControlPlane.statusReport.enabled=false ์ค์ ์ด ์ ์ฉ๋์ด CiliumBGPNodeConfig์ status ํ๋๊ฐ ๋ ์ด์ ์ฃผ๊ธฐ์ ์ผ๋ก ๊ฐฑ์ ๋์ง ์๊ฒ ๋๋ค.
์๋น์ค ip๋ก ๋ก๋๋ฐธ๋ฐ์ ์์ฑํ๊ธฐ
์๋น์ค์ ํ ๋นํ LoadBalancer IP ๋์ญ์ Cilium์ ๋ฑ๋ก
# Cilium์ด Service=LoadBalancer์ ํ ๋นํ ์ธ๋ถ IP ํ(172.16.1.0/24)์ ์์ฑํ๋ค.
(โ|HomeLab:N/A) root@k8s-ctr:~# 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
ciliumloadbalancerippool.cilium.io/cilium-pool created
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl get ippool
NAME DISABLED CONFLICTING IPS AVAILABLE AGE
cilium-pool false False 254 5s
์น ํ๋ ์๋น์คํ์ ์ ๋ก๋๋ฐธ๋ฐ์๋ก ๋ณ๊ฒฝ
# ์๋น์ค๋ฅผ ๋ก๋๋ฐธ๋ฐ์๋ก ์ ํํ๋ค
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl patch svc webpod -p '{"spec": {"type": "LoadBalancer"}}'
service/webpod patched
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl get svc webpod
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
webpod LoadBalancer 10.96.180.250 172.16.1.1 80:30921/TCP 26h
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl get ippool
NAME DISABLED CONFLICTING IPS AVAILABLE AGE
cilium-pool false False 253 22s
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl describe svc webpod | grep 'Traffic Policy'
External Traffic Policy: Cluster
Internal Traffic Policy: Cluster
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg service list
ID Frontend Service Type Backend
1 10.96.0.1:443/TCP ClusterIP 1 => 192.168.10.100:6443/TCP (active)
2 10.96.77.222:443/TCP ClusterIP 1 => 192.168.20.100:4244/TCP (active)
3 10.96.170.16:80/TCP ClusterIP 1 => 172.20.0.103:4245/TCP (active)
4 0.0.0.0:30003/TCP NodePort 1 => 172.20.0.191:8081/TCP (active)
7 10.96.170.247:80/TCP ClusterIP 1 => 172.20.0.191:8081/TCP (active)
8 10.96.0.10:53/TCP ClusterIP 1 => 172.20.0.30:53/TCP (active)
2 => 172.20.0.84:53/TCP (active)
9 10.96.0.10:53/UDP ClusterIP 1 => 172.20.0.30:53/UDP (active)
2 => 172.20.0.84:53/UDP (active)
10 10.96.0.10:9153/TCP ClusterIP 1 => 172.20.0.30:9153/TCP (active)
2 => 172.20.0.84:9153/TCP (active)
11 10.96.176.103:443/TCP ClusterIP 1 => 172.20.0.128:10250/TCP (active)
12 10.96.180.250:80/TCP ClusterIP 1 => 172.20.0.205:80/TCP (active)
2 => 172.20.1.135:80/TCP (active)
3 => 172.20.1.157:80/TCP (active)
20 0.0.0.0:30921/TCP NodePort 1 => 172.20.0.205:80/TCP (active)
2 => 172.20.1.135:80/TCP (active)
3 => 172.20.1.157:80/TCP (active)
23 172.16.1.1:80/TCP LoadBalancer 1 => 172.20.0.205:80/TCP (active) # ๋ก๋๋ฐธ๋ฐ์ ํ์ธ
2 => 172.20.1.135:80/TCP (active)
3 => 172.20.1.157:80/TCP (active)
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl get svc webpod -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
LBIP=$(kubectl get svc webpod -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl -s $LBIP
172.16.1.1Hostname: webpod-697b545f57-8r5sl
IP: 127.0.0.1
IP: ::1
IP: 172.20.1.157
IP: fe80::b042:54ff:fea3:7ccc
RemoteAddr: 192.168.10.100:40224
GET / HTTP/1.1
Host: 172.16.1.1
User-Agent: curl/8.5.0
Accept: */*
CiliumBGPAdvertisement ๋ฆฌ์์ค ๋ฐฐํฌ
BGP Advertisement ์ ์ฉ ์ ์๋ ๋ผ์ฐํฐ๊ฐ 172.16.1.1๋ก ๊ฐ๋ ๊ฒฝ๋ก๋ฅผ ๋ชจ๋ฅด๋ ์ํ์ด๋ค.
(โ|HomeLab:N/A) root@k8s-ctr:~# 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
ciliumbgpadvertisement.cilium.io/bgp-advertisements-lb-exip-webpod created
(โ|HomeLab:N/A) root@k8s-ctr:~#
kubectl get CiliumBGPAdvertisement
NAME AGE
bgp-advertisements 25h
bgp-advertisements-lb-exip-webpod 4s
root@router:~# ip -c route
default via 192.168.163.2 dev eth0 proto dhcp src 192.168.163.168 metric 100
10.10.1.0/24 dev loop1 proto kernel scope link src 10.10.1.200
10.10.2.0/24 dev loop2 proto kernel scope link src 10.10.2.200
172.16.1.1 nhid 117 proto bgp metric 20 # ์ถ๊ฐ
nexthop via 192.168.20.100 dev eth2 weight 1 # ์ถ๊ฐ
nexthop via 192.168.10.101 dev eth1 weight 1 # ์ถ๊ฐ
nexthop via 192.168.10.100 dev eth1 weight 1 # ์ถ๊ฐ
172.20.0.0/24 nhid 60 via 192.168.10.100 dev eth1 proto bgp metric 20
172.20.1.0/24 nhid 59 via 192.168.10.101 dev eth1 proto bgp metric 20
172.20.2.0/24 nhid 56 via 192.168.20.100 dev eth2 proto bgp metric 20
192.168.10.0/24 dev eth1 proto kernel scope link src 192.168.10.200
192.168.20.0/24 dev eth2 proto kernel scope link src 192.168.20.200
192.168.163.0/24 dev eth0 proto kernel scope link src 192.168.163.168 metric 100
192.168.163.2 dev eth0 proto dhcp scope link src 192.168.163.168 metric 100
(โ|HomeLab:N/A) 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.2.0/24 (24..24) accept
65001 tor-switch-ipv4-Service-webpod-default-LoadBalancerIP export 192.168.10.200/32 172.16.1.1/32 (32..32) accept
(โ|HomeLab:N/A) root@k8s-ctr:~# cilium bgp routes available ipv4 unicast
Node VRouter Prefix NextHop Age Attrs
k8s-ctr 65001 172.16.1.1/32 0.0.0.0 7m47s [{Origin: i} {Nexthop: 0.0.0.0}]
65001 172.20.0.0/24 0.0.0.0 23m20s [{Origin: i} {Nexthop: 0.0.0.0}]
k8s-w0 65001 172.16.1.1/32 0.0.0.0 7m47s [{Origin: i} {Nexthop: 0.0.0.0}]
65001 172.20.2.0/24 0.0.0.0 23m20s [{Origin: i} {Nexthop: 0.0.0.0}]
k8s-w1 65001 172.16.1.1/32 0.0.0.0 7m47s [{Origin: i} {Nexthop: 0.0.0.0}]
65001 172.20.1.0/24 0.0.0.0 23m7s [{Origin: i} {Nexthop: 0.0.0.0}]
์ด ๊ฒฝ๋ก๊ฐ ์๊น์ผ๋ก์จ ์ธ๋ถ ๋ผ์ฐํฐ๊ฐ 172.16.1.1๋ก ํธ๋ํฝ์ ๋ณด๋ผ ์ ์๊ฒ ๋ ๊ฒ์ด๋ค.
root@router:~# sudo vtysh -c 'show ip bgp'
BGP table version is 8, local router ID is 192.168.10.200, vrf id 0
Default local pref 100, local AS 65000
Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes: i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found
Network Next Hop Metric LocPrf Weight Path
*> 10.10.1.0/24 0.0.0.0 0 32768 i
*= 172.16.1.1/32 192.168.10.101 0 65001 i # ๊ด๊ณ ํ ์๋น์ค ๋ก๋๋ฐธ๋ฐ์ Ip
*> 192.168.10.100 0 65001 i # ๋ฉ์ธ ๊ฒฝ๋ก
*= 192.168.20.100 0 65001 i
*> 172.20.0.0/24 192.168.10.100 0 65001 i
*> 172.20.1.0/24 192.168.10.101 0 65001 i
*> 172.20.2.0/24 192.168.20.100 0 65001 i
Displayed 5 routes and 7 total paths
root@router:~# sudo vtysh -c 'show ip bgp 172.16.1.1/32'
BGP routing table entry for 172.16.1.1/32, version 8
Paths: (3 available, best #2, table default)
Advertised to non peer-group peers:
192.168.10.100 192.168.10.101 192.168.20.100
65001
192.168.10.101 from 192.168.10.101 (192.168.10.101)
Origin IGP, valid, external, multipath
Last update: Fri Aug 15 00:56:05 2025
65001
192.168.10.100 from 192.168.10.100 (192.168.10.100)
Origin IGP, valid, external, multipath, best (Router ID)
Last update: Fri Aug 15 00:56:05 2025
65001
192.168.20.100 from 192.168.20.100 (192.168.20.100)
Origin IGP, valid, external, multipath
Last update: Fri Aug 15 00:56:05 2025
172.16.1.1/32๊ฐ ์ธ๋ถ ๋ผ์ฐํฐ์ BGP ํ ์ด๋ธ์ ๋ฑ๋ก๋์ด ์ธ๋ถ ๋คํธ์ํฌ์์ ์ด IP๋ก ์ ๊ทผ ๊ฐ๋ฅํ๋ค. 3๊ฐ์ ๋ ธ๋๊ฐ ๋ชจ๋ next hop์ผ๋ก ๊ด๊ณ ๋์ด ECMP๋ก ๋ถํ๋ถ์ฐ ๋์์ด ๊ฐ๋ฅํ๋ค.
Best Path๋ 192.168.10.100์ด์ง๋ง, multipath ์ค์ ์ด ์์ผ๋ฉด ๋๋จธ์ง๋ ๋์์ ์ฌ์ฉ ํ ์ ์๋ค.
๋ผ์ฐํฐ์์ ํต์ ํ์ธ
root@router:~# LBIP=172.16.1.1
curl -s $LBIP
Hostname: webpod-697b545f57-42lfb
IP: 127.0.0.1
IP: ::1
IP: 172.20.1.135
IP: fe80::f89a:ebff:fecd:683a
RemoteAddr: 192.168.20.100:53018
GET / HTTP/1.1
Host: 172.16.1.1
User-Agent: curl/8.5.0
Accept: */*
๋ฌธ์ ํ์ธ
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl scale deployment webpod --replicas 2
deployment.apps/webpod scaled
(โ|HomeLab:N/A) root@k8s-ctr:~# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
curl-pod 1/1 Running 0 26h 172.20.0.187 k8s-ctr <none> <none>
webpod-697b545f57-42lfb 1/1 Running 0 26h 172.20.1.135 k8s-w1 <none> <none>
webpod-697b545f57-fnz6w 1/1 Running 0 26h 172.20.0.205 k8s-ctr <none> <none>
(โ|HomeLab:N/A) root@k8s-ctr:~# cilium bgp routes
(Defaulting to `available ipv4 unicast` routes, please see help for more options)
Node VRouter Prefix NextHop Age Attrs
k8s-ctr 65001 172.16.1.1/32 0.0.0.0 15m0s [{Origin: i} {Nexthop: 0.0.0.0}]
65001 172.20.0.0/24 0.0.0.0 30m33s [{Origin: i} {Nexthop: 0.0.0.0}]
k8s-w0 65001 172.16.1.1/32 0.0.0.0 15m0s [{Origin: i} {Nexthop: 0.0.0.0}]
65001 172.20.2.0/24 0.0.0.0 30m33s [{Origin: i} {Nexthop: 0.0.0.0}]
k8s-w1 65001 172.16.1.1/32 0.0.0.0 15m0s [{Origin: i} {Nexthop: 0.0.0.0}]
65001 172.20.1.0/24 0.0.0.0 30m20s [{Origin: i} {Nexthop: 0.0.0.0}]
Cilium BGP๋ฅผ ํตํด LoadBalancer IP๋ฅผ ๋ชจ๋ ๋ ธ๋๊ฐ ๊ด๊ณ ํ์ง๋ง ์ค์ ๋ก ์๋น์ค ํ๋๊ฐ ์๋ ๋ ธ๋(w0)๋ ๊ด๊ณ ๋ฅผ ํ๊ณ ์์ด์ ๋ถํ์ํ ํธ๋ํฝ์ด ํด๋น ๋ ธ๋๋ก ์ ์ ๋ ์ ์๋ค.

External Traffic Policy (Local)

Cilium์ด ํ๋๊ฐ ์๋ ๋ ธ๋๋ง BGP ๊ด๊ณ ํ์ฌ 192.168.20.100(k8s-w0)์ ๊ด๊ณ ๋์์์ ์ ์ธ๋๋ค.
๋ฐ๋ผ์ ๋ผ์ฐํฐ๊ฐ ํด๋น ๋ ธ๋๋ก๋ ๋ ์ด์ ํธ๋ํฝ์ ๋ณด๋ด์ง ์๊ฒ ๋๋ค.
externalTrafficPolicy: Local์ ์๋น์ค์ ๋ก๋๋ฐธ๋ฐ์ IP๋ฅผ ๊ด๊ณ ํ ๋ ์ค์ ํ๋๊ฐ ์๋ ๋ ธ๋๋ง ํ๊ฒํ์ฌ BGP ๋ผ์ฐํ ์์ ๋ถํ์ํ ๊ฒฝ๋ก๋ฅผ ์ ๊ฑฐํ๊ณ ์๋น์ค ์ ๊ทผ ๊ฒฝ๋ก๋ฅผ ์ต์ ํํ ์ ์๋ค.
ECMP hash policy
root@router:~# sudo sysctl -w net.ipv4.fib_multipath_hash_policy=1
echo "net.ipv4.fib_multipath_hash_policy=1" >> /etc/sysctl.conf
net.ipv4.fib_multipath_hash_policy = 1
root@router:~# for i in {1..100}; do curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr
36 Hostname: webpod-697b545f57-22gbp
33 Hostname: webpod-697b545f57-42lfb
31 Hostname: webpod-697b545f57-fnz6w
ECMP ๋ผ์ฐํ ์ ๋์ผํ ๋ชฉ์ ์ง๋ก ๊ฐ๋ ์ฌ๋ฌ ๊ฒฝ๋ก๋ฅผ ๋์์ ์ฌ์ฉํ์ฌ ๋ถํ๋ฅผ ๋ถ์ฐํ๋๋ฐ, ๋ฆฌ๋ ์ค์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก (์์คIP, ๋ชฉ์ ์งIP, L4ํฌํธ)๋ฅผ ํด์ ํค๋ก ์ฌ์ฉํ์ฌ ๋ค์ ํ์ ๊ฒฐ์ ํ์ง๋ง 'fib_multipath_hash_policy=1'๋ก ๋ณ๊ฒฝํ๋ฉด ํฌํธ๋ฅผ ๋ฌด์ํ๊ณ (์์คIP, ๋ชฉ์ ์งIP)๋ง ์ฌ์ฉํ๋ค.
์ด๋ ๊ฒ ์ฌ์ฉํจ์ผ๋ก์จ ํฌํธ ๊ธฐ๋ฐ ํด์ ํธํฅ์ ์ค์ฌ ๋ถํ ๋ถ์ฐ์ ๋ ๊ท ๋ฑํ๊ฒ ๋ง๋ค ์ ์๋ค.