si c'est dans le slab c'est que ton code ebpf ou ton loader côté userland leak des objets kernel.
tu utilises quoi ? cilium/ebpf en go ou libbpf en c ?
on est sur du go avec cilium/ebpf. on utilise des hash maps pour stocker le state des connexions entre le connect et le close.
classique. tu dois avoir une map qui ne se vide jamais ou des fd de maps qui restent ouverts.
regarde le contenu de /proc/slabinfo ou utilise slabtop pour confirmer le kmalloc-1k.
slabtop -o | head -n 10
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
2450321 2450100 99% 1.00K 153145 16 2.4G kmalloc-1k
2.4G de kmalloc-1k. c'est énorme pour des simples traces tcp.
tes maps ebpf sont définies comment ? si c'est du BPF_MAP_TYPE_HASH sans taille max cohérente ou si tu oublies le delete...
fais un bpftool map dump id de ta map pour voir le nombre d'entrées.
bpftool map show
15: hash name tcp_stats flags 0x0
key 16B value 1024B max_entries 1000000 memlock 1048576000B
la value fait 1024 bytes (1k). ça matche parfaitement le slab kmalloc-1k.
bingo. ton max_entries est à 1 million. le kernel pré-alloue pas forcément tout mais il garde les entrées tant que tu les vires pas.
si tu as beaucoup de connexions courtes et que tu loupes le tcp_close tu accumules à l'infini.
pourquoi tu louperais le tcp_close ? kprobe sur tcp_close est assez fiable pourtant.
vérifie si tu as des erreurs de lecture dans /sys/kernel/debug/tracing/trace_pipe.
j'ai rien dans trace_pipe. par contre je viens de voir un truc. on a des pods qui se font sigkill violemment.
si le pod meurt les connexions tcp sont fermées par le kernel mais est-ce que tcp_close est appelé de la même façon ?
oui mais si ton agent ebpf tourne aussi dans un pod et qu'il redémarre il perd le lien avec les maps si elles sont épinglées (pinned) dans bpffs.
est-ce que tu réutilises la même map après restart sans la clean ?
non on ne pin pas la map. par contre je viens de trouver le bug dans le code go.
sur tcp_close on tente de delete la key mais si la socket est dans un état particulier la key générée est légèrement différente de celle du connect.
// bug ici
err := bpfMaps.TcpStats.Delete(key)
if err != nil {
// on ignorait l'erreur sans loguer
}
aie. l'erreur silencieuse sur le delete d'une map ebpf c'est le meilleur moyen de leak le slab.
pour corriger ça proprement utilise plutôt une BPF_MAP_TYPE_LRU_HASH. comme ça quand c'est plein le kernel évince les plus vieux tout seul.
je vais passer sur une LRU hash et fixer la génération de la key pour qu'elle soit consistante.
j'ai testé le patch sur un node : le kmalloc-1k reste stable à 50mb. plus d'explosion du slab.
parfait. oublie pas de rajouter des métriques sur les échecs de delete dans ton agent go pour pas que ça revienne.
c'est implémenté. déploiement en cours sur tout le cluster. merci les gars.
Vous devez être connecté pour poster un message !
Recevoir les derniers articles gratuitement en créant un compte !
S'inscrire
richard-marin
Membre depuis le 31/08/2019hello. on déploie un nouvel agent d'observabilité basé sur ebpf (kprobes sur tcp_connect et tcp_close).
après 4h de run les nodes k8s saturent en ram. le plus bizarre c'est que l'usage rss de l'agent est stable.
c'est le slab kernel qui explose. kmalloc-1k prend des gigas.