Blog
Thoughts on engineering, design, and building great products.
Case-study: Một Gói Đi Qua Datapath eBPF Của Cilium
Mười chín bài đã mổ từng mảnh: verifier, maps, XDP, tc, tail call, perf ring, identity. Bài này ghép chúng lại thành một câu chuyện liền mạch — đi theo đúng một gói khi một pod gọi Service DNS của cụm, từ lúc rời pod nguồn tới lúc tới pod CoreDNS, qua từng chương trình eBPF và từng BPF map mà nó chạm vào. Không khái niệm mới; chỉ là thấy toàn bộ cỗ máy chạy như một thể thống nhất, với dữ liệu thật từ chính cụm đã dùng suốt series.
Off-CPU và Độ Trễ Scheduler: Đo Thời Gian Tiến Trình KHÔNG Chạy
Profiling on-CPU (Bài 17) chỉ thấy lúc CPU bận. Nhưng phần lớn độ trễ một ứng dụng cảm nhận lại nằm ở lúc nó KHÔNG chạy: chờ đĩa, chờ khóa, chờ tới lượt CPU. eBPF đo được khoảng off-CPU đó qua tracepoint của scheduler. Bài này đo hai thứ trên node thật: run-queue latency — thời gian từ lúc một tác vụ được đánh thức tới lúc thật sự chạy, phơi ra cái đuôi 16-32ms khi CPU bị tranh; và off-CPU time — một tác vụ nằm ngoài CPU bao lâu mỗi lần, với đuôi tới vài giây cho tác vụ bị chặn.
CPU Profiling Bằng perf_event: Lấy Mẫu Stack, Nền Của Flame Graph
Muốn biết CPU đang bận làm gì, ta lấy mẫu: vài chục lần mỗi giây, đóng băng mỗi CPU và ghi lại stack đang chạy. eBPF làm việc này qua loại chương trình perf_event — gắn vào một bộ đếm lấy mẫu của nhân, mỗi lần nổ thì chụp stack rồi gộp ngay trong nhân. Bài này profile một node thật ở 99Hz, thấy dd ngốn CPU qua đường đọc /dev/zero còn lõi rảnh thì nằm trong vòng idle, gộp theo tiến trình ra dd 479 mẫu — và đây chính là dữ liệu mà flame graph vẽ lên.
seccomp-bpf: BPF Cổ Điển Lọc Syscall Trong Mọi Container
Trước eBPF có cBPF — BPF cổ điển, thứ tcpdump dùng. Và nó vẫn đang chạy: seccomp-bpf lọc syscall bằng cBPF, là lớp sandbox nền của container. Bài này phân biệt cBPF với eBPF, soi seccomp thật trên cụm (pause container và CSI sidecar bị giới hạn, pod privileged thì không, systemd-resolved chồng 28 filter), rồi tự viết một filter cBPF chặn mkdir bằng EPERM — tám lệnh thao tác trên struct seccomp_data, cài bằng prctl, chặn thật trong khi printf vẫn chạy.
tc/sched_cls và Mổ Datapath Cilium Đang Chạy
Sau XDP là tc — hook nơi gói đã có sk_buff, thấy được cả ingress lẫn egress, và là nơi Cilium đặt gần như toàn bộ datapath của nó. Bài này không viết tc mẫu cho có; nó mổ thẳng 74 chương trình sched_cls đang chạy thật trên một node của cụm: chúng gắn vào đâu (card mạng, từng pod), gọi nhau qua tail call thế nào, và tra những BPF map nào để cân bằng tải một Service hay áp NetworkPolicy. Load balancing kube-proxy-less hóa ra chỉ là một lần tra map.
XDP: Xử Lý Gói Ở Điểm Sớm Nhất, Viết Một Firewall
XDP gắn chương trình eBPF vào driver mạng, chạy trên mỗi gói tới trước cả khi nhân cấp phát sk_buff — điểm sớm nhất có thể đụng vào một gói. Nó trả về một verdict: PASS, DROP, TX, REDIRECT. Bài này dựng một XDP firewall nhỏ drop ICMP trên một interface thật, gắn vào card mạng của node bằng bptool, rồi xem ping rớt từ 0% lên 100% loss trong khi SSH vẫn sống — và thấy nó nằm trước datapath tc của Cilium trên cùng interface ra sao.
cilium/ebpf: Nạp eBPF Từ Go
Bài 9 dựng execsnoop bằng C với libbpf. Bài này viết lại đúng công cụ đó nhưng nạp từ Go bằng thư viện cilium/ebpf — cách hệ sinh thái Kubernetes (Cilium, Tetragon, Falco) build ứng dụng eBPF. Phía nhân không đổi; bpf2go biên dịch nó và nhúng thẳng object vào mã Go, rồi một chương trình Go gắn tracepoint và đọc ring buffer. Kết quả là một binary tĩnh duy nhất, không phụ thuộc libbpf.so — và ta gặp đúng những cái bẫy thật khi dựng nó.