•9 min read

Quan sát hiệu suất cao với eBPF trong Kubernetes: Vượt qua gánh nặng Sidecar

Quan sát hiệu suất cao với eBPF trong Kubernetes: Vượt qua gánh nặng Sidecar

Trong thập kỷ qua, khả năng quan sát cloud-native trong Kubernetes gần như chỉ dựa vào các trừu tượng không gian người dùng. Nếu bạn muốn theo dõi phân tán, mTLS (mutual TLS) và các số liệu mạng Lớp 7 trên các microservice của mình, cách làm tiêu chuẩn trong ngành là inject một proxy sidecar Envoy vào mỗi pod ứng dụng (như Istio và Linkerd đã phổ biến).

Mặc dù sidecar đã thành công trong việc trừu tượng hóa logic mạng khỏi các nhà phát triển ứng dụng, nhưng chúng đã tạo ra một khoản chi phí vận hành ẩn khổng lồ mà các nhóm kỹ thuật nền tảng gọi là "Thuế Sidecar".

Chạy một proxy sidecar có nghĩa là:

  • Mỗi gói tin mạng vào và ra đều đi qua ngăn xếp mạng Linux hai lần.
  • Hàng trăm container proxy tiêu thụ hàng gigabyte bộ nhớ cluster và các lõi CPU chuyên dụng.
  • Độ trễ tăng thêm 2 đến 4 mili giây cho mỗi bước nhảy dịch vụ.

eBPF (Extended Berkeley Packet Filter) đã làm thay đổi hoàn toàn mô hình này. Bằng cách thực thi bytecode đã được xác minh, chạy trong sandbox trực tiếp bên trong nhân Linux, eBPF thu thập dữ liệu đo từ xa mạng sâu, các sự kiện socket và theo dõi phân tán tại ranh giới nhân với chi phí gần như bằng không—mà không cần inject sidecar hoặc sửa đổi một dòng mã nguồn ứng dụng nào.

Trong hướng dẫn này, chúng ta sẽ phân tích cơ chế quan sát của eBPF, khám phá BPF CO-RE (Compile Once – Run Everywhere), xây dựng một kernel probe bằng Go và đánh giá các điểm chuẩn hiệu suất so với các service mesh truyền thống.


Audio Briefing
0:00 / 0:00

Thuế Sidecar: Hiểu rõ nút thắt cổ chai

Để hiểu tại sao eBPF đại diện cho một bước đột phá về kiến trúc, hãy xem xét vòng đời gói tin bên trong một service mesh tiêu chuẩn:

[Traditional Service Mesh: 4 Context Switches per Hop]
  Pod A (App) ──► Loopback ──► Envoy Proxy (Pod A) ──► Host Network
                                                           │
                                                      Wire Transit
                                                           │
  Pod B (App) ◄── Loopback ◄── Envoy Proxy (Pod B) ◄── Host Network

Khi Pod A gửi một yêu cầu HTTP đến Pod B:

  1. Pod A thực thi một send() syscall trong không gian người dùng.
  2. Kernel xử lý gói tin và định tuyến nó đến container proxy Envoy của Pod A thông qua giao diện loopback cục bộ.
  3. Envoy chặn gói tin, phân tích cú pháp tiêu đề trong không gian người dùng và gửi nó ra giao diện mạng vật lý.
  4. Trên nút nhận, proxy Envoy của Pod B chặn gói tin đến, đánh giá lại các chính sách bảo mật và chuyển tiếp nó đến container ứng dụng của Pod B.

Việc chuyển đổi ngữ cảnh từ không gian người dùng sang không gian kernel này gây ra tình trạng thrashing bộ đệm CPU và độ trễ bộ nhớ đáng kể. Trong các cluster lớn chạy 1.000 pod, các proxy sidecar thường tiêu thụ 20% đến 35% tổng bộ nhớ cluster chỉ để chờ I/O!


Advertisement

Giải pháp eBPF: Hook cấp Kernel trong suốt

Bởi vì tất cả các gói mạng, quá trình thực thi và cấp phát bộ nhớ đều phải đi qua nhân hệ điều hành, nên nhân là nơi đáng tin cậy nhất để quan sát hành vi hệ thống.

Với eBPF, một daemonset duy nhất chạy trên mỗi worker node Kubernetes sẽ gắn các hook sự kiện nhẹ vào các hàm kernel:

[eBPF Observability: Direct Kernel Interception]
  Pod A (Application) ─────────────────────────────► Pod B (Application)
         │                                                    │
  ───────┼────────────────────────────────────────────────────┼───────
         ▼                                                    ▼
  [Kernel Socket Layer] ◄─── (eBPF Tracepoint Hook) ───► [Kernel Socket Layer]
                                     │
                             eBPF Ring Buffer
                                     │
                                     ▼
                    eBPF Collector (Node DaemonSet)
  1. Không cần công cụ hóa mã: Chương trình eBPF hook vào việc tạo socket (sys_enter_connect) và các chuyển đổi trạng thái TCP. Nó tự động kiểm tra các tiêu đề HTTP, loại phương thức và mã trạng thái gRPC một cách minh bạch mà không yêu cầu nhà phát triển cài đặt OpenTelemetry SDK trong Node, Go hoặc Python.
  2. Đường dẫn nhanh được rút ngắn: Sử dụng các chương trình sockops, eBPF có thể bỏ qua hoàn toàn ngăn xếp TCP/IP cho các pod giao tiếp trên cùng một node, truyền bộ nhớ trực tiếp từ bộ đệm socket này sang bộ đệm socket khác (giảm độ trễ IPC cục bộ tới 80%).

An toàn Kernel: Trình xác minh trong Kernel

Việc chạy mã tùy chỉnh bên trong nhân Linux trước đây yêu cầu biên dịch một Kernel Module tùy chỉnh (.ko), trong đó một lỗi tham chiếu con trỏ null hoặc vòng lặp vô hạn duy nhất sẽ ngay lập tức gây ra kernel panic và làm sập toàn bộ máy chủ vật lý.

eBPF ngăn chặn điều này thông qua Trình xác minh Kernel:

  • Đảm bảo kết thúc: Trình xác minh xác thực tĩnh rằng tất cả các vòng lặp đều có giới hạn và được đảm bảo kết thúc.
  • An toàn bộ nhớ: Các chương trình không thể đọc bộ nhớ chưa được khởi tạo hoặc truy cập các địa chỉ con trỏ ngoài giới hạn.
  • Giới hạn độ phức tạp: Các lệnh bytecode bị giới hạn nghiêm ngặt để đảm bảo các chương trình thực thi trong vài nano giây, ngăn chặn tấn công từ chối dịch vụ CPU.

Xây dựng một eBPF Network Probe tối thiểu với Go và Cilium/ebpf

Phát triển eBPF hiện đại sử dụng BPF CO-RE (Compile Once – Run Everywhere) với BTF (BPF Type Format), cho phép một binary eBPF đã biên dịch chạy trên các phiên bản kernel Linux khác nhau mà không cần biên dịch lại.

1. Chương trình Kernel (C)

Chương trình eBPF này gắn vào tracepoint sys_enter_connect để bắt khi một kết nối TCP đi ra được khởi tạo:

// probe.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

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

struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 256 * 1024); // 256 KB ring buffer
} events SEC(".maps");

SEC("tracepoint/syscalls/sys_enter_connect")
int trace_connect(struct trace_event_raw_sys_enter *ctx) {
    struct event *e;
    
    // Reserve slot in shared ring buffer
    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_ringbuf_submit(e, 0);
    return 0;
}

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

2. Trình tải không gian người dùng (Go)

Daemon Go trong không gian người dùng đọc các sự kiện từ ring buffer không khóa thông lượng cao và đẩy các số liệu đến bộ thu Prometheus của bạn:

// main.go
package main

import (
	"bytes"
	"encoding/binary"
	"log"
	"os"
	"os/signal"
	"syscall"

	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/ringbuf"
)

type Event struct {
	PID  uint32
	Comm [16]byte
}

func main() {
	// Load compiled eBPF ELF into the Linux kernel
	objs := probeObjects{}
	if err := loadProbeObjects(&objs, nil); err != nil {
		log.Fatalf("Failed loading eBPF objects: %v", err)
	}
	defer objs.Close()

	// Attach to sys_enter_connect tracepoint
	tp, err := link.Tracepoint("syscalls", "sys_enter_connect", objs.TraceConnect, nil)
	if err != nil {
		log.Fatalf("Failed attaching tracepoint: %v", err)
	}
	defer tp.Close()

	// Open lockless ring buffer reader
	rd, err := ringbuf.NewReader(objs.Events)
	if err != nil {
		log.Fatalf("Failed creating ringbuf reader: %v", err)
	}
	defer rd.Close()

	log.Println("eBPF network probe active. Capturing outbound TCP connections...")

	for {
		record, err := rd.Read()
		if err != nil {
			break
		}

		var event Event
		binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event)
		commStr := string(bytes.Trim(event.Comm[:], "\x00"))
		log.Printf("[Kernel Event] Process %s (PID %d) initiated TCP connection\n", commStr, event.PID)
	}
}

Advertisement

Điểm chuẩn thực nghiệm: Sidecar truyền thống so với khả năng quan sát eBPF

Bảng dưới đây phác thảo độ trễ và mức sử dụng tài nguyên được đo trên một điểm chuẩn HTTP 5.000 yêu cầu/giây giống hệt nhau chạy trong một cluster Kubernetes 100 node:

Số liệuSidecar Istio EnvoyCilium eBPF (Không Sidecar)Tác động ròng
Độ trễ mạng p994.8 ms1.1 msĐộ trễ p99 thấp hơn 77%
Dấu chân bộ nhớ Cluster48 GB RAM (50MB/pod)1.8 GB RAM (1 daemon/node)Tiết kiệm 96% bộ nhớ
Mức sử dụng CPU Cluster18.2 Cores2.1 CoresGiảm 88% CPU
Chi phí khởi động lại Pod ứng dụngCao (Inject webhook)Bằng không (Kernel trong suốt)Khởi động pod tức thì

Các câu hỏi thường gặp

eBPF có yêu cầu đặc quyền nâng cao hoặc quyền root trong Kubernetes không?

Daemonset eBPF tải các chương trình vào kernel yêu cầu khả năng CAP_BPF hoặc CAP_SYS_ADMIN. Tuy nhiên, các workload và pod ứng dụng chạy với các tài khoản người dùng hoàn toàn không có đặc quyền và hệ thống tệp gốc chỉ đọc mà không yêu cầu bất kỳ đặc quyền đặc biệt nào.

eBPF có thể giải mã và kiểm tra lưu lượng HTTPS/TLS không?

Có. eBPF có thể hook vào các thư viện mã hóa trong không gian người dùng (như OpenSSL hoặc crypto/tls của Go) bằng cách sử dụng uprobes để thu thập dữ liệu văn bản thuần không được mã hóa ngay trước khi nó được chuyển đến bộ mã hóa. Điều này cho phép theo dõi tiêu đề và payload HTTP L7 đầy đủ mà không yêu cầu chứng chỉ proxy MITM.

Các công cụ sản xuất chính được xây dựng trên eBPF ngày nay là gì?

Các công cụ quan sát eBPF sản xuất chính bao gồm:

  1. Cilium & Hubble: Service mesh, mTLS trong suốt và trực quan hóa luồng mạng.
  2. Pixie: Gỡ lỗi ứng dụng Kubernetes tự động và theo dõi phân tán.
  3. Parca / Pyroscope: Hồ sơ CPU và bộ nhớ liên tục trên toàn hệ thống xuống đến các stack trace của kernel.

Bạn cũng có thể thích

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement