Akvorado release 2.0

Vincent Bernat

Akvorado 2.0 est sorti aujourd’hui ! Akvorado est un collecteur de flux réseau IPFIX et sFlow. Il enrichit les flux et les stocke dans une base de données ClickHouse. Les utilisateurs peuvent parcourir les données via une console web. Cette version introduit un changement architectural important et d’autres améliorations mineures. Regardons plus en détails ! 🤿

$ git diff --shortstat v1.11.5
 493 files changed, 25015 insertions(+), 21135 deletions(-)

Nouveau service « outlet »#

Le changement majeur dans Akvorado 2.0 est la séparation du service inlet en deux parties : l’inlet et l’outlet. Auparavant, l’inlet gérait tout le traitement des flux : réception, décodage et enrichissement. Les flux étaient ensuite envoyés à Kafka pour stockage dans ClickHouse :

Traitement des flux Akvorado avant le changement : les flux sont reçus et
traités par l'inlet, envoyés à Kafka et stockés dans ClickHouse
Traitement des flux Akvorado avant l'introduction du service outlet

Les flux réseau atteignent le service inlet via des chaussettes UDP, un protocole non fiable. L’inlet doit les traiter assez rapidement pour éviter de perdre des paquets. Pour gérer un grand nombre de flux, l’inlet crée plusieurs ensembles de workers pour recevoir les flux, récupérer les métadonnées et assembler les flux enrichis pour Kafka. De nombreuses options de configuration existaient pour la montée en charge, ce qui augmentait la complexité pour les utilisateurs. Le code devait éviter tout blocage, rendant la chaîne de traitement complexe et parfois peu fiable, notamment le composant BMP1. L’ajout de nouvelles fonctionnalités sans aggraver le problème devenait difficile2.

Dans Akvorado 2.0, l’inlet reçoit les flux et les pousse vers Kafka sans les décoder. Le nouveau service outlet gère les tâches restantes :

Traitement des flux Akvorado après le changement : les flux sont reçus par
l'inlet, envoyés à Kafka, traités par l'outlet et insérés dans ClickHouse
Traitement des flux Akvorado après l'introduction du service outlet

Ce changement va au-delà d’un simple découpage3 : l’outlet lit maintenant les flux depuis Kafka et les pousse vers ClickHouse, deux tâches qu’Akvorado ne gérait pas auparavant. Les flux sont groupés par lots pour augmenter l’efficacité et réduire la charge sur ClickHouse en utilisant ch-go, un client Go bas niveau pour ClickHouse. Quand les lots sont trop petits, des insertions asynchrones sont utilisées (e20645). Le nombre de workers de l’outlet s’ajuste dynamiquement (e5a625) en fonction de la cible sur la taille des lots et de la latence (50 000 flux et 5 secondes par défaut).

Cette nouvelle architecture nous permet également de simplifier et optimiser le code. L’outlet récupère les métadonnées de façon synchrone (e20645). Le composant BMP devient plus simple en supprimant le multitâche coopératif (3b9486). Réutiliser le même objet RawFlow pour décoder les flux Protobuf depuis Kafka réduit la pression sur le ramasse-miettes (8b580f).

L’effet sur les performances globales d’Akvorado était quelque peu incertain, mais un utilisateur a indiqué gagner 35 % d’utilisation CPU en moins après migration depuis la version précédente, ainsi que la résolution du problème de longue date du composant BMP. 🥳

Autres changements#

Cette nouvelle version inclut de nombreux changements divers. On y trouve par exemple la complétion pour les ports sources et destinations (f92d2e), et le redémarrage automatique de l’orchestrateur (0f72ff) lors de changements de configuration pour éviter un piège courant pour les nouveaux utilisateurs.

Concentrons-nous sur quelques domaines clés pour cette version : observabilité, documentation, CI, Docker, Go et JavaScript.

Observabilité#

Akvorado expose des métriques pour fournir une visibilité sur la chaîne de traitement et aider à diagnostiquer les problèmes. Elles sont disponibles au format Prometheus, via par exemple /api/v0/inlet/metrics. Avec l’introduction de l’outlet, de nombreuses métriques ont été déplacées. Certaines ont également été renommées (4c0b15) pour correspondre aux bonnes pratiques Prometheus. Une métrique pour mesurer le décalage des consommateurs Kafka a été ajoutée (e3a778).

Si vous n’avez pas votre propre pile d’observabilité, la configuration Docker Compose fournie avec Akvorado en propose une. Vous pouvez l’activer avec les profils introduits à cette fin (529a8f).

Le profil prometheus fournit Prometheus pour stocker les métriques et Alloy pour les collecter (2b3c46, f81299 et 8eb7cd). Les métriques Redis et Kafka sont collectées via l’exporteur intégré à Alloy (560113). Les autres métriques sont exposées au format Prometheus et sont automatiquement récupérées par Alloy à l’aide d’annotations Docker. C’est similaire à ce qui est fait pour configurer Traefik. cAdvisor a également été ajouté (83d855) pour fournir des métriques liées aux conteneurs.

Le profil loki fournit Loki pour stocker les logs (45c684). Bien qu’Alloy puisse collecter et envoyer les logs vers Loki, ses capacités d’analyse sont limitées : je n’ai pas trouvé de moyen de préserver toutes les métadonnées associées aux logs structurés produits par de nombreuses applications, y compris Akvorado. Vector remplace Alloy (95e201) et propose un langage, VRL, pour transformer les logs. Malheureusement, il ne peut actuellement pas récupérer les logs Docker précédenat son démarrage.

Enfin, le profil grafana fournit Grafana, mais les tableaux de bord fournis sont pour le moment cassés. Ce sera corrigé dans une prochaine version.

Documentation#

La configuration Docker Compose fournie par Akvorado facilite la mise en route rapide. Cependant, Akvorado nécessite quelques étapes obligatoires pour être fonctionnel. Il est livré avec une documentation complète, y compris un chapitre sur le dépannage des problèmes. J’espérais que cette documentation réduirait la charge de support. Il est difficile de savoir si cela fonctionne. Les utilisateurs satisfaits rapportent rarement leurs succès, tandis qu’une poignée d’utilisateurs ouvrent des discussions pour demander de l’aide sans avoir vraiment lu la documentation.

Dans cette version, la documentation a été considérablement améliorée.

$ git diff --shortstat v1.11.5 -- console/data/docs
 10 files changed, 1873 insertions(+), 1203 deletions(-)

La documentation a été mise à jour (fc1028) pour correspondre à la nouvelle architecture d’Akvorado. La section de dépannage a été réécrite (17a272). Une documentation sur comment améliorer les performances de ClickHouse lors de la mise à niveau depuis des versions antérieures à 1.10.0 a été ajoutée (5f1e9a). Un LLM a relu l’ensemble (06e3f3). La documentation destinée aux développeurs a également été améliorée (548bbb, e41bae et 871fc5).

Du point de vue de l’utilisabilité, les sections de la table des matières peuvent maintenant se replier (c142e5). Les avertissements aident à attirer l’attention des utilisateurs sur les points importants (8ac894).

Avertissement dans la documentation d'Akvorado demandant à un utilisateur de
ne pas ouvrir un ticket ou démarrer une discussion avant d'avoir lu la
documentation
Exemple d'utilisation des avertissements dans la documentation d'Akvorado

Intégration continue#

Cette version inclut des efforts pour accélérer l’intégration continue sur GitHub. Les tests de couverture et de concurrence s’exécutent en parallèle (6af216 et fa9e48). L’image Docker se construit pendant les tests mais ne reçoit son étiquette qu’après leurs succès (8b0dce).

Flux GitHub pour la CI avec de nombreuses tâches, certaines s'exécutant
en parallèle, d'autres non
Flux GitHub pour tester et construire Akvorado

Les tests de bout en bout (883e19) s’assurent que la configuration Docker Compose fournie fonctionne comme attendu. Hurl exécute des tests sur diverses URL, notamment pour vérifier les métriques (42679b et 169fa9). Par exemple :

## Test inlet has received NetFlow flows
GET http://127.0.0.1:8080/prometheus/api/v1/query
[Query]
query: sum(akvorado_inlet_flow_input_udp_packets_total{job="akvorado-inlet",listener=":2055"})
HTTP 200
[Captures]
inlet_receivedflows: jsonpath "$.data.result[0].value[1]" toInt
[Asserts]
variable "inlet_receivedflows" > 10

## Test inlet has sent them to Kafka
GET http://127.0.0.1:8080/prometheus/api/v1/query
[Query]
query: sum(akvorado_inlet_kafka_sent_messages_total{job="akvorado-inlet"})
HTTP 200
[Captures]
inlet_sentflows: jsonpath "$.data.result[0].value[1]" toInt
[Asserts]
variable "inlet_sentflows" >= {{ inlet_receivedflows }}

Docker#

Akvorado est fourni avec une configuration Docker Compose complète pour aider les utilisateurs à démarrer rapidement. Elle assure un déploiement cohérent, éliminant de nombreux problèmes liés à l’environnement et la configuration. Elle sert également de documentation.

Cette version apporte quelques petites améliorations autour de Docker :

Auparavant, de nombreuses images Docker provenaient de la bibliothèque de conteneurs Bitnami. Cependant, Bitnami a été racheté par VMWare en 2019 et VMWare a été racheté par Broadcom en 2023. En conséquence, les images Bitnami ont été dépréciées en moins d’un mois. Ce n’était pas vraiment une surprise4. Les versions précédentes d’Akvorado avaient déjà commencé à s’en éloigner. Dans cette version, l’image Kafka du projet Apache remplace celle de Bitnami (1eb382). Grâce au passage au mode KRaft, Zookeeper n’est plus nécessaire (0a2ea1, 8a49ca et f65d20).

Les images Docker d’Akvorado étaient auparavant compilées avec Nix. Cependant, construire des images AArch64 sur x86-64 est lent car cela repose sur l’émulation de QEMU. Le Dockerfile mis à jour utilise des constructions multi-étapes et multi-plateformes : une étape construit la partie JavaScript sur la plateforme hôte, une étape construit la partie Go en compilation croisée sur la plateforme hôte, et la dernière étape assemble l’image en se basant sur une image légère (268e95 et d526ca).

# Ceci est une version simplifiée
FROM --platform=$BUILDPLATFORM node:20-alpine AS build-js
RUN apk add --no-cache make
WORKDIR /build
COPY console/frontend console/frontend
COPY Makefile .
RUN make console/data/frontend

FROM --platform=$BUILDPLATFORM golang:alpine AS build-go
RUN apk add --no-cache make curl zip
WORKDIR /build
COPY . .
COPY --from=build-js /build/console/data/frontend console/data/frontend
RUN go mod download
RUN make all-indep
ARG TARGETOS TARGETARCH TARGETVARIANT VERSION
RUN make

FROM gcr.io/distroless/static:latest
COPY --from=build-go /build/bin/akvorado /usr/local/bin/akvorado
ENTRYPOINT [ "/usr/local/bin/akvorado" ]

Lors de la construction pour plusieurs plateformes avec --platform linux/amd64,linux/arm64,linux/arm/v7, les étapes de construction jusqu’à la ligne surlignée ne s’exécutent qu’une seule fois pour toutes les plateformes. Cela accélère considérablement la compilation. 🚅

Akvorado livre maintenant des images Docker pour ces plateformes : linux/amd64, linux/amd64/v3, linux/arm64 et linux/arm/v7. Lors du téléchargement de ghcr.io/akvorado/akvorado, Docker sélectionne la meilleure image pour le CPU actuel. Sur x86-64, il y a deux choix. Si votre CPU est assez récent, Docker télécharge linux/amd64/v3. Cette version contient des optimisations supplémentaires et devrait s’exécuter plus rapidement que la version linux/amd64. Il serait intéressant de livrer une image pour linux/arm64/v8.2, mais Docker ne supporte pas encore le même mécanisme pour AArch64 (792808).

Go#

Cette version inclut de nombreux changements liés à Go mais non visibles pour les utilisateurs.

Chaîne de compilation#

Par le passé, Akvorado supportait les deux dernières versions de Go, empêchant l’utilisation immédiate des dernières améliorations. L’objectif était de permettre aux utilisateurs de distributions stables d’utiliser les versions de Go fournies avec leur distribution pour compiler Akvorado. Cependant, cela pouvait être frustrant quand des fonctionnalités intéressantes, comme go tool, étaient publiées. Akvorado 2.0 nécessite Go 1.25 (77306d) mais peut être compilé avec des chaînes de compilation plus anciennes en téléchargeant automatiquement une version plus récente (94fb1c).5 Les utilisateurs peuvent toujours modifier GOTOOLCHAIN pour annuler cette décision. La chaîne de compilation recommandée est mise à jour hebdomadairement via la CI pour s’assurer d’obtenir la dernière version mineure (5b11ec). Ce changement simplifie aussi les mises à jour vers des versions plus récentes : seul go.mod doit être mis à jour.

Grâce à ce changement, Akvorado utilise maintenant wg.Go() (77306d) et j’ai commencé à convertir certains tests unitaires vers le nouveau paquet test/synctest (bd787e, 7016d8 et 159085).

Tests#

Pour tester l’égalité, j’utilise une fonction Diff() qui affiche les différences en cas d’échec :

got := input.Keys()
expected := []int{1, 2, 3}
if diff := helpers.Diff(got, expected); diff != "" {
    t.Fatalf("Keys() (-got, +want):\n%s", diff)
}

Cette fonction utilise kylelemons/godebug. Ce paquet n’est plus maintenu et présente des lacunes : par exemple, par défaut, elle ne compare pas les champs privés des structures, ce qui peut causer des tests qui réussissent de façon inattendue. Je l’ai remplacé par google/go-cmp, qui est plus strict et formatte la différence de manière plus lisible (e2f1df).

Un autre paquet pour Kafka#

Un autre changement est le passage de Sarama à franz-go pour interagir avec Kafka (756e4a et 2d26c5). La principale motivation pour ce changement est de travailler avec un meilleur modèle de concurrence. Sarama repose fortement sur les canaux et il est difficile de savoir quand un objet n’est plus utilisé. franz-go utilise une approche plus moderne6 et performante avec des fonctions de rappel. Il est également livré avec un paquet pour lancer de faux clusters Kafka, ce qui est plus pratique que les fonctions de simulation fournies par Sarama.

Table de routage améliorée pour BMP#

Pour stocker les routes, le composant BMP utilisait kentik/patricia, une implémentation d’un arbre patricia axée sur la réduction de la pression sur le ramasse-miettes. gaissmai/bart est une alternative plus récente utilisant une adaptation de l’algorithme ART de Donald Kunth qui promet de meilleures performances et les fournit : recherches 90 % plus rapides et insertions 27 % plus rapides (92ee2e et fdb65c).

Contrairement à kentik/patricia, gaissmai/bart n’aide pas à stocker efficacement les valeurs attachées à chaque préfixe. J’ai adapté la même approche utilisée par kentik/patricia pour stocker les listes de routes pour chaque préfixe : stocker un index 32 bits pour chaque préfixe, et l’utiliser pour construire un index 64 bits pour rechercher les routes dans un tableau associatif, qui est une structure particulièrement efficace de Go.

gaissmai/bart supporte également une version sans verrous de la table de routage, mais ce n’est pas aussi simple car nous aurions besoin d’étendre cela au tableau stockant les routes et au mécanisme d’internement. J’ai également tenté d’utiliser le nouveau paquet unique de Go pour remplacer le paquet intern inclus dans Akvorado, mais les performances étaient pires7.

Divers#

Les versions précédentes d’Akvorado utilisaient un encodeur Protobuf personnalisé pour les performances et la flexibilité. Avec l’introduction du service outlet, Akvorado n’a besoin que d’un schéma statique simple, donc ce code a été supprimé. Cependant, il est possible d’améliorer les performances avec planetscale/vtprotobuf (e49a74 et 8b580f). De plus, la dépendance à protoc, un programme C++, était quelque peu ennuyeuse. Par conséquent, Akvorado utilise maintenant buf, écrit en Go, pour convertir un schéma Protobuf en code Go (f4c879).

Une autre petite optimisation pour réduire la taille du binaire d’Akvorado de 10 MB a consisté à compresser les ressources statiques intégrées dans Akvorado dans un fichier ZIP. Cela inclut la base des numéros d’AS, ainsi que les images SVG pour la documentation. Un bout de code rend ce changement transparent (b1d638 et e69b91).

JavaScript#

Récemment, deux grandes attaques de la chaîne d’approvisionnement ont frappé l’écosystème JavaScript : une affectant les paquets populaires chalk et debug et une autre impactant le paquet populaire @ctrl/tinycolor. Ces attaques existent aussi dans d’autres écosystèmes, mais JavaScript est une cible privilégiée en raison de l’usage intensif de petites dépendances. La version précédente d’Akvorado reposait sur 653 dépendances.

npm-run-all a été supprimé (3424e8, 132 dépendances). patch-package a été supprimé (625805 et e85ff0, 69 dépendances) en déplaçant les définitions TypeScript manquantes vers env.d.ts. eslint a été remplacé par oxlint, un linter écrit en Rust (97fd8c, 125 dépendances, plugins inclus).

J’ai basculé de npm vers Pnpm, un gestionnaire de paquets alternatif (fce383). Pnpm n’exécute pas les scripts d’installation par défaut8 et empêche l’installation de paquets trop récents. Il est également significativement plus rapide9. Contrairement à npm, il n’est pas livré avec Node.js. Cependant, Node.js contient Corepack, qui nous permet d’utiliser Pnpm sans l’installer. Pnpm peut également lister les licences utilisées par chaque dépendance, supprimant le besoin d’utiliser license-compliance (a35ca8, 42 dépendances).

Pour des améliorations de vitesse supplémentaires, au-delà du passage à Pnpm et Oxlint, Vite a été remplacé par sa version Rolldown plus rapide (463827).

Après ces changements, Akvorado ne nécessite plus « que » 225 dépendances. 😱

Prochaines étapes#

J’aimerais intégrer trois fonctionnalités dans la prochaine version d’Akvorado :

  • Ajouter des tableaux de bord Grafana pour compléter la pile d’observabilité. Voir le ticket #1906 pour les détails.

  • Intégrer le plugin Grafana d’OVH en fournissant une API stable pour de telles intégrations. La console web d’Akvorado resterait utile pour parcourir les résultats, mais si vous voulez créer et partager des tableaux de bord, vous devrez passer à Grafana. Voir le ticket #1895.

  • Déplacer une partie du travail actuellement fait dans ClickHouse (dictionnaires personnalisés, GeoIP et ajout d’informations liées aux IP) vers le service outlet. Cela devrait donner plus de flexibilité pour ajouter des fonctionnalités comme celle demandée dans le ticket #1030.


J’ai commencé à travailler sur la division de l’inlet il y a plus d’un an. J’ai retrouvé de la motivation ces derniers mois, en partie grâce à Claude Code, que j’ai utilisé comme canard en plastique. Presqu’aucun code produit n’a été conservé10 : c’est comme un stagiaire qui n’apprend pas. D’ailleurs, il est aussi flemmard. Pour la traduction en Français, il a fallu insister de nombreuses fois pour traduire l’intégralité de l’article. 🦆


  1. De nombreuses tentatives ont été faites pour rendre le composant BMP à la fois performant et non bloquant. Voir par exemple PR #254, PR #255 et PR #278. Malgré ces efforts, ce composant restait problématique pour la plupart des utilisateurs. Voir le ticket #1461 comme exemple. ↩︎

  2. Certaines fonctionnalités ont été déplacées vers ClickHouse pour éviter le coût de traitement dans l’inlet. Voir par exemple PR #1059↩︎

  3. C’est le plus gros changement :

    $ git show --shortstat ac68c5970e2c | tail -1
    231 files changed, 6474 insertions(+), 3877 deletions(-)
    
    ↩︎
  4. Broadcom est connu pour ses actions hostiles aux utilisateurs. Regardez ce qui s’est passé avec VMWare. ↩︎

  5. En tant que développeur Debian, je n’aime pas ces mécanismes qui contournent le gestionnaire de paquets de la distribution. J’ai finalement changé d’avis quand Go 1.25 a passé un mois dans la file NEW de Debian, un mécanisme arbitraire que je n’aime pas du tout. ↩︎

  6. Aux débuts de Go, l’utilisation des canaux était fortement encouragée. Sarama a été conçu pendant cette période. Quelques années plus tard, une approche plus nuancée a émergé. Voir notamment « Go channels are bad and you should feel bad ». ↩︎

  7. Cela devrait être étudié davantage, mais ma théorie est que le paquet intern utilise des entiers 32 bits, tandis que unique utilise des pointeurs 64 bits. Voir le commit 74e5ac↩︎

  8. C’est aussi possible avec npm. Voir le commit dab2f7↩︎

  9. Une alternative encore plus rapide est Bun, mais elle est moins disponible. ↩︎

  10. Les exceptions sont une partie du code pour les blocs d’avertissement, le code pour replier la table des matières et une partie de la documentation. ↩︎