Kubernetes 네트워킹의 진화: CNI부터 Cilium까지, 그리고 eBPF가 바꾼 풍경
네트워킹, 쿠버네티스가 의도적으로 손대지 않은 영역
쿠버네티스를 처음 만졌을 때 제일 신기했던 게 네트워킹이었습니다. Pod이 알아서 IP를 받고, 서로 통신하고, Service가 로드밸런싱까지 해주거든요. 그런데 운영하면서 안쪽을 들여다보니 묘했습니다. 쿠버네티스 자체는 네트워킹을 거의 구현하지 않더라고요.
쿠버네티스가 Pod을 스케줄링할 때 깔고 가는 가정은 의외로 짧습니다.
"이 Pod는 IP 주소를 받아야 하고, 다른 모든 것과 통신할 수 있어야 한다"
딱 이 선언 하나입니다. 정작 이걸 실현하는 일은 전부 남의 몫이고요. Linux 네트워크 네임스페이스를 만들고, 라우팅 테이블을 짜고, VXLAN 같은 오버레이를 세우고, iptables 규칙을 프로그래밍하고, IP 주소 할당을 관리하는 것. 이 무거운 작업을 쿠버네티스는 손에 쥐지 않았습니다. 통째로 위임했죠. 받는 쪽이 Container Network Interface입니다.
처음엔 설계가 덜 끝난 것처럼 보였어요. 중요한 기능을 왜 밖으로 빼놨지? 한참 지나고 나서야 이게 의도된 선택이라는 걸 알았습니다.
Container Network Interface, 네트워킹을 외부에 위임하는 약속
CNI는 사양이자 플러그인 생태계입니다. 쿠버네티스와 실제 네트워킹 시스템 사이의 계약을 정의하죠. 흐름 자체는 짧습니다. 쿠버네티스가 "이 Pod에 네트워킹 좀 설정해줘"라고 부르면, CNI 플러그인이 실제 구성 작업을 하고, 끝나면 "완료했어, 네트워킹 정보는 이거야"라고 돌려줍니다. 런타임 라이프사이클도 셋뿐입니다. 새 Pod을 위한 네트워킹을 만드는 ADD, Pod이 죽을 때 걷어내는 DELETE, 상태를 확인하는 CHECK.
kubelet이 Pod을 만들면 CNI 바이너리를 실행합니다. 그러면 플러그인이 IP 주소를 할당하고(IPAM), 가상 이더넷 쌍(veth)을 만들고, 그 인터페이스를 Pod 네임스페이스에 밀어넣고, 마지막으로 라우팅과 정책 규칙을 구성합니다.
CNI가 없으면 쿠버네티스 네트워킹은 그냥 멈춥니다. Pod은 IP를 못 받고, Service는 트래픽을 어디로도 보내지 못하죠. 실제로 클러스터를 새로 깔다가 CNI 설치를 빼먹으면 Pod이 ContainerCreating에서 영원히 멈춰 있는 걸 보게 됩니다. 그 순간 "아, 쿠버네티스가 네트워킹을 안 한다"는 말이 체감으로 들어옵니다.
쿠버네티스는 왜 플러그인 모델을 선택했을까
초기 쿠버네티스 시대를 떠올려보면, 네트워킹 업체들이 각자 자기 제품을 들고 나왔습니다. 클라우드 제공업체는 네이티브 통합을 원했고, 가상화 업체는 SDN 스택을 밀었고, 오픈소스 커뮤니티는 오버레이를 만들었습니다. 다들 자기가 표준이 되고 싶어 했죠.
쿠버네티스는 여기서 흥미로운 선택을 합니다. 승자를 고르지 않았어요. 대신 구현이 아니라 인터페이스를 표준화했습니다.
덕분에 같은 애플리케이션이 환경을 옮겨 다닙니다. 로컬 클러스터에서는 간단한 오버레이로, 관리형 클라우드에서는 제공업체 네트워크에 직접 붙어서, 베어메탈에서는 고성능 라우팅으로. 애플리케이션은 그대로인데 아래층만 갈아끼우는 식이죠. 네트워킹의 새 아이디어가 쿠버네티스 코어 바깥에서 자랄 수 있게 된 것도 같은 이유입니다. 기술이 새로 나와도 본체를 뜯어고칠 일이 없으니까요. CNI 사양 자체도 기본 IP 할당에서 출발해 대역폭 제어, 다중 네트워크 연결, 네이티브 커널 가속까지 계속 넓어졌습니다.
여러 클러스터를 들여다볼 기회가 있었는데, 같은 쿠버네티스를 쓰면서도 네트워킹 스택은 다 달랐습니다. 이게 가능했던 게 플러그인 모델 덕분이에요. 코어를 건드리지 않고도 각자 환경에 맞는 선택을 했던 겁니다. 가운데를 비워두고 인터페이스만 고정한 결정이, 생태계 전체의 실험 속도를 끌어올렸습니다.
전통적인 CNI의 한계, 그리고 iptables라는 벽
Flannel 같은 기존 구현을 열어보면 순서가 비슷합니다. CNI 플러그인이 노드 서브넷에서 IP를 할당하고, veth 쌍이 Pod을 호스트 네트워크에 연결하고, 라우팅과 포워딩 규칙이 깔리고, 노드를 넘어가는 트래픽은 터널(보통 VXLAN)에 캡슐화됩니다.
Pod A가 Pod B로 패킷을 보내는 장면으로 풀면 이렇습니다. 패킷이 가상 인터페이스를 통해 Pod을 떠나면, 노드가 그걸 VXLAN 패킷 안에 감싸서 UDP로 목적지 노드까지 보내고, 목적지에서 탈캡슐화해 대상 Pod에 전달합니다.
규모가 작을 때는 잘 돕니다. 문제는 클러스터가 커질 때예요. 전통적인 CNI는 라우팅 결정도, 로드밸런싱도, 정책 시행도 전부 iptables에 기댑니다. 그런데 iptables는 규칙을 순차적으로 평가하는 구조라, Pod 수가 늘면 규칙도 같이 불어납니다. 수백 개 Pod이면 수백 개 규칙, 수천 개 Pod이면 수천 개 규칙. 대형 클러스터에 가면 측정에 잡히는 수준의 패킷 지연과 CPU 오버헤드로 나타납니다.
패킷 하나가 커널 안에서 긴 규칙 체인을 끝까지 순회해야 한다.
이게 아키텍처 차원의 병목이었습니다. 어느 선을 넘으면 체인 순회 비용이 무시할 수 없는 수준으로 올라옵니다. Pod 하나하나는 멀쩡한데 전체로 보면 네트워크가 무거워지는, 어디서 많이 본 패턴이에요.
Cilium과 eBPF, 수행 방식 자체를 바꾸다
오해를 하나 짚고 가겠습니다. Cilium은 CNI를 대체하지 않습니다. CNI 사양을 구현하는 또 하나의 플러그인이에요. 차이는 무엇을 하느냐가 아니라 어떻게 하느냐에 있습니다.
iptables 자리에 Cilium이 놓는 건 extended Berkeley Packet Filter입니다. Linux 커널 안에 직접 내장된 프로그래머블 프레임워크죠. iptables가 긴 순차 규칙 체인을 위에서부터 평가한다면, eBPF는 커널 훅 지점에 미리 컴파일된 프로그램을 걸어두고 그 자리에서 실행합니다. 훅은 패킷이 네트워크 스택에 도달하기 전, 인그레스와 이그레스 처리 중, 그리고 애플리케이션별 소켓 수준까지 여러 단계에 잡힙니다. Cilium은 이 지점들 위에서 라우팅, 서비스 로드밸런싱, 네트워크 정책, 관찰가능성을 구현합니다.
가장 큰 변화는 여기입니다. 수천 개 규칙을 순회하는 대신, 패킷이 O(1) 조회로 커널 맵을 참조합니다. 규칙 체인을 걷는 모델에서, 맵을 한 번 찌르는 모델로 바뀐 거죠. Pod이 늘어도 조회 비용이 선형으로 따라 늘지 않습니다.
Cilium 아키텍처 들여다보기
모든 쿠버네티스 노드가 Cilium 에이전트를 DaemonSet으로 띄우고, 다섯 가지 역할을 맡습니다.
| 역할 | 설명 |
|---|---|
| CNI 서버 | kubelet의 ADD/DELETE 호출에 응답 |
| IP 주소 관리 | 클러스터/클라우드 통합으로 Pod 주소 할당 |
| eBPF 매니저 | 컴파일된 프로그램을 커널 훅에 로드 |
| Kubernetes Watcher | 클러스터 상태를 eBPF 맵으로 동기화 |
| Hubble | 네트워크 플로우 실시간 가시성 제공 |
구조의 무게중심은 Watcher와 eBPF 맵 사이에 있습니다. 수천 개 규칙을 매번 다시 프로그래밍하는 대신, Cilium은 쿠버네티스 상태를 커널 데이터 구조에 계속 동기화합니다. 쿠버네티스가 "이 Pod이 추가됐다"고 말하면 그 변화가 곧장 커널 맵에 반영되는 식이죠.
전통적인 CNI가 정적인 라우터처럼 동작한다면, Cilium은 커널 안에서 돌아가는 프로그래머블 네트워크 OS에 가깝습니다. 정책을 거는 게 아니라 네트워크 동작 자체를 코드로 정의하는 감각이에요.
실제 트래픽 플로우의 변화
Pod을 떠나는 트래픽은 eBPF 정책 프로그램에서 평가된 뒤 iptables 체인을 건너뛰고 목적지로 바로 라우팅됩니다. 지연시간이 마이크로초 수준으로 떨어집니다.
Service IP에 붙을 때도 마찬가지입니다. Cilium이 커널 공간에서 직접 로드밸런싱을 합니다. kube-proxy도, NAT 체인도, 규칙 폭발도 없습니다. kube-proxy를 걷어낸다는 건 운영하는 입장에서 꽤 큰 이야기예요. 그동안 규칙 동기화 지연이나 conntrack 이슈로 골치 아팠던 영역이 통째로 사라지니까요.
Layer-7 보안 정책
Cilium은 IP 주소만 보지 않습니다. 애플리케이션 동작을 기준으로 정책을 겁니다. 특정 도메인에만 HTTPS 트래픽을 허용하거나, 알 수 없는 외부 목적지를 거부하거나, HTTP 경로 수준으로 통제하는 식이죠. L3/L4에서 끝나던 정책이 L7까지 올라온다는 건, 보안 경계를 IP가 아니라 "무엇을 하려는 트래픽인가"로 그린다는 뜻입니다.
관찰가능성, 의외로 가장 큰 강점
솔직히 Cilium에서 제일 체감이 컸던 부분은 성능이 아니라 여기였습니다. 기존 쿠버네티스 네트워크 디버깅은 패킷 캡처와 추측의 반복이었어요. tcpdump 띄워놓고, 어느 노드에서 뭐가 막혔는지 감으로 좁혀가는 식이었죠. 그러다 엉뚱한 노드를 세 번쯤 파고 나서야 진짜 원인을 만나는 날도 있었고요.
Cilium은 Hubble로 실시간 플로우 텔레메트리를 열어줍니다. 어떤 Pod이 어느 Service와 통신했는지, 정책이 그 트래픽을 허용했는지 거부했는지가 그대로 보입니다. DNS와 HTTP 수준의 가시성도 붙고, 지나간 플로우를 되짚는 것도 됩니다.
"네트워크가 왜 실패했지?"라고 묻던 자리에서, 네트워크 동작을 직접 들여다보는 자리로 옮겨간 셈입니다. 장애 대응할 때 이 차이는 생각보다 큽니다. 추측의 단계가 통째로 빠지니까요.
프로덕션 도입 전 점검 항목
- 충분한 eBPF 지원을 갖춘 Linux 커널 버전
- 최신 Cilium 릴리스
- kube-proxy 교체 활성화
- Hubble을 통한 관찰가능성
- 적절한 라우팅 또는 BGP 통합
첫 번째가 특히 발목을 잡습니다. 베어메탈이나 오래된 OS 이미지를 쓰는 환경이 그렇죠. 커널이 eBPF 기능을 충분히 받쳐주지 못하면 Cilium의 장점이 반쪽이 됩니다. 도입을 검토한다면 커널 버전부터 보는 게 순서입니다.
운영 관점에서 재미있는 건, 기능이 늘어나는데 오히려 복잡성은 줄어드는 경우가 많다는 점입니다. 네트워킹과 로드밸런싱과 보안이 따로 놀던 걸 한 플랫폼으로 모으니까요. 관리할 컴포넌트 수가 줄면 장애 표면도 같이 줄어듭니다.
그래서
돌아보면 쿠버네티스가 네트워킹 승자를 고르지 않은 그 결정이 출발점이었습니다. 코어를 비워두고 CNI라는 인터페이스만 고정한 덕분에, 그 빈 공간에서 다음 구현이 자랐죠. 수년간 오버레이 터널과 iptables 규칙에 기대던 네트워킹이 이제는 프로그래머블 커널 로직으로 굴러가는 단계까지 왔습니다.
Cilium이 주목받는 이유는 의외로 밋밋합니다. 커널 기능이 마침내 무르익어서, 쿠버네티스가 처음부터 약속했던 속도와 확장성을 네트워킹이 따라갈 수 있게 됐거든요.
2026년 현재 많은 팀이 Cilium으로 옮겨가는 흐름도 이 맥락 위에 있다고 봅니다. 비워둔 자리에서 더 나은 구현이 자라났고, 그게 지금 표준 자리로 올라오는 중이고요. 다만 이 자리도 언제까지 그대로일지는 저도 잘 모르겠습니다.
이 글이 도움이 되셨나요?
버튼 하나가 다음 글을 쓰는 힘이 됩니다