CCA — 실리움 인증 어소시에이트 · 네트워킹과 kube-proxy 대체 · 이론
IP 가 바뀌어도 정책이 안 흔들리는 이유
한 줄 요약
Cilium 은 파드의 라벨 집합에 숫자 하나(아이덴티티) 를 붙이고 그 숫자로 정책을 판정합니다. 파드가 재스케줄돼 IP 가 바뀌어도 라벨이 같으면 아이덴티티가 같으므로, 정책 맵은 손대지 않고 ipcache 한 곳만 갱신하면 됩니다.
왜 이게 필요했나
IP 기반 정책의 근본 문제는 쿠버네티스에서 IP 가 임시 값이라는 데 있습니다. 파드는 수시로 죽고 다시 뜨며 그때마다 새 주소를 받습니다. 정책을 IP 로 표현하면 파드가 하나 재시작할 때마다 모든 노드의 규칙을 다시 계산해야 하고, 그 사이의 짧은 창에서 트래픽이 잘못 허용되거나 잘못 차단됩니다.
Cilium 은 이 문제를 한 단계 위에서 풉니다. 정책은 "app=frontend 가 app=backend 의 8080 에 접근 가능"이라는 의미로 표현되고, 그 의미가 커널까지 숫자로 보존됩니다. iptables 시대에는 이 의미가 IP 규칙으로 번역되며 소실됐습니다.
어떻게 동작하나
파드가 뜨면 에이전트는 보안 관련 라벨만 추려 아이덴티티를 계산합니다. 여기서 어떤 라벨이 들어가고 어떤 라벨이 빠지는지가 중요합니다.
포함되는 라벨 (예) 제외되는 라벨 (예)k8s:app=frontend k8s:pod-template-hash=7d4f9ck8s:team=payments k8s:controller-revision-hash=...k8s:io.kubernetes.pod. k8s:pod-template-generation=... namespace=shoppod-template-hash 류가 제외되는 이유는 명확합니다. 이 값은 디플로이먼트를 새로 배포할 때마다 바뀝니다. 아이덴티티에 포함시키면 롤아웃 한 번에 모든 파드가 새 아이덴티티를 받게 되고, 그러면 정책 맵을 전부 다시 계산해야 합니다. 라벨 기반 모델의 이점이 통째로 사라지는 셈입니다.
아이덴티티가 만들어지면 모든 노드의 cilium_ipcache 맵에 "IP → 아이덴티티" 매핑이 전파됩니다. 수신 측 엔드포인트의 정책 맵은 (소스 아이덴티티, 포트, 프로토콜, 방향) 을 키로 O(1) 판정합니다. 파드가 옮겨 다니면 ipcache 만 바뀌고 정책 맵은 그대로라는 것이 이 설계의 핵심 이득입니다.
kube-proxy 대체는 서비스 추상화를 맵 두 계층으로 구현한 것입니다. cilium_lb4_services_v2 에서 프론트엔드(IP, 포트)를 찾고, cilium_lb4_backends 에서 실제 파드 주소를 얻어 DNAT 합니다. 여기에 더해 소켓 로드밸런싱이라는 최적화가 있습니다. 파드 안 애플리케이션이 connect() 를 호출하는 그 순간, 커널의 소켓 훅에서 서비스 IP 를 백엔드 IP 로 바꿔 버립니다. 패킷이 아예 서비스 IP 로 나가지 않으므로 패킷 단위 NAT 도 conntrack 항목도 필요 없습니다.
kubeProxyReplacement 를 켤 때 k8sServiceHost 와 k8sServicePort 를 함께 줘야 하는 이유도 여기서 나옵니다. kube-proxy 가 없으면 kubernetes 서비스의 ClusterIP(보통 10.96.0.1)를 실제 API 서버 주소로 바꿔 줄 주체가 Cilium 자신인데, 부트스트랩 시점에는 그 Cilium 이 아직 뜨지 않았습니다. 닭과 달걀 문제를 피하려고 실제 주소를 직접 알려 주는 것입니다.
라우팅 모드 선택 기준은 단순합니다.
| 기준 | 터널(VXLAN/Geneve) | 네이티브 라우팅 |
| --- | --- | --- |
| 네트워크 요구 | 노드 간 UDP 포트만 열리면 됨 | 언더레이가 PodCIDR 경로를 알아야 함 |
| 오버헤드 | 약 50바이트, MTU 축소 | 없음 |
| 적합 환경 | 언더레이를 통제할 수 없는 환경 | BGP 피어링 가능한 온프레미스, 클라우드 ENI |
부하 분산 알고리즘에서는 Maglev 일관 해싱을 알아 둬야 합니다. 백엔드가 추가되거나 빠질 때 기존 플로우 대부분이 같은 백엔드에 계속 매핑되도록 해시 테이블 재배치를 최소화합니다. 여러 노드가 ECMP 로 같은 VIP 를 받는 구성에서 노드마다 같은 선택을 하게 만드는 효과도 큽니다.
현장에서 만나는 모습
저자의 홈랩은 PodCIDR 10.244.0.0/16 위에 Cilium 1.20.1 을 올렸고, 데이터패스 상태는 이렇게 보고됩니다.
KubeProxyReplacement: True [enp2s0 10.0.0.117 (Direct Routing)]Routing: Network: Tunnel [vxlan] Host: BPFMasquerading: BPF [enp2s0] 10.244.2.0/24Encryption: Wireguard [cilium_wg0 (Port: 51871, Peers: 2)]Modules Health: Stopped(0) Degraded(0) OK(92)읽는 법이 있습니다. Routing: Network Tunnel[vxlan] 은 노드 간에는 캡슐화를 쓴다는 뜻이고, Host: BPF 는 호스트 라우팅까지 eBPF 가 맡았다는 뜻입니다. Masquerading: BPF 는 SNAT 도 iptables 가 아니라 eBPF 로 처리된다는 뜻입니다. 즉 이 노드에는 kube-proxy 도, 마스커레이드용 iptables 규칙도 필요하지 않습니다. 실제로 KUBE- 체인은 0개였습니다.
k8sServiceHost 에는 컨트롤 플레인 노드의 실주소 10.0.0.120 이 들어갔습니다. 이 주소가 어떻게 정해졌는지도 교훈입니다. 원래 이 클러스터는 DHCP 임대로 .111 을 쓰다가 어느 날 .120 을 받아 죽었습니다. apiserver 인증서 SAN 에 .120 이 없었기 때문에 IP 만 되돌리는 것 외에는 복구가 어려웠고, 결국 static 으로 고정한 뒤 처음부터 다시 세웠습니다. 부트스트랩 주소는 나중에 바꾸기가 몹시 고통스러운 몇 안 되는 설정이라는 사실을 기억해 두면 좋습니다.
다음 실습에서 할 것
Cilium Helm values 파일을 직접 작성해 kube-proxy 대체와 데이터패스 옵션을 선언하고, 노드에 라벨을 붙여 워크로드 배치를 고정하고, 서비스와 디플로이먼트를 실제로 apply 해 백엔드가 잡히는지 확인한 뒤, kube-proxy 가 정말 없는지 검증하는 스크립트를 만듭니다.