이전 포스팅에서 이어집니다. 'On-Premise 환경에서 Kubernetes LoadBalancer 구현'을 읽고 오시길 권장드립니다.

On-Premise 환경에서 Kubernetes LoadBalancer 구현

CSP에서 제공하는 Kubernetes 서비스는 클라우드 인프라에 잘 통합되어 있어 있습니다. 그래서 간단한 명령어나 웹 UI를 통해 쉽게 클러스터를 생성할 수 있고 사용할 수 있습니다. 특히 Load Balancer

tech-recipe.tistory.com

문제의 발견

1. BGP 연결이 수상하다!

여러 경로 중 가장 좋은 경로만을 선택하는 BGP

  OPNsense에서 BGP의 경로를 확인해 보면 위와 같이 나타납니다. Kubernetes 워커노드 3대가 모두 Peer로 연결되어 있긴 하지만 그중 172.16.200.1에 도달하는 가장 좋은 경로는 192.168.200.31로 인식하고 있습니다.

Gateway가 하나로 설정되어 있는 것을 확인

 System > Route > Status에서 확인해 보아도 LB IP(172.16.200.1)에 대한 라우팅 게이트웨이가 192.168.200.31로 설정되어 있는 것을 확인할 수 있었습니다. 그래서 혹시나 하는 생각에 Hubble UI를 통해 트래픽 정보를 확인해 보았습니다.

2. 역시... 하나의 노드에만 몰리는 트래픽

Source IP가 10.0.2.101로 고정되어 있는 상태

 LB IP인 172.16.200.1을 호출하면 이를 Hubble UI에서 확인할 수 있습니다. 문제는 그 어떤 호출이라 할지라도 Source IP 주소가 항상 일정한 10.0.2.101로 출력된다는 것이었습니다. 도대체 저 IP 주소(10.0.2.101)가 무엇일까? Cilium Daemonset 중에 하나의 Cilium Host IP가 아닐까 의심이 들었습니다. OPNsense의 라우팅 정보에서 192.168.100.31(Worker 노드 1번)을 가리키고 있으니 해당 노드의 Cilium Host IP를 확인해 보았습니다.

#Cilium Daemonset Pod 확인
kubectl get pods -n kube-system -o wide | grep 192.168.200.31

cilium-envoy-57mks                        1/1     Running   0             25h   192.168.200.31   bgp-k8s-wkr-01    <none>           <none>
cilium-operator-78dc5dd875-25zdr          1/1     Running   1 (25h ago)   25h   192.168.200.31   bgp-k8s-wkr-01    <none>           <none>
cilium-shwwn                              1/1     Running   0             8h    192.168.200.31   bgp-k8s-wkr-01    <none>           <none>

#cilium-shwwn의 IP 주소 확인
kubectl exec -n kube-system cilium-shwwn -it -- ip addr

Defaulted container "cilium-agent" out of: cilium-agent, config (init), mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init), install-cni-binaries (init)
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:b1:7c:02 brd ff:ff:ff:ff:ff:ff
    altname enp3s0
    inet 192.168.200.31/24 brd 192.168.200.255 scope global ens160
       valid_lft forever preferred_lft forever
    inet6 fe80::250:56ff:feb1:7c02/64 scope link 
       valid_lft forever preferred_lft forever
3: cilium_net@cilium_host: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 76:95:31:5d:21:30 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::7495:31ff:fe5d:2130/64 scope link 
       valid_lft forever preferred_lft forever
4: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 42:13:c0:3a:eb:b5 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.101/32 scope global cilium_host          #예상대로 확인된 Source IP 주소
       valid_lft forever preferred_lft forever
    inet6 fe80::4013:c0ff:fe3a:ebb5/64 scope link 
       valid_lft forever preferred_lft forever
5: cilium_vxlan: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 22:20:cc:41:95:57 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::2020:ccff:fe41:9557/64 scope link 
       valid_lft forever preferred_lft forever
9: lxc3235ea9e75ca@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 42:59:2c:65:76:62 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::4059:2cff:fe65:7662/64 scope link 
       valid_lft forever preferred_lft forever
35: lxc_health@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 96:b2:38:45:b0:9f brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet6 fe80::94b2:38ff:fe45:b09f/64 scope link 
       valid_lft forever preferred_lft forever

 위 결과에서 볼 수 있듯, 192.168.200.31(Worker 노드 1번)의 Cilium Host의 주소가 10.0.2.101인 것을 확인할 수 있었습니다. 이것이 의미하는 바는 172.16.200.1로 향하는 트래픽이 모두 Worker 노드 1번으로 집중되었다가 다시 Pod를 찾아간다는 뜻이었습니다.
 물론 Kubernetes의 Service 레벨에서는 Endpoint인 Pod에 대한 로드밸런싱이 이루어지고는 있었지만, 노드로 접근하는 트래픽에 대한 로드밸런싱은 전혀 이루어지고 있지 않다는 뜻이었고, Endpoint Pod가 해당 노드에 존재하지 않으면 한번 더 트래픽이 이동해야 한다는 의미였습니다.

왜 이런 현상이 발생하나?

 문제의 원인은 BGP가 라우팅 프로토콜이라는 것입니다. 이게 무슨 말인가 하면, BGP는 특정 네트워크에 대해서 최적의 라우팅 경로를 찾는 알고리즘을 가지고 있는 것이지 부하를 분산하는 알고리즘을 가지고 있지는 않다는 것이었습니다. 그러니까 LB IP(172.16.200.1)로 향하는 다양한 경로에 대한 정보(192.168.200.31, 32, 33으로 향하면 172.16.200.1에 도달할 수 있음)가 전달되었으나 그중 가장 최적의 경로에 대해서 학습하고 나면 계속 그 경로만을 고집한다는 것입니다. 물론 192.168.200.3에 접근할 수 없게 된다면 다른 우회 경로를 찾겠지만 그전까지는 바뀌지 않습니다.

해결 방안 1 - 상위 라우터 ECMP 설정

 Cilium BGP Control Plane의 공식 문서를 살펴보면 아래와 같은 내용이 있습니다.

 When your upstream router supports Equal Cost Multi Path (ECMP), you can use this feature to load-balance traffic to the Service across multiple nodes by advertising the same virtual IPs from multiple nodes.

 상위 라우터, 즉 OPNsense에서 ECMP(Equal Cost Multi Path)를 지원하면 여러 노드에서 같은 IP 주소에 대한 경로를 제공하여 노드 레벨의 로드밸런싱이 가능하다는 것입니다. 실제로 OPNsense에서는 이 기능을 지원하고 있습니다.

1. OPNsense ECMP 활성화

net.route.mulipath 활성화
LB IP로의 Routing 경로가 다중으로 설정

 OPNsense의 웹 UI에서 System > Settings > Tunables로 이동합니다. 여기에서 net.route.multipath 항목을 검색합니다. 나타나는 메뉴를 연필모양 아이콘을 눌러 편집합니다. Value값의 기본은 0인데, 이것을 1로 바꿔주면 라우팅에서 Multipath를 사용할 수 있게 됩니다. 이를 통해 ECMP를 활성화할 수 있습니다.

! Warnning
Many routers have a limit on the number of ECMP paths they can hold in their routing table (Juniper). When advertising the Service VIPs from many nodes, you may exceed this limit. We recommend checking the limit with your network administrator before using this feature.

maximum-ecmp | Junos OS | Juniper Networks

Syntax maximum-ecmp next-hops; Hierarchy Level [edit chassis] Description MX Series) Configure 16, 32, or 64, and 128 ECMP next hops for RSVP or LDP LSPs, or MPLS static LSPs that are configured using set protocols mpls static-label-switched-path. This com

www.juniper.net

 단, Multipath의 수는 라우터에 따라 그 한계가 있습니다. Cilium의 공식 문서에도 이와 같은 내용에 대해서 언급하며 경고하고 있습니다. 여러 개의 노드에서 서비스 VIP를 광고하는 경우 그 한계를 초과할 수 있습니다. 따라서 이 기능을 사용하기 전에 네트워크 장비가 지원하는 최대 ECMP의 수를 확인해야 합니다.

2. OPNsense Maximum-paths 확인 및 설정

 OPNsense의 FRR에서는 ECMP 최대 경로수가 64개 입니다. 이를 Maximum-paths라고 하는데 이를 확인하고 설정하는 방법은 아래와 같습니다. 우선 System > Settings > Administration 메뉴에 접근합니다. Secure Shell 섹션에서 Enable Secure Shell을 선택하여 활성화합니다. Root Login과 Authentication Method를 모두 활성화 화여 root 사용자의 비밀번호 접근을 활성화합니다. 마지막으로 SSH 접근 대상이 되는 인터페이스를 선택합니다. 이렇게 하면 ssh로 OPNsense의 CLI 환경에 접근할 수 있습니다. 그러나 이는 보안상 권장하지 않으므로 설정을 끝내고 비활성화하는 것을 추천합니다.

OPNsense SSH 접근 설정

 이제 SSH를 사용하여 root@<SSH로 접근할 인터페이스 IP 주소>로 OPNsense에 접속합니다. 비밀번호를 입력하는것은 리눅스의 그것과 매우 흡사합니다. 로그인을 하고 나면 OPNsense를 최초로 설치했을 때 보았던 화면이 나옵니다.

  0) Logout                              7) Ping host
  1) Assign interfaces                   8) Shell
  2) Set interface IP address            9) pfTop
  3) Reset the root password            10) Firewall log
  4) Reset to factory defaults          11) Reload all services
  5) Power off system                   12) Update from console
  6) Reboot system                      13) Restore a backup

Enter an option: 8  #8 입력하여 Shell 실행

 '8'을 입력하여 Shell을 실행합니다. 그리고 아래 명령어를 입력합니다. 이 명령어들은 스위치의 콘솔에서 사용하는 명령어와 그 형식이 같습니다.

vtysh     #shell에서 이 명령어를 입력하면 FRR CLI 모드로 전환

Hello, this is FRRouting (version 8.5.7).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

configure terminal
route bgp <Router의 AS 번호>    #예) route bgp 65551
address-family ipv4 unicast
maximum-paths ?

#아래 처럼 출력
  (1-64)  Number of paths     #OPNsense의 경우 ECMP는 64개가 한계
  ibgp    iBGP-multipath
  
maximum-paths <1-64 사이 숫자 입력>    #예)maximum-paths 16
exit     #bgp router 설정에서 빠져나옴
do write     #설정 내용 저장
do show running-config     #설정 내용 확인

#아래 처럼 출력
Building configuration...

Current configuration:
!
frr version 8.5.7
frr defaults traditional
hostname <hostname>
log syslog notifications
!
router bgp 65551
 no bgp ebgp-requires-policy
 no bgp default ipv4-unicast
 neighbor 192.168.200.31 remote-as 65000
 neighbor 192.168.200.31 update-source vlan0.200
 neighbor 192.168.200.32 remote-as 65000
 neighbor 192.168.200.32 update-source vlan0.200
 neighbor 192.168.200.33 remote-as 65000
 neighbor 192.168.200.33 update-source vlan0.200
 !
 address-family ipv4 unicast
  neighbor 192.168.200.31 activate
  neighbor 192.168.200.32 activate
  neighbor 192.168.200.33 activate
  maximum-paths 16      #ECMP 최대 경로 수가 16으로 설정 됨
 exit-address-family
exit
!
end

 위 절차를 통해 OPNsense는 ECMP 경로의 최대 수가 64개 임을 확인할 수 있었고, 이를 설정하는 방법도 확인 하였습니다.

해결 방안 2 - 외부 Load Balancer 개발

 Kubernetes Load Balancer는 궁극적으로는 Node Port와 같습니다. 아래의 내용을 보시면 이해할 수 있을 것입니다. LoadBalancer 서비스이지만 Node Port를 노출하고 있고, <Kubernetes Noe IP>:<Node Port>로 호출하면 웹 서비스가 응답하는 것을 확인할 수 있습니다.

#Service 확인
kubectl describe svc test-service

Name:                     test-service
Namespace:                default
Labels:                   color=blue
Annotations:              <none>
Selector:                 app=test-app
Type:                     LoadBalancer
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.109.208.124
IPs:                      10.109.208.124
LoadBalancer Ingress:     172.16.200.1 (VIP)
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30199/TCP       #실제로 NodePort를 노출하고 있음
Endpoints:                10.0.0.184:80,10.0.5.16:80
Session Affinity:         None
External Traffic Policy:  Cluster
Internal Traffic Policy:  Cluster
Events:                   <none>

#Kubernetes노드에 NodePort로 호출 응답 확인
curl 192.168.200.31:30199

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
NodePort로 웹서비스에 접근이 가능

 이런 작동 원리를 이용하여 다음과 같은 Kubernetes에 통합된 외부 LB 서비스를 설계할 수 있을 것 같습니다.

외부 LB 아키텍처
  • Serivce의 상태를 감지하는 Operator를 Kubernetes에 배포
  • Loadbalancer 서비스가 생성되면 Operator가 이를 감지하고 인프라에 LB 구현체 프로비저닝을 요청
  • LB 구현체는 외부에서 접근할 수 있는 IP를 하나 부여받고 이를 Kubernetes LB Service의 EXTERNAL-IP와 바인딩
  • Operator은 LB Service의 Node Port를 감지하고 프로비저닝 된 LB 구현체에게 <Kubernetes 노드 IP>:<LB Servie Node Port>를 백앤드로 설정하도록 명령

 이러한 한계가 있음에도 불구하고 On-Premise 환경에서 Cilium BGP는 Kubernetes Loadbalaner를 구현하는데 매우 좋은 방법이 아닐까 합니다. 특별한 기능 개발이나, 물리적인 장비의 추가 없이도 기존 장비의 기능을 활용하여 Kubernetes의 가상 네트워크를 물리 네트워크로 확장하게 해주는 매우 유용한 기능이 아닐 수 없습니다.

이미지 출처: https://cilium.io/use-cases/bgp/

 CSP에서 제공하는 Kubernetes 서비스는 클라우드 인프라에 잘 통합되어 있어 있습니다. 그래서 간단한 명령어나 웹 UI를 통해 쉽게 클러스터를 생성할 수 있고 사용할 수 있습니다. 특히 Load Balancer 서비스에 대한 구현체라던가, CSI 뒤에서 작동하는 스토리지 프로바이더와 같은 Kubernetes에서는 직접적으로 그 기능을 제공하지는 않지만 꼭 필요한 요소들까지 함께 제공되어 사용의 편의성이 아주 높습니다.

 하지만 Kubernetes를 On-premise 환경에서 구현한다면 그 이야기가 달라집니다. 이런 요소 하나하나가 모두 관리의 포인트가 되기 때문입니다. 게다가 이를 Seamless 하게 통합하고 운영하는 것은 보통 일이 아닙니다. 그럼에도 불구하고 여러 가지 요인으로 인해 퍼블릭 클라우드의 Kubernetes 서비스를 사용할 수 없다면 이를 직접 구현할 수 있어야 합니다.

 이번 포스팅에서는 Kubernetes의 Service 타입 중 Load Balancer를 Cilium BGP Control Plane 기능을 통해 구현하는 방법에 대해서 소개하고자 합니다.

시작하기 전에

 On-Premise환경에 베어메탈, 또는 가상머신으로 구성된 Kubernetes 클러스터가 있어야 합니다. 또한 BGP 기능을 하는 라우터도 필요합니다. 이번 포스팅에서는 가상머신으로 구성된 Kubernetes 클러스터와 OPNsense의 BGP 기능을 기준으로 설명하도록 하겠습니다.

1. Kubernetes 클러스터

Host Name 역할 IP Address
bgp-k8s-ctrl-01 Kubernetes Control Plane Node 192.168.200.21
bgp-k8s-ctrl-02 Kubernetes Control Plane Node 192.168.200.22
bgp-k8s-ctrl-03 Kubernetes Control Plane Node 192.168.200.23
bgp-k8s-wkr-01 Kubernetes Worker Node / BGP Peer(AS 65000) 192.168.200.31
bgp-k8s-wkr-02 Kubernetes Worker Node / BGP Peer(AS 65000) 192.168.200.32
bgp-k8s-wkr-03 Kubernetes Worker Node / BGP Peer(AS 65000) 192.168.200.33
  • Kubernetes Version: 1.33.0
  • OS: Ubuntu 22.04
  • Control Plane Node 3대와 Worker Node 3대로 이루어진 구성
  • HAProxy를 통해 Kubernetes API 단일 진입점을 제공하고 있음(본 포스팅에서는 중요하지 않아 다루지 않음, 해당 내용 링크)
 

[Kubernetes] HAProxy와 Keepalived를 활용한 Kubernetes API 클러스터 HA 구현 - 1편

0. 들어가며 Kubernetes 클러스터는 크게 Master 노드(Control Plane 역할)와 Worker 노드(워크로드 구동 역할)로 나뉩니다. 프로덕션 환경에서는 고가용성(HA) 및 로드 밸런싱을 위해 Master 노드를 여러대로

tech-recipe.tistory.com

 

2. Cilium

  • Image version: 1.17.2
  • Helm chart version: 1.17.3
  • Cli version: 0.18.3

3. OPNsense

  • Version: OPNsense 25.1.5_5-amd64
  • OPNsense 설치 및 설정 포스팅 링크
  • BGP Peer IP(AS 65551): 192.168.200.1 (Kubernetes Node들의 Gateway IP 주소로 OPNsense의 인터페이스)
 

OPNsense로 방화벽 구축하기 [1편]

구축 배경 홈 랩을 확장하면서 가정용 Wi-Fi 네트워크와 홉 랩 인프라용 네트워크 간의 분리 및 격리, 라우팅 및 접근 제어 그리고 그 외 서비스에 필요한 기능 구현이 필요했습니다. 그래서 네트

tech-recipe.tistory.com

 

BGP의 기본 개념

 

BGP란 무엇인가? - 네트워킹의 Border Gateway Protocol 설명 - AWS

Border Gateway Protocol(BGP)은 인터넷에서 데이터를 전송하는 데 가장 적합한 네트워크 경로를 결정하는 일련의 규칙입니다. 인터넷은 표준화된 프로토콜, 디바이스 및 통신 기술을 통해 서로 연결된

aws.amazon.com

본격적인 실습에 들어가기 전에 BGP에 대해서 알아봅시다. BGP를 완벽하게 이해할 필요는 없지만 이번 실습에서 진행할 여러 과정에 꼭 필요한 개념 정도는 알고 가는것이 좋을 것 같습니다.

 BGP(Border Gateway Protocol)은 동적 라우팅 프로토콜의 한 종류입니다. 라우팅 정보를 공유하는 라우터를 그룹화 하고, 그룹 간에 게이트웨이를 두어 라우팅 정보를 교환하여 네트워크 연결을 만듭니다. 이 과정에서 라우터들은 최적의 경로에 대한 정보를 업데이트합니다.

1. AS(Autonomous System)

 BGP를 이야기하면 항상 등장하는 것이 바로 AS, 자율 시스템입니다. 이는 라우터의 그룹이라고 보면 되는데, 이 AS 안의 라우터들은 모두 서로의 라우팅 정보를 공유합니다. AS내의 각각의 라우터들은 서로의 라우팅 정보를 교환하여 네트워크 토폴로지를 인식합니다. 이를 통해 라우터 각자는 특정 네트워크로 향하는 최적의 경로를 계산합니다.

2. Border Gateway

 AS 간 경계에 있고 라우팅 경로를 교환하는 라우터를 Border Gateway라 합니다. 이를 통해 격리된 AS간에도 라우팅 정보를 교환할 수 있습니다. AS 내의 라우터들은 Border Gateway 라우터로 부터 전파되는 다른 AS의 라우팅 정보를 수신하고 네트워크 토폴로지를 업데이트 합니다. AS간 라우팅 정보 교환을 위해서 Border Gateway는 TCP/179 포트를 사용합니다.

네트워크 토폴로지

 이번 포스팅에서는 OPNsense와 Cilium이 각각 AS를 형성할 것입니다. Cilium은 Kubernetes에서 LoadBlancer Service가 생성되면 자신이 가지고 있는 LB IP Pool에서 IP를 하나 할당합니다. 그리고 이를 BGP를 통해 광고하고, OPNsense는 이것을 수신하여 다른 클라이언트가 접근할 수 있는 라우팅 정보를 제공합니다. 간략히 나타내면 아래와 같습니다.

Cilium BGP 토폴로지

  • Kubernetes에서 LoadBlancer Service 타입이 생성
  • Cilium이 이를 감지하고 CiliumLoadBalancerIPPool에서 IP를 할당
  • CiliumBGPAdvertisement가 생성된 LB IP를 AS 65000에 전파
  • AS 65000의 BGP Peer들이 목적지가 172.16.200.1인 패킷은 192.168.200.31, 192.168.200.32, 192.168.200.33으로 보내면 된다고 광고
  • 이를 수신한 AS 65551의 BGP Peer의 라우터(OPNsense)는 이 중 최적의 경로를 선정하여 라우팅 테이블을 업데이트
  • Client가 172.16.200.1에 대한 요청을 보내면 OPNsense에서 라우팅 정보 제공

Kubernetes Load Balancer 구현

 아래의 내용은 앞서 언급한 데로 Cilium이 배포된 Kubernetes Cluster와 kubectl 명령어, OPNsense가 이미 구성되어 있다는 가정하에 작성합니다.

1. Cilium CLI 설치

#리눅스 기준 아래 명령어 모두 복사하여 붙여넣고 실행
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}\

#설치 후 명령어가 잘 작동하는지 확인
cilium version

 이번 포스팅에서는 Cilium CLI를 통해 Cilium을 제어할 예정으로 CLI 설치가 필수입니다.

2. Cilium BGP 설정

#BPG Control Plane 활성화
cilium config set enable-bgp-control-plane true

#L2 announcements 활성화
cilium config set enable-l2-announcements true

#LB IPAM 활성화
cilium config set enable-lb-ipam true

 위 설정들을 통해 Cilium의 BGP 사용을 준비합니다. 다음은 Cilium BGP Control Plane Resource에 관한 설정입니다. 이 리소스들을 통해 Cilium BGP control plane을 유연하게 제어할 수 있습니다.

Cilium Control Plane Resource Diagram

2.1. BGP Cluster configuration

#bgp-cluster.yaml

apiVersion: cilium.io/v2alpha1
kind: CiliumBGPClusterConfig
metadata:
  name: cilium-bgp
spec:
  nodeSelector:
    matchLabels:
      cilium.io/bgp-instance: enable     #BGP instance가 될 노드를 Label로 지정
  bgpInstances:
    - name: instance-65000               #Cilium BGP의 AS 정보 설정
      localASN: 65000
      peers:
        - name: peer-opnsense
          peerASN: 65551                 #외부 AS 번호(이후 단계에서 OPNsense AS로 설정)
          peerAddress: 192.168.200.1     #외부 AS Peer(Border Gateway Router)의 IP 주소
          peerConfigRef:
            name: cilium-peer            #참조할 CiliumBGPPeerConfigf를 지정
  • CiliumBGPClusterConfig는 BGP Peer가 될 Kubernetes Node에 대한 명세
  • spec.nodeSelector.matchLabels의 Kye/Value의 값으로 BGP Peer가 될 Kubernetes Node 선택

2.2. Node Labels

#Kubernetes 워커노드에 Label 부여
kubectl label nodes bgp-k8s-wkr-01 bgp-k8s-wkr-02 bgp-k8s-wkr-03 cilium.io/bgp-instance=enable

2.3. BGP Peer Configuration

#bgp-peer.yaml

apiVersion: cilium.io/v2alpha1
kind: CiliumBGPPeerConfig
metadata:
  name: cilium-peer        #CiliumBGPClusterConfig에서 참조할 이름
spec:                      #BGP 관련 설정
  timers:
    connectRetryTimeSeconds: 5
    holdTimeSeconds: 9
    keepAliveTimeSeconds: 3
  ebgpMultihop: 1
  gracefulRestart:
    enabled: true
    restartTimeSeconds: 15
  families:                 #Peer가 광고할 네트워크 명세
    - afi: ipv4
      safi: unicast
      advertisements:       #참조할 CiliumBGPAdvertisement 이름
        matchLabels:
          advertise: bgp
  • spec.families는 Peer가 광고할 네트워크에 대한 정보가 명세됨
  • AFI: Address Family Identifier / SAFI: Subsequent Adress Family Identifier
  • 현재는 AFI/SAFI 옵션으로 {afi: ipv4, safi: unicast}{afi: ipv6, safi: unicast}만을 지원

2.4. BGP Advertisements

#bgp-advert.yaml

apiVersion: cilium.io/v2alpha1
kind: CiliumBGPAdvertisement
metadata:
  name: bgp-advertisements
  labels:
    advertise: bgp
spec:
  advertisements:
    - advertisementType: Service
      service:
        addresses:
          - LoadBalancerIP
      selector:
        matchLabels:
          color: blue     #Service 중에서 Label이 color=blue인 것 만을 BGP로 광고
  • BGP에 광고할 Kubernetes 네트워크에 대한 명세로 BGP Peer에서 이것을 참조
  • Pod CIDR이나 Service IP를 광고할 수 있음

2.5. LB IPAM

#blue-pool.yaml

apiVersion: cilium.io/v2alpha1
kind: CiliumLoadBalancerIPPool
metadata:
  name: blue-pool
spec:
  blocks:
    - cidr: "172.16.200.0/24"
  allowFirstLastIPs: "No"     #IP의 CIDR의 처음과 마지막 IP 주소를 사용하지 않는 옵션
  serviceSelector:
    matchLabels:
      color: blue             #Service의 Label이 color=blue이면서 LoadBalancer Type이면 Pool에서 IP를 할당
  • LoadBalancer 타입의 Service가 사용할 IP Pool을 관리
  • spec.allowFirstLastIPs 기본값은 Yes로 네트워크 혼란을 막기 위해 "No" 옵션을 권장

2.6. 설정 확인

 위에서 작성한 YAML을 모두 apply 한 후 잘 설정이 되었는지 확인이 필요합니다. 다음 명령어를 통해 확인할 수 있습니다.

#bgp peer 설정 확인
cilium bgp peers

#아래 처럼 출력
Node             Local AS   Peer AS   Peer Address    Session State   Uptime   Family         Received   Advertised
bgp-k8s-wkr-01   65000      65551     192.168.200.1   active          0s       ipv4/unicast   0          0    
bgp-k8s-wkr-02   65000      65551     192.168.200.1   active          0s       ipv4/unicast   0          0    
bgp-k8s-wkr-03   65000      65551     192.168.200.1   active          0s       ipv4/unicast   0          0

 우선 여기서 Session State가 active인 것은 현재 Cilium의 BGP가 작동하고 있다는 뜻입니다. 아직 OPNsense 쪽 BGP 설정을 하지 않았기 때문에 세션이 연결된 상태는 아닙니다.

3. OPNsense BGP 설정

3.1. FRR Plugin 설치

frr 플러그인 설치

  • OPNsense Web 콘솔 > System > Firmware > Plugins
  • frr 검색하여 설치

3.2. Routing 기능 설정

Routing 기능 활성화

  • Routing > General
  • 상단 Enable 체크 박스 활성화
  • Routing: General이 활성화되어야 라우팅 프로토콜이 작동됨

3.3. BGP 설정

BGP 기능 활성화

  • Routing > BGP > General 탭
  • enable 체크 박스 활성화 > BGP AS Number 65551(앞선 Cilium BGP Cluster Configuration에서 sepc.bgpInstances.peers.peerASN 값) 입력 > Apply

Neighbor 설정

  • Route > BGP > Neighbors 탭
  • [+] 버튼으로 Peer 추가
  • Description: Peer에 대한 설명으로 사람이 식별하기 좋은 정보 입력
  • Peer-IP: Kubernetes Worker 노드 IP 주소 입력
  • Rmote AS: CIlium BGP AS 번호 입력
  • Update-Source Interface: Kubernetes Worker 노드의 게이트웨이 인터페이스 선택
  • Kubernetes Worker 노드 모두 반복 작업
  • 우측 상단 서비스 재시작 버튼 클릭하여 BGP 서비스 재시작

 이렇게 BGP의 모든 설정을 마무리하였습니다. 이제 테스트 서비스를 만들어 잘 작동하는지 확인해 보도록 하겠습니다.


Load Balancer 테스트

 Nginx Deployment와 이를 노출할 LoadBlancer Service를 생성해 보겠습니다. 아래 YAML을 작성하여 apply 합니다.

#bgp-lb-test.yaml

#deployment
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        
#service
---
apiVersion: v1
kind: Service
metadata:
  name: test-service
  namespace: default
  labels:
    color: blue        #color=blue Label로 Cilium BGP에 적용 될 수 있도록 함
spec:
  type: LoadBalancer
  externalTrafficPolicy: Cluster
  loadBalancerClass: io.cilium/bgp-control-plane
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: test-app

1. Kubernetes 상태 확인

#Service 생성 확인
kubectl get svc

NAME           TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
kubernetes     ClusterIP      10.96.0.1        <none>         443/TCP        25h
test-service   LoadBalancer   10.109.208.124   172.16.200.1   80:30199/TCP   2m

#endpoint 확인
kubectl get endpointslice

NAME                 ADDRESSTYPE   PORTS   ENDPOINTS                                      AGE
kubernetes           IPv4          6443    192.168.200.21,192.168.200.22,192.168.200.23   25h
test-service-25qq7   IPv4          80      10.0.0.184,10.0.5.16                           2m
  • test-service의 EXTERNAL-IP에 172.16.200.1이 할당됨
  • CiliumLoadBalancerIPPool의 설정의 내용(spec.blocks.cidr: 172.16.200.0/24, spec.allowFirstLastIPs: "No")과 일치하는 것 확인할 수 있음

2. 브라우저에서 LB IP 접근 테스트

LB IP(172.16.200.1)에 접근 확인

3. Cilium CLI 확인

#Peer 확인
cilium bgp peers

Node             Local AS   Peer AS   Peer Address    Session State   Uptime     Family         Received   Advertised
bgp-k8s-wkr-01   65000      65551     192.168.200.1   established     7h46m36s   ipv4/unicast   1          2    
bgp-k8s-wkr-02   65000      65551     192.168.200.1   established     7h46m34s   ipv4/unicast   1          2    
bgp-k8s-wkr-03   65000      65551     192.168.200.1   established     7h46m35s   ipv4/unicast   1          2  

#Route 확인

cilium bgp routes available ipv4 unicast vrouter 65000
Node             VRouter   Prefix            NextHop   Age        Attrs
bgp-k8s-wkr-01   65000     172.16.200.1/32   0.0.0.0   7h49m21s   [{Origin: i} {Nexthop: 0.0.0.0}]   
bgp-k8s-wkr-02   65000     172.16.200.1/32   0.0.0.0   7h49m19s   [{Origin: i} {Nexthop: 0.0.0.0}]   
bgp-k8s-wkr-03   65000     172.16.200.1/32   0.0.0.0   7h49m20s   [{Origin: i} {Nexthop: 0.0.0.0}]
  • BGP Peer의 Session State가 active에서 established가 된 것을 확인할 수 있음

4. OPNsense 확인

BGP Route Diagnostics
Route Status

 이렇게 해서, Cilium BGP와 OPNsense를 활용하여 Kubernetes의 Load Balancer Service를 생성해 보았습니다. On-Premise 환경에서 특별한 솔루션을 사용하지 않는 경우에 Load Balancer를 사용할 수 있는 유용한 방법이라고 생각합니다.


참고 자료

1. Cilium BGP 공식 문서

2. OPNsense BGP 공식 문서

 

Dynamic Routing - BGP Tutorials — OPNsense documentation

Note Rules allowing traffic from LAN Router A to LAN Router B must be created in their respective LAN rulesets. Since traffic from LAN A to LAN B will use the peering connection, additional rules must be created in the Peering ruleset. Create rules to allo

docs.opnsense.org


문제 발생! BGP의 한계?

 

Cilium BGP로 구성된 LoadBalancer의 한계

이전 포스팅에서 이어집니다. 'On-Premise 환경에서 Kubernetes LoadBalancer 구현'을 읽고 오시길 권장드립니다. On-Premise 환경에서 Kubernetes LoadBalancer 구현CSP에서 제공하는 Kubernetes 서비스는 클라우드 인

tech-recipe.tistory.com

+ Recent posts