Blog
Thoughts on engineering, design, and building great products.
Tự Viết Một Chương Trình tc: __sk_buff và Chuỗi tcx
Bài 12 đọc datapath tc của Cilium từ ngoài. Bài này tự viết một chương trình tc — đếm và phân loại gói egress theo giao thức — để nắm __sk_buff từ trong ra. Điểm khác cốt lõi với XDP: tc thấy sk_buff với metadata đã điền sẵn (skb->protocol, skb->len), không phải gói thô. Ta gắn nó bằng tcx trên một interface thật, chạy ra số đếm đúng, rồi gặp một bài học thật: cùng chương trình đó gắn sau Cilium trên card mạng lại không chạy lần nào, vì cách chuỗi tcx kết thúc.
Verifier: Vì Sao eBPF Không Sập Nhân
Bài 1 nói thiết kế máy ảo eBPF cho phép verifier chứng minh an toàn. Bài này thấy nó làm thật: ta biên dịch một chương trình XDP đọc byte đầu của gói tin mà quên kiểm giới hạn, nạp vào nhân — verifier từ chối với log chỉ đích danh thanh ghi và lý do. Thêm đúng một câu kiểm data_end, verifier cho qua. Verifier là bộ chứng minh an toàn chạy lúc nạp, theo dõi trạng thái từng thanh ghi trên mọi nhánh — thứ khiến eBPF nạp được mã lạ vào nhân mà không như kernel module.
eBPF Từ Số Không: Chạy Chương Trình Trong Nhân Linux
Ngay lúc này, trên một worker của cụm Kubernetes ta dựng ở series trước, có 140 chương trình eBPF đang chạy bên trong nhân Linux — định tuyến từng gói tin, kiểm soát quyền truy cập thiết bị, gom metric. eBPF cho phép nạp mã vào nhân và chạy an toàn tại các điểm móc, không sửa mã nguồn nhân, không nạp module. Bài mở đầu series giải thích eBPF là gì, vì sao nó đổi cách mở rộng nhân, và một chương trình đi từ code tới mã máy chạy trong nhân ra sao.
Capstone: Tự Viết connmon — Monitor Kết Nối TCP Toàn Node
Bài cuối: ghép mọi thứ đã học thành một công cụ thật. connmon gắn kprobe vào tcp_connect trong nhân, đẩy mỗi kết nối TCP mới qua ring buffer, và một loader Go in chúng ra theo thời gian thực — pid, tiến trình, đích IP:port. Chỉ hơn trăm dòng, một binary tĩnh, chạy trên cụm thấy ngay coredns, kubelet, curl đang kết nối đi đâu. Kèm một cái bẫy build thật của kprobe. Rồi nhìn lại toàn bộ hành trình eBPF từ số không.
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.