지금까지 우리는 Cilium의 다양한 네트워킹 및 보안 기능을 학습하며 강력한 쿠버네티스 클러스터를 구축하는 방법을 익혔습니다. 하지만 클러스터의 규모가 수백, 수천 개의 노드로 확장되고 수만 개의 파드가 동작하는 대규모 환경이 되면, 우리는 새로운 도전에 직면하게 됩니다. 바로 '성능' 입니다. 응답이 느려지는 API 서버, 원인 모를 파드 생성 실패, 간헐적인 네트워크 드롭 등은 클러스터의 안정성을 심각하게 위협할 수 있습니다.
이번 7주차 스터디에서는 대규모 클러스터에서 발생할 수 있는 다양한 성능 병목 지점을 진단하고, 이를 해결하기 위한 구체적인 튜닝 전략을 심층적으로 다루고자 합니다. Kubernetes 컨트롤 플레인의 심장인 kube-apiserver와 etcd의 동작 원리부터 Cilium 데이터 플레인의 핵심인 eBPF Map 관리까지, 성능 최적화의 여정을 함께 떠나보겠습니다.

1. Kubernetes 성능 병목 현상 탐구: API 서버는 왜 느려지는가?
클러스터의 모든 상태 변경은 kube-apiserver를 통해 etcd에 기록됩니다. 따라서 클러스터의 규모가 커질수록 컨트롤 플레인의 부하는 기하급수적으로 증가하며, 이곳에서 가장 먼저 성능 문제가 발생합니다. 이러한 문제를 재현하고 분석하기 위해 kube-burner와 같은 부하 테스트 도구를 활용할 수 있습니다.
부하 테스트를 통해 발견된 병목 지점
kube-burner를 사용하여 수백 개의 파드를 동시에 생성하는 시나리오를 시뮬레이션하면 다음과 같은 대표적인 문제들을 마주하게 됩니다.
- Too many pods (노드 당 파드 개수 제한): Kubelet은 maxPods 설정값에 따라 노드 당 생성 가능한 파드의 최대 개수를 제한합니다. 이 한계를 초과하면 스케줄러는 더 이상 해당 노드에 파드를 할당하지 못하고 FailedScheduling 이벤트를 발생시킵니다.
- No IP addresses available (PodCIDR 고갈): 각 노드는 PodCIDR이라는 고유한 IP 대역을 할당받아 자신이 관리하는 파드들에게 IP를 순차적으로 부여합니다. 만약 maxPods를 늘리더라도 할당된 PodCIDR 대역 내의 IP를 모두 소진하면, CNI 플러그인은 FailedCreatePodSandBox 에러를 내며 파드 생성을 실패시킵니다.
이러한 현상들은 표면적인 문제일 뿐, 그 근본 원인은 대량의 리소스 생성 요청이 컨트롤 플레인에 집중될 때 발생하는 부하와 관련이 깊습니다.
근본 원인: 대규모 LIST 요청과 메모리 폭증
쿠버네티스 컨트롤러(kubelet, CNI 에이전트 등)들은 클러스터의 최신 상태를 유지하기 위해 API 서버에 주기적으로 리소스 목록을 요청(LIST API Call)합니다. 문제는 클러스터에 수만 개의 파드가 존재할 때 발생합니다.
- 동작 방식: 클라이언트가 kubectl get pods --all-namespaces와 같은 명령을 실행하면, kube-apiserver는 etcd로부터 모든 파드 정보를 가져와야 합니다. 이때 etcd는 트랜잭션의 일관성을 보장하기 위해 특정 시점의 데이터를 메모리에 복제한 후 응답합니다. kube-apiserver는 이 데이터를 받아 다시 Go 구조체로 변환(deserialization)하고, 최종적으로 클라이언트가 요청한 형식(JSON, YAML 등)으로 변환(serialization)하여 전달합니다.
- 결과: 이 과정에서 etcd와 kube-apiserver 양쪽 모두에서 응답 데이터 크기의 몇 배에 달하는 막대한 메모리가 일시적으로 할당됩니다. 만약 이러한 대규모 LIST 요청이 여러 클라이언트로부터 동시에 발생하면, 컨트롤 플레인 컴포넌트는 메모리 사용량이 급증하여 OOM(Out of Memory)으로 종료될 수 있습니다. 이는 클러스터 전체의 가용성을 위협하는 심각한 장애로 이어집니다.
2. Kubernetes 성능 튜닝 전략
컨트롤 플레인의 안정성을 확보하기 위해서는 API 요청을 효율적으로 관리하고 인프라 전반의 설정을 최적화하는 다각적인 접근이 필요합니다.
API 요청 관리 기법
- 페이지네이션 (Limit & Continue): 대규모 리소스를 조회하는 클라이언트는 반드시 limit과 continue 파라미터를 사용하여 응답을 여러 페이지로 나누어 요청해야 합니다. 이는 한 번의 요청으로 인한 메모리 부담을 줄이는 가장 기본적인 방법입니다. kubectl을 포함한 대부분의 공식 클라이언트는 이 방식을 기본적으로 사용합니다.
- API 서버 캐시 활용 (ResourceVersion="0"): LIST 요청 시 resourceVersion="0" 파라미터를 사용하면, kube-apiserver는 etcd에 직접 요청하는 대신 자신의 내부 캐시에서 데이터를 반환합니다. 이는 etcd의 부하를 극적으로 줄여주지만, 약간의 지연이 있는 데이터(eventual consistency)를 받을 수 있다는 점을 감안해야 합니다. 하지만 대부분의 컨트롤러 동기화 작업에는 이 방식으로 충분합니다.
- API 우선순위 및 공정성 (APF - API Priority and Fairness): Kubernetes 1.20부터 도입된 APF는 단순한 요청 속도 제한을 넘어, 요청의 중요도에 따라 우선순위를 부여하고 큐를 분리하여 관리합니다.
- FlowSchema: 들어오는 요청을 '누가(Subject)', '무엇을(Resource)' 요청하는지에 따라 분류합니다.
- PriorityLevelConfiguration: 각 요청 흐름에 'system-critical', 'workload-high' 등과 같은 우선순위 레벨과 동시 처리 한도를 할당합니다. 이를 통해 중요도가 낮은 컨트롤러의 과도한 요청이 노드의 keepalive와 같은 핵심 시스템 동작을 방해하는 것을 방지하고, 클러스터의 안정성을 보장합니다.
인프라 및 커널 튜닝
- 컨트롤 플레인 고가용성(HA): 운영 환경에서는 최소 3대 이상의 컨트롤 플레인 노드를 구성하고, etcd 클러스터는 API 서버와 분리된 전용 노드에 배치하는 것이 권장됩니다. 특히, 생성과 삭제가 빈번한 Event 리소스는 별도의 etcd 클러스터에 저장하여 메인 etcd의 부하를 줄일 수 있습니다.
- ARP 캐시 튜닝: 대규모 L2 네트워크 환경에서는 노드 간 ARP 요청이 빈번해져 커널의 ARP 캐시가 가득 차는 neighbor table overflow 문제가 발생할 수 있습니다. sysctl을 통해 net.ipv4.neigh.default.gc_thresh 관련 파라미터들을 상향 조정하여 캐시 크기를 늘려야 합니다.
- Kubelet 동시성 제어: serializeImagePulls 플래그를 false로 설정하면 노드에서 여러 컨테이너 이미지를 병렬로 다운로드하여 파드 생성 속도를 높일 수 있습니다. 또한, Kubelet이 API 서버에 가하는 부하를 제어하기 위해 kubeAPIQPS와 kubeAPIBurst 값을 적절히 조정해야 합니다(1.27부터 기본값이 상향됨).
핵심 성능 지표 모니터링
Prometheus와 Grafana를 활용하여 다음과 같은 핵심 지표를 지속적으로 모니터링해야 병목 지점을 조기에 발견할 수 있습니다.
- apiserver_request_total: API 서버의 QPS와 에러율
- apiserver_request_duration_seconds_bucket: API 요청의 99%ile 지연 시간
- etcd_request_duration_seconds_bucket: etcd 요청 지연 시간
- workqueue_depth: 컨트롤러의 작업 큐 깊이 (큐가 계속 쌓이면 병목)
- workqueue_queue_duration_seconds_bucket: 작업이 큐에서 대기한 시간
3. Cilium 성능 분석 및 최적화
Cilium은 eBPF를 통해 고성능 데이터 플레인을 제공하지만, 대규모 환경에서는 Cilium 자체의 내부 상태 관리 메커니즘이 성능에 영향을 미칠 수 있습니다.
BPF Map Pressure와 StateDB
- BPF Map: Cilium은 서비스, 엔드포인트, 정책 등의 정보를 커널 공간의 BPF Map이라는 특수한 자료구조에 저장하여 빠르게 조회합니다. 하지만 이 Map들은 생성 시 최대 크기가 정해져 있어, 서비스나 엔드포인트의 수가 이 한계를 초과하면 더 이상 새로운 정보를 저장할 수 없습니다.
- 장애 현상: BPF Map이 가득 차면(BPF map pressure 메트릭 증가), Cilium 에이전트는 새로운 서비스 정보를 Map에 업데이트하지 못하고 Service backend not found와 같은 사유로 패킷을 드롭(drop)하기 시작합니다.
- Cilium의 회복성 설계: Cilium은 이러한 일시적인 실패에 대응하기 위해 StateDB라는 인메모리 데이터베이스와 Reconciler 패턴을 사용합니다. API 서버로부터 받은 모든 desired state는 먼저 StateDB에 저장됩니다. Reconciler는 주기적으로 StateDB의 상태와 실제 BPF Map의 상태를 비교하여, 실패했거나 누락된 항목을 재시도하여 최종적 일관성을 보장합니다.
Cilium 튜닝 파라미터
- bpf.mapDynamicSizeRatio: 노드의 전체 메모리 대비 BPF Map에 할당할 메모리의 비율을 조정합니다. 서비스나 엔드포인트가 매우 많은 환경에서는 이 비율을 기본값(0.0025)보다 높여 BPF Map의 크기를 늘려야 합니다.
- k8s.client-qps / k8s.client-burst: Cilium 에이전트가 kube-apiserver에 요청을 보내는 속도를 제어합니다. 대규모 클러스터에서는 이 값을 적절히 상향 조정하여 Cilium이 클러스터 변경 사항을 더 빠르게 동기화하도록 할 수 있습니다.
결론
대규모 쿠버네티스 클러스터의 성능 튜닝은 단일 파라미터를 조정하는 것을 넘어, 시스템 전반의 동작 원리를 깊이 이해하는 것에서 시작됩니다. 특히 컨트롤 플레인의 심장인 API 서버와 etcd의 상호작용, 그리고 각 컨트롤러의 동기화 메커니즘을 파악하는 것이 중요합니다. kube-burner와 같은 도구로 한계 상황을 시뮬레이션하고, Prometheus 메트릭을 통해 병목 지점을 정량적으로 분석하는 체계적인 접근이 필요합니다.
Cilium 환경에서는 여기에 더해, eBPF 데이터 플레인의 핵심인 BPF Map의 동작 방식과 StateDB를 통한 회복성 메커니즘을 이해하고, 관련 파라미터를 클러스터의 특성에 맞게 최적화해야 합니다. 성능 문제는 복잡하고 다층적이지만, 그 원리를 파고들면 반드시 명확한 해결책을 찾을 수 있습니다.
'Study > CS' 카테고리의 다른 글
| [Cilium Study] 8주차 - 네트워크를 넘어 커널까지: Cilium Security와 Tetragon (0) | 2025.09.07 |
|---|---|
| [Cilium Study] 6주차 - Sidecar를 넘어, eBPF로 구현하는 서비스 메시 (5) | 2025.08.23 |
| [Cilium Study] 5주차 - BGP와 ClusterMesh로 클러스터 경계 넘기 (10) | 2025.08.17 |
| [Cilium Study] 4주차 - Service와 LoadBalancer로 세상과 소통하기 (7) | 2025.08.09 |
| [Cilium Study] 3주차 - 쿠버네티스 네트워킹 심층 분석 (11) | 2025.08.02 |