[네떡스터디🔥kans] Calico CNI 알아보기 - BGP

 

CloudNet@ 가시다님이 진행하는 쿠버네티스 네트워크 스터디 KANS 3기 내용을 정리한 글입니다.

 

Calico는 BGP를 활용해 네트워크 토폴로지를 관리하고, 각 노드 간 경로 정보를 효율적으로 교환합니다. 이를 통해 클러스터 내 노드가 자동으로 라우팅 정보를 갱신하고, 네트워크 확장 시 복잡한 설정 없이도 트래픽 흐름을 최적화할 수 있습니다. 특히 라우트 리플렉터 기능을 사용하면, 전체 네트워크의 피어링 수를 줄여 트래픽 부하를 줄이고 성능을 향상시킬 수 있습니다.

 

Full Mesh (✰ Node-to-Node 기본 설정)

Node-to-Node 메시 설정은 Calico에서 기본적으로 모든 노드가 서로 직접 BGP 피어링을 맺어 경로 정보를 교환하는 방식입니다. 이 설정은 클러스터 내의 모든 노드들이 다른 노드와 BGP 세션을 형성하여 네트워크 경로를 공유합니다. 이 방식은 소규모 클러스터에서는 간단하고 유용하지만, 노드 수가 많아질수록 각 노드가 설정해야 하는 피어링의 수가 기하급수적으로 증가하여 네트워크 부하가 커집니다.

 

node-to-node mesh 기본 설정

 

node-to-node로 full mesh 피어링이 되어있는 모습니다.

node-to-node 설정

 

Peer Type 

Calico에서는 BGP 피어를 설정할 때 두 가지 범위를 사용할 수 있습니다: 글로벌 피어노드별 피어입니다. 쉽게 말해, 이 범위는 어떤 노드가 해당 피어와 연결할지 결정합니다.

Global 피어

글로벌 피어는 클러스터 내 모든 노드가 해당 피어와 BGP 연결을 시도하는 경우입니다. 설정할 때는 node나 nodeSelector 필드를 생략하면 됩니다. 이 방식은 전체 네트워크에 걸쳐 동일한 피어링을 적용하고 싶을 때 유용합니다. 예를 들어, 외부 라우터나 주요 네트워크 노드와 모든 클러스터 노드가 연결되도록 설정할 때 사용합니다.

노드 별 피어 (Node-specific 피어)

반면, 노드별 피어는 특정 노드 또는 특정 레이블을 가진 노드들만 BGP 피어링을 시도하도록 할 수 있습니다. 이를 설정하려면 node 필드를 사용해 특정 노드를 지정하거나, nodeSelector 필드를 사용해 특정 조건에 맞는 노드들만 피어링하도록 설정할 수 있습니다. 예를 들어, 일부 노드만 특정 네트워크 장비와 연결하고 싶을 때 유용합니다.

 

클러스터 내에서 노드 간 BGP 세션을 직접 설정하고 싶다면, 기본적으로 활성화되어 있는 노드 간 메시(Node-to-Node Mesh)를 비활성화해야 합니다. 이를 위해 BGPConfiguration 리소스에서 nodeToNodeMeshEnabled 값을 false로 설정해야 합니다. 이렇게 하면, BGPPeer 리소스를 사용하여 각 노드 간의 BGP 연결을 세부적으로 관리할 수 있습니다.

 

 

Route Reflector 설정해보기

Route Reflector (RR)이란

기본적으로 BGP는 모든 노드가 서로 피어링(Peering)을 맺어야 하므로, 노드 수가 많아지면 피어링 연결의 수가 기하급수적으로 증가합니다. 예를 들어, 10개의 노드가 있으면 각 노드는 다른 9개 노드와 BGP 피어링을 설정해야 합니다. 100개의 노드가 있다면 어떨까요? 각 노드가 99개 노드와 피어링을 맺어야 하므로, 피어링의 수가 급격히 증가하고, 이로 인해 네트워크 트래픽과 자원 사용량도 커지게 됩니다.

라우트 리플렉터는 이런 상황을 해결하기 위해 존재합니다. 라우트 리플렉터는 특정 노드가 모든 BGP 경로 정보를 받아서 다른 노드에게 전달하는 역할을 합니다. 

 

 

 

Route Reflector 구조 로 만들어보기

route reflector 설정할 노드 선정

일반적으로 OnPremise 환경에서는 RR이 될 노드는 렉별로 하나씩 선정하는게 기본인 것 같습니다.

테스트에서는 단순하게 aws에서 구성할 예정이기 때문에 임의의 노드를 하나 선정해서 진행해보도록 하겠습니다.

선정 된 노드는 Cluster ID라는 라우트 리플렉터가 클러스터 내에서 다른 리플렉터들과 라우팅 정보를 교환할 때 필요한 고유 식별자를 붙여야 합니다.

# annotations으로 진행할 때
kubectl annotate node k8s-w0 projectcalico.org/RouteReflectorClusterID=244.0.0.1

# calicoctl을 이용할 
calicoctl patch node k8s-w0 -p '{"spec": {"bgp": {"routeReflectorClusterID": "244.0.0.1"}}}'

 

 

🚨 routeReflectorClusterID를 노드의 스펙에 추가하면 해당 노드는 즉시 노드 간 BGP 메시에서 제거되며, 기존 BGP 세션이 종료됩니다. 새로운 BGP 피어링이 추가되면 새로운 BGP 세션이 생성되지만, 이 과정에서 약 2초간 데이터플레인 트래픽이 중단될 수 있습니다. 아래의 사진의 선정 전과 후의 BGP 세션 정보를 확인해볼 수 있습니다.

 

route reflector로 선정 전

 

route reflector로 노드를 지정 후

 

 

이 이후에 추가적으로 bgp peer를 쉽게 맺기 위한 라벨을 노드에 추가로 붙입니다.

kubectl label node k8s-w0 route-reflector=true

 

BGP 피어링 설정

기본적으로는 BGP 피어와 관련한 설정은 없습니다.

 

여기에 라우터 리플렉터와 BGP 피어를 맺는다는 설정을 넣어보도록 하겠습니다. 해당 설정은 calicoctl로 추가할 수 있습니다. (kubectl 불가) 

 

✔︎ Global 타입으로 피어 맺기 

아래의 설정은 route-reflector 라벨이 없는 노드들이 route-reflector와 피어를 맺는 설정 하나와 route-reflector 라벨이 붙은 노드들끼리 피어링을 맺는 설정으로 나뉘어있습니다. 이렇게 두개로 나눈 경우 좀더 세분화한 피어링 설정을 할 수 있습니다. 

kind: BGPPeer
apiVersion: projectcalico.org/v3
metadata:
  name: node-peer-to-rr
spec:
  nodeSelector: !has(route-reflector)
  peerSelector: has(route-reflector)
---
kind: BGPPeer
apiVersion: projectcalico.org/v3
metadata:
  name: rr-to-rr-peer
spec:
  nodeSelector: has(route-reflector)
  peerSelector: has(route-reflector)

 

 

✔︎ 노드 별 피어 맺기 

아래의 설정은 모든 노드들이 route reflector라벨이 달린 노드와만 피어를 맺는 설정입니다.

kind: BGPPeer
apiVersion: projectcalico.org/v3
metadata:
  name: peer-with-route-reflectors
spec:
  nodeSelector: all()
  peerSelector: route-reflector == 'true'

 

이제 설정을 추가하면 다음과 같이 세션정보가 변경되게 됩니다. 

 

BGP 설정

마지막으로 node-to-node BGP메시 설정을 끄면 됩니다. 가이드에는 default의 설정을 변경하라고나와있으나

최신 3.28 버전의 calico 설치시에는 default설정이 존재하지 않기 때문에 새로 생성해야합니다.

 

다음의 설정을 calicoctl을 통해 설정해보도록 하겠습니다.

apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
  name: default
spec:
  logSeverityScreen: Info
  nodeToNodeMeshEnabled: false

 

노드 간 BGP 세션 확인

최종적으로 bgp 피어링이 RR로 설정되어있는 노드만 남아있는 것을 확인할 수 있습니다. 

 

 

위에서 했던 대로 annotation으로 cluster ID를 붙여주고 노드에 route reflector라는 라벨을 붙여주니 다음과 같이 BGP 설정이 되는 것을 확인할 수 있습니다.

 

통신 테스트

전체 호스트 노드에 nginx 파드를 띄워서 각 파드에서 통신이 잘 되는지 확인해보도록 한다. 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - nginx
              topologyKey: "kubernetes.io/hostname"
      containers:
      - image: nginx
        name: nginx

 

노드에 잘 띄워졌는지 확인해보도록 하겠습니다.

kubectl get pods -o custom-columns=NAME:.metadata.name,READY:.status.containerStatuses[0].ready,STATUS:.status.phase,IP:.status.podIP,NODE:.spec.nodeName

 

 

direct routing이 설정되어있기 때문에 노드에서 통신이 가능하다. 노드에서 호출해보았을때 잘 통신되는 것을 확인할 수 있습니다. 혹시 노드의 대역이 다른게 있다면 대역이 다른 노드끼리는 CrossSubnet(IPIP)모드로 통신되도록 설정해두어야 합니다.  

node_name=<노드 이름 설정>
pod_ip=<파드 아이피 설정>
echo "[$(date '+%H:%M')][$node_name] Response Code: $(curl -o /dev/null -s -w "%{http_code}" http://$pod_ip)"
[k8s-w0] Response Code: 200
[k8s-w1] Response Code: 200
[k8s-w2] Response Code: 200

 

운영환경에서 변경하기 (skip)

운영환경에서 변경하기 위한 가이드는 다음의 링크를 참고하시길 바랍니다.

https://docs.tigera.io/calico/latest/networking/configuring/bgp#change-from-node-to-node-mesh-to-route-reflectors-without-any-traffic-disruption

 

Configure BGP peering | Calico Documentation

Configure BGP peering with full mesh, node-specific peering, ToR, and/or Calico route reflectors.

docs.tigera.io