CCA — 실리움 인증 어소시에이트 · 네트워킹과 kube-proxy 대체 · 실습
kube-proxy 를 지웠더니 서비스 규칙이 사라졌다
목표
kube-proxy 없이 도는 진짜 Cilium 1.20.1 에서 서비스 부하 분산이 어느 BPF 맵과 어느 eBPF 프로그램으로 옮겨 갔는지 직접 읽습니다.
iptables 가 비어 있음을 확인하고, 서비스 맵·conntrack 맵·파드 장치의 프로그램·NodePort·백엔드 없는 서비스의 응답까지 따라갑니다.
왜 중요한가
kube-proxy 의 iptables 모드는 서비스와 엔드포인트마다 규칙을 만들고, 규칙은 위에서부터 차례로 평가됩니다. 서비스가 늘면 규칙이 늘고,
바뀔 때마다 규칙을 다시 씁니다. Cilium 의 kube-proxy 대체는 서비스를 해시 맵 항목으로 두고, 파드와 노드 장치에 붙은 eBPF 프로그램이
패킷이 지나갈 때 맵을 찾아 목적지를 바꿉니다.
이 차이를 알아야 장애 때 어디를 볼지 압니다. "서비스가 안 된다" 는 신고에 iptables-save 를 뒤지면 아무것도 나오지 않습니다. 대신
서비스 맵의 백엔드 슬롯, conntrack 의 항목, 파드 장치에 프로그램이 붙어 있는지를 봐야 합니다.
VM 안의 개인 k3s(Cilium 1.20.1, kubeProxyReplacement=true)만 사용하세요. 환경 준비에 약 5분이 걸립니다. 세션이 끝나면 /root/cca-datapath 의 파일은 사라집니다.
단계
1. kubectl apply -f /opt/fixtures/cca-datapath/web.yaml 로 cca-dp 네임스페이스에 web(레플리카 2)과 ClusterIP 서비스 web 을 띄우세요. 파드가 Ready 가 되면 VM 셸에서 iptables-save 를 읽어 /root/cca-datapath/iptables.json 에 기록합니다 — cluster_ip(서비스 web 의 ClusterIP), kube_svc_lines(KUBE-SVC 를 포함한 줄 수), cluster_ip_lines(그 ClusterIP 를 포함한 줄 수), cilium_lines(CILIUM 을 포함한 줄 수), kube_proxy_replacement(에이전트 cilium-dbg status 의 KubeProxyReplacement 값). 수는 숫자로 적습니다.
2. 에이전트의 cilium-dbg service list -o json 에서 cca-dp/web 의 ClusterIP 항목을 찾고, cilium-dbg bpf lb list 에서 같은 프런트엔드의 슬롯을 확인하세요. /root/cca-datapath/svc.json 에 service_id(숫자), frontend(ClusterIP:80), backends(백엔드 ip:port 2개 목록), backend_pods(그 IP 를 가진 web 파드 이름 2개) 를 기록합니다.
3. web 을 레플리카 4개로 늘리세요. 네 파드가 Ready 가 되고 bpf lb list 의 web 프런트엔드 백엔드 슬롯이 4개가 될 때까지 짧게 폴링한 뒤, /root/cca-datapath/scale.json 에 backends(지금 맵에 있는 ip:port 4개), new_backends(svc.json 에 없던 것 2개) 를 기록합니다. EndpointSlice 의 ready 주소와도 같아야 합니다.
4. kubectl apply -f /opt/fixtures/cca-datapath/holder.yaml 로 holder 파드를 띄우세요. holder 는 출발 포트 40404 로 web 서비스(ClusterIP:80)에 TCP 연결 하나를 열고 쥐고 있으며, 로그에 앱이 getpeername 으로 본 상대 주소를 찍습니다. 세 곳에서 같은 연결을 보세요 — holder 로그, holder 안 netstat -tn, 에이전트의 cilium-dbg bpf ct list global 의 :40404 항목. /root/cca-datapath/ct.json 에 client(holderIP:40404), app_peer(로그의 상대 주소 ip:port), socket_peer(holder netstat 의 Foreign Address), backend(conntrack OUT 항목의 목적지 ip:port), backend_pod(그 IP 의 web 파드 이름), svc_entries(:40404 를 가진 TCP SVC 항목 수, 숫자), socket_lb_coverage(cilium-dbg status --verbose 의 Socket LB Coverage) 를 기록합니다. 백엔드 파드 안 netstat 에도 holder 의 IP:40404 가 보여야 합니다.
5. holder 의 엔드포인트 번호(kubectl -n cca-dp get cep holder 의 status.id)로 에이전트 cilium-dbg endpoint get <번호> -o json 을 읽어 호스트 쪽 장치 이름과 ifindex 를 찾고, VM 셸에서 bpftool net show dev <장치> 와 tc filter show dev <장치> ingress 를 비교하세요. /root/cca-datapath/prog.json 에 endpoint_id, interface, ifindex, attach(bpftool 이 보여 준 붙은 자리), program(프로그램 이름), policy_map(cilium-dbg map list 에서 찾은 이 엔드포인트의 정책 맵 이름), tc_filter_lines(tc filter 출력 줄 수, 숫자) 를 기록합니다.
6. cca-dp 에 NodePort 서비스 web-np 를 만드세요 — selector app=web, port 80, targetPort 8080, nodePort 30780. 노드 InternalIP 의 30780 으로 VM 셸에서 요청해 200 을 확인하고, /root/cca-datapath/nodeport.json 에 node_ip, node_port, http_code(숫자), iptables_lines(iptables-save 에서 30780 을 포함한 줄 수), listen_sockets(ss -Htln 'sport = :30780' 줄 수), service_id(에이전트 목록의 0.0.0.0:30780 NodePort 항목 번호) 를 기록합니다.
7. cca-dp 에 selector app=ghost, port 80 → targetPort 8080 인 ClusterIP 서비스 ghost 를 만드세요(그 라벨의 파드는 만들지 않습니다). curl 파드 probe(이미지 curlimages/curl:8.10.1@sha256:d9b4541e214bcd85196d6e92e2753ac6d0ea699f0af5741f8c6cccbfcf00ef4b, 명령 sleep 86400)를 띄워 그 안에서 ghost 의 ClusterIP 로 5초 제한 요청을 보냅니다. /root/cca-datapath/ghost.json 에 cluster_ip, backends(에이전트 목록의 백엔드 수, 숫자), curl_exit(curl 종료 코드), seconds(curl 의 time_total, 숫자), no_backend_response(cilium-dbg config -a 의 ServiceNoBackendResponse 값) 를 기록합니다.
8. /root/cca-datapath/report.txt 에 키=값 일곱 줄을 씁니다 — kube_proxy_replacement, socket_lb_coverage(status --verbose 의 Socket LB Coverage), web_backends(지금 bpf lb list 의 web 백엔드 슬롯 수), holder_backend(지금 conntrack 에서 holder 연결이 향한 ip:port), holder_program(prog.json 의 program), nodeport_iptables_lines(지금 30780 을 포함한 iptables 줄 수), ghost_no_backend_response. 값은 모두 지금 상태와 앞 기록에 일치해야 합니다.
참고
- Kubernetes Without kube-proxy(소켓 LB, hostNamespaceOnly, NodePort): https://docs.cilium.io/en/v1.20/network/kubernetes/kubeproxy-free/
- eBPF 맵 목록과 크기: https://docs.cilium.io/en/v1.20/network/ebpf/maps/
- BPF 와 XDP 참고 가이드: https://docs.cilium.io/en/v1.20/reference-guides/bpf/index.html
- 에이전트 명령:
kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg service list | bpf lb list | bpf ct list global | map list | endpoint get <번호>. - VM 셸에는 bpftool·tc·ss·iptables-save·jq 가 있습니다. python 이미지 파드 안에는 busybox netstat 이 있습니다.
단계 8개
- kube-proxy 가 없는데 서비스는 누가 돌리나
- 서비스 번호표를 BPF 맵에서 찾는다
- 레플리카를 늘리자 맵이 먼저 안다
- 앱은 서비스에 붙었다고 믿지만 소켓은 이미 파드에 붙어 있다
- 파드 옆 장치에 붙은 프로그램과 그 파드만의 정책 맵
- 리슨하는 프로세스가 없는데 NodePort 가 답한다
- 파드가 하나도 없는 서비스는 기다리지 않는다
- kube-proxy 가 하던 일의 새 주소록