iamjjanga blog

Cilium Study Networking ServiceLB Ipam

Intro

Migrating from MetalLB to Cilium

기존 직접구축한 환경 혹은 테스트 환경에서 Kubernetes 클러스터의 LB(LoadBalancer)를 사용하기 위해서 MetalLB를 많이 사용한다.

Cilium 1.14 릴리즈로 발행된 포스팅에서는 Cilium의 L2 Announcement 기술로 MetalLB의 로드밸런서 기능을 더이상 사용하지 않아도 된다고 설명되어있다.

실습환경

alt text

이전 “**[Cilium/Networking] Native Routing의 한계와 Overlay Network”** 포스팅에서 생성했던 VXLAN 환경으로 구성한다.

MetalLB의 사용사례

  1. Layer 2 Mode
    1. MetalLB를 Layer 2 Mode로 가장 많이 사용된다. 1개의 Node가 Local Network에서 service를 광고(advertise)하는 역할을 담당한다. (네트워크 관점에서는) 해당 머신이 NIC를 여러 개의 IP 주소가 할당된것으로 보이게 된다.
    2. IPv4의 경우는 ARP 요청에 응답하고 / IPv6의 경우는 NDP 요청에 응답
  2. Load Balancer
    1. MetalLB는 클러스터의 L2에 연결하는 부분과 부하 분산 장치 역할에서 가장 많이 사용되고, Kubernetes의 LoadBalancer type의 IP 주소관리(IPAM) 기능을 제공한다.

Cilium과 MetalLB

초기 Cilium에서는 BGP를 지원하면서 MetalLB를 많이 사용했지만, 현재는 BGP를 위한 MetalLB를 활용하지않고 있다. (BGP 역시, GoBGP를 사용)

alt text
https://cdn.sanity.io/images/xinsvxfu/production/854c9e82d0ec4345c09bd8921eea112eff73efd0-1200x630.jpg

LoadBalancer IP Address Management(LB IPAM)

LoadBalancer IP Address Management (LB IPAM) — Cilium 1.18.0 documentation

alt text
https://isovalent.com/blog/post/migrating-from-metallb-to-cilium/#load-balancer-ipam

외부 로드 밸런서가 하는 역할 중 하나는 Kubernetes Service에 External IP 주소를 할당하는 것이다. (MetalLB 역할 중 일부)

LB IPAMCilium이 IP 주소를 LoadBalancer 유형의 서비스에 할할 수 있게 해주는 기능이다. 일반적으로 클라우드 환경에서는 맡겨지지만, Private 환경이거나 로컬 테스트 환경 등에서 이러한 기능을 사용하기 위해서는 추가적인 설정이 불가피하다.

특징

구성요소 1 (Pools)

CIDRs, Ranges and Reserved IPs

Cilium에게 요청을 해 CIDR 범위 내의 IP를 할당가능하다.

 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가 변경될 수 있다.

Github Issue #40358

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

구성요소 2 (Services)

spec.type=LoadBalancer 인 어떤 Service는 CiliumLoadBalancerIPPool Selector를 통해서 IP Pool에서 IP를 할당받을 수 있다.

LoadBalancerClass

동일 Cluster에 다수의 LoadBalancer가 있는 경우에 각각 다른 LoadBalancerClass로 선택할 수 있다.

Requesting IPs

Service는 특정 IP를 요청할 수 있다. 기존방식은 .spec.loadBalancerIP 의 단일 IP 주소를 사용하는 방식이지만 향후 Deprecated 될 예정 (≥ 1.24)

 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를 통해서 사용가능하다.

 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

충돌나지 않는 대역대를 확인하고 배포

⚠️ 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

alt text
https://isovalent.com/blog/post/migrating-from-metallb-to-cilium/#l2-announcement-with-arp

L2 Announcements는 온프레미스·캠퍼스처럼 BGP 라우팅이 없는 L2 네트워크에서 Kubernetes Service를 로컬 LAN에 “직접 가시화하고(ARP/NDP 응답), 도달 가능하게” 만드는 기능이다.

이 기능은 Service의 ExternalIP 혹은 LoadBalancer IP를 실제 노드 인터페이스에 바인딩하지 않고도, 선택된 노드가 해당 VIP에 대한 ARP/NDP 질의에 응답해 북-남향(North/South) 트래픽의 진입점이 되도록 설계되어있다.

해당 노드는 서비스 로드 밸런싱 기능으로 로드 밸런싱을 수행하여 north/south 로드 밸런서를 역할을 한다.

동작방식

특징과 장점

한계와 주의사항

설정 및 확인

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

정책 설정

제약 사항

 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: */*

Reference