Bandwidth Manager

Bandwidth Manager (대역폭 관리자)

Cilium의 bandwidth manager는 EDT(Earliest Departure Time)와 eBPF를 이용해 TCP·UDP 워크로드를 최적화하고, 필요하면 개별 Pod의 트래픽을 효율적으로 rate limit하는 기능이에요. Pod에 대한 BBR 혼잡 제어를 활성화하기 위한 전제 조건이기도 해요.

출처: Bandwidth Manager

본문

이 가이드는 Cilium의 bandwidth manager를 구성해 TCP·UDP 워크로드를 최적화하고, 필요하면 EDT(Earliest Departure Time)와 eBPF의 도움으로 개별 Pod를 효율적으로 rate limit하는 방법을 설명해요. Cilium의 bandwidth manager는 아래에서 설명하듯 Pod에 BBR 혼잡 제어를 활성화하기 위한 전제 조건이기도 해요.

Bandwidth manager는 CNI chaining에 의존하지 않고 Cilium에 네이티브로 통합돼요. 따라서 bandwidth CNI 플러그인을 사용하지 않아요. 특히 multi-queue 네트워크 인터페이스의 확장성 우려 때문에, EDT가 아닌 TBF(Token Bucket Filter) 기반인 bandwidth CNI 플러그인은 사용을 권장하지 않아요.

Note Bandwidth Manager는 BPF Host Routing과 함께 사용하는 것을 강력히 권장해요. 그렇지 않으면 upper stack을 통한 레거시 라우팅이 원치 않는 높은 지연 시간을 초래할 수 있어요 (자세한 내용은 이 비교 참고).

Cilium의 bandwidth manager는 kubernetes.io/egress-bandwidth와 kubernetes.io/ingress-bandwidth Pod 어노테이션을 모두 지원해요. egress-bandwidth는 네이티브 호스트 네트워킹 디바이스에서 EDT(Earliest Departure Time)로 이그레스에서 집행되고, ingress-bandwidth는 eBPF 기반 token bucket 구현으로 집행돼요. 대역폭 집행은 Cilium의 direct routing과 tunneling 모드 모두에서 지원돼요.

Helm 저장소를 설정하세요:

Helm Repository OCI Registry

helm repo add cilium https://helm.cilium.io/

Cilium 차트는 OCI 레지스트리(Quay.io 및 Docker Hub)에서도 사용할 수 있어요. 설정이 필요 없으며 oci:// URL로 직접 설치할 수 있어요.

차트 서명 검증과 digest 기반 설치를 포함한 자세한 내용은 OCI Registry section을 참고하세요.

Cilium의 bandwidth manager는 새 설치에서 기본적으로 비활성화되어 있어요. bandwidth manager를 활성화해 Cilium을 설치하려면 다음을 실행하세요:

Helm Repository OCI Registry

helm install cilium cilium/cilium --version 1.20.2 \
   --namespace kube-system \
   --set bandwidthManager.enabled=true
helm install cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
   --namespace kube-system \
   --set bandwidthManager.enabled=true

기존 설치에서 bandwidth manager를 활성화하려면 다음을 실행하세요:

Helm Repository OCI Registry

helm upgrade cilium cilium/cilium --version 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set bandwidthManager.enabled=true
kubectl -n kube-system rollout restart ds/cilium
helm upgrade cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set bandwidthManager.enabled=true
kubectl -n kube-system rollout restart ds/cilium

네이티브 호스트 네트워킹 디바이스는 호스트에 기본 라우트가 있거나 Kubernetes InternalIP 또는 ExternalIP가 할당된 네이티브 디바이스로 자동 감지돼요. 둘 다 존재하면 InternalIP가 ExternalIP보다 우선해요. 디바이스를 변경해 수동으로 지정하려면 devices helm 옵션에 이름을 설정하세요 (예: devices='eth0,eth1,eth2'). 나열된 각 디바이스는 모든 Cilium 관리 노드에서 같은 이름이어야 해요.

Cilium Pod가 올바르게 떠올랐는지 확인하세요:

$ kubectl -n kube-system get pods -l k8s-app=cilium
NAME                READY     STATUS    RESTARTS   AGE
cilium-crf7f        1/1       Running   0          10m
cilium-db21a        1/1       Running   0          10m

Cilium에서 bandwidth manager 기능이 활성화됐는지 확인하려면 cilium status CLI 명령이 BandwidthManager 정보 줄을 통해 가시성을 제공해요. 또한 egress 대역폭 제한이 집행되는 디바이스 목록도 출력해요:

$ kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep BandwidthManager
BandwidthManager:       EDT with BPF [BBR] [eth0]

대역폭 제한이 실제로 집행되는지 확인하려면 서로 다른 노드에 두 개의 netperf Pod를 배포할 수 있어요:

---
apiVersion: v1
kind: Pod
metadata:
  annotations:
    # Limits egress bandwidth to 10Mbit/s and ingress bandwidth to 20Mbit/s.
    kubernetes.io/egress-bandwidth: "10M"
    kubernetes.io/ingress-bandwidth: "20M"
  labels:
    # This pod will act as server.
    app.kubernetes.io/name: netperf-server
  name: netperf-server
spec:
  containers:
  - name: netperf
    image: cilium/netperf
    args:
    - iperf3
    - "-s"
    ports:
    - containerPort: 5201
---
apiVersion: v1
kind: Pod
metadata:
  # This Pod will act as client.
  name: netperf-client
spec:
  affinity:
    # Prevents the client from being scheduled to the
    # same node as the server.
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app.kubernetes.io/name
            operator: In
            values:
            - netperf-server
        topologyKey: kubernetes.io/hostname
  containers:
  - name: netperf
    args:
    - sleep
    - infinity
    image: cilium/netperf

실행된 후 netperf-client Pod로 netperf-server Pod의 대역폭 집행을 테스트할 수 있어요. 먼저 egress 대역폭을 테스트하세요:

$ NETPERF_SERVER_IP=$(kubectl get pod netperf-server -o jsonpath='{.status.podIP}')
$ kubectl exec netperf-client -- \
   iperf3 -R -c "${NETPERF_SERVER_IP}"
   Connecting to host 10.42.0.52, port 5201
   Reverse mode, remote host 10.42.0.52 is sending
   [  5] local 10.42.1.23 port 49422 connected to 10.42.0.52 port 5201
   [ ID] Interval           Transfer     Bitrate
   [  5]   0.00-1.00   sec  1.19 MBytes  9.99 Mbits/sec
   [  5]   1.00-2.00   sec  1.17 MBytes  9.77 Mbits/sec
   [  5]   2.00-3.00   sec  1.10 MBytes  9.26 Mbits/sec
   [  5]   3.00-4.00   sec  1.17 MBytes  9.77 Mbits/sec
   [  5]   4.00-5.00   sec  1.17 MBytes  9.77 Mbits/sec
   [  5]   5.00-6.00   sec  1.10 MBytes  9.26 Mbits/sec
   [  5]   6.00-7.00   sec  1.17 MBytes  9.77 Mbits/sec
   [  5]   7.00-8.00   sec  1.10 MBytes  9.26 Mbits/sec
   [  5]   8.00-9.00   sec  1.17 MBytes  9.77 Mbits/sec
   [  5]   9.00-10.00  sec  1.10 MBytes  9.26 Mbits/sec
   - - - - - - - - - - - - - - - - - - - - - - - - -
   [ ID] Interval           Transfer     Bitrate         Retr
   [  5]   0.00-10.09  sec  14.1 MBytes  11.7 Mbits/sec    0             sender
   [  5]   0.00-10.00  sec  11.4 MBytes  9.59 Mbits/sec                  receiver

보시다시피 netperf-server Pod의 egress 트래픽이 초당 10Mbit로 제한됐어요. 그다음 ingress 대역폭을 테스트하세요.

$ NETPERF_SERVER_IP=$(kubectl get pod netperf-server -o jsonpath='{.status.podIP}')
$ kubectl exec netperf-client -- \
   iperf3 -c "${NETPERF_SERVER_IP}"
   Connecting to host 10.42.0.52, port 5201
   [  5] local 10.42.1.23 port 40058 connected to 10.42.0.52 port 5201
   [ ID] Interval           Transfer     Bitrate         Retr  Cwnd
   [  5]   0.00-1.00   sec  6.73 MBytes  56.4 Mbits/sec  551   25.9 KBytes
   [  5]   1.00-2.00   sec  3.56 MBytes  29.9 Mbits/sec  159   8.19 KBytes
   [  5]   2.00-3.00   sec  2.45 MBytes  20.6 Mbits/sec  191   2.73 KBytes
   [  5]   3.00-4.00   sec  1.17 MBytes  9.77 Mbits/sec  170   34.1 KBytes
   [  5]   4.00-5.00   sec  2.39 MBytes  20.1 Mbits/sec  224   8.19 KBytes
   [  5]   5.00-6.00   sec  2.45 MBytes  20.6 Mbits/sec  274   6.83 KBytes
   [  5]   6.00-7.00   sec  2.39 MBytes  20.1 Mbits/sec  170   2.73 KBytes
   [  5]   7.00-8.00   sec  2.45 MBytes  20.6 Mbits/sec  262   5.46 KBytes
   [  5]   8.00-9.00   sec  2.45 MBytes  20.6 Mbits/sec  260   5.46 KBytes
   [  5]   9.00-10.00  sec  2.42 MBytes  20.3 Mbits/sec  210   32.8 KBytes
   - - - - - - - - - - - - - - - - - - - - - - - - -
   [ ID] Interval           Transfer     Bitrate         Retr
   [  5]   0.00-10.00  sec  28.5 MBytes  23.9 Mbits/sec  2471             sender
   [  5]   0.00-10.04  sec  25.6 MBytes  21.4 Mbits/sec                  receiver

보시다시피 netperf-server Pod의 ingress 트래픽이 초당 20Mbit로 제한됐어요.

BPF 쪽에서 현재 엔드포인트 대역폭 설정을 들여다보려면 다음 명령을 실행할 수 있어요 (cilium-xxxxx를 netperf-server Pod와 같은 위치에 있는 Cilium Pod 이름으로 바꾸세요):

$ kubectl exec -it -n kube-system cilium-xxxxxx -- cilium-dbg bpf bandwidth list
IDENTITY   DIRECTION   PRIO   BANDWIDTH (BitsPerSec)
724        Egress      0      10M
724        Ingress     0      50M

각 Pod는 Cilium에서 identity를 가진 Endpoint로 표현돼요. 위 identity는 cilium-dbg endpoint list 명령과 연관시킬 수 있어요.

Note 대역폭 제한은 Pod 단위 범위로 적용돼요. 예제에서 Pod의 replica를 여러 개 만들면 각 Pod 인스턴스가 10M 대역폭 제한을 받아요.

Pod용 BBR (BBR for Pods)

Cilium의 bandwidth manager가 제공하는 MQ/FQ 설정 기반 인프라는 Pod에 TCP BBR congestion control을 사용하는 것도 허용해요.

BBR은 특히 Pod가 인터넷의 외부 클라이언트를 마주하는 Kubernetes Services 뒤에 노출될 때 적합해요. BBR은 인터넷 트래픽에 대해 더 높은 대역폭과 더 낮은 지연 시간을 달성해요. 예를 들어 BBR의 처리량은 현재 최고의 loss-based 혼잡 제어보다 최대 2,700배 높고 queueing 지연은 25배 낮을 수 있다고 보여졌어요.

Note Pod용 BBR은 v5.18.x 이상의 Linux 커널이 필요해요.

BBR 혼잡 제어와 함께 bandwidth manager를 활성화하려면 다음으로 배포하세요:

Helm Repository OCI Registry

helm upgrade cilium cilium/cilium --version 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set bandwidthManager.enabled=true \
   --set bandwidthManager.bbr=true
kubectl -n kube-system rollout restart ds/cilium
helm upgrade cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set bandwidthManager.enabled=true \
   --set bandwidthManager.bbr=true
kubectl -n kube-system rollout restart ds/cilium

Pod에서 BBR이 안정적으로 동작하려면 5.18 이상 커널이 필요해요. Linux Plumbers 2021 발표에서 설명했듯이, 이전 커널은 네트워크 패킷이 Pod 네트워크 네임스페이스에서 호스트 네트워크 네임스페이스로 전환될 때 패킷의 타임스탬프를 유지하지 않기 때문에 필요해요. 그 때문에 커널의 pacing 인프라가 일반적으로 제대로 동작하지 않아요 (Cilium 특정 문제가 아님).

우리는 최신 커널에서 이 문제를 수정해 타임스탬프를 유지하고 Pod용 BBR이 동작하도록 도왔어요. 그 커널 이전에는 BBR이 초기 네트워크 네임스페이스(hostns)에 있는 소켓에서만 동작했어요. BBR은 패킷이 호스트 네임스페이스의 물리 디바이스에서 FQ queueing discipline에 도달할 때까지 네트워크 패킷의 소켓 연관을 유지하기 위해 eBPF Host-Routing도 필요해요. (eBPF Host-Routing이 없으면 패킷의 소켓 연관이 호스트 스택의 forwarding/routing 계층 안에서 고아가 될 거예요.)

Cilium에서 BBR을 포함한 bandwidth manager가 활성화됐는지 확인하려면 cilium status CLI 명령이 BandwidthManager 정보 줄을 통해 가시성을 다시 제공해요:

$ kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep BandwidthManager
BandwidthManager:       EDT with BPF [BBR] [eth0]

이 설정이 활성화되면 새로 생성되는 모든 Pod의 기본값으로 BBR을 사용해요. 이상적으로는 클러스터의 모든 노드와 Pod가 동질적으로 BBR을 사용하도록 클러스터 생성 시 첫 Cilium 설치에서 BBR을 선택하는 것이 좋아요. 그렇지 않으면 여전히 CUBIC을 사용하는 다른 연결에 대해 잠재적인 불공정 문제가 있을 수 있어요. 또한 BBR의 probing 특성 때문에 CUBIC에 비해 더 높은 TCP 재전송률을 관찰할 수 있다는 점에 유의하세요.

BBR은 특히 Pod가 인터넷에서 연결하는 외부 클라이언트를 서빙하는 Service로 노출되는 클러스터에서 사용하는 것을 권장해요.

호스트용 BBR (BBR for The Host)

레거시 라우팅 모드에서는 위에서 언급한 이유로 Cilium 관리 pod(hostNetwork: false)에 BBR을 활성화할 수 없어요. 하지만 bandwidthManager.bbrHostNamespaceOnly=true 플래그를 추가하면 오직 호스트 네트워크 네임스페이스에만 BBR을 활성화할 수 있어요.

Helm Repository OCI Registry

helm upgrade cilium cilium/cilium --version 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set bandwidthManager.enabled=true \
   --set bandwidthManager.bbr=true \
   --set bandwidthManager.bbrHostNamespaceOnly=true
kubectl -n kube-system rollout restart ds/cilium
helm upgrade cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
   --namespace kube-system \
   --reuse-values \
   --set bandwidthManager.enabled=true \
   --set bandwidthManager.bbr=true \
   --set bandwidthManager.bbrHostNamespaceOnly=true
kubectl -n kube-system rollout restart ds/cilium

bandwidthManager.bbrHostNamespaceOnly를 사용하면 호스트 네트워크 네임스페이스의 프로세스( hostNetwork를 true로 설정한 Pod 포함)가 BBR을 사용해요.

제한 사항 (Limitations)

  • 대역폭 집행은 현재 L7 Cilium Network Policies와 함께 동작하지 않아요. 이들이 egress에서 Pod를 선택하면 해당 Pod들에 대한 대역폭 집행이 비활성화돼요.
  • 대역폭 집행은 Kind 같은 중첩 네트워크 네임스페이스 환경에서 동작하지 않아요. 이러한 환경은 일반적으로 /proc/sys/net/core의 전역 sysctl에 접근할 수 없고, 대역폭 집행이 그에 의존하기 때문이에요.

Video Cilium bandwidth manager에 대한 더 많은 인사이트는 KubeCon talk on Better Bandwidth Management with eBPF와 eCHO episode 98: Exploring the bandwidth manager with Cilium을 확인하세요.

더 알아보기 (Learn more)