상태: 초안 (2026-10-17), 일부 실측. 기준 판 cilium/cilium v1.20.1(커밋 7d68cfb394f2). 작은 클러스터가 바로 이 판(에이전트· operator-generic·hubble-relay v1.20.1, cilium-envoy 1.37 계열)을 돌리고 있어서, 실측은 2026-10-17 그 클러스터에서 읽기로만 했습니다(에이전트 파드 하나에
cilium-dbg·bpftool·hubble observe읽기 명령, ConfigMap·정책 개수 읽기, relay 로그 읽기). DNS 패턴의 접속 결과는 기여를 준비하며 2026-10-16~17 사이에 잰 기록(PR 초안)을 인용합니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 확인하지 못한 것은 「미확인」으로 표시합니다.
목차
1. 한 줄 요약
Cilium 을 한 줄로 줄이면 「노드마다 있는 사무실(에이전트)이 규칙표(BPF 맵)를 고쳐 쓰고, 커널 곳곳의 검문소(BPF 프로그램)는 규칙표만 보고 통과·차단·주소 변환을 결정한다」 입니다. 검문소는 사무실에 전화를 걸지 않습니다. 패킷 하나마다 사용자 공간으로 올라오면 느리기 때문에, 판단에 필요한 것은 전부 미리 맵에 적어 둡니다. 그래서 Cilium 코드를 읽을 때 가장 먼저 물어야 할 질문은 언제나 「이 정보는 누가 어느 맵에 쓰고, 어느 훅이 읽는가」 입니다.
| 구성요소 | 하는 일 | 어디서 도나 |
|---|---|---|
| cilium-agent | 쿠버네티스 상태를 받아 정책·서비스·identity 를 계산하고 BPF 맵에 쓴다. BPF 프로그램을 컴파일·적재한다. DNS 프록시와 Hubble 관찰기도 이 프로세스 안에 있다 | 노드마다(DaemonSet) |
BPF 프로그램 (bpf/) |
파드 veth·호스트 장치·터널 장치·소켓 cgroup 에 붙어 패킷과 connect() 를 처리 |
커널 안 |
| cilium-operator | CRD 등록, IPAM(모드에 따라), 남은 identity·CiliumEndpoint 정리, LB-IPAM | 클러스터에 하나(리더 선출) |
| cilium-envoy | HTTP 같은 L7 정책과 Ingress·Gateway 의 프록시. 에이전트에게 xDS 로 설정을 받는다 | 노드마다(별도 DaemonSet) |
| Hubble | 에이전트 안 관찰기가 BPF 가 내는 이벤트를 흐름으로 해석해 링버퍼에 담고, relay 가 노드들의 흐름을 모은다 | 관찰기는 에이전트 안, relay 는 클러스터에 하나 |
2. 구조도
- 위 두 상자는 클러스터에 하나씩입니다. 에이전트는 API 서버를 직접 감시합니다. operator 를 거치지 않으므로, operator 가 잠시 멈춰도 이미 도는 노드의 데이터패스는 그대로입니다.
- 가운데 큰 상자가 노드 하나입니다. 에이전트 상자 안의 칩 여섯 개가 이 글에서 따라갈 부품이고, 아래 원통이 그 부품들이 쓰는 BPF 맵입니다.
- 맨 아래 표가 「훅 자리 → 붙는 프로그램」입니다. 파일 이름이 곧 붙는 자리입니다.
bpf_lxc.c는 파드 쪽,bpf_host.c는 호스트 쪽,bpf_overlay.c는 터널 장치,bpf_sock.c는 소켓 시스템 호출입니다. 점선 칸(XDP)은 작은 클러스터에서 꺼져 있습니다. - 왼쪽 점선 화살표는 BPF 가
cilium_eventsperf 맵으로 내보낸 이벤트가 에이전트 안의 Hubble 관찰기로 올라가는 길입니다(4.4).
3. 코드 지도
저장소는 크지만 처음 볼 곳은 정해져 있습니다.
| 경로 | 무엇 | 처음 읽을 파일 |
|---|---|---|
daemon/ |
에이전트 진입점과 셀 묶음 | daemon/cmd/cells.go |
bpf/ |
커널에 올라가는 C 프로그램. 진입 파일 bpf_*.c 와 공용 헤더 bpf/lib/ |
bpf/bpf_lxc.c |
pkg/datapath/ |
BPF 컴파일·적재(loader), 장치·라우트·sysctl 조정, 맵 정의 |
pkg/datapath/loader/endpoint.go |
pkg/loadbalancer/ |
1.18 이후 새로 짠 서비스 부하 분산 제어부(StateDB 표 + reconciler) | pkg/loadbalancer/README.md |
pkg/policy/ |
정책 저장소, selector cache, 엔드포인트별 정책 계산 | pkg/policy/selectorcache.go |
pkg/fqdn/ |
DNS 프록시, 이름→IP 캐시, matchPattern 정규식 변환 |
pkg/fqdn/matchpattern/matchpattern.go |
pkg/hubble/, hubble-relay/ |
흐름 해석·링버퍼·gRPC 서버, 그리고 relay | pkg/hubble/observer/local_observer.go |
operator/ |
operator 진입점과 셀 | operator/cmd/root.go |
에이전트와 operator 는 둘 다 hive 라는 의존성 주입 틀로 조립됩니다. 기능 하나가 「셀(cell)」 하나이고, 셀은 무엇을 제공하고 무엇이
필요한지만 선언합니다. 에이전트 main 은 daemon/main.go:11 에서
hive.New(cmd.Agent) 한 줄이고, 그 Agent 가 daemon/cmd/cells.go:103
에서 Infrastructure·ControlPlane·datapath.Cell 세 묶음으로 나뉩니다. ControlPlane(cells.go:194)
주석이 나눈 이유를 직접 말합니다. 제어부는 「순수한 업무 논리」라서 커널 권한 없이 통합 시험을 돌릴 수 있게 하려는 것입니다. BPF 를 실제로
만지는 일은 pkg/datapath/cells.go:52 의 datapath 모듈로 몰았습니다.
실측한 cilium-dbg status --verbose 의 모듈 트리도 controlplane / datapath / infra 셋으로 갈렸습니다(6절).
4. 핵심 경로 따라가기
4.1 에이전트가 BPF 를 컴파일·적재하는 길
Cilium 은 BPF 오브젝트를 이미지에 미리 구워 두지 않고, 노드에서 clang 으로 컴파일합니다(에이전트 이미지에 clang 19.1.7 이 들어 있음,
실측). 노드마다 커널 기능과 설정(터널 여부, 마스커레이드, IPv6 등)이 달라서, 쓰지 않는 기능을 #ifdef 로 아예 빼야 검증기를 통과하고
명령어 수 한도에도 걸리지 않기 때문입니다.
- 기반 데이터패스.
pkg/datapath/orchestrator/orchestrator.go:167reconciler가 노드 설정이 바뀔 때마다Reinitialize를 부르고,pkg/datapath/loader/base.go:298Reinitialize가 터널 장치용bpf_overlay.c, XDP, 그리고 소켓 LB 가 켜져 있으면bpf_sock.c를 컴파일해 (base.go:406) cgroup 에 붙입니다 (pkg/socketlb/socketlb.go:83Enable). - 엔드포인트(파드)마다.
pkg/datapath/loader/endpoint.go:83ReloadDatapath는 먼저cache.go:176fetchOrCompile을 부릅니다. 여기가 설계의 요점입니다. 엔드포인트의 설정에서 파드마다 다른 값(엔드포인트 ID, IP, ifindex 등)을 뺀 나머지로 해시를 만들고, 같은 해시의 오브젝트가 이미 있으면 컴파일하지 않고 복사해 씁니다. 실제 컴파일은compile.go:160compile이 clang 을 실행합니다. - 값 꽂고 붙이기.
endpoint.go:195reloadEndpoint가 템플릿에 파드별 값을 상수로 덮어쓰며(Constants: endpointConfiguration(...)) 적재하고, 정책 프로그램을 tail call 맵에 먼저 넣은 뒤cil_from_container를 veth 의 ingress 에 붙입니다. 순서에 주석이 달려 있습니다. 입구를 먼저 열면 반쯤 준비된 프로그램으로 패킷이 들어가기 때문입니다. 파드 쪽 egress 프로그램(cil_to_container)은RequireEgressProg()가 참일 때만 붙습니다.
실측이 이유를 보여 줍니다. 노드 하나의 엔드포인트 13개가 템플릿 해시 하나를 나눠 썼고, 템플릿은 bpf_lxc.o(약 1.0MB)와
bpf_host.o(약 1.4MB) 둘뿐이었습니다. 컴파일은 한 번, 나머지는 상수만 바꿔 적재하니 파드 생성 지연에서 clang 이 빠집니다.
4.2 kube-proxy 대체: Service 가 connect() 에서 백엔드로 바뀌기까지
kube-proxy 는 Service 를 iptables(또는 IPVS) 규칙으로 바꿉니다. Cilium 은 같은 정보를 BPF 해시 맵에 넣고, 가능하면 패킷이 생기기도
전에 소켓의 목적지 주소를 바꿉니다. 1.18 에서 이 제어부를 StateDB 기반으로 새로 짰고, 그 구조는
pkg/loadbalancer/README.md 의 그림 그대로입니다.
- 반사(reflect).
pkg/loadbalancer/reflectors/k8s.go:208runServiceEndpointsReflector가 Service 와 EndpointSlice 변경을 잠깐 모았다가 한 트랜잭션으로 씁니다(대량 롤아웃 때 맵을 수천 번 따로 고치지 않으려는 것). - 표에 쓰기.
pkg/loadbalancer/writer/writer.go:326UpsertServiceAndFrontends등이 서비스·프런트엔드·백엔드 세 표의 참조를 맞춥니다. 출처가 여럿이어도 이 Writer 로만 들어옵니다. - 맵으로 맞추기.
pkg/loadbalancer/reconciler/bpf_reconciler.go:725Update가 프런트엔드 표를 지켜보다가updateFrontend(842행) 에서 맵을 씁니다. 맵cilium_lb4_services_v2(bpf/lib/lb.h:284) 는 키가(주소, 포트, 프로토콜, 슬롯)이고, 슬롯 0 이 「머리 칸」(백엔드 수·플래그), 슬롯 1..n 이 백엔드 ID 입니다. 재시도는 reconciler 가 맡아 데이터 출처는 맵 오류를 몰라도 됩니다. - 소켓에서 풀기.
bpf/bpf_sock.c:467cil_sock4_connect가 cgroupconnect4훅에서__sock4_xlate_fwd(298행) 를 부르고, 거기서lb4_lookup_service로 머리 칸을 찾은 뒤sock_select_slot() % count + 1로 슬롯을 골라 소켓 주소를 백엔드로 바꿉니다. 응용은 ClusterIP 에 연결했다고 믿지만 커널 소켓은 처음부터 백엔드에 붙어 있어서, 패킷마다 NAT 할 일이 없습니다.
그런데 4단계에는 문이 하나 있습니다.
if (is_defined(ENABLE_SOCKET_LB_HOST_ONLY) && !in_hostns)
return -ENXIO;
if (!udp_only && !sock_proto_enabled(protocol))
return -ENOTSUP;
/* In case a direct match fails, we try to look-up surrogate
* service entries via wildcarded lookup for NodePort and
* HostPort services.
*/
svc = lb4_lookup_service(&key, true);
if (!svc)
svc = sock4_wildcard_lookup_full(&key, in_hostns);
if (!svc)
return -ENXIO;
출처: cilium v1.20.1 bpf/bpf_sock.c 322–335행, GPL-2.0-only OR BSD-2-Clause
socketLB.hostNamespaceOnly 를 켜면 파드 네임스페이스의 소켓은 여기서 그냥 돌아갑니다. 그러면 서비스 주소 변환은 누가 하나요?
bpf_lxc.c 가 컴파일 때 스스로 판단합니다.
#if !defined(ENABLE_SOCKET_LB_FULL) || \
defined(ENABLE_SOCKET_LB_HOST_ONLY) || \
defined(ENABLE_L7_LB) || \
defined(ENABLE_SCTP) || \
defined(ENABLE_CLUSTER_AWARE_ADDRESSING)
# define ENABLE_PER_PACKET_LB 1
#endif
출처: cilium v1.20.1 bpf/bpf_lxc.c 90–96행, GPL-2.0-only OR BSD-2-Clause
소켓에서 다 풀 수 없는 경우가 하나라도 있으면 파드 veth 의 tc 프로그램에 패킷 단위 LB
(__per_packet_lb_svc_xlate_4, 152행)가 들어갑니다. 공식 문서는
이 우회를 Istio 사이드카처럼 원래 ClusterIP 가 필요한 경우와, KubeVirt·Kata·gVisor 처럼 소켓 LB 가 효과가 없는 파드를 위한 것이라고
설명합니다(kubeproxy-free 문서 「Socket LoadBalancer Bypass in Pod Namespace」). 실측한 작은 클러스터가 정확히 이 모드였습니다
(Socket LB Coverage: Hostns-only). TT Lab 클러스터는 KubeVirt VM 을 돌리므로 이 선택에는 이유가 있습니다
(KubeVirt 실습 VM 시리즈). 결과적으로 파드에서 서비스로 가는 연결은 cil_from_container 안에서
풀리고, 소켓 역변환 맵(cilium_lb4_reverse_sk)에는 호스트 네임스페이스 소켓 13개만 있었습니다. 「kube-proxy 대체 = 소켓 LB」 라고
외워 두면 틀리는 지점입니다.
4.3 toFQDNs 정책이 DNS 프록시를 거쳐 「이 IP 허용」이 되는 길
BPF 는 IP 와 포트만 봅니다. 그런데 사람은 *.docker.com 처럼 이름으로 정책을 쓰고 싶어 합니다. Cilium 의 답은 DNS 응답을 가로채서
이름→IP 를 배우고, 배운 IP 에 이름에서 나온 identity 를 붙이는 것입니다. 이름은 BPF 까지 내려가지 않고, BPF 는 늘 하던 대로
「이 identity 허용?」 만 묻습니다.
- 셀렉터 등록. 정책에
toFQDNs가 있으면pkg/fqdn/namemanager/manager.go:140RegisterFQDNSelector가 셀렉터를 받아 두고, 이미 캐시에 있는 이름 중 맞는 것이 있으면 바로 IP 를 표시합니다. 셀렉터의 정규식은pkg/policy/api/fqdn.go:115ToRegex가 matchpattern 으로 만듭니다. - 질의 가로채기. 같은 정책의
rules.dns(포트 53) 때문에bpf_lxc가 그 파드의 DNS 패킷을 노드의 DNS 프록시로 돌립니다.pkg/fqdn/dnsproxy/proxy.go:895ServeDNS가CheckAllowed(763행) 로 질의 이름을 검사합니다. 그 정규식은GeneratePattern(1439행) 이rules.dns의 패턴들을|로 묶어 만든 것입니다. 맞지 않으면 거절 응답을 돌려줍니다(작은 클러스터 설정은refused). - 응답 → 이름 캐시. 허용되면 위로 보내고, 응답을 받으면 클라이언트에 돌려주기 전에
NotifyOnDNSMsg를 부릅니다. 이것이pkg/fqdn/messagehandler/message_handler.go:277UpdateOnDNSMsg를 거쳐namemanager/manager.go:214UpdateGenerateDNS로 갑니다. - 이름 매칭.
manager.go:422deriveLabelsForName이 응답 이름을 모든 FQDN 셀렉터의 정규식에 대 보고, 맞는 셀렉터마다fqdn:<패턴>라벨을 모읍니다. - IP 에 identity.
manager.go:315updateMetadata가 응답의 IP 마다 그 라벨을 ipcache 에 올리고, ipcache 가 라벨 묶음에 맞는 identity 를 할당합니다. 새 identity 는pkg/policy/selectorcache.go:768UpdateIdentities로 셀렉터 캐시에 들어가고, 그 셀렉터를 쓰는 엔드포인트의 정책 맵에 허용 칸이 생깁니다. - 기다렸다 돌려주기.
UpdateGenerateDNS는 ipcache 가 그 변경을 맵까지 반영할 때까지(WaitForRevision) 기다리는 채널을 돌려줍니다. 핸들러는tofqdns-proxy-response-max-delay(작은 클러스터 100ms) 동안만 기다리고 응답을 파드에 씁니다. 파드가 응답을 받자마자 연결해도 정책 맵이 이미 열려 있게 하려는 순서입니다.
여기서 matchpattern 이 두 번 쓰인다는 점이 중요합니다(그림의 ③·⑤). 변환 규칙은 짧습니다.
func escapeRegexpCharacters(pattern string) string {
// Convert '.' in the match pattern as literal '.' for regex pattern.
pattern = strings.ReplaceAll(pattern, ".", "[.]")
// '**.' in match pattern prefix is a subdomain wildcard specifier which matches one ore more
// entire labels.
pattern = subdomainWildcardSpecifierPrefix.ReplaceAllString(pattern, dnsWildcardREGroup)
// Base case: * becomes .*, but only for DNS valid characters
// `*` wildcard matches all DNS characters within the subdomain boundary(doesn't include '.' literal)
pattern = wildcardSpecifier.ReplaceAllString(pattern, allowedDNSCharsREGroup+"*")
return pattern
}
출처: cilium v1.20.1 pkg/fqdn/matchpattern/matchpattern.go 131–144행, Apache-2.0
* 는 [-a-zA-Z0-9_]*(17행)로 바뀌어
점을 넘지 못합니다. 여러 칸을 받는 것은 맨 앞의 **. 하나뿐이고(37행의 ^[*]{2,}\[\.\], 23행의 dnsWildcardREGroup), 가운데의
** 는 마지막 줄에서 그냥 * 처럼 바뀝니다. 끝에 점을 붙이는 정규화는 Sanitize(87행)
가 합니다.
실측 사례. 기여를 준비하며 작은 클러스터에서 잰 기록(2026-10-16~17, PR 초안)입니다. 이미지 레지스트리용 정책이
toFQDNs: matchPattern: "*.docker.com" 이고 DNS 는 * 로 열린 파드에서 세 이름을 두 번씩 접속했습니다.
| 이름 | DNS 해석 | 접속 | 이유 |
|---|---|---|---|
hub.docker.com |
됨 | 연결(HTTP 200) | 한 칸 → *.docker.com 에 맞음 |
production.cloudflare.docker.com |
됨 | 시간 초과(curl 28) | 두 칸 → * 가 점을 못 넘음 |
docker.com |
됨 | 시간 초과 | *. 앞에 한 칸이 있어야 함 |
둘째 줄은 도커 허브의 이미지 계층을 내려주는 CDN 이름이라, 정책은 맞아 보이는데 이미지 받기만 실패하는 증상이 됩니다. 같은 기록에서
main 의 matchpattern 을 Go 로 직접 돌린(Validate(Sanitize(p))) 결과도 표와 같았고(**.docker.com 은 둘째 줄에 맞음), 글을 쓰며 변환 규칙을
파이썬으로 옮겨 다시 돌린 결과도 같았습니다(그림 아래 표). DNS 는 모두 해석되었으므로 막힌 곳은 ③이 아니라 ⑤입니다. 같은 성질이
실습 파드의 DNS 출구 정책에서 어떤 문제를 만들었는지는 DNS 로 새는 길 막기 에 따로 적었습니다.
이 글은 그 성질이 코드의 어느 줄에서 나오는지까지만 다룹니다.
4.4 Hubble: perf 링버퍼에서 relay 까지
- BPF 가 이벤트를 낸다. 패킷이 전달·차단·정책 판정될 때
bpf/lib/trace.h:210_send_trace_notify등이cilium_events(bpf/lib/events.h:13,PERF_EVENT_ARRAY)로 짧은 기록을 씁니다. 모든 패킷을 쓰면 비싸므로MONITOR_AGGREGATION으로 줄입니다. 실측한 엔드포인트의ep_config.h는MONITOR_AGGREGATION 3(활성 연결의 추적을 간추림, ConfigMap 의monitor-aggregation: medium)이었습니다. - 에이전트가 읽는다.
pkg/monitor/agent/agent.go:324handleEvents가 perf 리더를 열고, 레코드마다notifyPerfEventLocked(428행) 로 소비자들에게 나눕니다. Hubble 은 그 소비자 중 하나입니다(pkg/hubble/monitor/consumer.go:73). - 해석해 링버퍼에.
pkg/hubble/observer/local_observer.go:116Start의 루프가 이벤트를Decode해 흐름(Flow)으로 만들고, 지표 훅을 거쳐container/ring.go:168Ring.Write로 고정 크기 링에 넣습니다. 크기는 2의 거듭제곱 빼기 1(기본 4095)이라 인덱스를 나눗셈 대신 마스크로 구합니다.GetFlows(260행)는 이 링을 읽기만 합니다. - relay 가 모은다. relay 는 각 에이전트의 Hubble 서버(:4244, TLS)에 붙어 둡니다(
pkg/hubble/relay/pool/manager.go:51). 요청이 오면pkg/hubble/relay/observer/server.go:62GetFlows가 모든 피어에 같은 요청을 보내고(observer.go:37), 돌아온 흐름을 시간순으로 다시 정렬해(sortFlows, 67행) 내보냅니다.
설계의 뜻은 「저장하지 않는다」입니다. 흐름은 노드 메모리의 링에만 있고 relay 도 보관하지 않습니다. 실측한 노드는 링이 4095/4095 로 찬 채 초당 58.5개가 들어오고 있었으니, 한 노드가 기억하는 과거는 약 70초입니다. 오래 남기려면 지표나 exporter 를 따로 씁니다.
5. 사용자 기여와의 연결
글쓴이는 4.3의 성질을 문서에 바로잡는 변경을 준비해 두었습니다(원장 5절 「준비만 된 것」). 아직 게시하지 않았습니다. Cilium 은 AI 가 상당 부분 쓴 소통(PR 본문 등)을 받지 않는 정책이라, 본문은 글쓴이가 직접 써야 합니다. 그래서 여기서도 「준비만 했다」 까지만 적습니다.
| 문서 위치(v1.20.1) | 지금 내용 | 코드 근거 |
|---|---|---|
Documentation/security/policy/layer7.rst:159 |
L7 DNS 의 matchPattern 설명에 **. 가 없음 |
GeneratePattern 도 ToUnAnchoredRegexp → 같은 escapeRegexpCharacters |
layer7.rst:166, :227 |
한 칸만 맞는 예를 「any subdomains」 라고 부름 | * → [-a-zA-Z0-9_]*(141행) |
layer3.rst:548 |
**. 설명은 있으나 「앞머리에서만」이 없음 |
37행 정규식이 ^ 로 고정, 시험의 test.**.cilium.io. 사례 |
근거는 4.3의 실측 표와 Go 재현 결과입니다. 문서 세 파일과 matchpattern.go 는 v1.20.1 과 main 이 같다는 것을 준비 때 확인했다고
초안에 적혀 있습니다.
6. 실측
2026-10-17, 작은 클러스터(노드 8대, kube-proxy DaemonSet 없음)에서 컨트롤 플레인 노드 하나의 에이전트 파드에만 읽기 명령을 보냈습니다. 원자료는 따로 보관했고, 아래는 이름·주소를 뺀 값입니다.
| 명령 | 본 것 |
|---|---|
cilium-dbg status |
KubeProxyReplacement: True, Routing: Network: Tunnel [vxlan] Host: BPF, Attach Mode: TCX, Device Mode: veth, Masquerading: BPF, Host firewall: Enabled, 컨트롤러 90/90, 모듈 125 OK, 클러스터 건강 8/8 |
cilium-dbg status --verbose |
Socket LB: Enabled, Socket LB Coverage: Hostns-only, Backend Selection: Random, XDP Acceleration: Disabled, IPAM 은 노드당 /24 에서 15개 할당 |
cilium-dbg service list |
프런트엔드 211개(ClusterIP 171, NodePort 20, LoadBalancer 20) |
cilium-dbg bpf lb list, map list |
cilium_lb4_services_v2 573칸 = 머리 칸 358 + 백엔드 슬롯 215. 머리 칸 중 127개는 포트 0/ANY 와일드카드 칸(용도 세부는 미확인). 백엔드 맵 163, 역변환 맵 231, 소켓 역변환 13 |
bpftool net show |
lxc 장치 13개 모두 tcx/ingress cil_from_container 만, egress 없음. 물리 NIC cil_from/to_netdev, cilium_host·cilium_net cil_to_host·cil_from_host, cilium_vxlan cil_from/to_overlay, XDP 비어 있음 |
bpftool prog show |
Cilium 프로그램 231개(tc 218, cgroup sock_addr 8, cgroup sock 3, 소켓 종료용 tracing 2). tc 쪽은 엔드포인트마다 대략 진입 3개 + tail call 10개 |
bpftool cgroup tree |
cgroup 루트에 cil_sock4_connect·sendmsg4·recvmsg4·getpeername4·post_bind4·sock_release 등 11개 |
상태 디렉터리 ls |
템플릿 2개(bpf_lxc.o 1,086,040바이트, bpf_host.o 1,452,768바이트). 엔드포인트 13개의 template.txt 해시가 모두 같음 |
hubble observe --last 500 |
500개가 10.5초 분량, FORWARDED 489 · DROPPED 11(모두 UNSUPPORTED_L3_PROTOCOL). 링 4095/4095, 58.5 흐름/초 |
cilium-dbg fqdn cache list, fqdn names |
이 노드는 비어 있음(FQDN 셀렉터 0) — 이 노드에는 DNS 규칙이 걸린 파드가 없었음 |
| 정책 개수(클러스터 전체) | CNP 22개 중 rules.dns 가 있는 것 5, toFQDNs 가 있는 것 2 |
| relay 로그 | 피어 8개에 :4244, TLS 로 연결 |
읽을 것이 셋입니다.
- 소켓 LB 는 반쪽만 켜져 있습니다. 4.2에서 본
ENABLE_SOCKET_LB_HOST_ONLY경로입니다. 프로그램 개수에서도 보입니다. 엔드포인트마다tail_nodeport_rev_dnat_ipv4·tail_no_service_ipv4같은 tc 쪽 LB tail call 이 붙어 있고, 소켓 역변환 맵은 13칸뿐입니다. - 파드 쪽 egress 훅이 없습니다. 파드로 들어가는 패킷은 veth 의 egress 가 아니라, 호스트 쪽 프로그램이 정책 tail call
(
cil_lxc_policy)을 부른 뒤 바로 넘겨 줍니다. 4.1의RequireEgressProg()가 거짓인 경우입니다. 「파드마다 프로그램 두 개」 라는 흔한 그림과 다릅니다. - FQDN 은 이 노드에서 볼 것이 없었습니다. 에이전트 하나만 읽었으므로, DNS 규칙이 걸린 파드가 있는 노드의 캐시는 미실측입니다.
7. 어디부터 읽을까
daemon/cmd/cells.go:103— 셀 목록을 훑으며 에이전트에 무엇이 들어 있는지 지도를 만듭니다. 주석이 친절합니다.pkg/loadbalancer/README.md와bpf_reconciler.go:842— 「표 → reconciler → 맵」 이라는 Cilium 의 새 제어부 문법을 가장 짧게 배우는 곳입니다.bpf/bpf_sock.c:298— 맵을 읽는 쪽. 2번에서 쓴 칸이 어떻게 읽히는지 맞춰 봅니다.pkg/datapath/loader/endpoint.go:195— 템플릿·상수·tcx 부착 순서.pkg/fqdn/dnsproxy/proxy.go:895에서namemanager/manager.go:214까지 — 사용자 공간 프록시와 BPF 정책이 만나는 곳.bpf/bpf_lxc.c:1780cil_from_container— 마지막에 읽습니다. tail call 과#ifdef가 많아서, 앞의 다섯을 알고 봐야 길을 잃지 않습니다.
8. 첫 기여하기 좋은 곳
소스를 읽으며 실제로 걸린 것들입니다(게시하지 않았고, main 에서 그대로인지는 미확인).
pkg/loadbalancer/README.md:33의 없는 파일 이름. 쿠버네티스 데이터 출처가reflector.go에 있다고 하지만, v1.20.1 에는 그 파일이 없고 실제 구현은reflectors/k8s.go입니다(같은 README 의 61행 목록은reflectors디렉터리를 맞게 적음). 한 줄 문서 수정감입니다.bpf/bpf_lxc.c:88주석의 오타. 「enable per-packet LB is SCTP is enabled」 는 「if」 입니다. 4.2에서 인용한 조건 바로 위라 읽는 사람이 한 번씩 멈추는 줄입니다.pkg/fqdn/matchpattern/matchpattern.go:19·:135의 「one ore more」. 오타 두 곳. 그리고ToAnchoredRegexp(95–99행) 문서 주석은*만 설명하고**.를 말하지 않습니다. 5절의 문서 변경과 같은 주제라 함께 다루기 좋습니다.- 와일드카드 머리 칸의 설명. 실측한 머리 칸의 3분의 1이 포트 0/ANY 칸이었는데, 용도는
bpf-lb-enable-wildcard-entries옵션 주석 한 줄 말고 찾지 못했습니다.bpf lb list의/ANY를 설명하는 문서 후보입니다(기존 문서 여부 미확인). - 공식 안내. 처음이라면 기여 안내
가 가리키는
good-first-issue라벨부터 보십시오. AI 정책: Cilium 은 AI 가 상당 부분 쓴 PR 본문·댓글을 받지 않으므로 소통은 직접 씁니다.
9. 한계
- 다루지 않은 것: ClusterMesh, BGP 제어부, WireGuard·IPsec 암호화, Gateway API·Ingress 의 Envoy 설정 경로(
pkg/ciliumenvoyconfig), egress gateway, Maglev, XDP 가속, IPv6, identity 할당 세부, operator 의 IPAM 할당기(빌드 태그ipam_provider_*로 넣음 — 작은 클러스터는ipam: kubernetes라 쓰지 않음). - 미실측: DNS 규칙이 걸린 파드가 있는 노드의 FQDN 캐시와 셀렉터,
tofqdns-proxy-response-max-delay를 넘겨 응답이 먼저 나가는 경우, BPF 컴파일에 걸리는 시간(템플릿이 약 6주 전 에이전트 시작 때 만들어져 이번에 볼 수 없었음), 노드 재시작 때의 엔드포인트 복구. - 4.3의 접속 표는 이번(2026-10-17)에 다시 잰 값이 아니라 기여 준비 때의 기록입니다. 새 파드를 띄우지 않는다는 이번 실측의 규칙 때문입니다.
- Envoy 는 xDS 관계만 적었습니다. L7 정책 경로는 미실측입니다(
Proxy Status는 「0 redirects active」).
티티 한마디. Cilium 의 검문소는 전화를 안 받아요. 미리 받은 규칙표만 보죠. 그러니까 이상하면 검문소 말고 누가 그 표를 썼는지를 찾아가 보세요. 그리고
*는 점 하나도 못 넘는 겁쟁이라는 것, 잊지 마세요.
참고
모든 자료는 2026-10-17 에 열람했습니다. 소스는 cilium/cilium v1.20.1 태그(커밋 7d68cfb394f2)입니다.
- 에이전트 셀: https://github.com/cilium/cilium/blob/v1.20.1/daemon/cmd/cells.go, https://github.com/cilium/cilium/blob/v1.20.1/pkg/datapath/cells.go
- BPF 적재: https://github.com/cilium/cilium/tree/v1.20.1/pkg/datapath/loader, https://github.com/cilium/cilium/blob/v1.20.1/pkg/datapath/orchestrator/orchestrator.go
- BPF 프로그램: https://github.com/cilium/cilium/tree/v1.20.1/bpf (
bpf_lxc.c,bpf_host.c,bpf_overlay.c,bpf_sock.c,bpf_xdp.c,lib/lb.h,lib/trace.h,lib/events.h) - 부하 분산 제어부: https://github.com/cilium/cilium/tree/v1.20.1/pkg/loadbalancer
- FQDN: https://github.com/cilium/cilium/tree/v1.20.1/pkg/fqdn, https://github.com/cilium/cilium/blob/v1.20.1/pkg/policy/api/fqdn.go, https://github.com/cilium/cilium/blob/v1.20.1/pkg/policy/selectorcache.go
- Hubble: https://github.com/cilium/cilium/tree/v1.20.1/pkg/hubble, https://github.com/cilium/cilium/tree/v1.20.1/pkg/monitor/agent, https://github.com/cilium/cilium/tree/v1.20.1/hubble-relay
- operator: https://github.com/cilium/cilium/blob/v1.20.1/operator/cmd/root.go, https://github.com/cilium/cilium/tree/v1.20.1/operator/pkg/ipam
- kube-proxy 대체 문서(소켓 LB 우회): https://github.com/cilium/cilium/blob/v1.20.1/Documentation/network/kubernetes/kubeproxy-free.rst
- DNS 정책 문서: https://github.com/cilium/cilium/blob/v1.20.1/Documentation/security/policy/layer3.rst, https://github.com/cilium/cilium/blob/v1.20.1/Documentation/security/policy/layer7.rst
- 기여 안내: https://github.com/cilium/cilium/blob/v1.20.1/Documentation/contributing/development/contributing_guide.rst
- 같은 시리즈의 다른 편: MetalLB 읽기(LoadBalancer 서비스의 주소를 누가 광고하나)
인용한 코드: bpf/bpf_sock.c·bpf/bpf_lxc.c 는 GPL-2.0-only OR BSD-2-Clause, pkg/fqdn/matchpattern/matchpattern.go 는 Apache-2.0 (Copyright Authors of Cilium).