docs: premiere creation de documentation

Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
GnomeZworc 2026-08-26 22:59:49 +02:00
commit cb904c4744
Signed by: nicolas.boufideline
GPG key ID: 4406BBBF8845D632
34 changed files with 1896 additions and 30 deletions

View file

@ -0,0 +1,86 @@
Invariants et pièges
====================
Contraintes découvertes en production ou en corrigeant des bugs. Les enfreindre casse quelque
chose qui fonctionne, souvent en silence.
.. note::
Cette page reprend la section « Invariants et pièges » de ``CLAUDE.md``, qui reste la
référence de développement et fait foi en cas d'écart.
Réseau
------
* **Ne pas retirer la route ``/32`` vers ``169.254.169.254``** de l'option 121, même quand elle
paraît redondante avec la route par défaut. La DNAT vers le serveur de metadata est posée dans
le netns du VPC en ``PREROUTING`` : le paquet n'y est traité en L3 que si son next-hop est
``interface_ip``. Avec un autre next-hop, la trame est commutée en L2 sans traverser
``PREROUTING``, et le provisionnement cloud-init échoue.
* **RFC 3442** : un client qui lit l'option 121 **ignore l'option 3**. Toute route par défaut
doit donc figurer dans la 121 ; l'option 3 ne sert que les clients qui n'implémentent pas la
121.
* **La route vers le CIDR du VPC garde ``interface_ip`` comme next-hop** dans tous les modes sauf
``bridge`` : sur un subnet à IP publique, le trafic interne ne doit pas sortir par la gateway
publique.
* ``169.254.169.254`` est centralisé dans ``metadata.ServiceIP`` — ne pas le réécrire en dur.
QEMU et VM
----------
* **Un seul disque ``vdX`` par VM** ; les disques additionnels passent par le SCSI (``sdX``). La
carte PCI en dépend : NIC en ``0x03``, contrôleur SCSI en ``0x1e``, virtio-blk en ``0x1f``.
* ``bus=pci.0`` est explicite sur les trois ``-device`` : un passage de la machine en **q35**
casserait le démarrage (``Bus 'pci.0' not found``).
* QEMU est lancé par ``systemd-run --scope``, **jamais** en unit transitoire : le scope est
exécuté par le processus appelant et hérite du netns posé par ``netns.Call``. Une unit
transitoire, forkée par PID 1, démarrerait dans le netns racine et ne verrait pas le tap.
* L'arrêt d'une VM ne touche **jamais** aux fichiers disque. En revanche, un ``quit`` brutal est
envoyé à l'expiration de ``dispatcher.timeout_seconds``.
Arrêt de l'agent
----------------
* Ordre imposé : serveurs HTTP → drainage des workers → fermeture de la base. L'inverser crée une
course.
* Budget d'arrêt dépassé ⇒ **la base n'est pas fermée** : fermer Badger sous un écrivain
concurrent est pire qu'un rejeu du journal au démarrage suivant.
* Jamais de ``log.Fatal`` dans une goroutine : ``os.Exit`` n'exécute aucun ``defer``.
cloud-init
----------
* ``network-config.tmpl`` cible ``eth0`` alors que les guests sont en ``ens3`` : il ne s'applique
donc à rien, et le réseau vient du DHCP. **Ne pas le « corriger » ni le supprimer** — le rendre
opérant ferait remplacer par cloud-init la configuration réseau de l'image sur toutes les VM.
* Un document fourni par l'appelant est servi **verbatim** ; un document absent retombe sur le
template ; un document explicitement vide est servi vide. Les trois cas sont distincts.
* ``metadata.password`` est un **hash**, pas un mot de passe en clair.
* ``instance-id`` vaut le nom de la VM : recréer une VM du même nom sur le même disque fait que
cloud-init la reconnaît et **n'applique pas** le user-data.
Configuration
-------------
* Le chargement se fait par **viper** : tags ``mapstructure``, jamais ``yaml``.
* Un chemin configurable se propage par les signatures de fonction, jamais par une variable ou un
setter de paquet.
Sécurité connue et acceptée
---------------------------
Ces points sont documentés parce qu'ils sont **assumés en l'état**, pas parce qu'ils sont sans
conséquence. Ils doivent être réévalués avant toute exposition élargie de l'API.
* **L'API n'a aucune authentification** et l'exemple de configuration l'expose sur
``0.0.0.0:8080``. Quiconque atteint ce port pilote le host KVM.
* ``vm/<name>/password`` est stocké tel quel (c'est un hash) et restitué par ``/db?prefix=vm/``
du serveur d'administration — contenu par ``admin.enabled: false`` et l'écoute en boucle
locale.
* ``/run/two/metadata/<vm>/vendor-data`` est en ``0644`` et contient ce hash : tout compte local
du host peut le lire.
* ``pkg/systemd.New()`` n'a **pas de timeout** : si le socket D-Bus accepte sans répondre,
l'appelant se fige. Concerne le watchdog, la création et la suppression de subnets, et le
serveur de metadata.
* Il n'y a **pas de rollback** : un échec partiel de création laisse des objets réseau orphelins
jusqu'à un ``DELETE`` explicite.

View file

@ -0,0 +1,13 @@
Architecture
============
L'organisation interne de l'agent : découpage en paquets, stockage, et contraintes à ne pas
enfreindre. Ces pages s'adressent à qui modifie le code ; ``CLAUDE.md``, à la racine du dépôt,
reste la référence de développement et fait foi en cas d'écart.
.. toctree::
:maxdepth: 1
vue-densemble
stockage
contraintes

View file

@ -0,0 +1,51 @@
Schéma des clés
===============
Toutes les valeurs stockées dans Badger sont des **chaînes plates** : une clé, une valeur, pas
de sérialisation structurée.
.. code-block:: text
vpc/<name>/state → creating | running | error | deleting | deleted
vpc/<name>/cidr → <cidr>
subnet/<name>/state → creating | running | error | deleting | deleted
subnet/<name>/vpc → <vpc-name>
subnet/<name>/mode → vxlan | bridge | public_ip
subnet/<name>/vxlan_id → <id> (mode vxlan uniquement)
subnet/<name>/cidr → <cidr>
subnet/<name>/interface_ip → <ip> (gateway, portée par br-<subnetID>)
subnet/<name>/local_iface → <bridge-name>
subnet/<name>/default_route → "true" | "false"
subnet/<name>/gateway → <ip> (optionnel)
subnet/<name>/dhcp/<ip> → <mac>
vm/<name>/state → creating | running | error | deleting | deleted
vm/<name>/subnet → <subnet-name>
vm/<name>/tap_id → <int>
vm/<name>/ip → <ip>
vm/<name>/metadata_port → <port>
vm/<name>/disk/<dev> → <path> (une clé par disque : sda, vda, …)
vm/<name>/memory → <int> (Mo)
vm/<name>/cpus → <int>
vm/<name>/uefi → "true" (absent si SeaBIOS)
vm/<name>/password → <hash> (optionnel — un hash, pas un mot de passe)
vm/<name>/sshkey → <pubkey> (optionnel)
vm/<name>/metadata/<document> → <contenu brut> (optionnel : user-data, vendor-data, …)
Règles
------
**Pas de duplication.** Une ressource ne stocke que ce qui lui est propre. Une VM garde le lien
``vm/<name>/subnet`` ; le VPC, le bridge et l'``interface_ip`` sont lus depuis le subnet, leur
source canonique.
**Les états passent par ``state``.** Toujours ``state.Set`` / ``state.Get`` : ``Set`` refuse une
valeur hors énumération, ``Get`` refuse de retourner une valeur non reconnue. ``error`` n'est
écrit que par ``Dispatcher.Dispatch``, via ``cmd.Key()``.
**Tout entier lu depuis la base peut être corrompu.** Les erreurs de conversion sont retournées,
jamais ignorées : une valeur absente ou illisible est un état d'erreur réel.
**Un seul ouvreur.** L'agent est le seul processus à ouvrir la base. Le serveur de metadata lit
des fichiers écrits par l'agent sous ``metadata.run_dir``, jamais Badger.

View file

@ -0,0 +1,90 @@
Vue d'ensemble
==============
Cycle d'une requête
-------------------
.. code-block:: text
HTTP → internal/api/agent → Dispatcher.Prepare() → Dispatcher.Dispatch() → worker.Queue → Command.Execute()
**Prepare** (synchrone, dans le handler HTTP)
valide l'état, écrit l'état initial (``creating`` / ``deleting``) en base, et retourne 202 ou
une erreur.
**Dispatch** (asynchrone)
place la commande sur un canal bufferisé ; une goroutine worker appelle ``Execute``. C'est
``Dispatch``, et lui seul, qui marque la ressource en ``error`` si ``Execute`` échoue.
**Execute**
effectue le travail réseau (netns, netif, VXLAN, veth, bridge, DHCP), puis met l'état à
``running`` / ``deleted``.
Paquets
-------
.. list-table::
:header-rows: 1
:widths: 32 68
* - Chemin
- Rôle
* - ``internal/api/agent``
- handlers HTTP de ``/vpcs``, ``/subnets`` et ``/vms``
* - ``internal/dispatcher/agent``
- interface ``Command`` (``Prepare``/``Execute``/``Key``) et commandes concrètes
* - ``internal/state``
- énumération des états, ``CanDelete``/``IsTransient``, seul point d'écriture des états
* - ``internal/migration``
- migrations idempotentes jouées au démarrage de l'agent
* - ``internal/vpc``, ``internal/subnet``
- création et suppression bas niveau (netns + netif)
* - ``internal/netns``
- network namespaces : create/enter/delete/call
* - ``internal/netif``
- netlink : bridge, veth, vxlan, tap, routes, adresses
* - ``internal/ebtables``, ``internal/iptables``
- wrappers dédiés ; ne pas appeler ces binaires ailleurs
* - ``internal/qemu``, ``internal/qmp``
- lancement de QEMU et client QMP sur socket Unix
* - ``internal/vm``
- cycle de vie d'une VM : tap, iptables, metadata, qemu
* - ``internal/dhcp``
- génération des configurations dnsmasq et entrées ip → mac
* - ``internal/metadata``
- serveur de metadata cloud-init et ses templates
* - ``internal/watchdog``
- vérification périodique en lecture seule
* - ``internal/config/agent``
- chargement par viper — tags ``mapstructure``, jamais ``yaml``
* - ``internal/prometheus/agent``
- collector des métriques ``syonad_*``
* - ``pkg/db/kv``
- wrapper Badger ; toutes les valeurs sont des chaînes plates
* - ``pkg/worker``
- pool de goroutines sur canal
* - ``pkg/systemd``
- client D-Bus systemd
* - ``pkg/logger``, ``pkg/prometheus``
- journalisation ``slog`` et serveur de métriques
Ajouter un type de ressource
----------------------------
#. ajouter les helpers KV dans ``pkg/db/kv`` si nécessaire ;
#. définir ``Create<X>`` / ``Delete<X>`` dans un nouveau paquet ``internal/<x>/`` ;
#. ajouter ``Create<X>Command`` / ``Delete<X>Command`` dans ``internal/dispatcher/agent/``, dont
``Key()`` qui retourne ``<x>/<name>`` et le contrôle ``state.CanDelete`` dans
``Delete<X>Command.Prepare`` ;
#. ajouter les handlers HTTP dans ``internal/api/agent/`` et les routes dans ``server.go``.
Stubs de plateforme
-------------------
Les fichiers ``_linux.go`` portent l'implémentation netlink/netns réelle ; les ``_other.go``
correspondants retournent une erreur « not supported on this platform ». Tous les paquets
**compilent** sur macOS, ce qui permet d'y tester la logique qui ne touche ni netlink ni netns.
Deux exceptions à connaître : les stubs de ``netns`` exécutent ``fn`` **sans changer de
namespace** — ``netns.Call`` réussit donc hors Linux — et ``netif`` compile partout parce que
netlink fournit une implémentation « unspecified ».