eBPF 2026 - Kiến trúc Observability, Networking và Runtime Security cho Linux Production với Cilium, Tetragon, Parca, OpenTelemetry eBPF, XDP, Sockmap, CO-RE và Auto-instrumentation không Sidecar

Posted on: 4/15/2026 8:11:22 PM

Table of contents

  1. 1. eBPF - Công nghệ đang viết lại cách chúng ta quan sát và điều khiển Linux
    1. Vì sao một kỹ sư hệ thống năm 2026 bắt buộc phải hiểu eBPF?
  2. 2. Từ BPF gốc tới eBPF hiện đại - bản đồ tiến hóa
  3. 3. Kiến trúc eBPF - verifier, JIT, maps và helpers
    1. 3.1. Verifier - vệ binh tĩnh của kernel
    2. 3.2. Maps - cấu trúc dữ liệu chia sẻ kernel <-> userspace
    3. 3.3. Helpers và kfuncs - API ổn định giữa eBPF và kernel
    4. 3.4. JIT - từ bytecode tới native code
  4. 4. Hook points - eBPF gắn vào những đâu trong kernel?
    1. Khi nào chọn hook nào?
  5. 5. eBPF cho Networking - Cilium, XDP và bypass netfilter
    1. 5.1. Vì sao eBPF đánh bại iptables ở scale cụm lớn?
    2. 5.2. XDP - tốc độ đường truyền trên commodity hardware
    3. 5.3. Sockmap - service mesh không sidecar
  6. 6. eBPF cho Observability - profiling và auto-instrumentation
    1. 6.1. Continuous profiling với Parca/Pyroscope
    2. 6.2. Auto-instrumentation HTTP/gRPC không cần sửa code
      1. Giới hạn cần biết trước khi gỡ toàn bộ SDK
  7. 7. eBPF cho Runtime Security - Tetragon, Falco và LSM BPF
    1. 7.1. Quan sát + Enforce trong một đường
    2. 7.2. So sánh nhanh với mô hình cổ điển
  8. 8. CO-RE - điểm đổi đời cho eBPF portable
  9. 9. Kiến trúc tham chiếu: Observability + Networking + Security trên cùng node
    1. 9.1. Các lưu ý thiết kế khi chạy nhiều eBPF trên cùng kernel
  10. 10. So sánh eBPF với các cách tiếp cận truyền thống
  11. 11. Thực chiến - viết và nạp một chương trình eBPF với libbpf CO-RE
  12. 12. Giới hạn và những điểm cần tỉnh táo
    1. eBPF không phải viên đạn bạc
  13. 13. Checklist triển khai eBPF production 2026
  14. 14. Kết luận - eBPF là tầng hạ tầng mới của Linux production
  15. 15. Nguồn tham khảo

1. eBPF - Công nghệ đang viết lại cách chúng ta quan sát và điều khiển Linux

Nếu phải chọn một công nghệ hạ tầng có tác động sâu rộng nhất tới cách các hệ thống Linux production được quan sát, bảo vệ và kết nối trong năm năm gần đây, câu trả lời gần như không có đối thủ: đó là eBPF. Từ chỗ là một nhánh nhỏ của Berkeley Packet Filter sinh ra để lọc gói tin tcpdump, eBPF đã trở thành nền tảng cho Cilium, Tetragon, Pixie, Parca, Pyroscope, Katran, và là xương sống của các dịch vụ đám mây như Facebook Load Balancer, Google Cloud Service Mesh, Azure CNI, AWS VPC CNI mới.

Điều làm eBPF đặc biệt không nằm ở việc nó cho phép viết "chương trình chạy trong kernel" - DTrace đã làm điều tương tự hơn hai thập kỷ trước. Điểm đột phá là eBPF đặt một verifier tĩnh và một JIT compiler giữa chương trình người dùng và đường thực thi kernel, biến mọi can thiệp trở thành an toàn theo kiểm chứng. Bạn có thể thêm một khối logic quan sát mới vào production kernel đang chạy mà không cần biên dịch lại kernel, không cần load module, không cần reboot, và không có nguy cơ panic.

2014eBPF bắt đầu xuất hiện trong kernel 3.18
10M+Số node Linux đang chạy eBPF production
10xThông lượng XDP so với iptables trên cùng phần cứng
<1%Overhead typical của continuous profiling eBPF

Vì sao một kỹ sư hệ thống năm 2026 bắt buộc phải hiểu eBPF?

Các stack observability, networking, và runtime security thế hệ mới đều đang dịch chuyển từ mô hình sidecar/agent trong userspace sang mô hình in-kernel instrumentation. Điều này làm giảm đáng kể overhead, xóa bỏ vấn đề đồng bộ cấu hình, và mang lại một điểm quan sát duy nhất cho mọi tiến trình trên node - bất kể ngôn ngữ, runtime, hay container runtime. Nếu hệ thống bạn đang vận hành có Kubernetes, có service mesh, có APM, có profiling hoặc có compliance runtime, thì nhiều khả năng eBPF đã và đang ở dưới lớp hạ tầng đó - chỉ là bạn chưa nhìn thấy.

2. Từ BPF gốc tới eBPF hiện đại - bản đồ tiến hóa

Để hiểu tại sao eBPF lại có kiến trúc như hiện tại, cần nhìn lại hành trình hai thập kỷ của một con máy ảo nhỏ bé trong kernel Linux.

1992 - BSD Packet Filter
Steven McCanne và Van Jacobson công bố The BSD Packet Filter tại USENIX. Mục tiêu duy nhất: lọc gói tin cho tcpdump mà không phải copy từng gói lên userspace. Một máy ảo nhỏ, 32-bit, hai thanh ghi, chạy trong kernel BSD.
1997 - BPF vào Linux 2.1.75
Linux nhận classic BPF (cBPF) chủ yếu cho AF_PACKET và tcpdump. Trong gần hai thập kỷ sau đó, cBPF gần như không thay đổi - vẫn là một filter engine đơn giản cho gói tin.
2014 - eBPF ra mắt cùng kernel 3.18
Alexei Starovoitov viết lại máy ảo: 64-bit, 10 thanh ghi, instruction set giống với phần cứng hiện đại, có maps để chia sẻ dữ liệu giữa kernel và userspace, có helpers để gọi dịch vụ kernel. Cái tên "extended BPF" (eBPF) xuất hiện, và BPF cũ trở thành cBPF.
2016 - XDP và networking tốc độ phần cứng
XDP (eXpress Data Path) cho phép chạy chương trình eBPF ngay tại driver NIC, trước cả SKB allocation. Một bước ngoặt: Linux có thể xử lý 20-50 triệu gói/giây trên commodity hardware với độ trễ vài trăm nanosecond.
2018 - BTF và CO-RE - portable eBPF
BPF Type Format (BTF) và Compile Once - Run Everywhere (CO-RE) giải quyết vấn đề nhức nhối nhất của eBPF: chương trình biên dịch trên một kernel version không chạy được trên kernel khác. Sau CO-RE, bạn phân phối một binary eBPF duy nhất cho mọi distro, mọi phiên bản kernel hỗ trợ.
2020 - LSM BPF - security policy runtime
eBPF có thể cắm vào Linux Security Module hook, cho phép viết policy kiểm soát truy cập tài nguyên (file, socket, capability) bằng chương trình eBPF thay vì SELinux/AppArmor. Đây là nền tảng cho Tetragon và các engine runtime security thế hệ mới.
2023-2026 - eBPF trở thành chuẩn de-facto
Kernel 6.x hoàn thiện fentry/fexit, dynptr, bpf_arena, struct_ops, kfuncs. eBPF Foundation đặt dưới Linux Foundation với thành viên sáng lập là Meta, Google, Microsoft, Red Hat, Isovalent. Năm 2026, eBPF đã trở thành công nghệ nền tảng được yêu cầu trong hầu hết các benchmark cloud-native hiện đại.

3. Kiến trúc eBPF - verifier, JIT, maps và helpers

Trước khi đi vào các ứng dụng như Cilium hay Pixie, cần hiểu rõ luồng sống của một chương trình eBPF từ lúc được viết cho tới lúc chạy trong kernel. Toàn bộ giá trị của eBPF - bao gồm cả tính an toàn, tính portable và hiệu năng - đều xuất phát từ chuỗi mắt xích này.

flowchart LR
    A[Source C/Rust] --> B[Clang+LLVM BPF backend]
    B --> C[ELF object with BTF]
    C --> D[Loader libbpf]
    D --> E[bpf syscall]
    E --> F[Verifier]
    F -->|safe| G[JIT compiler]
    F -->|unsafe| X[Reject]
    G --> H[Native code in kernel]
    H --> I[Hook point: kprobe/tc/xdp/lsm...]
    I --> J[Run on event]
    J --> K[Read/Write Maps]
    K -.-> L[Userspace reader]

Hình 1: Luồng sống của một chương trình eBPF - từ mã nguồn tới lúc chạy trong kernel

3.1. Verifier - vệ binh tĩnh của kernel

Verifier là thành phần làm eBPF trở thành eBPF. Trước khi một byte instruction nào được JIT và chạy trong kernel, verifier thực hiện một quá trình phân tích static nghiêm ngặt:

  • Chứng minh chương trình kết thúc. Verifier đi qua mọi nhánh có thể có của control flow graph. Chương trình eBPF cổ điển không được phép có vòng lặp không cận; kernel 5.3+ cho phép bounded loops nhưng số lần lặp phải xác định được tĩnh.
  • Chứng minh mọi truy cập bộ nhớ đều hợp lệ. Verifier theo dấu vùng nhớ gốc của mỗi con trỏ (packet, stack, map value, context) và từ chối bất kỳ truy cập nào ngoài biên. Không có dereference ngẫu nhiên.
  • Chứng minh không có privilege leak. Các con trỏ kernel không được phép chảy về userspace qua maps hay return value.
  • Giới hạn độ phức tạp. Mặc định 1 triệu instruction state-expansion. Vượt quá sẽ bị từ chối, ngay cả khi chương trình vốn đúng về mặt logic.

Đó là lý do viết eBPF "sạch" không giống viết C thông thường: mỗi con trỏ phải được kiểm tra null ngay sau khi nhận; mỗi vùng packet phải được bounds check trước khi dereference; mọi vòng lặp phải có giới hạn hằng số. Người viết lần đầu thường mất vài ngày chỉ để "hiểu verifier".

3.2. Maps - cấu trúc dữ liệu chia sẻ kernel <-> userspace

Chương trình eBPF không có toàn cục. Toàn bộ dữ liệu duy trì trạng thái đều nằm trong maps - những cấu trúc dữ liệu do kernel quản lý, có khóa bằng fd, có thể mmap từ userspace. Các loại map thường gặp:

Loại mapCông dụng điển hìnhĐặc điểm
BPF_MAP_TYPE_HASHBảng tra cứu generic key-valueConcurrency qua spinlock, cỡ cố định
BPF_MAP_TYPE_LRU_HASHCache connection, flow tableEviction LRU tự động
BPF_MAP_TYPE_PERCPU_HASHĐếm sự kiện hot pathKhông khóa, phải merge ở userspace
BPF_MAP_TYPE_ARRAYCounter, config cố địnhIndex nguyên, truy cập O(1)
BPF_MAP_TYPE_RINGBUFStreaming sự kiện lên userspaceMPSC, không mất event khi full, thay thế perf ring buffer
BPF_MAP_TYPE_LPM_TRIELongest prefix match cho IP/CIDRXương sống của routing eBPF
BPF_MAP_TYPE_SOCKHASHSockmap cho socket redirectCho phép zero-copy giữa hai socket
BPF_MAP_TYPE_STACK_TRACELưu stack trace kernel/userNền tảng của profiling eBPF

3.3. Helpers và kfuncs - API ổn định giữa eBPF và kernel

Chương trình eBPF không thể gọi trực tiếp hàm kernel bất kỳ. Nó chỉ được gọi một tập hợp có kiểm soát gồm helperskfuncs. Helpers là tập API cũ, được kernel chấp nhận là ổn định (ví dụ bpf_map_lookup_elem, bpf_ktime_get_ns, bpf_get_current_pid_tgid). Kfuncs là cơ chế mới hơn, cho phép kernel module khai báo hàm mà eBPF có thể gọi với kiểu dữ liệu rõ ràng, verifier chịu trách nhiệm kiểm tra chữ ký. Về dài hạn kfunc đang dần thay thế helper vì tính linh hoạt và khả năng tiến hóa tốt hơn.

3.4. JIT - từ bytecode tới native code

Sau khi verifier cho qua, JIT compiler chuyển bytecode eBPF thành mã máy native cho kiến trúc đang chạy (x86_64, arm64, riscv, ppc64). Native code này nằm trong vùng nhớ kernel executable, không bị phân mảnh bởi interpret, và chạy gần như không phân biệt được với code kernel tự viết. Trên x86_64 mặc định mỗi call tới helper là một call trực tiếp sau khi JIT, không qua bảng dispatch.

4. Hook points - eBPF gắn vào những đâu trong kernel?

eBPF có thể gắn vào rất nhiều điểm trong kernel. Mỗi hook point có context riêng (cấu trúc dữ liệu mà chương trình nhận được) và ngữ nghĩa khác nhau về cái mà chương trình có thể đọc/ghi. Dưới đây là các loại hook phổ biến nhất:

HookVị tríNgữ cảnhDùng cho
kprobe / kretprobeĐầu/cuối hàm kernel bất kỳpt_regsTrace nội bộ kernel, debugging runtime
fentry / fexitĐầu/cuối hàm kernel (BTF-based)Tham số thật của hàmThay thế kprobe với overhead thấp hơn và type-safe
uprobe / uretprobeHàm userspacept_regsTrace Go, Python, Node, JVM runtime
tracepoint / raw_tpCác tracepoint tĩnh kernel định nghĩa sẵnStruct tracepointỔn định theo ABI, dùng cho observability chính thức
XDPĐầu driver NIC, trước SKBxdp_mdPacket processing tốc độ đường truyền
tc (traffic control)Ingress/egress qdisc__sk_buffPolicy mạng, load balancing L3/L4
cgroup skbGói tin vào/ra cgroup__sk_buffNetwork policy theo pod/container
sock_ops / sockmapVòng đời TCP socketbpf_sock_opsRedirect socket, tối ưu TCP, service mesh không sidecar
LSM hookCác security hook của kernelĐối số LSMRuntime security, access control
perf_eventSự kiện phần cứng/PMUSampleContinuous profiling, CPU performance analysis

Khi nào chọn hook nào?

Quy tắc thực dụng: tracepoint/raw_tp ổn định về ABI và nên ưu tiên cho observability production; fentry/fexit cho phép quan sát hàm kernel bất kỳ với overhead thấp nhất nhưng đòi kernel có BTF; kprobe chỉ nên dùng khi hai cái trên không phủ được; XDP/tc dành cho data plane mạng; LSM BPF cho runtime security.

5. eBPF cho Networking - Cilium, XDP và bypass netfilter

Cilium là minh chứng rõ nét nhất cho khả năng của eBPF ở tầng mạng. Thay vì dựa vào iptables - một cơ chế chuỗi rule tuần tự có độ phức tạp O(n) và sinh ra conntrack lớn khi số pod tăng - Cilium cài đặt lại toàn bộ data path Kubernetes bằng chương trình eBPF gắn vào tcXDP.

5.1. Vì sao eBPF đánh bại iptables ở scale cụm lớn?

Một cụm Kubernetes trung bình có vài nghìn service và hàng chục nghìn pod. Kube-proxy với iptables phải build chuỗi rule có độ dài tỉ lệ tuyến tính với số service × số endpoint. Khi số rule tăng, cả thời gian build lại và thời gian match từng gói đều đi lên. Ngược lại, eBPF có thể dùng BPF_MAP_TYPE_HASH để tra cứu endpoint mục tiêu theo VIP trong O(1), và toàn bộ quyết định định tuyến xảy ra bên trong một chương trình JIT native.

flowchart TB
    subgraph Host
        direction TB
        N[NIC] --> X[XDP program]
        X -->|drop/pass| T[tc ingress]
        T --> C[cgroup skb]
        C --> P[Pod network namespace]
        P --> E[cgroup egress]
        E --> Y[tc egress]
        Y --> N2[NIC out]
    end
    X -. lookup .-> M1[(LPM trie
IP blocks)] T -. lookup .-> M2[(Hash map
Service VIP)] C -. lookup .-> M3[(Hash map
Policy)]

Hình 2: Data path Cilium - chuỗi chương trình eBPF thay thế iptables và kube-proxy

5.2. XDP - tốc độ đường truyền trên commodity hardware

XDP chạy trước cả khi kernel allocate sk_buff, nghĩa là nó thấy gói tin ngay khi driver NIC đặt DMA descriptor. Ba hành động XDP trả về - XDP_DROP, XDP_PASS, XDP_TX, XDP_REDIRECT - đủ để cài đặt các load balancer như Katran (Meta) và XDP-based DDoS mitigation xử lý hàng chục triệu pps/core. Với DDoS mitigation dựa trên iptables, cùng lưu lượng sẽ làm CPU sụp từ rất sớm vì mỗi gói tạo SKB và chạy qua chuỗi netfilter; với XDP, quyết định drop xảy ra trước bước đó và về cơ bản không tốn gì ngoài vài chục chu kỳ CPU.

5.3. Sockmap - service mesh không sidecar

Hai tiến trình trên cùng một host giao tiếp qua TCP thường đi một đường vòng dài: user buffer → kernel socket → TCP stack → loopback → TCP stack → socket → user buffer. Với sockmap, eBPF có thể redirect hai socket "dính nhau": dữ liệu ghi vào sk_msg của socket này được chuyển thẳng sang socket đối tác, bỏ qua toàn bộ TCP stack trung gian. Đây là cơ chế làm cho service mesh eBPF-based (Cilium Service Mesh, Istio Ambient Mesh) có thể loại bỏ sidecar Envoy mà vẫn giữ đặc tính L7 load balancing, mTLS và retry.

6. eBPF cho Observability - profiling và auto-instrumentation

6.1. Continuous profiling với Parca/Pyroscope

Trước eBPF, continuous profiling ở scale production là chuyện khó: gắn profiler vào từng runtime (Java, Go, Python) vừa tốn CPU vừa xung đột với GC. eBPF cung cấp một đường vào duy nhất: gắn chương trình eBPF vào perf_event với tần suất lấy mẫu 19 hoặc 97 Hz, mỗi sample dùng bpf_get_stackid để lấy stack trace kernel và user, lưu vào BPF_MAP_TYPE_STACK_TRACE. Overhead điển hình dưới 1% CPU trên cả host, không cần thay đổi ứng dụng, hoạt động với mọi ngôn ngữ.

Thách thức còn lại là symbolization: stack trace chỉ có địa chỉ, phải được giải mã ngược về tên hàm. Đối với binary native, Parca/Pyroscope dùng BuildID + debug info (debuginfod). Đối với runtime JIT như JVM, .NET và V8, cần perf map hoặc agent runtime phát ra mapping. DWARF unwinding thuần eBPF (frame-pointer-less unwind) đã được hoàn thiện trong Parca Agent 0.20+, giảm đáng kể yêu cầu về frame pointer trong binary production.

6.2. Auto-instrumentation HTTP/gRPC không cần sửa code

Nếu muốn thấy một span trace cho mọi request HTTP mà ứng dụng Go/Node/Python/Java đang phục vụ, phương án cổ điển là import OpenTelemetry SDK vào code và build lại. Phương án eBPF mới hơn: gắn uprobe vào các hàm đã biết của runtime (Go: net/http.(*Server).Serve, Node: TLSWrap::OnRead, Python: PyEval_EvalFrameEx), đọc cấu trúc hàm từ uprobe context, và phát span trực tiếp sang collector.

flowchart LR
    P[Process Go/Node/Java] --> U[uprobe / USDT]
    K[Kernel tracepoint tcp] --> U
    U --> R[Ring buffer]
    R --> A[Userspace agent]
    A --> B[Build spans OTLP]
    B --> O[OpenTelemetry Collector]
    O --> X[Traces / Metrics / Logs]

Hình 3: Auto-instrumentation eBPF - từ uprobe tới OTLP không cần sửa ứng dụng

Các dự án thực tế đi theo hướng này gồm: OpenTelemetry eBPF profiler (dự án CNCF đã ổn định), Pixie (New Relic), Grafana Beyla, Odigos, và Coroot. Điểm chung của chúng là một binary agent duy nhất chạy trên mỗi node và sinh ra tín hiệu observability cho mọi ngôn ngữ, thay cho việc tích hợp SDK vào từng service.

Giới hạn cần biết trước khi gỡ toàn bộ SDK

Auto-instrumentation eBPF cho bạn một khối lượng lớn dữ liệu "miễn phí", nhưng có những thứ nó không thể thấy: business context (tenant id, customer id), correlation thủ công giữa các service khác kernel, các sampling quyết định theo domain. Chiến lược dài hạn là kết hợp: auto-instrumentation làm baseline, SDK làm rich context ở các điểm quan trọng.

7. eBPF cho Runtime Security - Tetragon, Falco và LSM BPF

Năm 2020 kernel thêm BPF LSM, cho phép một chương trình eBPF cắm vào các hook an ninh của Linux (file_open, bprm_check_security, socket_connect, v.v.) và trả về 0 hoặc lỗi. Điều này làm cho eBPF trở thành công cụ runtime security khả thi thay vì chỉ là công cụ quan sát.

7.1. Quan sát + Enforce trong một đường

Tetragon của Isovalent là đại diện tiêu biểu: mỗi policy là một CRD mô tả quy luật, ví dụ "không một tiến trình nào trong pod có label tier=frontend được phép mở socket đến subnet 10.0.5.0/24". Tetragon sinh ra chương trình eBPF thực thi quy luật này ở ngay hook kernel, phát event khi xảy ra vi phạm và tùy chọn kill process ngay trong kernel trước khi syscall trả về userspace. Phản ứng ở microseconds - thay vì hàng chục millisecond của các agent userspace phải đi qua Netlink.

7.2. So sánh nhanh với mô hình cổ điển

Khía cạnhAgent userspace truyền thốngTetragon / eBPF LSM
Điểm đặtSau khi syscall hoàn thànhỞ chính hook kernel
Độ trễ phát hiện10-100 ms<100 µs
Khả năng chặnKhông (chỉ phát hiện)Có (trả lỗi từ kernel)
Overhead3-10% CPU node điển hình<1-2% CPU node điển hình
Phụ thuộcMột daemon mỗi nodeMột daemon + BTF kernel

8. CO-RE - điểm đổi đời cho eBPF portable

Cho đến 2018, muốn phân phối một chương trình eBPF, bạn phải biên dịch nó trên đúng kernel mà node production đang chạy, vì offset của các struct kernel thay đổi giữa các phiên bản. Cách làm khi đó là BCC: mang clang, LLVM và kernel headers xuống mỗi node và biên dịch runtime. Rất chậm, rất cồng kềnh, và rất khó vận hành.

Compile Once - Run Everywhere (CO-RE) xoay ngược bài toán bằng hai thành phần:

  • BTF - BPF Type Format. Kernel (từ 5.4+) tự mô tả kiểu dữ liệu của mình qua BTF và expose ở /sys/kernel/btf/vmlinux. Bất kỳ ai cũng có thể đọc BTF để biết layout struct của kernel đang chạy.
  • CO-RE relocations. Khi biên dịch, libbpf thay vì hard-code offset sẽ chèn relocation placeholder. Lúc load, libbpf đọc BTF kernel đích, tính ra offset thực và patch vào bytecode trước khi đưa cho verifier.

Kết quả: một ELF file duy nhất chạy được trên hầu hết các kernel 4.18+. Các binary production như Cilium, Tetragon, Parca Agent, Beyla đều là CO-RE thuần túy - không mang theo clang, không đọc kernel headers.

9. Kiến trúc tham chiếu: Observability + Networking + Security trên cùng node

Một hệ thống production điển hình năm 2026 thường chạy nhiều thành phần eBPF song song: CNI plugin (Cilium), profiler (Parca), auto-instrumentation (Beyla/OTel eBPF profiler), và runtime security (Tetragon). Cả bốn đều cắm vào kernel của cùng một node. Để chúng hoạt động ổn định, cần thiết kế có ý thức:

flowchart TB
    subgraph Node[Node Linux - Kernel 6.x with BTF]
        direction TB
        subgraph Programs[eBPF programs]
            C1[Cilium
tc/xdp/cgroup] C2[Parca Agent
perf_event] C3[OTel eBPF
uprobe/kprobe] C4[Tetragon
LSM/tracepoint] end Programs --> M[Shared Kernel] end C1 --> MN[Metrics + Hubble flows] C2 --> PR[Profile samples] C3 --> OT[OTLP traces] C4 --> EV[Security events] MN --> OTC[OpenTelemetry Collector] PR --> OTC OT --> OTC EV --> SIEM[SIEM / Audit] OTC --> BE[Grafana / Tempo / Loki / Prometheus]

Hình 4: Kiến trúc tham chiếu - bốn "người dùng" eBPF cùng chạy trên một node production

9.1. Các lưu ý thiết kế khi chạy nhiều eBPF trên cùng kernel

  • Kernel BTF là bắt buộc. Tất cả các binary trên nên là CO-RE. Distro không bật CONFIG_DEBUG_INFO_BTF sẽ phải dùng BTFhub hoặc tự sinh BTF; đây là rào cản thật khi vận hành các legacy kernel.
  • Chia sẻ hook an toàn. Với fentry/fexit cùng hàm, các agent không xung đột; với kprobe cùng địa chỉ, kernel serialize nên vẫn chạy được nhưng overhead cộng dồn. Tránh hai agent cùng kprobe hàm hot path.
  • Ringbuf nên được ưu tiên hơn perf buffer. Ringbuf cho mỗi agent không cần một buffer per-CPU, tiết kiệm bộ nhớ nhiều khi node có 64-128 core.
  • Giới hạn khối lượng map. Tổng khối lượng map của tất cả agent bị tính vào RLIMIT_MEMLOCK. Kernel 5.11+ mặc định không còn phụ thuộc rlim này nhưng một số distro vẫn giữ, cần audit.
  • Giữ kernel up-to-date. Hầu hết lỗi eBPF nghiêm trọng trong vài năm qua đều được sửa ở kernel mainline. Rollout một baseline 6.1 LTS trở lên cho node Linux là quyết định vận hành đúng năm 2026.

10. So sánh eBPF với các cách tiếp cận truyền thống

Vấn đềCách cũCách eBPFHệ quả
Network policy Kubernetesiptables/kube-proxyCilium tc/XDPGiảm độ trễ p99, xóa chuỗi rule tuyến tính
Service mesh L7Envoy sidecar mỗi podSockmap + envoy ambientGiảm 30-50% CPU overhead mesh
APM tracingSDK trong codeAuto-instrument eBPFKhông phải build lại ứng dụng
Continuous profilingProfiler theo runtimeperf_event eBPFCross-language, <1% overhead
Runtime securityAudit daemon + NetlinkTetragon LSM BPFEnforce trong kernel, độ trễ microsecond
L4 load balancerLVS, IPVSKatran, Cilium LBTriệu cps/core trên commodity NIC
DDoS mitigationiptables dropXDP dropDrop xảy ra trước SKB allocation
DNS policyCoreDNS plugineBPF DNS redirect/ECS rewritePolicy L7 không tốn sidecar

11. Thực chiến - viết và nạp một chương trình eBPF với libbpf CO-RE

Để cảm nhận được vòng đời thực tế, dưới đây là khung xương một chương trình eBPF "theo dõi các lần open file" thuần libbpf CO-RE. Đây không phải là một chương trình production - mục đích là minh họa chuỗi biên dịch và nạp vào kernel.

// trace_open.bpf.c
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>

struct event {
    __u32 pid;
    char comm[16];
    char filename[128];
};

struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 1 << 20);
} events SEC(".maps");

SEC("fentry/do_sys_openat2")
int BPF_PROG(trace_openat, int dfd, const char *filename)
{
    struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
    if (!e) return 0;
    e->pid = bpf_get_current_pid_tgid() >> 32;
    bpf_get_current_comm(&e->comm, sizeof(e->comm));
    bpf_probe_read_user_str(&e->filename, sizeof(e->filename), filename);
    bpf_ringbuf_submit(e, 0);
    return 0;
}

char LICENSE[] SEC("license") = "GPL";

Biên dịch bằng clang với target bpf, nhúng BTF, rồi sinh skeleton bằng bpftool gen skeleton. Phía userspace dùng skeleton để open, load, attach và poll ringbuf:

// loader.c (rút gọn)
struct trace_open_bpf *skel = trace_open_bpf__open_and_load();
trace_open_bpf__attach(skel);
struct ring_buffer *rb = ring_buffer__new(
    bpf_map__fd(skel->maps.events), handle_event, NULL, NULL);
while (ring_buffer__poll(rb, 100) >= 0) { /* ... */ }

Không cần clang trên node đích. Không cần kernel headers. Binary này chạy được từ RHEL 8 tới Ubuntu 24.04 tới Debian 12 - miễn là kernel có BTF và hàm do_sys_openat2 (tức kernel 5.6+).

12. Giới hạn và những điểm cần tỉnh táo

eBPF không phải viên đạn bạc

Verifier nghiêm ngặt làm giảm rủi ro nhưng cũng làm giảm độ biểu đạt. Không phải thuật toán nào cũng viết được trong eBPF. Nhiều chương trình "đơn giản trên giấy" bị verifier từ chối vì không chứng minh được cận trên của loop hoặc vì quá phức tạp. Ngoài ra eBPF không phải là môi trường chống malicious actor - quyền nạp eBPF về bản chất là quyền root: CAP_BPF, CAP_PERFMON và CAP_SYS_ADMIN trong nhiều trường hợp.

  • Hạn chế instruction count. Mặc định 1M state-expansion. Chương trình lớn phải chia ra qua tail call hoặc BPF-to-BPF function call. Debug verifier là một kỹ năng riêng.
  • Stack 512 byte. Tất cả biến local của một chương trình eBPF chia sẻ một stack 512 byte. Cấu trúc lớn phải cất vào per-CPU array map.
  • Kernel ABI drift. CO-RE giải quyết phần lớn nhưng những thay đổi lớn của struct kernel vẫn có thể phá script. Luôn test trên ít nhất 2-3 phiên bản kernel LTS mục tiêu.
  • Chi phí context switching nhỏ nhưng có thật. Mỗi hook là một chi phí cố định - từ 50 ns đến vài trăm ns tùy loại. Gắn quá nhiều kprobe lên hot path có thể làm chậm hệ thống đo được.
  • Khó troubleshoot khi sai. Khi một chương trình bị reject, thông báo lỗi verifier thường dài hàng trăm dòng. Các công cụ như bpftool prog profile, bpftool prog trace, và plugin VS Code của libbpf mới bắt đầu làm trải nghiệm này dễ chịu hơn.

13. Checklist triển khai eBPF production 2026

  1. Chuẩn hóa kernel baseline. Ép tất cả node Linux production ở tối thiểu kernel 6.1 LTS, đủ cho toàn bộ hook hiện đại (fentry/fexit, ringbuf, LSM BPF, kfuncs).
  2. Bật BTF kernel. Kiểm tra /sys/kernel/btf/vmlinux tồn tại trên mọi node. Đây là điều kiện tiên quyết của CO-RE.
  3. Chọn CNI eBPF làm mặc định. Với Kubernetes, Cilium (hoặc Calico eBPF dataplane) trở thành baseline; kube-proxy iptables chỉ dùng cho legacy.
  4. Continuous profiling toàn cụm. Một agent Parca/Pyroscope trên mỗi node, kết hợp với BuildID registry để symbolize sau.
  5. Auto-instrumentation song song với SDK. Đừng bỏ SDK OpenTelemetry của service quan trọng. Dùng eBPF profiler cho coverage rộng, SDK cho context chiều sâu.
  6. Runtime security dựa trên policy-as-code. Tetragon/Falco với policy được version trong Git, audit theo CRD, và có chế độ dry-run trước khi bật enforce.
  7. Giám sát chính các agent eBPF. Expose các metric bpf_prog_run_time_ns, map_element_count, dropped_events qua bpf_stats_enabled. Agent eBPF cũng có thể trở thành nguồn overhead nếu chương trình lỗi thời.
  8. Tách quyền CAP_BPF. Với kernel 5.8+, tách CAP_BPF khỏi CAP_SYS_ADMIN. Các agent chỉ cần CAP_BPF + CAP_PERFMON thay vì full root.
  9. Có kế hoạch rollback. Mỗi rollout chương trình eBPF mới phải có cơ chế detach nhanh. Bật/tắt theo feature flag ở tầng agent, không cần reboot.
  10. Đầu tư kỹ năng đội vận hành. Ít nhất hai kỹ sư trong đội SRE cần đọc được output của bpftool, perf, và hiểu ra "verifier complained vì sao".

14. Kết luận - eBPF là tầng hạ tầng mới của Linux production

Năm 2014 eBPF trông giống như một tính năng nhỏ được thêm vào kernel để tăng tốc tcpdump. Mười hai năm sau, nó là một trong những thay đổi kiến trúc lớn nhất mà Linux đã đón nhận kể từ khi có namespaces và cgroups. Không cần khởi động lại, không cần module, không cần thay distro - eBPF cho phép đội kỹ sư mở rộng kernel theo nhu cầu của ứng dụng của họ, với một mức độ an toàn được xác lập ở compile-time.

Điều quan trọng với người xây hệ thống không phải là "biết viết một chương trình eBPF". Quan trọng hơn là hiểu rằng bên dưới các sản phẩm bạn đang chọn ở năm 2026 - CNI, APM, profiler, runtime security - có một tầng hạ tầng chung đang dịch chuyển về eBPF. Quyết định chọn Cilium thay vì kube-proxy, chọn Parca thay vì profiler theo ngôn ngữ, chọn Tetragon thay vì audit log cổ điển không còn là quyết định kỹ thuật đơn lẻ - chúng là các mảnh của cùng một kiến trúc nền tảng. Và kiến trúc đó có tên là eBPF.

Người kỹ sư hệ thống không cần viết eBPF hàng ngày, nhưng cần biết nó ở đâu, biết điểm mạnh và điểm yếu, biết chẩn đoán khi nó sai, và biết khi nào nó là lựa chọn đúng. Đó là kiến thức nền của thập kỷ này.

15. Nguồn tham khảo