Kafka

Rôle

Bus de messages central du pipeline de logs SOC — voir Vue d'ensemble SOC pour le flux complet et le schéma d'ensemble. Deux catégories de topics coexistent :

  • 5 topics historiques (logs-cloud, logs-firewall, logs-identity, logs-infra, logs-windows) alimentés par une source externe au périmètre IaC (non documentée) — le pont Logstash vers Elasticsearch/syslog-ng pour ces 5 topics n'est, au moment de la rédaction, toujours pas construit.
  • logs-audit (2026-07-17) : seul topic dont le pont Logstash → ELK est fonctionnel de bout en bout — voir section dédiée ci-dessous et ELK.

Le cluster réel n'a jamais été déployé par ce projet IaC

Ce dépôt (/root/kafka, Terraform + Ansible) décrit un cluster Kafka théorique, jamais appliqué contre les nœuds réels. Le cluster effectivement en production (INDSOC002/003/004) a été construit entièrement à la main par l'exploitant, diverge du code sur plusieurs points structurels (version, chemins, service) et contient des données réelles de production. Voir « Architecture » ci-dessous pour le détail complet de ces divergences, et « Procédure de déploiement » pour l'avertissement explicite à ne jamais exécuter ce playbook contre les hôtes réels.

Architecture

Projet IaC local vs cluster réel — la distinction centrale de cette page

flowchart TD
    subgraph reel["Cluster réel -- EN PRODUCTION, construit manuellement"]
        direction LR
        K1["kafka-node01<br/>INDSOC002 . 10.100.20.131"]
        K2["kafka-node02<br/>INDSOC003 . 10.100.20.132"]
        K3["kafka-node03<br/>INDSOC004 . 10.100.20.133"]
    end

    LocalRole["Projet /root/kafka<br/>Terraform + Ansible<br/>NE JAMAIS EXECUTER contre le cluster reel"]
    LocalRole -.->|"decrit un cluster distinct,<br/>jamais applique ici"| reel

    Fleet["Flotte Indio<br/>rsyslog omkafka"] -->|"topic logs-audit"| reel
    ExtSrc["Source externe<br/>non documentee"] -->|"5 topics historiques"| reel

    reel -->|"topic logs-audit"| LSA["Logstash audit-kafka<br/>INDSOC014-015"]
    LSA -->|"data stream logs-audit"| ES[("Elasticsearch soc-indio")]

    reel -.->|"5 topics, pont non construit"| Gap["pont Logstash prevu<br/>NON CONSTRUIT"]
Projet local /root/kafka (Terraform + Ansible) Cluster réel en production
Origine Décrit un cluster théorique — jamais appliqué contre les nœuds réels Construit manuellement par l'exploitant (confirmé le 2026-07-16 : « ouais c'est moi qui l'ai déjà fait, garde le tel quel »)
Version Kafka Confluent Platform 8.3, jar effectif 8.3.0-ccs 4.3.1
Fichier de configuration /etc/kafka/server.properties /opt/kafka/config/server.properties
Résolution des noms de nœuds Inventaire Ansible (INDSOC002-004) /etc/hosts local sur chacun des 3 nœuds (kafka-node01/02/03) — voir « Résolution DNS » ci-dessous
JMX Non configuré par le rôle (l'override systemd du rôle ne fixe que le heap JVM et Restart=on-failure) JMX_PORT=9999 fixe (override systemd posé manuellement)
Données Aucune — jamais exécuté 5 topics historiques + logs-audit, données réelles de production (~737 000 messages mesurés sur une seule partition lors d'un audit du 2026-07-16 — total réel plus élevé)

Le reste de cette page documente les deux en parallèle : le code tel qu'il existe dans le dépôt (Provisioning/Configuration ci-dessous), et l'état réel du cluster tel que constaté en lecture seule.

Cluster réel — topologie constatée

3 VM Rocky 9.6 (tmpl-rocky96-hardened), segment seg-soc :

Nœud IP kafka_node_id
INDSOC002 10.100.20.131 1
INDSOC003 10.100.20.132 2
INDSOC004 10.100.20.133 3
  • Mode KRaft (sans Zookeeper) : les 3 nœuds combinent les rôles broker + controller (process.roles=broker,controller).
  • Réplication : 3 partitions par défaut, default.replication.factor=3, min.insync.replicas=2 — tolère la perte d'un nœud sur 3.
  • Disque de données dédié (250 Go par défaut côté Terraform, second disque vSphere monté sur /var/lib/kafka), séparé du disque OS.
  • Historique : ce cluster a été détruit puis reconstruit le 2026-07-06, avec migration vers le segment seg-soc réel et passage d'une installation tarball Apache Kafka à une installation par paquet RPM Confluent officiel — voir Historique des décommissionnements. Le cluster réellement en production aujourd'hui (4.3.1) a depuis divergé de cette version RPM 8.3 — reconstruit à la main par l'exploitant sans repasser par ce rôle Ansible (voir tableau ci-dessus).

Topic logs-audit

Reçoit les événements auditd de toute la flotte (~53 VM), relayés par audisp-syslog (plugin auditd) puis un module omkafka rsyslog dédié — en parallèle du forward générique existant vers syslog-ng, sans le remplacer (deux destinations indépendantes pour le même flux audisp-syslog, le reste du trafic syslog continue de suivre uniquement le chemin syslog-ng). Mêmes paramètres que les 5 topics historiques (3 partitions, réplication 3, min.insync.replicas=2, rétention Kafka 3 jours retention.ms=259200000).

Chaîne complète : auditdaudisp-syslog → rsyslog (omkafka, filtre $programname=='audisp-syslog') → logs-audit → Logstash (parsing, 2 nœuds INDSOC014/INDSOC015, même group_id donc partitions réparties automatiquement) → data stream Elasticsearch logs-audit (ILM 90 jours) — voir ELK pour le détail du pipeline Logstash et du parsing.

Piège SELinux : blocage silencieux du trafic sortant vers Kafka

Le domaine SELinux syslogd_t (rsyslogd) n'a par défaut aucune règle pour initier une connexion TCP sortante vers un port applicatif non répertorié dans la policy de référence (9092 tombe dans la catégorie générique unreserved_port_t). Sans correctif, le trafic omkafka échoue en boucle silencieuse (retry infini configuré côté rsyslog, donc pas de perte de données, mais rien n'atteint Kafka — un consumer lag à 0 est trompeur, il signifie juste "rien de nouveau à consommer", pas "tout va bien en amont"). Diagnostiqué via une AVC denial réelle (ausearch -m avc, name_connect refusé, permissive=0). Corrigé par un module de policy SELinux dédié (allow syslogd_t unreserved_port_t:tcp_socket name_connect;, généré via audit2allow et déployé fleet-wide via semodule -i) plutôt que le booléen nis_enabled suggéré par l'outil (sans rapport sémantique avec Kafka). Le déblocage a fait remonter d'un coup ~460 000 messages accumulés côté queue disque rsyslog depuis le déploiement initial.

Résolution DNS kafka-node01/02/03

Le broker Kafka publie advertised.listeners=PLAINTEXT://kafka-node0N:9092 (noms internes, pas les IP nues) — tout producteur externe au cluster (rsyslog omkafka fleet-wide, futur pont Logstash pour les 5 topics historiques) doit donc pouvoir résoudre ces 3 noms après la connexion bootstrap initiale. Enregistrements A créés sur le DNS AD (INDIDEN008, kafka-node01/02/03.infra.indio10.100.20.131/132/133) — une IP nue suffit pour le premier contact mais pas au-delà. Ces enregistrements DNS servent uniquement aux clients externes : les 3 nœuds entre eux continuent de se résoudre via leur /etc/hosts local (voir tableau comparatif ci-dessus), jamais via ce DNS.

Procédure manuelle

Le cluster réellement en production n'a été construit par aucun script ni playbook — ce qui suit est une reconstitution à partir de l'état système observé (mêmes constats que le tableau comparatif ci-dessus), pas une transcription d'un historique de commandes réel : personne n'a journalisé la séquence exacte suivie par l'exploitant le 2026-07-06. L'objectif est de documenter une procédure qui reproduirait l'état constaté, en distinguant ce qui est confirmé de ce qui est déduit.

  1. Provisioning des 3 VM — hors de ce dépôt Terraform (jamais appliqué, voir avertissement en tête de page) : VM INDSOC002/003/004 créées côté vCenter par l'exploitant, segment seg-soc, mêmes caractéristiques que décrites dans « Cluster réel — topologie constatée » ci-dessus.
  2. Installation du logiciel Kafka 4.3.1 (déduit, non confirmé directement — reconnexion SSH impossible depuis le nœud de contrôle au moment de la rédaction, cf. Points d'attention) : le chemin de configuration réel (/opt/kafka/config/server.properties) correspond à une extraction manuelle du tarball Apache Kafka officiel sous /opt/kafka — pas une installation par paquet RPM (le rôle Ansible local, qui installe le paquet Confluent, écrit lui dans /etc/kafka/, jamais utilisé sur ce cluster). Prérequis : un JRE compatible Kafka 4.x (Java 17 minimum requis par Apache Kafka 4.x ; le rôle Ansible local installe java-21-openjdk-headless comme référence, non confirmé identique sur le cluster réel).
  3. Génération d'un cluster.id KRaft partagé : un UUID généré une seule fois (kafka-storage.sh random-uuid) puis utilisé identique sur les 3 nœuds pour le formatage (kafka-storage.sh format --cluster-id <uuid> -c server.properties) — un cluster.id différent par nœud empêcherait le quorum KRaft de se former.
  4. server.properties par nœud : process.roles=broker,controller, node.id unique (1/2/3, voir table ci-dessus), controller.quorum.voters listant les 3 nœuds sur le port controller (9093 par convention Kafka), listeners/advertised.listeners publiant les noms internes kafka-node0N (voir « Résolution DNS » ci-dessus) plutôt que les IP nues.
  5. /etc/hosts local sur chacun des 3 nœuds, une entrée par nœud du cluster (les 3 nœuds se résolvent entre eux uniquement par ce fichier, jamais par DNS AD — voir « Résolution DNS » ci-dessus).
  6. Service systemd + override JMX : une unité de service (nom non confirmé — le rôle Ansible local utilise confluent-kafka.service, mais ce cluster n'étant pas issu de ce rôle, le nom réel pourrait différer, ex. kafka.service pour une installation tarball) et un drop-in (systemctl edit <service>) ajoutant Environment="JMX_PORT=9999" — c'est cette variable d'environnement qui explique le port JMX fixe constaté (voir tableau comparatif), absent du rôle Ansible local.
  7. Ouverture firewalld : au minimum 9092/tcp (client) et 9093/tcp (controller, trafic inter-nœuds) — par analogie avec les ports standards Kafka/KRaft et avec ce que fait le rôle Ansible local pour un cluster équivalent ; non revérifié directement sur le cluster réel.
  8. Démarrage puis vérification du quorum avant toute création de topic :
    kafka-metadata-quorum.sh --bootstrap-server kafka-node01:9092 describe --status
    
  9. Création des topics (kafka-topics.sh --create), un par flux : logs-cloud, logs-firewall, logs-identity, logs-infra, logs-windows, puis plus tard logs-audit — mêmes paramètres pour tous (3 partitions, --replication-factor 3, --config min.insync.replicas=2 ; logs-audit ajoute --config retention.ms=259200000, voir section dédiée ci-dessus).
  10. Enregistrements DNS A côté AD (kafka-node01/02/03.infra.indio10.100.20.131/132/133) — ajoutés pour permettre aux clients externes (rsyslog omkafka fleet-wide, futur pont Logstash) de résoudre les noms publiés par advertised.listeners, en plus du /etc/hosts interne au cluster (voir « Résolution DNS » ci-dessus pour le détail de cette distinction).

Degré de confiance de cette reconstitution

Les étapes 1, 3 (le fait qu'un cluster.id partagé existe), 4 (le contenu logique de server.properties), 5, 6 (l'existence d'un override JMX_PORT=9999), 9 et 10 s'appuient sur des faits directement constatés (fichiers, ports, comportement réel du cluster). Le mode d'installation exact (tarball vs autre méthode), la version de Java et le nom du service systemd sont déduits par recoupement, pas vérifiés en direct — une inspection SSH du cluster réel (lecture seule) permettrait de lever ces dernières incertitudes.

Provisioning Terraform

Fichiers : main.tf, variables.tf, versions.tf, terraform.tfvars.example.

  • vsphere_virtual_machine.kafka en for_each sur var.nodes (3 nœuds par défaut), 2 disques par VM (OS + disque data kafka_data_disk_gb, 250 Go par défaut).
  • 2 vCPU / 4096 Mo RAM par défaut, réseau seg-soc, réservation IP via NetBox.
  • Mêmes providers/conventions que les autres dépôts VM (hashicorp/vsphere, e-breuninger/netbox).

Rappel : ce Terraform n'a jamais été appliqué pour créer les VM INDSOC002-004 réelles — celles-ci ont été construites manuellement par l'exploitant, en dehors de tout provisioning Indio.

Configuration Ansible

Rôle unique kafka :

  • Installe Java (java-21-openjdk-headless, recommandation officielle Kafka 4.x), ajoute le dépôt YUM Confluent (packages.confluent.io) et installe uniquement le paquet confluent-kafka (broker seul, ~125 Mo — pas le méta-paquet confluent-community qui embarque en plus ksqlDB/Schema Registry/REST proxy, +700 Mo inutiles).
  • Confluent Platform 8.3 (bundle Apache Kafka 4.3, jar effectif 8.3.0-ccs), branche KRaft uniquement (Zookeeper totalement retiré).
  • Partitionne/monte le disque data dédié en XFS, génère server.properties (template calculant controller.quorum.voters depuis l'inventaire), formate le répertoire KRaft une seule fois (kafka-storage format, cluster.id fixe partagé par les 3 nœuds), ouvre les ports 9092 (client) et 9093 (controller) dans firewalld, démarre confluent-kafka.service.
  • Override systemd (confluent-kafka-override.conf.j2) : fixe uniquement KAFKA_HEAP_OPTS et Restart=on-failure/RestartSec=5 — ne configure pas de port JMX (contrairement au cluster réel, voir tableau comparatif ci-dessus).

Procédure de déploiement

Ne pas exécuter ce playbook contre le cluster réel

L'inventory.ini du projet porte un avertissement explicite : le cluster Kafka réellement en production a été reconstruit manuellement par l'exploitant (confirmé le 2026-07-16) et diverge fortement de ce que ce rôle déploierait — voir le tableau comparatif en haut de cette page pour le détail complet (version, chemins, JMX, données).

Un kafka-storage format ou un remplacement de server.properties détruirait ces données. Ce projet Terraform/Ansible est conservé pour référence et trace IaC, pas comme procédure à rejouer en l'état.

Procédure telle qu'écrite dans le rôle (pertinente uniquement pour un nouveau cluster ou un environnement de test isolé, pas contre le cluster réel actuel — voir avertissement ci-dessus) :

cd /root/kafka/ansible
ansible-playbook --syntax-check site.yml
ansible-playbook site.yml -e @/root/.hap_secrets.yml

Vérification (sur un cluster nouvellement formé) :

kafka-broker-api-versions --bootstrap-server 10.100.20.131:9092
kafka-metadata-quorum --bootstrap-server 10.100.20.131:9092 describe --status

Points d'attention

  • Inspection SSH en direct du cluster réel non aboutie le 2026-07-19 (tentative en lecture seule, no route to host depuis le nœud de contrôle vers 10.100.20.131) — cause non déterminée (règle DFW SOC scopée n'incluant pas SSH depuis mgmt, ou indisponibilité transitoire). La section « Procédure manuelle » ci-dessus s'appuie donc sur les faits déjà connus (mémoire opérationnelle du projet) plutôt que sur une vérification directe pour ses points marqués comme déduits — à refaire si un accès SSH redevient possible.

  • Voir l'avertissement de la section précédente : le cluster réel diverge du code IaC et contient des données de production — toute action destructive (formatage, remplacement de configuration) est à proscrire sans vérification préalable exhaustive.

  • MANUEL_INSTALLATION.md (racine du projet) décrit une procédure encore antérieure (tarball Apache Kafka 3.9.0 téléchargé depuis archive.apache.org, service kafka.service) : ne correspond ni au rôle Ansible actuel (paquet RPM Confluent, service confluent-kafka), ni à la version réellement en production (4.3.1) — trois versions distinctes cohabitent donc dans l'historique de ce composant, à ne pas confondre : procédure manuelle périmée (3.9.0), rôle Ansible actuel (8.3/RPM Confluent, jamais exécuté contre le réel), cluster réel (4.3.1, construit à la main).
  • Le rôle protège le (re)formatage KRaft par un test d'idempotence sur meta.properties, mais cela ne protège pas contre un rejeu de playbook qui pointerait le service vers un cluster.id ou une configuration incompatible avec les données déjà en place.
  • Supervision JMX incomplète : le port JMX fixe (9999) répond, mais la négociation RMI de Kafka ouvre ensuite un second port aléatoire par connexion — non ouvert côté DFW SOC, jamais résolu (plage de ports fixe ou socket factory dédiée non mises en place). Le template Zabbix « Apache Kafka by JMX » reste donc incomplet ; jugé non critique par l'exploitant le 2026-07-17, non retenu comme prioritaire. Le reste de la supervision Kafka (agent Zabbix classique, port 10050) fonctionne normalement — voir Zabbix.