Aller au contenu

eBPF pour les SRE : Guide de Profilage en Production

· 13 min read · default
ebpfperformanceprofilingsrelinuxobservabilitymonitoring-&-observability

Introduction

Le profilage en production a toujours été le domaine des audacieux. Pendant des années, les SRE ont fait face à un compromis inconfortable : soit attacher des outils de profilage lourds qui introduisaient une latence mesurable et risquaient de déstabiliser les charges de travail en production, soit naviguer à l'aveugle en se fiant à des tableaux de bord de métriques qui montraient les symptômes mais jamais les causes racines. L'émergence d'eBPF comme technologie mainstream du noyau Linux a fondamentalement changé ce calcul. Avec eBPF, vous pouvez instrumenter presque chaque couche du noyau et de l'espace utilisateur avec un surcoût négligeable, générant le type de données d'observabilité approfondie qui n'était auparavant disponible que dans les environnements de staging.

eBPF, qui signifie extended Berkeley Packet Filter, a évolué depuis ses origines de mécanisme de filtrage de paquets réseau vers une machine virtuelle à usage général au sein du noyau. Les programmes écrits pour eBPF sont vérifiés par le noyau avant leur exécution, garantissant qu'ils ne peuvent pas faire crasher le système, entrer dans des boucles infinies ou accéder à de la mémoire non autorisée. Cette garantie de sécurité est ce qui rend eBPF uniquement adapté au profilage en production. Vous pouvez attacher des sondes aux fonctions du noyau, aux tracepoints, aux entrées de fonctions de l'espace utilisateur et aux compteurs de performance matérielle sans recompiler le noyau ni redémarrer les services.

Ce guide couvre les workflows pratiques que les équipes SRE utilisent quotidiennement pour diagnostiquer les problèmes de performance dans les systèmes Linux en production. Nous parcourrons le profilage CPU, l'analyse de latence, la détection de fuites mémoire, le traçage réseau et le profilage d'E/S en utilisant la chaîne d'outils standard eBPF. Chaque commande et script présenté ici a été utilisé dans des environnements de production exécutant le noyau 5.15 et ultérieur.

Pourquoi eBPF Change le Profilage en Production

Les outils de profilage traditionnels imposent des coûts qui les rendent impraticables en production. Exécuter strace sur un service chargé peut le ralentir d'un facteur de 100x ou plus car strace utilise ptrace pour intercepter chaque syscall, alternant le contexte entre le processus tracé et le processus traceur pour chaque invocation. Même perf, qui utilise les compteurs de performance matérielle et est bien plus léger que strace, nécessite encore l'écriture des données d'échantillonnage sur disque et peut générer une pression d'E/S substantielle sous des taux d'échantillonnage élevés.

eBPF change l'équation de trois façons importantes. Premièrement, les programmes eBPF s'exécutent à l'intérieur du noyau, éliminant le surcoût de changement de contexte des outils basés sur ptrace. Lorsque vous attachez un kprobe ou un tracepoint, le programme eBPF s'exécute en ligne avec le chemin de code du noyau, ajoutant typiquement seulement des dizaines de nanosecondes par invocation. Deuxièmement, les programmes eBPF peuvent agréger des données dans le noyau en utilisant des maps, ce qui signifie que vous pouvez calculer des histogrammes, des comptages et des résumés sans copier chaque événement vers l'espace utilisateur. Un histogramme de latence qui générerait des millions d'événements par seconde avec strace produit une seule lecture de map par intervalle avec eBPF. Troisièmement, le vérificateur eBPF garantit la sécurité : votre programme ne peut pas déréférencer des pointeurs nuls, accéder à de la mémoire hors limites ni boucler indéfiniment.

L'impact pratique est dramatique. Des outils comme biolatency peuvent tracer chaque requête d'E/S de bloc sur un système gérant des centaines de milliers d'IOPS, produisant un histogramme de latence avec moins de 1% de surcoût CPU. Vous pouvez exécuter funclatency sur une fonction chaude de votre serveur d'application tout en servant le trafic de pointe. Cela n'était tout simplement pas possible avec les générations précédentes d'outils de traçage.

Configuration de la Chaîne d'Outils eBPF

L'écosystème eBPF s'est consolidé autour de trois ensembles d'outils principaux : bcc-tools, bpftrace et les programmes CO-RE basés sur libbpf. Chacun sert un cas d'utilisation différent, et un poste de travail SRE bien équipé devrait avoir les trois disponibles.

Sur les systèmes Ubuntu et Debian, installez la chaîne d'outils complète :

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

Sur les systèmes RHEL et Fedora :

sudo dnf install -y bcc-tools bpftrace kernel-devel-$(uname -r)
sudo dnf install -y libbpf-devel bpftool

Vérifiez que votre noyau supporte BTF, requis pour les fonctionnalités modernes de bpftrace et les programmes CO-RE :

ls /sys/kernel/btf/vmlinux
bpftool btf dump file /sys/kernel/btf/vmlinux format raw | head -c 100

Si BTF n'est pas disponible, vous devrez vous assurer que votre noyau a été compilé avec CONFIG_DEBUG_INFO_BTF=y. La plupart des noyaux de distribution depuis 2022 incluent le support BTF.

Le paquet bcc-tools fournit des dizaines d'outils prêts à l'emploi couvrant les scénarios de profilage les plus courants. Ceux-ci sont typiquement installés comme exécutables avec un suffixe -bpfcc sur les systèmes basés sur Debian ou directement sous /usr/share/bcc/tools/ sur RHEL. Bpftrace fournit un langage de script de haut niveau pour écrire des one-liners et des scripts courts personnalisés. Libbpf et les programmes CO-RE (Compile Once, Run Everywhere) sont utilisés pour construire des outils eBPF portables de grade production qui sont distribués comme des binaires autonomes.

Workflows de Profilage CPU

Le profilage CPU est le point de départ le plus courant lors de l'investigation de problèmes de performance. L'objectif est d'identifier quelles fonctions consomment le plus de temps CPU, que ce soit dans l'espace noyau ou utilisateur, et de générer des graphiques de flammes qui rendent les points chauds visuellement évidents.

L'approche la plus simple utilise l'outil profile de bcc pour échantillonner les traces de pile à une fréquence fixe :

sudo profile-bpfcc -F 99 -a --stack-storage-size 16384 30 > /tmp/cpu-stacks.txt

Cela échantillonne tous les CPU à 99 Hz pendant 30 secondes. La fréquence de 99 Hz évite l'aliasing avec l'activité basée sur les timers qui s'exécute souvent à 100 Hz. La sortie contient des traces de pile repliées qui peuvent être directement alimentées dans les outils FlameGraph de Brendan Gregg :

git clone https://github.com/brendangregg/FlameGraph.git
cat /tmp/cpu-stacks.txt | FlameGraph/stackcollapse-bpf.pl | FlameGraph/flamegraph.pl > cpu-flame.svg

Pour un profilage plus ciblé, bpftrace vous permet de profiler un processus spécifique et de filtrer par CPU :

sudo bpftrace -e 'profile:hz:99 /pid == 12345/ { @[ustack(perf), comm] = count(); }' > stacks.bt

Lorsque vous devez comprendre le comportement de planification CPU plutôt que le temps d'exécution, l'outil cpudist montre combien de temps les threads s'exécutent sur le CPU avant d'être déplanifiés :

sudo cpudist-bpfcc -p 12345 10 1

Cela affiche un histogramme des durées sur CPU pour le processus 12345 sur un intervalle de 10 secondes. Des temps courts sur CPU combinés avec des changements de contexte élevés suggèrent une contention de verrous. Des temps longs sur CPU avec un faible débit suggèrent des goulots d'étranglement computationnels.

Pour investiguer les problèmes de migration CPU dans les systèmes NUMA, vous pouvez tracer les événements du planificateur :

sudo bpftrace -e 'tracepoint:sched:sched_migrate_task {
    printf("pid=%d comm=%s from_cpu=%d to_cpu=%d\n",
        args->pid, args->comm, args->orig_cpu, args->dest_cpu);
}'

Analyse de Latence

L'analyse de latence est là où eBPF brille véritablement car il peut mesurer le temps entre des événements arbitraires sans perturber le chemin de code mesuré. La collection bcc-tools inclut plusieurs outils de latence spécialisés.

La latence d'E/S de bloc est mesurée avec biolatency, qui trace le temps de la requête d'E/S de bloc à son achèvement :

sudo biolatency-bpfcc -D 10 1

Le drapeau -D décompose la latence par dispositif de disque, facilitant l'identification des disques lents. La sortie est un histogramme en puissance de deux montrant la distribution des latences en microsecondes.

La latence de file d'attente d'exécution, qui mesure combien de temps les threads attendent dans la file du planificateur avant d'obtenir du temps CPU, est mesurée avec runqlat :

sudo runqlat-bpfcc -p 12345 10 1

Une latence élevée de file d'attente signifie que vos processus attendent le CPU, ce qui indique une saturation CPU. Si vous voyez des latences supérieures à 10 millisecondes pendant le fonctionnement normal, vous avez besoin de plus de capacité CPU ou devez investiguer ce qui consomme le CPU.

La latence de fonction mesure le temps d'exécution d'une fonction spécifique du noyau ou de l'espace utilisateur :

sudo funclatency-bpfcc -p 12345 'c:malloc' 10 1

Cela trace les appels malloc dans la libc du processus 12345 et montre un histogramme de latence. Vous pouvez tracer n'importe quelle fonction qui a un symbole dans le binaire ou la bibliothèque partagée. Pour les fonctions du noyau :

sudo funclatency-bpfcc 'vfs_read' 10 1

Pour le traçage de latence au niveau applicatif avec bpftrace, vous pouvez mesurer le temps entre deux points de sonde :

sudo bpftrace -e '
uprobe:/usr/bin/myapp:process_request { @start[tid] = nsecs; }
uretprobe:/usr/bin/myapp:process_request /@start[tid]/ {
    @latency_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}
END { clear(@start); }
'

Analyse Mémoire

Les problèmes mémoire en production vont des fuites graduelles causant des OOM kills sur plusieurs jours aux inefficacités de cache qui dégradent la performance. eBPF fournit plusieurs outils pour chaque catégorie.

L'outil memleak trace les appels d'allocation et de libération mémoire, suivant les allocations en cours pour identifier les fuites :

sudo memleak-bpfcc -p 12345 --combined-only 30

Cela s'attache au processus 12345 et après 30 secondes affiche les traces de pile des allocations non libérées, triées par total d'octets en cours. C'est inestimable pour détecter les fuites dans les services longue durée sans les redémarrer avec le débogage spécialisé d'allocateur activé.

Pour le suivi d'allocation mémoire du noyau :

sudo memleak-bpfcc --combined-only 30

Sans le drapeau pid, memleak trace les allocations du noyau via kmalloc et kfree, ce qui peut identifier les fuites mémoire du noyau causées par des pilotes ou modules du noyau.

Le comportement du cache a un impact énorme sur la performance de l'application. L'outil cachestat fournit des statistiques par seconde sur le cache de pages :

sudo cachestat-bpfcc 5

La sortie inclut les hits, misses, pages sales et ratios lecture/écriture. Un ratio élevé de misses indique que votre ensemble de travail dépasse la mémoire disponible, et vous devriez considérer si votre application a besoin de plus de RAM ou de meilleurs patterns d'accès.

Les OOM kills peuvent être tracés en temps réel pour comprendre quels processus sont tués et pourquoi :

sudo bpftrace -e 'kprobe:oom_kill_process {
    printf("OOM kill: pid=%d comm=%s pages=%d\n",
        ((struct task_struct *)arg1)->pid,
        ((struct task_struct *)arg1)->comm,
        arg0);
}'

Pour une approche plus simple, l'outil oomkill de bcc capture cela automatiquement :

sudo oomkill-bpfcc

Cela affiche une ligne à chaque invocation de l'OOM killer, incluant les détails du processus déclencheur et du processus tué.

Traçage Réseau

Les problèmes de performance réseau sont notoirement difficiles à diagnostiquer car ils impliquent des interactions entre l'application, la pile TCP du noyau et l'infrastructure réseau. eBPF fournit des outils chirurgicaux pour chaque couche.

L'outil tcplife trace les durées de vie des sessions TCP, montrant quand les connexions sont établies et fermées avec les octets transférés :

sudo tcplife-bpfcc -D

Le drapeau -D inclut les horodatages. Chaque ligne montre le PID, le nom du processus, les adresses locales et distantes, les ports, la durée et les octets envoyés/reçus. C'est essentiel pour identifier le churn de connexions, les connexions de courte durée inattendues ou les services qui gardent les connexions ouvertes plus longtemps que prévu.

Les retransmissions TCP sont un indicateur critique de la santé du réseau :

sudo tcpretrans-bpfcc -l

Le drapeau -l inclut les tail loss probes. Chaque événement de retransmission est affiché avec les adresses source et destination, l'état et la fonction du noyau qui a déclenché la retransmission. Des groupes de retransmissions vers une destination spécifique indiquent des problèmes de chemin réseau.

Les paquets perdus au niveau TCP peuvent être tracés avec tcpdrop :

sudo tcpdrop-bpfcc

Chaque paquet perdu est affiché avec sa trace de pile du noyau, qui vous indique exactement pourquoi le noyau a abandonné le paquet. Les causes courantes incluent le débordement de buffer de socket, les resets de connexion et la pression mémoire.

Pour une analyse détaillée de l'état des connexions TCP, vous pouvez écrire un script bpftrace qui trace les transitions d'état :

sudo bpftrace -e '
tracepoint:tcp:tcp_set_state {
    printf("pid=%d sport=%d dport=%d oldstate=%d newstate=%d\n",
        pid, args->sport, args->dport, args->oldstate, args->newstate);
}
'

La latence de résolution DNS est souvent une source négligée de latence applicative. Vous pouvez tracer les recherches DNS au niveau du résolveur :

sudo bpftrace -e '
uprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo { @start[tid] = nsecs; }
uretprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo /@start[tid]/ {
    @dns_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}
'

Profilage d'E/S

L'E/S de stockage est l'une des sources les plus courantes de latence en production, et eBPF fournit des outils qui tracent l'E/S à plusieurs niveaux de la pile de stockage.

L'outil biosnoop trace les opérations individuelles d'E/S de bloc avec horodatages, latences et attribution de processus :

sudo biosnoop-bpfcc -d sda 10

Cela trace toute l'E/S de bloc vers le dispositif sda pendant 10 secondes, montrant le PID de chaque opération, le nom du processus, le disque, le type d'opération, le secteur, les octets et la latence. C'est l'outil de prédilection quand vous avez besoin de comprendre exactement ce qui génère de l'E/S sur un disque spécifique.

Le traçage au niveau du système de fichiers fournit un contexte de plus haut niveau. L'outil ext4slower trace les opérations ext4 qui dépassent un seuil de latence :

sudo ext4slower-bpfcc 10

Cela affiche toutes les opérations ext4 plus lentes que 10 millisecondes, incluant les lectures, écritures, ouvertures et syncs. Cela identifie immédiatement les opérations lentes du système de fichiers sans nécessiter de corréler les traces au niveau bloc avec les métadonnées du système de fichiers.

Pour le traçage général du système de fichiers à travers tous les types, fileslower sert le même objectif :

sudo fileslower-bpfcc 10

Les patterns d'écriture sont critiques pour comprendre comment les applications interagissent avec le stockage. L'équivalent bpftrace de filetop montre quels fichiers sont lus et écrits le plus fréquemment :

sudo bpftrace -e '
tracepoint:syscalls:sys_enter_write {
    @writes[comm, pid] = count();
}
interval:s:5 { print(@writes); clear(@writes); }
'

Pour les charges de travail lourdes en fsync comme les bases de données, tracer les opérations de synchronisation peut révéler des patterns de flush inattendus :

sudo bpftrace -e '
kprobe:vfs_fsync_range {
    @fsync_latency[comm] = count();
}
kretprobe:vfs_fsync_range {
    printf("fsync completed: comm=%s\n", comm);
}
'

Construction de One-Liners Personnalisés avec bpftrace

La véritable puissance d'eBPF vient de la capacité d'écrire des programmes de traçage personnalisés adaptés à votre pile technique spécifique. Le langage de script de bpftrace rend cela accessible à quiconque sait écrire un script shell.

La structure de base d'un one-liner bpftrace est une spécification de sonde suivie d'un bloc d'action. Les sondes peuvent s'attacher aux kprobes (fonctions du noyau), uprobes (fonctions de l'espace utilisateur), tracepoints (événements stables du noyau) et événements logiciels.

Compter les syscalls par processus :

sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

Histogramme des tailles de lecture par processus :

sudo bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret > 0/ {
    @read_bytes[comm] = hist(args->ret);
}'

Tracer la création de nouveaux processus avec les lignes de commande complètes :

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
    printf("exec: pid=%d ppid=%d %s\n", pid, curtask->real_parent->pid, str(args->filename));
}'

Tracer les ouvertures de fichiers avec les chemins complets :

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat {
    printf("open: pid=%d comm=%s file=%s\n", pid, comm, str(args->filename));
}'

Mesurer le temps que votre application passe dans une fonction spécifique, agrégé par pile d'appels :

sudo bpftrace -e '
uprobe:/opt/myapp/bin/server:DatabaseQuery { @start[tid] = nsecs; }
uretprobe:/opt/myapp/bin/server:DatabaseQuery /@start[tid]/ {
    @query_ns[ustack(5)] = hist(nsecs - @start[tid]);
    delete(@start[tid]);
}
'

Pour les événements à haute fréquence, utilisez les maps pour agréger dans le noyau et éviter de submerger le buffer de sortie :

sudo bpftrace -e '
tracepoint:syscalls:sys_exit_read /args->ret > 0/ {
    @total_bytes[comm] = sum(args->ret);
    @total_calls[comm] = count();
}
interval:s:10 {
    printf("\n--- Top readers (10s window) ---\n");
    print(@total_bytes, 10);
    print(@total_calls, 10);
    clear(@total_bytes);
    clear(@total_calls);
}
'

Intégration du Profilage Continu

Alors que le profilage ad hoc avec bpftrace et bcc-tools est essentiel pour la réponse aux incidents, le profilage continu fournit une visibilité permanente sur la performance. Deux projets open source mènent cet espace : Parca et Pyroscope.

Parca utilise eBPF pour échantillonner continuellement les traces de pile à travers tous les processus sur un hôte avec un surcoût minimal. L'agent Parca s'exécute comme un DaemonSet dans Kubernetes ou un service systemd sur du bare metal :

sudo parca-agent --remote-store-address=parca-server:7070 \
  --node=production-host-01 \
  --sampling-ratio=1.0 \
  --http-address=:7071

L'agent découvre automatiquement tous les processus en cours d'exécution, résout les symboles à partir des informations de débogage et des données BTF, et transmet les données de profilage au serveur Parca. Le serveur stocke les profils dans un format colonnaire optimisé pour les requêtes de séries temporelles et fournit une interface pour explorer les graphiques de flammes, comparer les profils entre les fenêtres temporelles et identifier les régressions.

Pyroscope adopte une approche similaire mais supporte des modes de profilage supplémentaires au-delà du CPU, incluant le profilage d'allocation mémoire, le profilage de contention de verrous et le profilage de goroutines pour les applications Go :

# pyroscope-agent.yaml
server-address: http://pyroscope-server:4040
log-level: info
targets:
  - service-name: api-server
    spy-name: ebpfspy
    application-name: api-server
  - service-name: worker
    spy-name: ebpfspy
    application-name: worker-pool

Les deux outils s'intègrent avec Grafana pour la création de tableaux de bord et les alertes. Une configuration typique de profilage continu inclut :

# Grafana data source configuration for Parca
curl -X POST http://grafana:3000/api/datasources \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Parca",
    "type": "parca",
    "url": "http://parca-server:7070",
    "access": "proxy"
  }'

La véritable valeur du profilage continu émerge avec le temps. Lorsqu'une régression de performance est déployée, vous pouvez comparer le profil actuel avec une référence d'avant le déploiement et voir immédiatement quelles fonctions consomment plus de CPU. Cela transforme le débogage de performance d'une investigation réactive en un système de détection proactif.

Bonnes Pratiques et Sécurité en Production

Les garanties de sécurité d'eBPF ne signifient pas que vous pouvez exécuter n'importe quel programme eBPF sur n'importe quel système de production sans réfléchir. Bien que le vérificateur empêche les crashs du noyau, les programmes eBPF mal écrits peuvent encore consommer un CPU excessif, générer une sortie écrasante ou interférer avec la performance du système.

Définissez toujours des limites de temps sur bpftrace et bcc-tools. Chaque session de profilage devrait avoir une durée explicite :

# Bon : durée explicite de 30 secondes
sudo biolatency-bpfcc 30 1

# Dangereux : s'exécute jusqu'à l'arrêt manuel
sudo biolatency-bpfcc

Soyez prudent avec les sondes à haute fréquence. Attacher un kprobe à une fonction qui se déclenche des millions de fois par seconde ajoutera un surcoût mesurable même si le programme eBPF lui-même est trivial. Avant d'attacher une sonde en production, estimez la fréquence :

sudo bpftrace -e 'kprobe:vfs_read { @count = count(); } interval:s:1 { print(@count); clear(@count); }'

Si la fonction se déclenche plus de quelques centaines de milliers de fois par seconde, considérez si vous pouvez utiliser un tracepoint au lieu d'un kprobe, ajouter un filtre pour réduire le nombre d'événements traités, ou échantillonner plutôt que tracer chaque événement.

Utilisez le drapeau --dry-run de bpftrace pour valider les scripts avant l'exécution :

sudo bpftrace --dry-run -e 'kprobe:vfs_read { @[comm] = count(); }'

Limitez les tailles de map pour empêcher la croissance mémoire illimitée :

sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' --map-max-entries=10000

Pour les systèmes de production, établissez un runbook d'outils et scripts eBPF pré-approuvés. La collection bcc-tools est bien testée et sûre pour l'utilisation en production. Les scripts bpftrace personnalisés doivent être revus par l'équipe et testés en staging avant utilisation en production.

Envisagez d'exécuter les outils eBPF dans des conteneurs avec les capacités appropriées plutôt qu'avec un accès root complet :

docker run --rm -it --privileged \
  -v /sys/kernel/debug:/sys/kernel/debug:ro \
  -v /sys/kernel/btf:/sys/kernel/btf:ro \
  -v /proc:/proc:ro \
  quay.io/iovisor/bpftrace:latest \
  bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

Enfin, documentez vos workflows de profilage. Quand un incident survient à 3h du matin, vous ne voulez pas écrire des scripts bpftrace depuis zéro. Maintenez un référentiel de scripts de profilage testés organisés par symptôme : CPU élevé, latence élevée, croissance mémoire, erreurs réseau et saturation d'E/S. Chaque script devrait inclure des commentaires expliquant ce qu'il mesure, le surcoût attendu et comment interpréter la sortie. eBPF vous donne des superpouvoirs en production, mais seulement si vous avez pratiqué leur utilisation avant l'arrivée de l'urgence.