왜 kube-proxy가 병목이 되는가
쿠버네티스의 ClusterIP/NodePort 서비스는 기본적으로 kube-proxy가 iptables 규칙으로 구현한다. 문제는 서비스와 엔드포인트가 늘어날수록 규칙이 선형(O(n))으로 증가한다는 점이다. 패킷 하나가 목적지 백엔드를 찾으려면 규칙 체인을 순차 탐색하므로, 서비스 수천 개 규모에서는 패킷 처리 지연과 규칙 갱신 시간(수 초 이상)이 눈에 띄게 커진다. IPVS 모드가 해시 기반이라 조금 낫지만, 여전히 커널 netfilter 훅을 거치고 conntrack 부하가 남는다.
eBPF 기반 로드밸런싱의 원리
Cilium은 iptables 대신 eBPF 프로그램을 커널의 여러 훅 지점에 붙인다. 서비스 → 백엔드 매핑은 eBPF 해시맵에 저장되고, 조회는 규칙 개수와 무관하게 O(1)이다. 특히 소켓 레벨 로드밸런싱(socket LB)은 애플리케이션이 connect()를 호출하는 순간 cgroup/connect4 훅에서 목적지 주소를 백엔드 Pod IP로 직접 바꿔치기한다. 즉 패킷이 네트워크 스택을 타기 전에 목적지가 확정되므로, 노드 내 통신에서는 conntrack과 DNAT 오버헤드 자체가 사라진다.
kube-proxy 제거 후 설치
새 클러스터라면 kube-proxy를 아예 띄우지 않고 부트스트랩한 뒤 Cilium에 대체시킨다. 기존 클러스터라면 kube-proxy DaemonSet을 먼저 정리한다.
# 기존 kube-proxy 제거 (kubeadm 클러스터 기준)
kubectl -n kube-system delete ds kube-proxy
kubectl -n kube-system delete cm kube-proxy
# Cilium을 kube-proxy 대체 모드로 설치
# API 서버 주소는 kube-proxy가 없으므로 직접 지정해야 한다
cilium install --version 1.16.3 \
--set kubeProxyReplacement=true \
--set k8sServiceHost=10.0.0.10 \
--set k8sServicePort=6443 \
--set socketLB.hostNamespaceOnly=false
# 검증: True 로 표시되어야 정상
cilium status | grep KubeProxyReplacement
kubectl -n kube-system exec ds/cilium -- cilium-dbg service list
DSR와 Maglev로 외부 트래픽 최적화
NodePort/LoadBalancer로 들어온 외부 트래픽은 기본적으로 SNAT되어 원본 클라이언트 IP가 사라지고, 응답이 진입 노드를 되돌아 나가는 비대칭 경로가 생긴다. DSR(Direct Server Return)을 켜면 백엔드가 클라이언트로 직접 응답하므로 진입 노드 부하와 홉이 줄고 클라이언트 IP도 보존된다. 여기에 Maglev 해시를 쓰면 노드 간 백엔드 선택이 일관되어, 특정 노드가 빠져도 재해싱 영향이 최소화된다.
apiVersion: cilium.io/v2
# values 발췌 — helm/cilium install 시 --set 로도 지정 가능
kubeProxyReplacement: true
loadBalancer:
mode: dsr # snat | dsr | hybrid
algorithm: maglev # random | maglev
maglev:
tableSize: 65521 # 소수 권장, 백엔드 수보다 충분히 크게
externalIPs:
enabled: true
단, DSR는 노드 간 라우팅 경로가 패킷을 그대로 전달할 수 있어야 한다. 중간 네트워크가 소스/목적지가 다른 패킷을 막거나(uRPF), 캡슐화 헤더를 드롭하면 동작하지 않는다. 이 경우 hybrid 모드로 TCP는 DSR, UDP는 SNAT처럼 섞어 쓰는 것이 현실적이다.
iptables 대비 무엇이 달라지나
| 항목 | kube-proxy (iptables) | Cilium eBPF |
|---|---|---|
| 서비스 조회 복잡도 | O(n) 체인 탐색 | O(1) 해시맵 |
| 규칙 갱신 | 전체 테이블 재작성 | 맵 항목만 갱신 |
| 노드 내 통신 | DNAT + conntrack | 소켓 LB, 오버헤드 제거 |
| 클라이언트 IP 보존 | SNAT로 손실 잦음 | DSR로 보존 |
운영 시 주의점
첫째, kubeProxyReplacement=true는 커널 요구사항이 있다. 최소 4.19 이상, DSR·소켓 LB 등 전 기능을 쓰려면 5.x대 커널을 권장한다. 마운트된 커널 헤더와 BPF 파일시스템(/sys/fs/bpf)이 있어야 한다.
둘째, socket LB는 hostNetwork Pod나 사이드카(예: Istio)가 iptables 리다이렉트에 의존할 때 충돌할 수 있다. 이 경우 socketLB.hostNamespaceOnly=true로 호스트 네임스페이스에 한정하거나 예외를 둔다.
셋째, 마이그레이션은 반드시 conntrack 초기화를 동반해야 한다. iptables 규칙을 지웠어도 기존 conntrack 항목이 남아 옛 경로로 패킷을 흘려보내면 간헐적 연결 실패가 발생한다. 롤아웃 후 cilium-dbg service list와 실제 트래픽 드롭(cilium monitor --type drop)을 함께 확인하는 것이 안전하다.