Hier ist die TL;DR für die Ungeduldigen:

  • eBPF ermöglicht das Ausführen von isolierten Programmen im Linux-Kernel
  • Es bietet tiefe Einblicke in das Verhalten von Systemen und Anwendungen
  • Geringer Overhead bedeutet, dass es in der Produktion genutzt werden kann, ohne ins Schwitzen zu kommen
  • Es ist vielseitig: Von der Netzwerkanalyse bis zur Sicherheitsüberwachung, eBPF ist für Sie da

eBPF 101: Die Grundlagen

Bevor wir ins Detail gehen, lassen Sie uns erklären, wie eBPF funktioniert. Stellen Sie sich vor, Sie pflanzen winzige, effiziente Spione in Ihrem Linux-Kernel. Diese Spione (eBPF-Programme) können alles von Systemaufrufen bis zu Netzwerkpaketen melden, ohne dass Sie Ihren Kernel ändern oder Ihr System neu starten müssen.

Hier ist der magische Trick:

  1. Sie schreiben ein eBPF-Programm (normalerweise in C)
  2. Dieses Programm wird in eBPF-Bytecode kompiliert
  3. Der Bytecode wird auf Sicherheit überprüft (keine Kernel-Paniken, vielen Dank)
  4. Er wird dann JIT-kompiliert und in den Kernel injiziert
  5. Ihr Programm läuft, wenn bestimmte Ereignisse auftreten, sammelt Daten oder ändert das Verhalten

Es ist, als hätten Sie ein Team hochqualifizierter, mikroskopisch kleiner Ninjas, die bereit sind, jeden Aspekt Ihres Systems jederzeit zu melden.

Einrichtung Ihres eBPF-Labors

Bereit, sich die Hände schmutzig zu machen? Lassen Sie uns einen eBPF-Spielplatz einrichten. Zuerst benötigen Sie einen relativ aktuellen Linux-Kernel (4.4+, aber neuer ist besser). Dann installieren wir einige Werkzeuge:


sudo apt-get update
sudo apt-get install -y bpfcc-tools linux-headers-$(uname -r)

Dies installiert BCC (BPF Compiler Collection), das uns eine schöne Python-Schnittstelle bietet, um mit eBPF zu arbeiten. Jetzt schreiben wir unser erstes eBPF-Programm:


from bcc import BPF

# Definieren Sie das eBPF-Programm
prog = """
int hello(void *ctx) {
    bpf_trace_printk("Hallo, eBPF-Welt!\\n");
    return 0;
}
"""

# Laden Sie das eBPF-Programm
b = BPF(text=prog)

# An eine Kernel-Funktion anhängen
b.attach_kprobe(event="sys_clone", fn_name="hello")

# Ausgabe drucken
print("eBPF-Programm geladen. Verfolgen von sys_clone()... Strg+C zum Beenden.")
b.trace_print()

Speichern Sie dies als hello_ebpf.py und führen Sie es mit sudo python3 hello_ebpf.py aus. Herzlichen Glückwunsch! Sie haben gerade ein eBPF-Programm erstellt, das jedes Mal "Hallo" sagt, wenn ein neuer Prozess erstellt wird.

Prozessüberwachung: Wer frisst meine CPU?

Jetzt, da wir einen ersten Einblick haben, gehen wir tiefer. Ein häufiger Anwendungsfall für eBPF ist die Überwachung des Prozessverhaltens. Lassen Sie uns ein Programm erstellen, das die CPU-Nutzung pro Prozess verfolgt:


from bcc import BPF
from time import sleep

# eBPF-Programm
prog = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>

struct key_t {
    u32 pid;
    u64 cpu;
};

BPF_HASH(stats, struct key_t);

int on_switch(struct pt_regs *ctx, struct task_struct *prev) {
    struct key_t key = {};
    u64 delta, *time;

    key.pid = prev->pid;
    key.cpu = bpf_get_smp_processor_id();

    time = stats.lookup(&key);
    if (time) {
        delta = bpf_ktime_get_ns() - *time;
        stats.increment(key, delta);
    }

    key.pid = bpf_get_current_pid_tgid() >> 32;
    stats.update(&key, &bpf_ktime_get_ns());

    return 0;
}
"""

b = BPF(text=prog)
b.attach_kprobe(event="finish_task_switch", fn_name="on_switch")

print("Überwachung der CPU-Nutzung... Strg+C zum Beenden")
while True:
    sleep(1)
    print("\nTop 5 CPU-hungrige Prozesse:")
    b["stats"].print_log2_hist("CPU-Nutzung", "pid", section_print_fn=lambda k, v: f"PID {k.pid:<6} CPU {k.cpu:<3}")

Dieses Skript hängt sich an die finish_task_switch-Funktion an, sodass wir verfolgen können, wie lange jeder Prozess auf der CPU verbringt. Führen Sie es aus und beobachten Sie, welche Prozesse Ihre wertvollen CPU-Zyklen beanspruchen!

Netzwerkermittlung mit eBPF

eBPF ist nicht nur für die Prozessüberwachung; es ist auch ein leistungsstarkes Werkzeug für die Netzwerkanalyse. Lassen Sie uns ein einfaches Programm erstellen, um Netzwerkverbindungen zu verfolgen:


from bcc import BPF

# eBPF-Programm
prog = """
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>

BPF_HASH(ipv4_sends, u32, u64);

int trace_ip_send(struct pt_regs *ctx, struct sock *sk) {
    u32 dst = sk->__sk_common.skc_daddr;
    ipv4_sends.increment(dst);
    return 0;
}
"""

b = BPF(text=prog)
b.attach_kprobe(event="ip_send_skb", fn_name="trace_ip_send")

print("Verfolgen von IP-Sendungen... Strg+C zum Beenden")
try:
    while True:
        b.perf_buffer_poll()
except KeyboardInterrupt:
    print("\nTop-IP-Ziele:")
    b["ipv4_sends"].print_log2_hist("Pakete", "IP")

Dieses Skript verfolgt ausgehende IPv4-Verbindungen und gibt Ihnen einen Überblick darüber, wohin Ihr System Pakete sendet. Es ist, als hätten Sie Ihr eigenes kleines Wireshark direkt im Kernel!

Integration von eBPF mit Prometheus und Grafana

Jetzt, da wir all diese wertvollen Daten sammeln, lassen Sie uns sie visualisieren! Wir verwenden Prometheus, um unsere eBPF-Metriken zu erfassen, und Grafana, um schöne Dashboards zu erstellen.

Zuerst ändern wir unser CPU-Überwachungsskript, um Metriken für Prometheus bereitzustellen:


from bcc import BPF
from prometheus_client import start_http_server, Gauge
import time

# ... (vorheriger eBPF-Programmkode hier)

# Prometheus-Metriken
PROCESS_CPU_USAGE = Gauge('process_cpu_usage', 'CPU-Nutzung pro Prozess', ['pid', 'cpu'])

def update_metrics():
    for k, v in b["stats"].items():
        PROCESS_CPU_USAGE.labels(pid=k.pid, cpu=k.cpu).set(v.value)

if __name__ == '__main__':
    b = BPF(text=prog)
    b.attach_kprobe(event="finish_task_switch", fn_name="on_switch")

    # Starten Sie den Prometheus-HTTP-Server
    start_http_server(8000)

    print("Überwachung der CPU-Nutzung... Metriken verfügbar unter :8000")
    while True:
        time.sleep(1)
        update_metrics()

Konfigurieren Sie nun Prometheus, um diese Metriken zu erfassen, und richten Sie ein Grafana-Dashboard ein, um sie zu visualisieren. Sie haben eine Echtzeitansicht der CPU-Nutzung Ihres Systems, die jeden Systemadministrator begeistern würde!

Sicherheitsüberwachung: Die Bösewichte fangen

eBPF ist nicht nur für die Leistung; es ist auch ein leistungsstarkes Werkzeug für die Sicherheitsüberwachung. Lassen Sie uns ein einfaches Intrusion-Detection-System erstellen, das verdächtige Dateizugriffe überwacht:


from bcc import BPF

prog = """
#include <uapi/linux/ptrace.h>
#include <linux/fs.h>

BPF_HASH(suspicious_accesses, u32);

int trace_open(struct pt_regs *ctx, const char __user *filename, int flags) {
    char fname[256];
    bpf_probe_read_user_str(fname, sizeof(fname), (void *)filename);
    
    if (strstr(fname, "/etc/shadow") != NULL) {
        u32 pid = bpf_get_current_pid_tgid() >> 32;
        suspicious_accesses.increment(pid);
    }
    
    return 0;
}
"""

b = BPF(text=prog)
b.attach_kprobe(event="do_sys_open", fn_name="trace_open")

print("Überwachung verdächtiger Dateizugriffe... Strg+C zum Beenden")
try:
    while True:
        b.perf_buffer_poll()
except KeyboardInterrupt:
    print("\nVerdächtige Zugriffe erkannt:")
    b["suspicious_accesses"].print_log2_hist("Versuche", "PID")

Dieses Skript überwacht Versuche, auf die Datei /etc/shadow zuzugreifen, was darauf hindeuten könnte, dass jemand versucht, Passwort-Hashes zu stehlen. Es ist ein einfaches Beispiel, aber es zeigt, wie eBPF für die Echtzeit-Sicherheitsüberwachung verwendet werden kann.

Leistungsoptimierung: Jeden letzten Tropfen herausquetschen

Verwenden wir eBPF, um langsame Festplatten-I/O-Operationen zu identifizieren und zu optimieren:


from bcc import BPF
import time

prog = """
#include <uapi/linux/ptrace.h>
#include <linux/blkdev.h>

BPF_HISTOGRAM(dist);

int trace_req_start(struct pt_regs *ctx, struct request *req) {
    u64 ts = bpf_ktime_get_ns();
    bpf_map_update_elem(&start, &req, &ts, BPF_ANY);
    return 0;
}

int trace_req_completion(struct pt_regs *ctx, struct request *req) {
    u64 *tsp, delta;
    tsp = bpf_map_lookup_elem(&start, &req);
    if (tsp != 0) {
        delta = bpf_ktime_get_ns() - *tsp;
        dist.increment(bpf_log2l(delta / 1000));
        bpf_map_delete_elem(&start, &req);
    }
    return 0;
}
"""

b = BPF(text=prog)
b.attach_kprobe(event="blk_start_request", fn_name="trace_req_start")
b.attach_kprobe(event="blk_mq_start_request", fn_name="trace_req_start")
b.attach_kprobe(event="blk_account_io_completion", fn_name="trace_req_completion")

print("Verfolgen von Blockgeräte-I/O... Drücken Sie Strg+C zum Beenden.")
try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    print("\nBlock-I/O-Latenz-Histogramm:")
    b["dist"].print_log2_hist("usecs")

Dieses Skript misst die Latenz von Festplatten-I/O-Operationen und hilft Ihnen, potenzielle Engpässe in Ihrem Speichersystem zu identifizieren. Mit diesen Informationen können Sie fundierte Entscheidungen über die Optimierung Ihres Festplattenlayouts oder die Aufrüstung Ihrer Hardware treffen.

eBPF-Tools: Ihre neuen besten Freunde

Wir haben gerade erst an der Oberfläche dessen gekratzt, was mit eBPF möglich ist. Hier sind einige leistungsstarke Tools, die Sie kennen sollten:

  • bpftrace: Eine hochentwickelte Tracing-Sprache für eBPF
  • bcc: Die BPF Compiler Collection, die wir in unseren Beispielen verwendet haben
  • cilium: Netzwerksicherheit und -sichtbarkeit mit eBPF
  • falco: Laufzeitsicherheitsüberwachung mit eBPF
  • pixie: Automatische Instrumentierung und Überwachung für Kubernetes

Jedes dieser Tools nutzt eBPF auf einzigartige Weise und bietet leistungsstarke Funktionen für Überwachung, Sicherheit und Leistungsoptimierung.

Zusammenfassung: Die eBPF-Revolution

eBPF revolutioniert die Art und Weise, wie wir Linux-Systeme überwachen und optimieren. Es bietet uns beispiellose Einblicke in das Systemverhalten mit minimalem Overhead, sodass wir unsere Systeme auf eine Weise debuggen, sichern und optimieren können, die zuvor unmöglich oder unpraktisch war.

Wie wir gesehen haben, kann eBPF verwendet werden für:

  • Tiefe Systeminspektion
  • Leistungsüberwachung und -optimierung
  • Netzwerkanalyse und -sicherheit
  • Individuelle Überwachungslösungen

Das Beste daran? Das ist erst der Anfang. Während sich eBPF weiterentwickelt, können wir noch leistungsfähigere Tools und Techniken erwarten.

Also, das nächste Mal, wenn Sie vor einem mysteriösen Leistungsproblem stehen oder sich über einen schwer fassbaren Fehler den Kopf zerbrechen, denken Sie daran: eBPF ist Ihre Geheimwaffe. Viel Spaß beim Überwachen, und mögen Ihre Systeme immer reibungslos laufen!

"Mit großer Macht kommt große Verantwortung." Während eBPF Ihnen Superkräfte verleiht, denken Sie daran, sie weise einzusetzen. Testen Sie Ihre eBPF-Programme immer gründlich und achten Sie auf ihre Auswirkungen auf die Systemleistung.

Gehen Sie nun hinaus und erobern Sie diese Linux-Systeme mit Ihren neu erworbenen eBPF-Fähigkeiten!