eBPF로 쿠버네티스 네트워크 관찰하기: Cilium과 Hubble로 트래픽을 커널 수준에서 들여다보는 법
핵심 요약
이 글에서 확인할 내용
커널 수준에서 패킷을 들여다보는 것이 왜 중요한가 쿠버네티스 클러스터를 운영하다 보면 "이 파드 간 통신이 왜 실패하는가"라는 질문 앞에서 막막함을 느낄 때가 있습니다. kubectl logs 에는 아무런 에러가 없고, kubectl describe pod 를 봐도 이상이 없습니다. 그런데 A 서비스가 B 서비스를 호출하면 간헐적으로 연결이 끊깁니다.
- 1. eBPF란 무엇인가
- 2. 기존 iptables 한계와 eBPF의 차이
- 3. eBPF 프로그램 유형
커널 수준에서 패킷을 들여다보는 것이 왜 중요한가
쿠버네티스 클러스터를 운영하다 보면 "이 파드 간 통신이 왜 실패하는가"라는 질문 앞에서 막막함을 느낄 때가 있습니다. kubectl logs에는 아무런 에러가 없고, kubectl describe pod를 봐도 이상이 없습니다. 그런데 A 서비스가 B 서비스를 호출하면 간헐적으로 연결이 끊깁니다. 필자가 속한 팀에서는 수백 개의 파드가 뜨고 지는 대용량 클러스터에서 정확히 이 상황을 마주했습니다. 결국 원인은 iptables NAT 테이블의 conntrack 엔트리가 고갈되면서 발생한 패킷 드롭이었는데, 이것을 찾아내는 데 기존 모니터링 스택으로는 수 시간이 걸렸습니다.
eBPF 기반의 Cilium과 Hubble이 이 문제를 어떻게 바꾸는지 이 글에서 정리합니다. 쿠버네티스 오토스케일링 전략에 대해서는 HPA·VPA·KEDA 운영 가이드를, OpenTelemetry 기반 관측성 설계에 대해서는 OpenTelemetry 운영 관측성 설계를 함께 참고하시면 좋습니다.
1. eBPF란 무엇인가
eBPF는 "extended Berkeley Packet Filter"의 약자입니다. 공식 사이트 ebpf.io는 eBPF를 다음과 같이 정의합니다. "eBPF is a revolutionary technology with origins in the Linux kernel that can run sandboxed programs in a privileged context such as the operating system kernel."
동작 방식: 사용자 공간에서 C로 작성한 소스를 LLVM/Clang이 eBPF 바이트코드로 컴파일합니다. 이 바이트코드를 bpf() 시스템 콜로 커널에 전달하면, 커널 내 검증기(verifier)가 안전성을 먼저 확인합니다. 검증을 통과한 프로그램은 JIT 컴파일러가 네이티브 기계어로 변환해 해당 훅 포인트에 부착합니다.
검증기가 보장하는 안전성은 세 가지입니다. 프로그램이 무한 루프에 빠지지 않는다는 것, 허용되지 않은 커널 메모리 영역에 접근하지 않는다는 것, 그리고 특정 커널 함수만 호출한다는 것입니다.
실제로 노드에 접속해 어떤 eBPF 프로그램이 떠 있는지 확인하고 싶다면 bpftool prog list와 bpftool prog show id <ID> --pretty 명령이 출발점이 됩니다. Cilium이 설치된 노드에서 이 명령을 실행하면 cil_from_container, cil_to_container, cil_from_host처럼 방향성을 알 수 있는 이름의 프로그램들이 TC 훅에 부착되어 있는 것을 확인할 수 있습니다. 검증기를 통과한 프로그램이라도 명령어 개수 제한(complexity limit)에 걸릴 수 있는데, 커널 5.2 이후로는 이 제한이 대폭 완화되어 복잡한 L7 파싱 로직도 하나의 프로그램에 담을 수 있게 되었습니다. 이 완화 덕분에 Cilium 같은 프로젝트가 HTTP·gRPC 파싱까지 eBPF 레이어에 밀어 넣을 수 있는 것입니다.
2. 기존 iptables 한계와 eBPF의 차이
| 항목 | iptables/kube-proxy | eBPF/Cilium |
|---|---|---|
| 서비스 라우팅 복잡도 | O(n) — 순차 탐색 | O(1) — eBPF map 해시 조회 |
| 네트워크 정책 구현 | iptables 규칙 체인 | eBPF 프로그램 직접 적용 |
| L7 가시성 | 불가 | eBPF 파서로 HTTP/gRPC 헤더 추출 |
| conntrack 의존도 | 높음 | DSR 모드 등에서 conntrack 우회 가능 |
| 규칙 업데이트 방식 | 전체 iptables 체인 재작성 | eBPF map 원자적 업데이트 |
iptables 기반에서 또 다른 문제는 conntrack 테이블입니다. NAT를 위해 모든 연결을 conntrack에 기록하는데, 클러스터 내 파드 간 동서 트래픽이 많으면 conntrack 테이블이 포화될 수 있습니다.
실전 트러블슈팅: conntrack 테이블 고갈은 이렇게 찾아냈다
필자의 팀이 마주쳤던 conntrack 고갈 사고를 조금 더 구체적으로 복기해 보겠습니다. 장애 당시 증상은 명확했습니다. 특정 시간대에 결제 서비스에서 주문 서비스로의 호출 중 3~5%가 타임아웃으로 실패했고, 실패한 요청은 재시도하면 대부분 성공했습니다. 애플리케이션 로그에는 별다른 이상이 없었고, 파드의 CPU와 메모리 사용률도 정상 범위였습니다. 문제는 애플리케이션 레이어가 아니라 노드 레벨에 있었습니다.
노드에 접속해 dmesg -T | grep -i conntrack를 실행하자 nf_conntrack: table full, dropping packet 메시지가 반복적으로 찍혀 있었습니다. /proc/sys/net/netfilter/nf_conntrack_count와 nf_conntrack_max 값을 비교해 보니 카운트가 최댓값에 근접해 있었고, 노드 하나에 배치된 파드 수와 서비스 간 커넥션 풀 설정을 곱해 보면 충분히 납득이 가는 수치였습니다. 근본 원인은 단순했습니다. 각 파드가 짧은 커넥션을 매우 자주 새로 맺는 패턴이었는데, 이 패턴이 conntrack 엔트리를 빠르게 생성하고, 기본 TCP established 타임아웃(기본값 432,000초, 약 5일)이 길다 보니 엔트리가 제때 회수되지 않아 테이블이 채워진 것이었습니다.
임시 처방으로는 nf_conntrack_max를 늘리고 nf_conntrack_tcp_timeout_established을 짧게 조정해 대응했지만, 이는 증상 완화일 뿐 근본 해결책은 아니었습니다. 진짜 해법은 애플리케이션의 커넥션 재사용(keep-alive) 설정을 점검하는 것과, conntrack 자체에 대한 의존도를 구조적으로 낮추는 것이었습니다. 여기서 eBPF 기반 kube-proxy 대체가 갖는 의미가 드러납니다. Cilium이 소켓 레벨 로드밸런싱(Host-Reachable Services)을 사용하면, 파드 내부에서 나가는 커넥션의 목적지 주소를 connect(), sendmsg() 같은 소켓 시스템 콜 레벨에서 바로 실제 백엔드 파드 IP로 바꿔치기합니다. 이 경로에서는 별도의 DNAT/SNAT 왕복이 필요 없으므로 conntrack 엔트리 생성 자체가 줄어듭니다. 물론 이것이 conntrack을 완전히 없애 주는 것은 아닙니다. 파드 간 통신에는 여전히 상태 추적이 필요한 경우가 있고, 네트워크 정책 집행에도 연결 상태 정보가 활용되기 때문입니다. 다만 kube-proxy 시절과 비교하면 불필요한 왕복 홉이 줄어드는 만큼 conntrack 테이블에 가해지는 압력 자체가 낮아진다는 점이 핵심입니다.
3. eBPF 프로그램 유형
XDP (eXpress Data Path)는 네트워크 드라이버 수준에서 동작합니다. 패킷이 커널 네트워크 스택에 진입하기 전에 실행됩니다. DDoS 방어, 부하 분산, 패킷 포워딩에 적합합니다.
TC (Traffic Control)는 리눅스 tc 서브시스템의 훅 포인트입니다. 인그레스와 이그레스 양방향을 처리할 수 있고, 패킷 수정이 가능합니다. Cilium의 주요 데이터 패스는 TC 레벨 eBPF 프로그램으로 구현되어 있습니다.
kprobe와 kretprobe는 커널 함수의 진입점과 반환점에 부착합니다.
uprobe와 uretprobe는 사용자 공간 함수에 부착합니다. Hubble의 L7 가시성 일부가 이 방식을 활용합니다.
tracepoint는 커널 개발자가 명시적으로 설치한 안정적인 훅 포인트입니다.
Cilium은 이 다섯 가지 훅을 상황에 맞게 조합해서 씁니다. 파드 간 순수 포워딩에는 TC 프로그램을, 정책 통계와 드롭 사유 태깅에는 kprobe·tracepoint를, Hubble의 L7 가시성 일부에는 uprobe를 활용하는 식입니다. 하나의 훅으로 모든 것을 처리하는 것이 아니라, 각 훅의 성능 특성과 커널 안정성 보장 수준에 맞춰 역할을 분산시키는 것이 Cilium 아키텍처의 실제 설계 철학입니다.
4. Cilium 아키텍처
cilium-agent는 각 노드에 DaemonSet으로 배포되는 핵심 컴포넌트입니다. 쿠버네티스 API 서버를 watch하면서 파드, 서비스, 네트워크 정책 변경을 감지하고, 해당 노드의 eBPF 프로그램과 map을 업데이트합니다.
cilium-operator는 클러스터 전역 작업을 담당합니다. IPAM, CiliumNetworkPolicy CRD 관리, 노드 간 라우팅 정보 동기화가 여기에 속합니다.
Hubble은 Cilium 위에서 동작하는 네트워크 관측성 레이어입니다. cilium-agent의 eBPF 프로그램이 캡처한 이벤트를 실시간으로 집계하며, gRPC 스트리밍으로 제공합니다.
eBPF map은 cilium-agent가 관리하는 커널 내 데이터 구조입니다. cilium map list, cilium map get 명령으로 현재 map 상태를 직접 조회할 수 있습니다.
데이터패스 모드와 IPAM: 어떤 조합을 선택할 것인가
Cilium을 실제로 클러스터에 들일 때 가장 먼저 마주치는 선택은 데이터패스 모드입니다. 캡슐화(오버레이) 모드는 VXLAN이나 Geneve로 파드 트래픽을 감싸 노드 간 전송합니다. 기반 네트워크가 파드 CIDR을 라우팅해 주지 않아도 동작한다는 장점이 있어, 온프레미스나 네트워크 구성을 직접 통제하기 어려운 환경에서 기본 선택지가 됩니다. 네이티브 라우팅 모드는 캡슐화 오버헤드 없이 언더레이 라우터가 파드 CIDR을 직접 인지하도록 구성하는 방식으로, AWS ENI 모드나 Azure CNI powered by Cilium처럼 클라우드 네트워크 스택과 직접 통합되는 환경에서 지연 시간을 한 단계 더 줄여 줍니다. 다만 네이티브 라우팅은 언더레이가 파드 서브넷을 실제로 라우팅할 수 있어야 한다는 전제가 붙기 때문에, 클라우드 제공사의 VPC 라우팅 한도(예: AWS 라우팅 테이블당 항목 수)를 사전에 검토해야 합니다.
IPAM(IP 주소 관리) 방식도 함께 결정해야 합니다. cluster-scope는 Cilium이 클러스터 전체 파드 CIDR을 노드별로 쪼개 할당하는 기본 모드이고, kubernetes host-scope는 각 노드의 podCIDR 필드를 그대로 사용합니다. AWS EKS 환경이라면 ENI 기반 IPAM을 선택해 파드가 VPC 네이티브 IP를 직접 할당받게 할 수 있는데, 이 경우 VPC의 다른 리소스(RDS, ElastiCache 등)와 보안 그룹으로 직접 통신할 수 있다는 이점이 있지만 ENI당 할당 가능한 IP 개수라는 인스턴스 타입별 하드 리밋에 부딪힐 수 있습니다. 실무에서는 파드 밀도가 높은 노드 풀에서 이 IP 고갈 문제가 먼저 터지는 경우가 많으므로, 인스턴스 타입을 정할 때 최대 파드 수 계획을 함께 세우는 편이 안전합니다.
5. Hubble UI로 서비스 맵 시각화
Hubble은 eBPF 이벤트 스트림에서 HTTP 메서드, URL, 상태 코드, gRPC 메서드명, DNS 쿼리, 패킷 드롭 이유까지 추출해 실시간으로 집계합니다.
# 특정 네임스페이스의 모든 흐름 관찰
hubble observe --namespace production --follow
# HTTP 레이어 필터: 5xx 에러만 추려서 확인
hubble observe \
--namespace production \
--protocol http \
--http-status-code 500,502,503,504 \
--follow
# 특정 파드 간 통신만 필터링
hubble observe \
--from-pod production/order-service \
--to-pod production/payment-service \
--follow
# 패킷 드롭 이유 확인
hubble observe \
--namespace production \
--verdict DROPPED \
--follow \
-o json | jq '.flow | {source: .source.pod_name, destination: .destination.pod_name, drop_reason: .drop_reason_desc}'
drop_reason_desc 필드는 eBPF 프로그램이 패킷을 드롭한 이유를 문자열로 반환합니다. POLICY_DENIED는 네트워크 정책에 의한 차단, CT_MISSING_ENTRY는 conntrack 엔트리 없음 등 구체적인 이유를 확인할 수 있습니다.
Hubble이 실무에서 진가를 발휘하는 지점은 CLI 조회를 넘어 상시 모니터링 체계에 편입될 때입니다. hubble.metrics.enabled 옵션으로 활성화한 지표(flow, drop, tcp, dns, http, port-distribution)는 Prometheus 스크레이프 대상으로 노출되고, Grafana 대시보드로 서비스별 드롭율과 정책 거부율을 시계열로 관찰할 수 있습니다. 저희 팀은 destination_service 기준으로 POLICY_DENIED 드롭 비율에 대한 알림 규칙을 추가해, 배포 직후 신규 서비스가 의도치 않게 다른 서비스의 트래픽을 차단하는 상황을 몇 분 안에 감지하도록 만들었습니다. 이 알림 하나가 실제로 신규 마이크로서비스 배포 직후 잘못 설정된 CiliumNetworkPolicy로 인한 장애를 카나리 단계에서 잡아낸 적이 있는데, 만약 Hubble의 실시간 드롭 지표가 없었다면 사용자 리포트가 들어오고 나서야 원인 조사를 시작했을 것입니다.

6. 네트워크 정책 강화
쿠버네티스의 표준 NetworkPolicy는 L3/L4 레벨에서 인그레스와 이그레스를 제어합니다. Cilium은 CiliumNetworkPolicy CRD를 통해 L7 레벨 정책까지 확장합니다. (Cilium Security Policy)
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: payment-service-l7-policy
namespace: production
spec:
endpointSelector:
matchLabels:
app: payment-service
ingress:
- fromEndpoints:
- matchLabels:
app: order-service
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: "POST"
path: "/v1/charge"
- method: "GET"
path: "/v1/charge/[0-9]+"
- fromEndpoints:
- matchLabels:
app: billing-service
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: "GET"
path: "/v1/invoice"
이 정책이 적용되면 order-service에서 payment-service의 /v1/admin으로 접근 시도가 발생하면 Hubble에 즉시 POLICY_DENIED 이벤트로 기록됩니다.
실무에서 못지않게 자주 필요한 것이 외부 도메인에 대한 이그레스 제어입니다. 쿠버네티스 네이티브 NetworkPolicy는 IP 기반으로만 이그레스를 제한할 수 있는데, 외부 SaaS나 결제 게이트웨이는 IP가 수시로 바뀌는 CDN 뒤에 있는 경우가 많아 IP 화이트리스트 방식이 곧바로 깨집니다. CiliumNetworkPolicy는 toFQDNs 규칙으로 이 문제를 해결합니다.
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: payment-service-egress-fqdn
namespace: production
spec:
endpointSelector:
matchLabels:
app: payment-service
egress:
- toFQDNs:
- matchName: "api.stripe.com"
- matchPattern: "*.googleapis.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
rules:
dns:
- matchPattern: "*"
이 정책의 동작 원리는 Cilium 에이전트가 파드에서 나가는 DNS 쿼리를 가로채 응답으로 받은 IP를 toFQDNs 규칙과 매핑해 두었다가, 실제 연결이 시도될 때 그 IP가 매핑에 있는지 eBPF 레벨에서 검사하는 방식입니다. DNS 정책 규칙(toPorts의 dns 하위 규칙)을 함께 넣어 둬야 DNS 쿼리 자체가 차단되지 않는다는 점을 놓치기 쉬운데, 이 부분을 빠뜨리면 정책 배포 직후 파드가 아예 이름 해석부터 실패하는 사고로 이어질 수 있습니다.
7. L7 가시성: HTTP·gRPC 메타데이터
eBPF만으로는 패킷 내용을 파싱하기 어렵습니다. Cilium은 이를 위해 파드의 트래픽을 인라인 Envoy 프록시를 통과시키는 방식을 선택합니다.
| 필드 | HTTP | gRPC |
|---|---|---|
| 메서드 | GET, POST, PUT, DELETE | 서비스명/메서드명 |
| URL / 경로 | 전체 경로 | /package.Service/Method |
| 상태 코드 | 200, 404, 500 | gRPC status code |
| 응답 시간 | 요청/응답 쌍 매칭 | 요청/응답 쌍 매칭 |
이 메타데이터가 있으면 외부 서비스 메시(Istio, Linkerd)를 도입하지 않고도 서비스 간 L7 RED 지표(Rate, Error, Duration)를 Prometheus에서 볼 수 있습니다.
다만 L7 가시성이 모든 프로토콜에 대해 공짜로 주어지는 것은 아닙니다. Envoy 프록시로 트래픽을 리다이렉트하는 순간 해당 파드의 요청은 커널의 순수 eBPF 경로를 벗어나 사용자 공간 프록시를 한 번 더 거치게 되므로, L7 정책이 걸린 파드는 L3/L4만 쓰는 파드보다 레이턴시가 늘어납니다. 저희는 이 오버헤드를 감안해, 결제·인증처럼 감사 추적이 필수인 서비스에는 L7 가시성을 기본 활성화하되, 순수 내부 배치 작업처럼 처리량이 우선인 워크로드에는 L3/L4 정책만 적용하는 식으로 구분해서 운영하고 있습니다.
8. Cilium 설치와 kube-proxy 대체
helm repo add cilium https://helm.cilium.io/
helm repo update
helm install cilium cilium/cilium \
--version 1.15.4 \
--namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost="${API_SERVER_HOST}" \
--set k8sServicePort="${API_SERVER_PORT}" \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,icmp,http}" \
--set prometheus.enabled=true \
--set operator.prometheus.enabled=true \
--set bpf.masquerade=true
cilium status --wait
kubectl -n kube-system delete daemonset kube-proxy
kubectl -n kube-system delete configmap kube-proxy
cilium hubble port-forward &
hubble status
hubble observe --follow
kubeProxyReplacement=true 설정이 핵심입니다. 이 옵션이 활성화되면 Cilium이 kube-proxy의 역할인 ClusterIP, NodePort, LoadBalancer 서비스 라우팅을 모두 eBPF로 처리합니다.
단계적 전환과 롤백 계획
kube-proxy를 곧바로 삭제하는 것은 이미 운영 중인 클러스터에서는 상당히 위험한 결정입니다. 저희가 실제로 택한 순서는 다음과 같았습니다. 먼저 kubeProxyReplacement=true 없이, 즉 kube-proxy와 Cilium을 공존시키는 상태로 Cilium을 CNI로만 배포해 파드 네트워킹과 CiliumNetworkPolicy가 기존 애플리케이션 네트워크 정책과 동일하게 동작하는지 스테이징에서 먼저 검증했습니다. 이 단계에서 cilium connectivity test를 반드시 실행해, 파드-파드, 파드-서비스, 파드-외부 방향의 연결성이 모두 정상인지 자동으로 확인했습니다. 이 커맨드는 클러스터 안에 테스트용 파드를 여러 개 띄워 다양한 트래픽 패턴을 주고받으며 결과를 리포트해 주는데, 저희 환경에서는 이 테스트로 특정 노드 그룹의 보안 그룹 설정이 UDP 트래픽을 막고 있다는 사실을 처음 발견했습니다.
공존 단계가 안정화된 뒤에야 kubeProxyReplacement=true로 전환하고, 이때도 kube-proxy DaemonSet은 바로 지우지 않고 하루 이상 관찰 기간을 두었습니다. 관찰 기간 동안 hubble observe --verdict DROPPED로 드롭 이벤트를 모니터링하고, 애플리케이션 쪽 5xx 비율과 응답 지연 지표에 이상 징후가 없는지 대조했습니다. kube-proxy를 완전히 제거한 뒤 문제가 생기면 되돌리기가 훨씬 번거로워지므로, 이 관찰 기간이 사실상 유일한 안전망입니다. 특히 hostNetwork: true로 배포된 파드(모니터링 에이전트, 로그 수집기 등)는 kube-proxy 대체 이후 서비스 접근 방식이 달라질 수 있어 별도로 점검 목록에 넣어야 합니다.
9. 다른 CNI와의 비교: Calico, Flannel, VPC CNI와 무엇이 다른가
Cilium을 도입하기 전에 반드시 짚어야 할 질문은 "우리 클러스터에 정말 Cilium이 필요한가"입니다. CNI 선택은 한번 굳어지면 되돌리기 매우 비싼 결정이므로, 트래픽 패턴과 팀의 운영 역량을 함께 고려해야 합니다.
| 항목 | Flannel | Calico (iptables 모드) | Calico (eBPF 모드) | Cilium |
|---|---|---|---|---|
| 네트워크 정책 | 미지원 | L3/L4 지원 | L3/L4 지원 | L3~L7 지원 |
| kube-proxy 대체 | 불가 | 불가 | 가능 | 가능(선구자) |
| 관측성 | 없음 | 기본 로그 | 기본 로그 | Hubble 내장 |
| 학습 곡선 | 낮음 | 중간 | 중간~높음 | 높음 |
| 디버깅 도구 친숙도 | 높음(tcpdump로 충분) | 높음 | 중간 | 낮음(eBPF 전용 도구 필요) |
| 대규모 확장성 | 제한적 | BGP 기반으로 우수 | 우수 | 우수(해시 조회 O(1)) |
Flannel은 오버레이 네트워킹만 제공하는 가장 단순한 CNI입니다. 네트워크 정책 자체를 지원하지 않으므로, 보안 경계가 중요하지 않은 개발 클러스터나 학습 환경에는 적합하지만 프로덕션 멀티테넌시 환경에는 권장하기 어렵습니다.
Calico는 BGP 기반 라우팅을 오랫동안 프로덕션에서 검증받아 온 CNI입니다. iptables 모드는 tcpdump, iptables -L 같은 표준 도구로 그대로 디버깅할 수 있다는 점이 큰 장점이라, 네트워크 엔지니어링팀이 전통적인 방화벽 개념에 익숙한 조직이라면 오히려 Calico 쪽이 운영 마찰이 적습니다. Calico도 eBPF 데이터패스와 kube-proxy 대체 기능을 제공하지만, L7 정책과 통합 관측성 측면에서는 Cilium이 한발 앞서 있다는 평가가 일반적입니다. (Tigera의 Cilium vs Calico 비교)
AWS의 기본 VPC CNI는 파드에 VPC 네이티브 IP를 직접 부여해 네트워크 홉을 최소화한다는 장점이 있지만, 네트워크 정책 집행 자체는 Calico나 Cilium을 별도로 체이닝해야 하는 경우가 많습니다. 결국 실무에서 던져야 할 질문은 다음과 같이 좁혀집니다. L7 가시성과 애플리케이션 레벨 정책이 꼭 필요한가, 팀이 eBPF 기반 디버깅 도구(hubble, bpftool, cilium-dbg)를 새로 학습할 여력이 있는가, 그리고 이미 관리형 서비스가 Cilium을 기본값으로 채택하고 있어 사실상 선택의 여지가 없는가입니다. 실제로 GKE의 Dataplane V2는 내부적으로 Cilium을 anetd라는 DaemonSet으로 구동하며, Autopilot 클러스터에서는 기본으로 활성화되어 있습니다. Azure 역시 'Azure CNI powered by Cilium'을 AKS Automatic 클러스터의 기본 가상 네트워크로 채택했고, 표준 AKS에서도 --network-dataplane cilium 옵션 하나로 켤 수 있습니다. 두 클라우드 모두 이미 결정을 내려 준 셈이라, 해당 관리형 서비스를 쓰는 팀이라면 Cilium 도입 여부보다는 Cilium을 얼마나 깊이 활용할 것인가를 고민하는 편이 더 생산적입니다. (Microsoft: Azure CNI powered by Cilium 성능)
10. 퍼포먼스 특성과 벤치마크 관점
서비스 수가 증가할 때 kube-proxy(iptables 모드)와 Cilium의 차이가 가장 두드러집니다. 서비스 1만 개 규모에서 iptables는 규칙 업데이트마다 수 초가 걸리는 반면, Cilium의 eBPF map 업데이트는 원자적이며 밀리초 수준에서 완료됩니다.
L7 가시성 활성화 시에는 추가 오버헤드를 고려해야 합니다. L7 정책이 적용된 파드는 Envoy 프록시가 인라인으로 삽입되므로 추가 CPU와 메모리가 소요됩니다.
구체적인 수치로 감을 잡아 보면, Microsoft가 Azure CNI powered by Cilium을 대상으로 진행한 벤치마크에서는 파드 수를 16,000개까지 늘렸을 때 kube-proxy 대비 서비스 라우팅 레이턴시가 약 30% 감소했다고 보고되었습니다. (Microsoft Azure Blog) 저희가 자체적으로 측정한 사내 벤치마크에서도 서비스 수가 2,000개를 넘어서는 구간부터 iptables 모드의 신규 서비스 반영 지연이 체감될 정도로 늘어났고, Cilium으로 전환한 뒤에는 서비스 추가·삭제가 초 단위가 아니라 밀리초 단위로 반영되는 것을 확인했습니다.
암호화까지 고려한다면 WireGuard 기반 노드 간 트랜스페어런트 암호화의 오버헤드도 미리 가늠해 둘 필요가 있습니다. Cilium 공식 벤치마크 문서에 따르면 WireGuard는 IPsec보다 최대 처리량은 높지만 동일 처리량을 내는 데 필요한 CPU 자원은 IPsec이 더 효율적이며, 일반적인 트래픽 조건에서는 1~5% 수준의 CPU 증가와 서브 밀리초 수준의 지연 영향이 보고됩니다. (Cilium CNI Performance Benchmark) 멀티 테넌트 클러스터에서 노드 간 트래픽 암호화가 컴플라이언스 요구사항이라면 이 정도의 CPU 여유를 미리 확보해 두는 편이 안전합니다.
11. 제한사항과 커널 버전 요구사항
| 기능 | 최소 커널 버전 |
|---|---|
| 기본 eBPF 데이터 패스 | 4.19 |
| BPF Host Routing | 5.10 |
| eBPF Socket LB (kube-proxy 완전 대체) | 5.8 |
| Bandwidth Manager | 5.1 |
| WireGuard 투명 암호화 | 5.6 |
AWS EKS, GKE, AKS 등 관리형 쿠버네티스 서비스는 기본 노드 AMI가 커널 5.10 이상을 사용하는 경우가 많아 대부분의 기능을 사용할 수 있습니다.
Hubble의 이벤트 버퍼 오버플로우도 실무에서 마주칩니다. 트래픽이 매우 많은 클러스터에서는 버퍼가 빠르게 채워져 이벤트가 드롭될 수 있습니다.
버전 관리도 가볍게 볼 문제가 아닙니다. Cilium 커뮤니티는 최근 세 개의 마이너 버전만 보안 패치 대상으로 유지하는 정책을 취하고 있어, 이 글의 설치 예시에 쓰인 1.15 계열은 이미 지원 종료(EOL)된 branch입니다. 프로덕션에 적용할 때는 반드시 공식 릴리스 페이지에서 현재 지원 중인 최신 마이너 버전을 확인하고, 업그레이드 가이드에 따라 최소 한 마이너 버전씩 순차적으로 올려야 합니다. 마이너 버전을 건너뛰고 올리면 eBPF map 스키마 변경으로 인한 예기치 못한 호환성 문제를 만날 수 있습니다.
또한 클러스터가 여러 리전이나 여러 클라우드에 흩어져 있다면 Cilium ClusterMesh를 고려하게 되는데, 이는 별도의 도입 결정이 필요한 확장 기능입니다. ClusterMesh는 게이트웨이나 프록시 없이 클러스터 간 파드가 직접 통신하도록 연결해 주지만, 사전 조건으로 각 클러스터의 파드·서비스 CIDR이 서로 겹치지 않아야 합니다. 이미 운영 중인 클러스터들이 동일한 CIDR 대역을 쓰고 있었다면, ClusterMesh를 도입하기 위해 클러스터를 통째로 재구성해야 하는 상황에 부딪힐 수 있으므로 초기 클러스터 설계 단계에서부터 CIDR 대역을 리전별로 분리해 두는 편이 나중의 고통을 크게 줄여 줍니다.
12. 실무자가 자주 묻는 질문
Q. Cilium을 도입하면 서비스 메시(Istio, Linkerd)는 필요 없어지나요?
완전히 대체되지는 않습니다. Cilium의 L7 가시성과 HTTP/gRPC 레벨 정책은 서비스 메시의 관측성·정책 기능과 상당 부분 겹치지만, mTLS 기반 종단 간 인증, 정교한 카나리·트래픽 분할, 재시도·서킷 브레이커 같은 애플리케이션 레벨 복원력 패턴은 여전히 서비스 메시의 영역입니다. Cilium도 자체 사이드카리스(sidecar-less) 서비스 메시 기능을 발전시키고 있어 경계가 계속 움직이고 있지만, 강력한 mTLS 정책 요구사항이 있는 조직이라면 서비스 메시와 Cilium을 함께 검토하는 것이 현실적입니다.
Q. 매니지드 쿠버네티스(EKS, GKE, AKS)에서도 직접 Cilium을 설치해야 하나요?
GKE는 Dataplane V2를 활성화하면 이미 Cilium 기반이고, AKS는 'Azure CNI powered by Cilium' 옵션을 켜면 관리형으로 제공됩니다. 반면 EKS는 기본 VPC CNI가 표준이므로, Cilium의 L7 가시성이나 kube-proxy 대체가 필요하다면 직접 설치하거나 체이닝 구성을 거쳐야 합니다.
Q. eBPF 프로그램이 커널 패닉을 일으킬 위험은 없나요?
검증기(verifier)가 이런 위험을 원천 차단하도록 설계되어 있습니다. 무한 루프, 범위를 벗어난 메모리 접근, 허용되지 않은 함수 호출은 모두 로드 시점에 거부됩니다. 다만 검증기를 통과했다고 논리적으로 완벽하다는 뜻은 아니므로, Cilium처럼 커뮤니티 검증을 오래 거친 프로젝트를 쓰는 것과 사내에서 직접 짠 커스텀 eBPF 프로그램을 프로덕션에 올리는 것은 리스크 수준이 전혀 다르다는 점을 구분해야 합니다.
Q. 온프레미스 환경에서도 이런 이점을 그대로 누릴 수 있나요?
커널 버전만 충족하면 클라우드 여부와 무관하게 동일한 이점을 누릴 수 있습니다. 다만 온프레미스에서는 IPAM을 직접 설계해야 하고, 네이티브 라우팅 모드를 쓰려면 물리 네트워크 장비가 파드 CIDR을 라우팅하도록 구성해야 하므로 네트워크팀과의 협업이 클라우드 환경보다 더 필요합니다.
마무리
- 커널 버전 확인: 모든 노드에서
uname -r로 커널 버전을 확인하고, 사용하려는 Cilium 기능에 필요한 최소 버전을 충족하는지 검증한다. - 단계적 전환: kube-proxy를 즉시 제거하지 않고, Cilium을 먼저 배포해 기존 네트워크 정책이 정상 동작하는지 확인합니다.
- Hubble 관측성 우선 활용: L7 정책 집행 전에 먼저 가시성 모드로 Hubble을 운영합니다.
- L7 가시성 범위 제한: 가시성이 필요한 핵심 서비스부터 우선 적용합니다.
- eBPF map 상태 모니터링:
cilium map list와 Prometheus 메트릭으로 map 업데이트 오류와 용량 한계를 확인합니다. - CNI 전환은 가역성이 낮다고 보고 결정한다: Cilium 도입 전 Calico·Flannel·관리형 CNI 대비 트레이드오프를 팀의 운영 인력과 디버깅 숙련도에 맞춰 검토합니다.
- conntrack과 kube-proxy 대체 효과를 함께 측정한다: 전환 전후
nf_conntrack_count와 서비스 라우팅 지연을 기록해 두면, 전환의 효과를 숫자로 설명할 수 있습니다.