Hubble Bên Trong: Từ Sự Kiện eBPF Tới Luồng Mạng Toàn Cụm
Bài 17-18 quan sát hiệu năng một node. Bài này khép Part VI bằng quan sát mạng toàn cụm: Hubble — thứ cho ta thấy mọi kết nối giữa các pod, theo tên service, kèm verdict policy, mà không cài gì vào pod. Nó là tầng trên cùng của chuỗi "eBPF làm nền observability". Ta mổ cách nó hoạt động, từ datapath lên tới luồng có nhãn.
Ba tầng: datapath → perf ring → giàu hóa
Hubble không phải một agent riêng cắm vào pod. Nó là tầng trên cùng của một chuỗi ba bước, mà hai bước dưới ta đã gặp:
[1] datapath eBPF (74 sched_cls, Bài 12) xử lý mỗi gói
│ bpf_perf_event_output() -- đẩy sự kiện trace/drop/policy
▼
[2] cilium_events (BPF map perf_event_array, một khe mỗi CPU)
│ cilium-agent mở perf reader, đọc sự kiện thô
▼ (cilium monitor cho thấy tầng này: identity dạng SỐ)
[3] Hubble: giàu hóa identity -> tên pod/service + verdict
▼
luồng đọc được: "host -> kube-system/coredns:8080 FORWARDED"
Tầng 2: perf ring cilium_events
Map mà datapath đẩy sự kiện vào chính là một perf_event_array đã gặp ở Bài 12:
sudo bpftool map show | grep cilium_events
23: perf_event_array name cilium_events max_entries 2 <- một khe mỗi CPU (node 2 CPU)
Các chương trình sched_cls của Cilium (cil_from_container, cil_to_netdev...) gọi helper bpf_perf_event_output() để ghi một bản ghi sự kiện vào cilium_events mỗi khi có việc đáng báo (gói được forward, bị drop, một verdict policy). perf_event_array có một khe cho mỗi CPU (max_entries 2 = 2 lõi) — mỗi CPU đẩy vào khe riêng, tránh tranh chấp trên đường nóng. Đây là họ hàng của ring buffer (Bài 9): cơ chế đẩy sự kiện từ nhân lên userspace, hiệu quả.
Tầng 2 đọc thô: cilium monitor
cilium monitor mở đúng perf reader trên cilium_events và in sự kiện thô — đúng thứ datapath đẩy lên, chưa giàu hóa:
sudo cilium monitor
-> endpoint 645 identity 16777217->18203 state reply ifindex lxc9d33... 10.0.1.11:6443 -> 10.200.0.140:59902 tcp ACK
-> endpoint 1797 identity host->35393 state new ifindex lxca0f2... 10.200.0.64:36830 -> 10.200.0.180:9808 tcp SYN
-> stack identity 35393->host state reply ifindex 0 10.200.0.180:9808 -> 10.200.0.64:36830 tcp SYN,ACK
Đọc ra cấu trúc một sự kiện datapath: hướng (-> endpoint gửi vào một pod, -> stack lên host stack, -> network ra card mạng), identity nguồn→đích (số!), state (new/established/reply — từ conntrack Bài 12), ifindex (veth pod nào), và gói thật (IP:port, cờ TCP). Chú ý: danh tính ở đây là số — 18203, 35393, 16777217. Đó là security identity của Cilium (Bài 12), chưa phải tên người đọc được. Tầng này là sự kiện eBPF trần.
Tầng 3: Hubble giàu hóa thành tên
hubble observe đọc cùng dòng sự kiện đó nhưng giàu hóa identity số thành tên pod/service:
hubble observe --last 12 -o compact
10.200.0.64:54764 (host) -> kube-system/coredns-87bb947d6-v29lc:8080 (ID:18203) to-endpoint FORWARDED (TCP Flags: SYN)
10.200.0.64:54764 (host) <- kube-system/coredns-87bb947d6-v29lc:8080 (ID:18203) to-stack FORWARDED (TCP Flags: SYN,ACK)
10.200.0.64:55954 (host) -> kube-system/ebs-csi-node-qq4nk:9808 (ID:35393) to-endpoint FORWARDED (TCP Flags: ACK,FIN)
10.0.1.20:47106 (host) -> 10.0.1.10:6443 (world) to-network FORWARDED (TCP Flags: ACK,PSH)
Cùng các flow của cilium monitor, nhưng giờ identity 18203 hiện thành kube-system/coredns-87bb947d6-v29lc, 35393 thành ebs-csi-node-qq4nk, 16777217 thành world (ngoài cụm). Verdict FORWARDED (hoặc DROPPED nếu policy chặn) rõ ràng. Nguồn của phép giàu hóa là kho identity→nhãn (Bài 12):
cilium identity get 18203
18203 k8s:k8s-app=kube-dns
k8s:io.kubernetes.pod.namespace=kube-system
k8s:io.cilium.k8s.policy.serviceaccount=coredns
Hubble tra số 18203 ra bộ nhãn k8s này, ghép với endpoint/pod tương ứng, thành tên đọc được. Toàn bộ thông tin gốc — ai nói với ai, verdict gì — đã có sẵn trong sự kiện eBPF; Hubble chỉ dịch số sang tên.
Vì sao mô hình này mạnh
Không pod nào bị cài sidecar, không ứng dụng nào bị sửa. Datapath eBPF vốn đã xử lý mọi gói của mọi pod (Bài 12) — thêm một bpf_perf_event_output là có ngay luồng sự kiện đầy đủ, gắn nhãn theo identity mà Cilium vốn dùng để áp policy. Đó là lý do quan sát mạng kiểu Hubble gần như miễn phí về mặt kiến trúc: nó tái dùng đúng datapath đang định tuyến và áp policy. So với mô hình cũ (mỗi node một agent bắt gói bằng libpcap, hay sidecar trong mỗi pod), eBPF cho cùng tầm nhìn từ một điểm gắn ở nhân, ít chi phí hơn nhiều.
🧹 Dọn dẹp
Bài này chỉ đọc sự kiện đang chảy (cilium monitor, hubble observe, cilium identity get) — không gắn hay sửa gì. Node vẫn 140 chương trình. Lệnh ở github.com/nghiadaulau/ebpf-from-scratch, thư mục 19-hubble.
Tổng kết
Hubble dựng bức tranh luồng mạng toàn cụm từ một chuỗi ba tầng, tái dùng datapath eBPF có sẵn. [1] Các chương trình sched_cls của Cilium (Bài 12) gọi bpf_perf_event_output() đẩy sự kiện trace/drop/policy vào [2] cilium_events, một perf_event_array một-khe-mỗi-CPU (họ hàng ring buffer Bài 9). [3] cilium-agent đọc perf ring đó — cilium monitor cho thấy sự kiện thô: hướng (endpoint/stack/network), identity nguồn→đích dạng số, state (từ conntrack), ifindex, gói thật. Hubble giàu hóa: tra security identity (18203) ra nhãn k8s (k8s-app=kube-dns) từ kho identity→nhãn rồi thành tên pod (kube-system/coredns-...), kèm verdict FORWARDED/DROPPED. Toàn bộ dữ liệu đã có trong sự kiện eBPF; Hubble chỉ dịch số sang tên. Vì datapath vốn đã thấy mọi gói, quan sát kiểu này gần như miễn phí — không sidecar, không sửa app, một điểm gắn ở nhân thấy toàn cụm.
Part VI khép lại — ta đã quan sát hiệu năng từ on-CPU profiling, off-CPU latency, tới luồng mạng toàn cụm. Part VII là phần cuối: một case-study Cilium end-to-end — ghép mọi mảnh của series (verifier, maps, XDP, tc, tail call, perf ring, identity) thành bức tranh hoàn chỉnh một gói pod đi từ container này sang container kia, rồi tổng kết hành trình eBPF từ số không.