[cilium] LB-IPAM 로드밸런서 아이피를 관리하기

LB IPAM은 Cilium이 LoadBalancer 타입의 Service에 IP 주소를 할당할 수 있게 해주는 기능입니다. 

내가 쓰는 노트북이 사양이 별로 안좋아서 virtualbox로 테스트 환경 구성하기가 어려워서 kind를 주로 쓰는데 이때 한번 써본 기능이긴하다.

2024.10.13 - [K8S/cilium] - Kind에서 Cilium으로 외부 접근 LoadBalancer 설정하기

 

이번에는 LB-IPAM의 동작 방식에 대해 한번 알아볼까합니다. 

먼저 LB IPAM은 Cilium BGP Control Plane 및 L2 Announcements / L2 Aware LB 같은 기능과 함께 동작합니다. LB IPAM이 Service 오브젝트에 IP를 할당/부여하는 역할을 맡고, 다른 기능들이 해당 IP의 로드밸런싱 및/또는 광고 를 담당합니다.

LB IPAM 말고 유사한 기능으론 Node IPAM이 있습니다. L2나 BGP를 사용할 수 없는 환경에서 서비스 LoadBalancer 안에 노드의 IP들을 직접 광고할 수 있게 해주는 기능입니다. 

 

기본적으론 LB IPAM이 활성화 되기 때문에 LB IPAM을 사용하기 위해 별다른 설정은 필요하지 않지만 첫 번째 IP 풀이 클러스터에 추가되어야지만 컨트롤러가 활성화됩니다.

 

설정 방법

아래와 같이 설정해서 Pool을 추가할 수 있는데 CIDR로 블럭을 넣거나 start~stop으로 범위를 지정할 수 있습니다. 하나의 풀에 IPv4/IPv6, 복수 CIDR/범위를 혼합해 구성 가능한것을 확인할 수 있습니다. 하지만 풀 업데이트 시에는 아이피 재할당이 일어나 서비스 IP가 바뀔 수 있습니다. 변경 전/후 영향 범위 체크가 필요합니다. 

 

 

apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
  name: blue-pool
spec:
  blocks:
    - cidr: "10.0.10.0/24"     # IPv4 CIDR
    - cidr: "2004::/112"       # IPv6 CIDR
    - start: "20.0.20.100"     # 범위 표기 (start/stop)
      stop:  "20.0.20.200"

 

위처럼 풀을 설정하는 경우에는 어떤 서비스에든 다 LB IP를 추가할 수 있지만 어떤 서비스에만 적용되게 할지 지정할 수 있습니다. 

spec:
  serviceSelector:
    matchExpressions:
      - key: color
        operator: In
        values: ["blue", "cyan"]

 

테스트 해보기

이번 테스트에서는 1.18.0을 사용합니다.

helm install cilium cilium/cilium --namespace kube-system \
  --set k8sServiceHost=auto --set ipam.mode="kubernetes" \
  --set k8s.requireIPv4PodCIDR=true --set ipv4NativeRoutingCIDR=10.244.0.0/16 \
  --set routingMode=native --set autoDirectNodeRoutes=true --set endpointRoutes.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

 

이제 아래 처럼 pool을 추가해봅니다. 

 

apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
  name: "blue-pool"
spec:
  blocks:
  - cidr: "20.0.10.0/24"
  serviceSelector:
    matchExpressions:
      - {key: color, operator: In, values: [blue, cyan]}
---
apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
  name: "red-pool"
spec:
  blocks:
  - cidr: "20.0.10.0/24"
  serviceSelector:
    matchLabels:
      color: red

 

그럼 이렇게 로그가 남습니다. 

time=2025-08-09T05:38:10.556098859Z level=debug source=/go/src/github.com/cilium/cilium/operator/pkg/lbipam/lbipam.go:1589 msg="Pool counts of 'blue-pool' changed, patching" module=operator.operator-controlplane.leader-lifecycle.lbipam
time=2025-08-09T05:38:10.567173989Z level=debug source=/go/src/github.com/cilium/cilium/operator/pkg/lbipam/lbipam.go:1586 msg="Updating pool counts" module=operator.operator-controlplane.leader-lifecycle.lbipam

 

 

그리고 두개의 풀이 대역이 겹치기 때문에 이렇게 CONFLICTING이 True가 된 쪽이 동작을 안합니다.

 

이렇게 blue 쪽에만 아이피가 안받는 것을 확인할 수 있습니다. 

 

blue pool을 20.0.10.0/24에서 20.0.20.0/24로 변경하면 잘 동작하는 것을 볼 수 있습니다.

 

하지만 이 아이피로 실제로 저 아이피로 통신이 되는지도 테스트해보겠습니다.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
  labels:
    app: curl
spec:
  nodeName: kind-control-plane
  containers:
  - name: curl
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

 

호출해보겠습니다. 먼저 Clsuter IP로 테스트를 해보겠습니다.

curl 10.96.205.70

 

이번엔 curl-pod에서 LB 아이피로 통신해보겠습니다.

curl 20.0.20.0

 

LB 아이피로도 클러스터 내부에서는 통신이 잘 되는 것을 알 수 있습니다. 

LB IPAM을 설정하면 bpf 맵에 해당 아이피가 설정되어 백엔드 아이피가 정해지게 되어있는 것 같습니다. 

 

서비스 리스트를 조회해봐도 동일하게 보이는것을 알 수 있습니다. 

 

kind 클러스터와 같은 대역으로 컨테이너를 띄워서도 테스트해보겠습니다. 

docker run -d --rm --name client --network kind nicolaka/netshoot tail -f /dev/null

 

그리고 들어가서 LB IP로 테스트해보면 통신이 안되는 것을 확인할 수 있습니다. 외부 대역에서 통신을 하려면 BGP나 L2 Announcements / L2 Aware LB 기능을 추가해야합니다. 

 

추가로 hubble로 확인해보면 LB IP로 호출을 해도 Cluster IP로 로그가 남습니다. 

 

TCP dump 떠보기

이제 veth로 덤프를 떠보겠습니다. 노드에서 veth는 cilium agent에서 cilium bpf endpoint list로 조회할 수 있습니다. 

 

client의 veth에서 확인했을때에도 이미 목적지 주소가 변경되어있습니다. 

 

내부에서 직접 tcpdump를 떠봐도 이미 목적지 주소가 변경되어있습니다.