Cilium Study Networking ServiceLB Ipam
Intro
Migrating from MetalLB to Cilium
기존 직접구축한 환경 혹은 테스트 환경에서 Kubernetes 클러스터의 LB(LoadBalancer)를 사용하기 위해서 MetalLB를 많이 사용한다.
Cilium 1.14 릴리즈로 발행된 포스팅에서는 Cilium의 L2 Announcement 기술로 MetalLB의 로드밸런서 기능을 더이상 사용하지 않아도 된다고 설명되어있다.
실습환경

이전 “**[Cilium/Networking] Native Routing의 한계와 Overlay Network”** 포스팅에서 생성했던 VXLAN 환경으로 구성한다.
MetalLB의 사용사례
- Layer 2 Mode
- MetalLB를 Layer 2 Mode로 가장 많이 사용된다. 1개의 Node가 Local Network에서 service를 광고(advertise)하는 역할을 담당한다. (네트워크 관점에서는) 해당 머신이 NIC를 여러 개의 IP 주소가 할당된것으로 보이게 된다.
- IPv4의 경우는 ARP 요청에 응답하고 / IPv6의 경우는 NDP 요청에 응답
- Load Balancer
- MetalLB는 클러스터의 L2에 연결하는 부분과 부하 분산 장치 역할에서 가장 많이 사용되고, Kubernetes의
LoadBalancertype의 IP 주소관리(IPAM) 기능을 제공한다.
- MetalLB는 클러스터의 L2에 연결하는 부분과 부하 분산 장치 역할에서 가장 많이 사용되고, Kubernetes의
Cilium과 MetalLB
초기 Cilium에서는 BGP를 지원하면서 MetalLB를 많이 사용했지만, 현재는 BGP를 위한 MetalLB를 활용하지않고 있다. (BGP 역시, GoBGP를 사용)
LoadBalancer IP Address Management(LB IPAM)
LoadBalancer IP Address Management (LB IPAM) — Cilium 1.18.0 documentation

외부 로드 밸런서가 하는 역할 중 하나는 Kubernetes Service에 External IP 주소를 할당하는 것이다. (MetalLB 역할 중 일부)
LB IPAM은 Cilium이 IP 주소를 LoadBalancer 유형의 서비스에 할당할 수 있게 해주는 기능이다. 일반적으로 클라우드 환경에서는 맡겨지지만, Private 환경이거나 로컬 테스트 환경 등에서 이러한 기능을 사용하기 위해서는 추가적인 설정이 불가피하다.
특징
- LB IPAM은 서비스 객체에 IP를 할당하는 역할
- LB IPAM은
Cilium BGP Control Plane및L2 Announcements/L2 Aware LB(Beta)와 같은 기능과 함께 작동한다.- Cilium BGP Control Plane을 사용해 LB IPAM이 할당한 IP 주소를 BPG를 통해서 광고
- L2 Announcements / L2 Aware LB (Beta)를 통해 Local로 광고(Advertisement)
- LB IPAM은 항상 활성화되어 있지만 휴면(Idle) 상태이다. 첫번째 IP Cluster Pool이 추가되면 컨트롤러가 활성화됨
구성요소 1 (Pools)
CIDRs, Ranges and Reserved IPs
Cilium에게 요청을 해 CIDR 범위 내의 IP를 할당가능하다.
- IP Pool은 다수의 IP Block을 가질 수 있음
- CIDR Block 포맷 :
<prefix>/<bits>orstart~stop - CIDR의 처음(fist)과 마지막(last) IP는 할당하지 않는게 좋음. 기본적으로 LB IPAM은 지정된 CIDR의 모든 IP를 사용
First IP : network address
Last IP : broadcast address
.spec.allowFirstLastIPs: No→ 첫번째 그리고 마지막 IP를 예약⚠️ [Default Value Change after v1.16]
- v1.15에서는 기본값이
No - v1.16이상에서는
Yes로 변경
- v1.15에서는 기본값이
1apiVersion: "cilium.io/v2"
2kind: CiliumLoadBalancerIPPool
3metadata:
4 name: "blue-pool"
5spec:
6 blocks:
7 - cidr: "10.0.10.0/24" # Subnetted IPv4 Address
8 - cidr: "2004::0/112" # IPv6
9 - start: "20.0.20.100" # IP Range (start, end)
10 stop: "20.0.20.200"
1**# 생성된 ip pool 조회
2kubectl get ippools**
3NAME DISABLED CONFLICTING IPS AVAILABLE AGE
4blue-pool false False 65892 2s
⚠️ IP Pool을 업데이트하면 IP 주소가 재할당되고 Service IP가 변경될 수 있다.
Service Selector
.spec.serviceSelector 필드를 통해서 특정 Service만 할당을 제한 할 수 있다.
1# matchExpression 예시
2apiVersion: "cilium.io/v2"
3kind: CiliumLoadBalancerIPPool
4metadata:
5 name: "blue-pool"
6spec:
7 blocks:
8 - cidr: "20.0.10.0/24"
9 serviceSelector:
10 matchExpressions:
11 - {key: color, operator: In, values: [blue, cyan]}
12# serviceSelector.matchLabels 예시
13apiVersion: "cilium.io/v2"
14kind: CiliumLoadBalancerIPPool
15metadata:
16 name: "red-pool"
17spec:
18 blocks:
19 - cidr: "20.0.10.0/24"
20 serviceSelector:
21 matchLabels:
22 color: red
23# serviceSelector.matchLabels: namespace 예시
24apiVersion: "cilium.io/v2"
25kind: CiliumLoadBalancerIPPool
26metadata:
27 name: "green-pool"
28spec:
29 blocks:
30 - cidr: "20.0.10.0/24"
31 serviceSelector:
32 matchLabels:
33 "io.kubernetes.service.namespace": "tenant-a"
그외 Conflict, Disabling a Pool
Conflict
IP Pool은 CIDR의 Overlapping하는걸 허용하지 않는다.
(Soft Error) 만약 겹치는 CIDR을 적용한 경우
Conflicting이라는 Mark로 표시되고 할당되지 않는다.1kubectl get ippools 2NAME DISABLED CONFLICTING IPS AVAILABLE AGE 3blue-pool false False 254 25m 4red-pool false True 254 11s
Disable a Pool
IP Pool에 새로운 IP가 할당되는걸 중단할 수 있다.
1apiVersion: "cilium.io/v2" 2kind: CiliumLoadBalancerIPPool 3metadata: 4 name: "blue-pool" 5spec: 6 blocks: 7 - cidr: "20.0.10.0/24" 8 disabled: true1kubectl get ippools 2NAME DISABLED CONFLICTING IPS AVAILABLE AGE 3blue-pool true False 254 41m
구성요소 2 (Services)
spec.type=LoadBalancer 인 어떤 Service는 CiliumLoadBalancerIPPool Selector를 통해서 IP Pool에서 IP를 할당받을 수 있다.
LoadBalancerClass
동일 Cluster에 다수의 LoadBalancer가 있는 경우에 각각 다른 LoadBalancerClass로 선택할 수 있다.
.spec.loadBalancerClassio.cilium/bgp-control-plance: BGPio.cilium/l2-Announcer: L2 Announcement
Requesting IPs
Service는 특정 IP를 요청할 수 있다. 기존방식은 .spec.loadBalancerIP 의 단일 IP 주소를 사용하는 방식이지만 향후 Deprecated 될 예정 (≥ 1.24)
- Annotaion
"lbipam.cilium.io/ips"
1# 예시
2apiVersion: v1
3kind: Service
4metadata:
5 name: service-blue
6 namespace: example
7 labels:
8 color: blue
9 annotations:
10 "lbipam.cilium.io/ips": "20.0.10.100,20.0.10.200"
11spec:
12 type: LoadBalancer
13 ports:
14 - port: 1234
1**kubectl -n example get svc**
2NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
3service-blue LoadBalancer 10.96.26.105 20.0.10.100,20.0.10.200 1234:30363/TCP 43s
Sharing Keys
External IP 1개에 각기 다른 Port를 통해서 사용가능하다.
lbipam.cilium.io/sharing-key
1# 동일한 Sharing Key로 IP 할당
2apiVersion: v1
3kind: Service
4metadata:
5 name: service-blue
6 namespace: example
7 labels:
8 color: blue
9 annotations:
10 "lbipam.cilium.io/sharing-key": "1234"
11spec:
12 type: LoadBalancer
13 ports:
14 - port: 1234
15---
16apiVersion: v1
17kind: Service
18metadata:
19 name: service-red
20 namespace: example
21 labels:
22 color: red
23 annotations:
24 "lbipam.cilium.io/sharing-key": "1234"
25spec:
26 type: LoadBalancer
27 ports:
28 - port: 2345
1**# 동일한 External IP를 할당받았지만, Port가 다른걸 확인할 수 있다.
2kubectl -n example get svc**
3NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
4service-blue LoadBalancer 10.96.26.105 **20.0.10.100** **1234**:30363/TCP 43s
5service-red LoadBalancer 10.96.26.106 **20.0.10.100** **2345**:30131/TCP 43s
실습 1 : (클러스터 내부) webpod 서비스를 LoadBalancer Type 설정 with Cilium LB IPAM
Sample Application 배포
1# 샘플 애플리케이션 배포 2cat << EOF | kubectl apply -f - 3apiVersion: apps/v1 4kind: Deployment 5metadata: 6 name: webpod 7spec: 8 replicas: 3 9 selector: 10 matchLabels: 11 app: webpod 12 template: 13 metadata: 14 labels: 15 app: webpod 16 spec: 17 affinity: 18 podAntiAffinity: 19 requiredDuringSchedulingIgnoredDuringExecution: 20 - labelSelector: 21 matchExpressions: 22 - key: app 23 operator: In 24 values: 25 - sample-app 26 topologyKey: "kubernetes.io/hostname" 27 containers: 28 - name: webpod 29 image: traefik/whoami 30 ports: 31 - containerPort: 80 32--- 33apiVersion: v1 34kind: Service 35metadata: 36 name: webpod 37 labels: 38 app: webpod 39spec: 40 selector: 41 app: webpod 42 ports: 43 - protocol: TCP 44 port: 80 45 targetPort: 80 46 type: ClusterIP 47EOF 48 49# k8s-ctr 노드에 curl-pod 파드 배포 50cat <<EOF | kubectl apply -f - 51apiVersion: v1 52kind: Pod 53metadata: 54 name: curl-pod 55 labels: 56 app: curl 57spec: 58 nodeName: k8s-ctr 59 containers: 60 - name: curl 61 image: nicolaka/netshoot 62 command: ["tail"] 63 args: ["-f", "/dev/null"] 64 terminationGracePeriodSeconds: 0 65EOF
충돌나지 않는 대역대를 확인하고 배포
⚠️ cilium 1.18 버전부터 CiliumLoadBalancerIPPool가 v2alpha1 에서 v2 로 변경에 주의
1cat << EOF | kubectl apply -f -
2apiVersion: "cilium.io/v2" # v1.17 : cilium.io/v2alpha1
3kind: CiliumLoadBalancerIPPool
4metadata:
5 name: "cilium-lb-ippool"
6spec:
7 blocks:
8 - start: "192.168.10.211"
9 stop: "192.168.10.215"
10EOF
1# 생성 확인
2**kubectl get ippools # 혹은 kubectl get CiliumLoadBalancerIPPool -A**
3NAME DISABLED CONFLICTING IPS AVAILABLE AGE
4cilium-lb-ippool false False 5 3s
webpod의 Service를 “LoadBalancer” Type으로 변경하고 External IP 할당 확인
1**kubectl patch svc webpod -p '{"spec":{"type":"LoadBalancer"}}'
2
3# 확인
4kubectl get svc webpod**
5NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
6webpod LoadBalancer 10.96.22.169 192.168.10.211 80:31333/TCP 38s
7
8# LB IP로 `curl-pod`에서 요청 확인
9LBIP=$(kubectl get svc webpod -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
10kubectl exec -it curl-pod -- curl -s $LBIP
11Hostname: webpod-697b545f57-p8r7n
12IP: 127.0.0.1
13IP: ::1
14IP: 172.20.2.228
15IP: fe80::4cd6:fff:fed6:617
16RemoteAddr: 172.20.0.182:42970
17GET / HTTP/1.1
18Host: 192.168.10.211
19User-Agent: curl/8.14.1
20Accept: */*
21
22# 100회 반복해서 부하분산 확인
23for i in {1..100}; do kubectl exec -it curl-pod -- curl -s $LBIP | grep Hostname; done | sort | uniq -c | sort -nr
24 37 Hostname: webpod-697b545f57-p8r7n
25 32 Hostname: webpod-697b545f57-g6h2h
26 31 Hostname: webpod-697b545f57-4khjh
하지만 Cluster 외부에서 webpod의 External IP로는 호출이 “불가능”하다.
→ BGP 혹은 L2 Announcements / L2 Aware LB(Beta)의 도움을 받아서 해결이 가능
1# router : K8S 외부에서 통신 불가!
2curl --connect-timeout 1 $LBIP
3arping -i eth1 $LBIP -c 1
4arping -i eth1 $LBIP -c 100000
5...
Cilium L2 Announcement / L2 Aware LB (beta)
L2 Announcements / L2 Aware LB (Beta) — Cilium 1.18.0 documentation

L2 Announcements는 온프레미스·캠퍼스처럼 BGP 라우팅이 없는 L2 네트워크에서 Kubernetes Service를 로컬 LAN에 “직접 가시화하고(ARP/NDP 응답), 도달 가능하게” 만드는 기능이다.
이 기능은 Service의 ExternalIP 혹은 LoadBalancer IP를 실제 노드 인터페이스에 바인딩하지 않고도, 선택된 노드가 해당 VIP에 대한 ARP/NDP 질의에 응답해 북-남향(North/South) 트래픽의 진입점이 되도록 설계되어있다.
해당 노드는 서비스 로드 밸런싱 기능으로 로드 밸런싱을 수행하여 north/south 로드 밸런서를 역할을 한다.
동작방식
- Service VIP(ExternalIP 또는 LoadBalancer IP)에 대해 클러스터 노드 간 리스(Lease) 기반 리더 선출이 이루어지고 리스 보유 노드가 VIP의 ARP 요청에 자신의 MAC으로 응답
- VIP는 네트워크 장비에 직접 구성되지 않는 가상 IP이며, 응답 중인 노드가 장애 시 리스 만료를 통해 다른 노드로 VIP 응답권이 이양되어 연속성을 보장
- 이 방식은 MetalLB의 L2 모드와 유사한 ARP 응답 모델이지만, Cilium 내장 기능으로 외부 컴포넌트 없이 구현된다는 점이 특징
특징과 장점
- 각 Service가 고유 VIP를 가지므로 동일 포트 번호를 여러 서비스가 사용할 수 있고, NodePort 대비 클라이언트 측 호스트 선택 부담을 제거한다.
- 노드 장애 시 VIP가 다른 노드로 자동 마이그레이션되므로 IP+Port 조합이 불능 상태에 빠지지 않고 지속성을 확보한다.
- eBPF를 통한 패킷 경로 내 처리로 성능과 일관된 로드밸런싱을 제공하며, 별도 LB 소프트웨어 없이 네이티브로 동작해 구성 단순성과 의존성 감소의 이점을 가진다.
- LB-IPAM과 결합하면 클러스터가 Service VIP를 자동 할당하고, 필요 시 BGP와도 통합 가능한 확장 경로를 제공한다
한계와 주의사항
- 현재는 IPv6/NDP를 지원하지 않음.
- L3 → L2로 프로토콜이 변환되는 작동 방식으로 트래픽이 클러스터에 도달하기 전에 특정 IP에대한 하나의 노드가 모든 ARP 요청이 몰려 부하 분산이 이루어질 수 없음
- 트래픽 분산 매커니즘이 없기때문에 동일 정책 내 노드가 비대칭적으로 로드될 수 있음.
- Service의
externalTrafficPolicy: Local와는 호환되지 않음. - Kubernetes API 사용량이 증가하므로 QPS/Burst 파라미터를 환경에 맞춰 적정하게 사이징 필요.
설정 및 확인
1# L2 Announcement 설정
2helm upgrade cilium cilium/cilium \
3 --namespace kube-system \
4 --version 1.18.0 \
5 --reuse-values \
6 --set l2announcements.enabled=true
1kubectl rollout restart -n kube-system ds/cilium
1# 확인
2kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg config --all | grep EnableL2Announcements
3EnableL2Announcements : true
4
5cilium config view | grep enable-l2
6enable-l2-announcements true
7enable-l2-neigh-discovery false
실습 2 : (클러스터 외부) webpod 서비스를 LB External IP로 호출 with L2 Announcement
정책 설정
- arp 광고하게 될 service 와 node 지정(controlplane 제외)
제약 사항
- L2 ARP 모드에서 LB IP Pool은 같은 네트워크 대역에서만 유효하다. ([ k8s-w0 ] 를 제외하는 이유 / 포함하는 경우 리더 선출과정에서 실패하는 상황 발생)
1cat << EOF | kubectl apply -f -
2apiVersion: "cilium.io/v2alpha1" # not v2
3kind: CiliumL2AnnouncementPolicy
4metadata:
5 name: policy1
6spec:
7 serviceSelector:
8 matchLabels:
9 app: webpod
10 nodeSelector:
11 matchExpressions:
12 - key: kubernetes.io/hostname
13 operator: NotIn
14 values:
15 - k8s-w0
16 interfaces:
17 - ^eth[1-9]+
18 externalIPs: true
19 loadBalancerIPs: true
20EOF
1# 확인
2kubectl -n kube-system get lease | grep "cilium-l2announce"
3cilium-l2announce-default-webpod k8s-w1 30s
4
5# 현재 리더 역할 노드 확인
6kubectl -n kube-system get lease/cilium-l2announce-default-webpod -o yaml | grep holderIdentity
7 holderIdentity: k8s-w1
8
9# Cilium 에이전트의 내부 StateDB에서 L2 Announcements 테이블을 조회
10k exec -n kube-system ds/cilium -- cilium-dbg shell -- db/show l2-announce
11IP NetworkInterface
12192.168.10.211 eth1
클러스터 외부환경([router])에서 LB External IP로 요청확인
1# [router] VM에서 진행
2export LBIP=192.168.10.211
3
4# L2(ARP)
5arping -i eth1 $LBIP -c 3
6ARPING 192.168.10.211
760 bytes from 08:00:27:bf:fc:a7 (192.168.10.211): index=0 time=794.731 usec
860 bytes from 08:00:27:bf:fc:a7 (192.168.10.211): index=1 time=575.788 usec
960 bytes from 08:00:27:bf:fc:a7 (192.168.10.211): index=2 time=913.248 usec
10
11--- 192.168.10.211 statistics ---
123 packets transmitted, 3 packets received, 0% unanswered (0 extra)
13rtt min/avg/max/std-dev = 0.576/0.761/0.913/0.140 ms
14
15# VIP(192.168.10.211)에 MAC 주소가 대한 리더 노드의 MAC 주소와 일치 (= 08:00:27:bf:fc:a7)
16kubectl exec -n kube-system ds/cilium -- cilium-dbg shell -- db/show devices | grep eth1
17eth1 3 true device 1500 08:00:27:bf:fc:a7 up|broadcast|multicast|running 192.168.10.101, fe80::a00:27ff:febf:fca7
18
19# L7
20curl --connect-timeout 1 $LBIP
21Hostname: webpod-697b545f57-4khjh
22IP: 127.0.0.1
23IP: ::1
24IP: 172.20.0.151
25IP: fe80::1cdf:a0ff:fe98:343
26RemoteAddr: 172.20.1.119:38116
27GET / HTTP/1.1
28Host: 192.168.10.211
29User-Agent: curl/8.5.0
30Accept: */*
