Architecture
Architecture
Avant de lire : Structure du projet (où se trouvent les choses), IaaS et cloud native (la place et les principes du moteur) et Initiation au cloud native (les mécanismes que les figures nomment).
Cette page est la carte dont un contributeur a besoin avant de toucher au backend : ce qu’est le moteur, qui lui parle, quels processus existent en exécution, comment les crates sont en couches et s’appellent entre eux, et où vit l’état sur disque. Elle suit le modèle C4 dans l’ordre — Niveau 1 contexte système, Niveau 2 containers (exécutables et processus), Niveau 3 composants (crates), et Niveau 4 flux au niveau du code sous forme de séquences. Chaque nœud et chaque flèche d’une figure nomme, dans le texte à côté, le fichier et le symbole contre lesquels il a été vérifié. Après elle, vous pourrez dire dans quel processus s’exécute un travail donné, à quelle couche appartient un crate et quelles dépendances il peut prendre, et où sur disque vit son état.
Le document canonique, plus long, est ARCHITECTURE.md à la racine du
dépôt ; les décisions derrière la structure se trouvent dans docs/adr/, surtout
l’ADR-0040. Si un terme vous est
inconnu, lisez d’abord IaaS et cloud native et
Fondations Linux ; pour l’arborescence elle-même (à quoi sert chaque
répertoire de premier niveau), voir Structure du projet.
Deux moitiés sur cette page. Le tableau des couches, la liste des ratchets et le graphe complet des crates sont générés par
python3 scripts/dev_docs.pydepuisCargo.tomletscripts/arch_fitness.py— ne les éditez pas à la main. Le reste est un récit et est relu après chaque changement structurel.
Comment lire les figures. Chaque figure utilise les mêmes formes et couleurs, et sa légende vient en premier :
| Forme | Signification |
|---|---|
| boîte arrondie, sombre | personne ou acteur externe (opérateur, kubelet, programme local) |
| boîte, rouge | le moteur Delonix dans son ensemble (ce dépôt) |
| boîte, blanche à bordure rouge | une brique du moteur : exécutable, processus ou crate |
| boîte, grise | un système externe (noyau, systemd, registre, hyperviseur, API distante) |
| cylindre, bleu | état sur disque |
| flèche pleine | un appel ou un flux de données ; l’étiquette dit ce qui circule |
| flèche pointillée | démarre, execute ou supervise un processus |
| région encadrée | une frontière de confiance ou de processus |
Identité et frontières du moteur#
Le texte canonique est la section «Identidade e fronteira do motor» en tête de
AGENTS.md. Ce qu’est le moteur, ce qu’il laisse à un control plane, et
comment chaque principe cloud native apparaît dans le code, c’est le contexte, expliqué dans
IaaS et cloud native.
Cette section ne garde que les parties qui façonnent la structure ci-dessous :
- Les providers se trouvent derrière des ports. Le noyau Linux, Cloud Hypervisor et libvirt,
Proxmox VE et le CRI de Kubernetes sont atteints via un trait, jamais via un
if provider == …dispersé dans le code. Les ports d’aujourd’hui :VmBackend(crates/adapters/delonix-vm/src/lib.rs) et les ports de compute danscrates/contexts/delonix-compute/src/ports.rsetlaunch.rs(ImageStore,StorageProvider,DeviceResolver,RunHost,NetworkProvider,VmNetwork,WorkloadRuntime). Un backend OpenStack est conçu (ADR-0039, Proposed) mais n’a pas encore de crate. - Un seul ensemble d’opérations, plusieurs interfaces — la CLI, le CRI, l’API de gestion
locale, le MCP et une tranche de l’API Docker Engine, avec le contrat de nœud comme API unique
visée (voir ci-dessous) ; l’observabilité passe par
crates/adapters/delonix-telemetry. - Daemonless et rootless-first décident du modèle de processus — ce qui doit persister
appartient à systemd ou à un processus par workload avec un propriétaire clair (le superviseur
d’un container, le pin réseau), et le privilège est un opt-in explicite (
--privileged,vm bridge). Le Niveau 2 montre ces processus. - Ne connaît aucun consommateur. Aucune plateforme, control plane, console ou agent n’est
nommé dans
crates/,bins/,proto/ou les manifestes, et il n’y a aucune notion de locataire, compte, offre ou facturation. Le namespace que vous verrez partout est le namespace d’isolement propre au moteur, pas un locataire.
Ce ne sont pas des conventions ; scripts/arch_fitness.py impose la moitié structurelle en CI :
| Vérification | Où dans arch_fitness.py |
|---|---|
| Une dépendance allant contre la direction des couches échoue, sauf exception déclarée nommant la phase de l’ADR-0040 qui la supprime | LAYERS, ALLOWED, EXCEPTIONS, rule_failures |
Un crate de fondation ou de contexte ne peut pas prendre une dépendance de runtime/serveur/CLI (tokio, tonic, reqwest, clap, …) |
HEAVY |
| Un binaire compose une interface | rule_failures (la vérification roles) |
| Un crate doit vivre dans le répertoire de sa couche | LAYER_DIR, misplaced |
Le nom d’un consommateur n’importe où sous crates/, bins/, proto/ (commentaires compris) fait échouer |
CONSUMER_NAMES, consumer_mentions |
Les versions de dépendance ne vivent que dans le [workspace. racine |
inline_versions |
Des ratchets qui ne peuvent que baisser (listés ci-dessous) — par ex. des crates de bibliothèque qui ré-exécutent le binaire du moteur lui-même, des println! dans des bibliothèques, des écritures dans l’environnement du processus, des adapters qui importent l’Error partagé comme s’il était le leur |
les motifs de ratchet (SELF_EXEC, PRINTS, ENV_WRITES, SHARED_ERROR, …), base de référence dans scripts/ |
scripts/arch_fitness.py maintient 6 cliquets de dette (référence dans scripts/arch_baseline.json) :
self_exec_siteslibrary_printsenv_writesshared_error_importsraw_error_variant_matchescontext_spawns
python3 scripts/arch_fitness.py --list montre ce que compte chaque ratchet aujourd’hui, fichier
par fichier.
Niveau 1 — Contexte système#
Légende — boîte arrondie sombre : personne ou acteur externe · boîte rouge : le moteur Delonix · boîte grise : système externe · flèche pleine : appel ou flux de données, étiqueté.
Le moteur se trouve entre quatre types d’appelants et les systèmes d’un nœud Linux ; il n’a pas d’API propre exposée au réseau, et tout ce qu’il atteint à distance est atteint vers l’extérieur.
flowchart LR
OP("operator<br/><small>shell, scripts, CI</small>")
KL("kubelet<br/><small>Kubernetes node agent</small>")
LC("local program<br/><small>same uid on the node</small>")
AI("AI client<br/><small>one MCP session</small>")
ENG["Delonix Engine<br/><small>containers and microVMs on one Linux node</small>"]
KER["Linux kernel<br/><small>namespaces, cgroup v2, overlayfs, nftables</small>"]
SYSD["systemd<br/><small>user or system manager</small>"]
REG["OCI registries<br/><small>public or private</small>"]
HV["local hypervisors<br/><small>Cloud Hypervisor, libvirt/QEMU</small>"]
RMT["remote management APIs<br/><small>one Proxmox VE node, TrueNAS SCALE</small>"]
SSH["remote hosts<br/><small>kubeadm cluster nodes</small>"]
OBS["observability backends<br/><small>OTLP collector, Prometheus</small>"]
OP -->|"argv, exit classes"| ENG
KL -->|"CRI runtime.v1: gRPC on a unix socket"| ENG
LC -->|"HTTP+JSON on a unix socket, same uid"| ENG
AI -->|"MCP: JSON-RPC over stdio"| ENG
ENG -->|"syscalls; ip, nft, nsenter"| KER
ENG -->|"units, timers, transient scopes"| SYSD
ENG -->|"pull and push over HTTPS"| REG
ENG -->|"VMM API socket, virsh"| HV
ENG -->|"REST over HTTPS"| RMT
ENG -->|"ssh, scp"| SSH
ENG -->|"OTLP spans"| OBS
OBS -->|"scrapes /metrics"| ENG
class OP,KL,LC,AI person
class ENG engine
class KER,SYSD,REG,HV,RMT,SSH,OBS external
classDef person fill:#191513,stroke:#191513,color:#ffffff
classDef engine fill:#cc2823,stroke:#8f1b17,color:#ffffff
classDef block fill:#ffffff,stroke:#cc2823,color:#191513
classDef external fill:#e1ddda,stroke:#8a817c,color:#191513
classDef store fill:#2390c8,stroke:#17618a,color:#ffffff
Où se trouve chaque flèche dans le code :
| Flèche | Code |
|---|---|
| opérateur → moteur | bins/ (main, run) ; classes de sortie dans crates/ |
| kubelet → moteur | crates/ (serve_blocking) |
| programme local → moteur | crates/ (serve_blocking, le routeur axum) |
| client d’IA → moteur | crates/ (serve_stdio) |
| moteur → noyau | crates/ (spawn, container_init) ; crates/ (sous-processus ip, nft, nsenter) |
| moteur → systemd | scopes transitoires busctl dans crates/ ; units de démarrage dans bins/ |
| moteur → registres | crates/ (resolve_or_pull, push_to_registry) |
| moteur → hyperviseurs | crates/ (CloudHypervisorBackend, LibvirtBackend) |
| moteur → API de gestion distantes | crates/, crates/ |
| moteur → hôtes distants | bins/ (ssh, scp), utilisé par cmd/cluster.rs |
| moteur ↔ observabilité | crates/ (OTLP), routes /metrics dans delonix-mgmt et delonix-cri |
Niveau 2 — Containers : exécutables et processus#
Dans C4, un container est quelque chose qui s’exécute. Le build livre quatre exécutables (voir le compte généré dans le README du manuel) ; plusieurs autres processus apparaissent par workload ou par nœud, chacun avec un propriétaire. Les trois figures ci-dessous découpent ce tableau par préoccupation : qui entre dans le moteur, ce que coûte un container en processus, et l’infrastructure réseau rootless.
Points d’entrée#
Légende
| Forme | Signification |
|---|---|
| boîte arrondie, sombre | appelant |
| boîte, blanche à bordure rouge | exécutable du moteur |
| cylindre, bleu | état sur disque |
| flèche pleine | requête ou accès fichier, étiqueté |
| flèche pointillée | exec ou démarrage d’un processus |
| région encadrée | frontière de processus |
Quatre portes mènent au moteur, mais seul le processus delonix à usage unique crée jamais un
container : les serveurs multithreads rappellent la CLI pour cela.
flowchart LR
OP("operator")
KL("kubelet")
LC("local program")
AI("AI client")
subgraph NODE["Linux node — one user, one state root"]
CLI["delonix<br/><small>CLI, one process per command; serve docker-api in-process</small>"]
subgraph SRV["multi-threaded servers — never clone"]
CRI["delonix-cri<br/><small>CRI server, long-lived</small>"]
MGMT["delonix-mgmt<br/><small>management API, long-lived</small>"]
MCP["delonix-mcp<br/><small>MCP server, one per session</small>"]
end
ST[("state root<br/><small>DELONIX_ROOT</small>")]
end
OP -->|"argv"| CLI
KL -->|"gRPC, SO_PEERCRED"| CRI
LC -->|"HTTP+JSON, SO_PEERCRED"| MGMT
AI -->|"JSON-RPC over stdio"| MCP
CLI -.->|"exec: serve cri, serve api, mcp"| SRV
SRV -.->|"spawn: delonix __apirun, stop, rm, net netns attach"| CLI
CLI -->|"records under flock"| ST
SRV -->|"reads records"| ST
class OP,KL,LC,AI person
class CLI,CRI,MGMT,MCP block
class ST store
classDef person fill:#191513,stroke:#191513,color:#ffffff
classDef engine fill:#cc2823,stroke:#8f1b17,color:#ffffff
classDef block fill:#ffffff,stroke:#cc2823,color:#191513
classDef external fill:#e1ddda,stroke:#8a817c,color:#191513
classDef store fill:#2390c8,stroke:#17618a,color:#ffffff
Le exec est cmd/serve.rs::exec_server (et cmd/mcp.rs) ; le rappel est l’assistant
delonix() du CRI et write_run_spec dans
crates/interfaces/delonix-cri/src/runtime_svc/lifecycle.rs, run_cli dans delonix-mgmt et
run_cli_blocking dans delonix-mcp, tous résolvant la CLI via
delonix_node::dispatch::cli_bin. Le CRI écrit aussi ses propres enregistrements sous cri/ —
voir État sur disque.
Un container détaché#
Légende
| Forme | Signification |
|---|---|
| boîte, blanche à bordure rouge | processus du moteur |
| boîte, grise | système externe |
| cylindre, bleu | fichier sur disque |
| flèche pleine | flux de données ou écriture, étiqueté |
| flèche pointillée | fork, clone ou spawn |
| région encadrée | vit aussi longtemps que le workload |
Un run -d laisse derrière lui exactement trois ou quatre processus, et le superviseur — pas un
daemon — est le parent du container.
flowchart LR
CLI["delonix<br/><small>container run -d, start</small>"]
subgraph WL["per container — lives as long as the workload"]
SUP["supervisor<br/><small>real parent, restart policy</small>"]
INIT["container init<br/><small>namespaces, then execvp the workload</small>"]
SHIM["log shim<br/><small>copies the output pipe</small>"]
SLIRP["slirp4netns<br/><small>only for -p without a custom network</small>"]
end
KER["Linux kernel<br/><small>id maps, cgroup v2 leaf</small>"]
HOST["host network<br/><small>published host ports</small>"]
REC[("container record<br/><small>containers/id.json</small>")]
LOG[("container log file")]
CLI -.->|"fork: launch::start → run_supervised"| SUP
SUP -->|"handshake pipe: first start ok, or the reason"| CLI
SUP -.->|"clone, then the go byte"| INIT
SUP -.->|"fork inside spawn"| SHIM
SUP -.->|"on_started hook: slirp_attach"| SLIRP
SUP -->|"uid/gid maps, cgroup limits"| KER
SUP -->|"save Running after the mounted byte; exit status"| REC
INIT -->|"stdout and stderr"| SHIM
SHIM -->|"appends lines"| LOG
SLIRP -->|"host forwards"| HOST
class CLI,SUP,INIT,SHIM,SLIRP block
class KER,HOST external
class REC,LOG store
classDef person fill:#191513,stroke:#191513,color:#ffffff
classDef engine fill:#cc2823,stroke:#8f1b17,color:#ffffff
classDef block fill:#ffffff,stroke:#cc2823,color:#191513
classDef external fill:#e1ddda,stroke:#8a817c,color:#191513
classDef store fill:#2390c8,stroke:#17618a,color:#ffffff
Le superviseur est crates/adapters/delonix-linux/src/supervise.rs::run_supervised, choisi par
delonix_compute::launch::start via HostWorkload::supervise
(crates/adapters/delonix-linux/src/workload.rs) ; à l’intérieur, create_with → spawn fait le
clone, le write_userns_maps, le cgroup, le hook on_started (rempli avec
delonix_sdn::slirp_attach par cmd/container.rs::with_host_workload) et fait un fork de
log_shim, le tout dans crates/adapters/delonix-linux/src/lib.rs. L’enregistrement est écrit
via delonix_state::Store. Un run au premier plan fait la même chose sans le superviseur.
Infrastructure réseau rootless#
Légende
| Forme | Signification |
|---|---|
| boîte, blanche à bordure rouge | processus du moteur |
| boîte, grise | système externe |
| cylindre, bleu | état sur disque |
| flèche pleine | requête ou trafic, étiqueté |
| flèche pointillée | spawn (la CLI démarre le processus) |
| région encadrée | les namespaces user, network et mount du pin |
Tout ce dont le réseau rootless a besoin vit dans un même ensemble de namespaces détenu par un processus qui ne fait que dormir ; le reste peut mourir et être redémarré autour de lui.
flowchart LR
CLI["delonix<br/><small>ensure_up, attach, publish</small>"]
subgraph NS["network holder — user + net + mount namespaces"]
PIN["pin<br/><small>delonix netns pin: holds the namespaces</small>"]
CTL["control<br/><small>control socket, DNS, DHCP, RA</small>"]
PROXY["L7 proxy<br/><small>delonix ingress-proxy</small>"]
CH["cloud-hypervisor<br/><small>one VMM per VM</small>"]
WLN["workloads on custom networks<br/><small>veth on a bridge</small>"]
end
SLIRP["slirp4netns<br/><small>single host uplink, tap0</small>"]
HOST["host network"]
LV["libvirt / QEMU<br/><small>domain in the host netns</small>"]
ING[("ingress/<br/><small>pidfiles, network and route definitions</small>")]
CLI -.->|"spawn: start_pin"| PIN
CLI -.->|"spawn via nsenter: start_control"| CTL
CLI -.->|"spawn: start_slirp"| SLIRP
CLI -->|"control socket: attach, publish, firewall"| CTL
CLI -->|"API socket: add_hostfwd"| SLIRP
CLI -.->|"spawn via infra_join_argv; SIGHUP reloads routes"| PROXY
CLI -.->|"launch_vmm through the join argv"| CH
CTL -->|"veth, nftables, leases, names"| WLN
SLIRP -->|"NAT uplink, host forwards"| HOST
CLI -->|"virsh"| LV
CLI -->|"pidfiles, definitions"| ING
class CLI,PIN,CTL,PROXY,CH,WLN,SLIRP block
class HOST,LV external
class ING store
classDef person fill:#191513,stroke:#191513,color:#ffffff
classDef engine fill:#cc2823,stroke:#8f1b17,color:#ffffff
classDef block fill:#ffffff,stroke:#cc2823,color:#191513
classDef external fill:#e1ddda,stroke:#8a817c,color:#191513
classDef store fill:#2390c8,stroke:#17618a,color:#ffffff
Le pin, le control et l’uplink sont start_pin/pin_main, start_control/control_main et
start_slirp dans crates/adapters/delonix-sdn/src/infra.rs ; les namespaces du pin sont créés
dans pin_userns.rs. Le proxy est cmd/ingress_proxy.rs::spawn_proxy ; le lancement du VMM est
delonix_vm::launch_vmm, auquel le port VmNetwork fournit l’argv de jonction. Un container
rejoint un réseau personnalisé en se ré-exécutant lui-même à l’intérieur des namespaces
(reexec_into_netns, voir la séquence de run au Niveau 4 ci-dessous). Une VM libvirt vit en
dehors du holder, sur virbr0 dans le network namespace de l’hôte.
Table des processus#
| Processus | Naît dans | Vit pour |
|---|---|---|
delonix |
bins/ (main, run) |
une commande. main intercepte les points d’entrée cachés (netns pin, netns control, netns run, __rmtree, __volsnap, __ovlmigrate, __ovlhold, __duusage, __buildtar, __apirun, __netnsconnect) avant que clap ne fasse le parsing |
delonix-cri |
crates/ → delonix_cri:: |
un service (typiquement une unit systemd). delonix serve cri en fait exec (cmd/) |
delonix-mgmt |
bins/ → delonix_mgmt:: |
un service ; delonix serve api en fait exec |
delonix-mcp |
bins/ → delonix_mcp:: |
une session de client d’IA (un processus enfant sur stdio) ; delonix mcp en fait exec |
| Tranche de l’API Docker | cmd/serve.rs → cmd::dockerapi::run, à l’intérieur du processus delonix |
tant que delonix serve docker-api s’exécute |
| superviseur | delonix_linux::, choisi par delonix_compute:: pour tout démarrage détaché que l’appelant peut fork |
la vie du container ; c’est le vrai parent, il collecte donc le statut de sortie et applique le --restart |
| init du container | delonix_linux::spawn → clone → container_init |
le container |
| shim de logs | fork à l’intérieur de spawn, exécutant log_shim |
le container |
slirp4netns par container |
delonix_sdn::, appelé comme le hook on_started |
la netns du container ; les orphelins sont collectés par reap_orphan_slirp |
| pin | infra::start_pin démarre delonix netns pin ; infra::pin_main crée les namespaces user, net et mount dans le processus (crates/) et dort |
l’infra ; son pid est ingress/holder.pid et ne change jamais |
| control | infra::start_control (nsenter -t <pin> -U -m -n -- delonix netns control) → infra::control_main |
redémarrable ; sert le socket de contrôle, le DNS (dns_server_main), les Router Advertisements (ra_sender_main) et le DHCP par bridge (dhcp_serve) |
slirp4netns unique |
infra::start_slirp (tap0 dans la netns du pin, --api-socket) |
l’infra |
| proxy d’ingress L7 | cmd/ via infra::infra_join_argv |
tant qu’un HTTPRoute/Ingress ou une route --expose existe ; recharge les routes sur SIGHUP |
cloud-hypervisor |
delonix_vm::launch_vmm, exécuté via l’argv de jonction de l’infra |
la VM |
| domaine libvirt | LibvirtBackend pilotant virsh |
la VM (le domaine vit dans libvirt) |
ensure_up (crates/adapters/delonix-sdn/src/infra.rs) est la seule fonction qui remonte l’infra
réseau, sous un verrou de fichier par racine, et elle distingue trois cas : pin et control
vivants (rien à faire) ; pin vivant et control disparu (redémarre uniquement le control
plane — aucun câblage ne bouge) ; pin disparu (démonte et reconstruit).
Un seul ensemble d’opérations, plusieurs interfaces#
| Interface | Transport | Entrée | Statut |
|---|---|---|---|
| CLI | argv | bins/ |
la surface complète |
CRI (runtime.v1) |
gRPC sur un socket unix, 0600 + SO_PEERCRED |
delonix_cri:: |
sert le kubelet |
| API de gestion | HTTP+JSON sur un socket unix, même uid seulement | delonix_mgmt:: (routes telles que /v1/containers, /v1/volumes, /metrics) |
locale uniquement (ADR-0010 a rejeté une API distante) ; destinée à être remplacée par le contrat de nœud |
| MCP | stdio | delonix_mcp:: |
locale, aucun locataire (ADR-0025) |
| Tranche de l’API Docker Engine | HTTP sur un socket unix | cmd::dockerapi::run |
une tranche de compatibilité, à l’intérieur de delonix |
Contrat de nœud delonix.node.v1 |
gRPC et HTTP/JSON sur un seul socket unix | proto/delonix/node/v1/ |
contrat seulement — pas encore de serveur |
Le contrat de nœud est l’API unique visée
(ADR-0040 D4,
ADR-0042). Les fichiers .proto sont la source
de vérité ; docs/api/openapi.yaml en est généré et jamais édité à la main.
scripts/contract_gate.py échoue sur : buf format, buf lint, buf breaking contre la
dernière tag portant proto/, un RPC sans mappage HTTP (ou un stream bidirectionnel en ayant
un), un document OpenAPI différent du généré, et deux chemins qui sont la même URL sous des noms
de variable différents. Trois règles qu’il protège : un message de requête par RPC, une identité
explicite (namespace/name) dans la requête, et des images adressées par paramètre de requête.
Niveau 3 — Composants : crates par couche#
Les couches et la direction autorisée#
Légende — boîte blanche à bordure rouge : une couche de crates · flèche pleine : peut dépendre de, avec l’usage de la dépendance.
Le D1 de l’ADR-0040 fixe une direction de dépendance : les contexts sont dépendus, jamais l’inverse, et les binaires sont le seul endroit où tout se rencontre.
flowchart TB BIN["Binaries<br/><small>bins/ — composition roots</small>"] IF["Interfaces<br/><small>crates/interfaces/ — CRI, management API, MCP</small>"] AD["Adapters<br/><small>crates/adapters/ — kernel, SDN, OCI, VMs, state</small>"] PR["Providers<br/><small>crates/providers/ — one remote management API each</small>"] CX["Contexts<br/><small>crates/contexts/ — use cases, ports, workload records</small>"] FD["Foundation<br/><small>crates/foundation/ — errors, plain-data records, pure rules</small>"] BIN -->|"composes one interface"| IF BIN -->|"wires adapters to ports"| AD IF -->|"calls use cases"| CX IF -->|"calls directly, today"| AD AD -->|"implements ports"| CX PR -->|"implements ports"| CX CX -->|"names records and errors"| FD AD -->|"names records and errors"| FD class BIN,IF,AD,PR,CX,FD block classDef person fill:#191513,stroke:#191513,color:#ffffff classDef engine fill:#cc2823,stroke:#8f1b17,color:#ffffff classDef block fill:#ffffff,stroke:#cc2823,color:#191513 classDef external fill:#e1ddda,stroke:#8a817c,color:#191513 classDef store fill:#2390c8,stroke:#17618a,color:#ffffff
- Foundation (
crates/foundation/) — types partagés, plus ou moins purs, que toute couche peut nommer. - Contexts (
crates/contexts/) — un crate par contexte borné, nommé d’après les groupes d’API publiés : les cas d’usage et les ports dont ils ont besoin. Pas de HTTP, pas de provider, et pas de montages, processus ou configuration réseau.delonix-nodeest le seul context qui lit l’hôte directement —/proc,/sys,kill(pid, 0),SO_PEERCRED— parce que ces questions sont la raison même de son existence, pour y répondre une fois pour toutes. - Adapters (
crates/adapters/) et providers (crates/providers/) — implémentent des ports : noyau, SDN, store OCI, backends de VM, état persisté ; les providers apportent un client HTTP pour une cible distante. - Interfaces (
crates/interfaces/) — CRI, API de gestion, MCP : analysent une requête, appellent le moteur, présentent. - Binaries (
bins/) — racines de composition.
La couche à laquelle appartient chaque crate, et la direction dans laquelle il peut dépendre :
| Couche | Peut dépendre de |
|---|---|
| Foundation | foundation |
| Contexts | foundation, contexts |
| Adapters | foundation, contexts |
| Providers | foundation, contexts |
| Interfaces | foundation, contexts, adapters, providers |
| Binaries | foundation, contexts, adapters, providers, interfaces |
Exceptions déclarées (chacune nomme la phase de l'ADR-0040 qui la supprime) :
delonix-linux→delonix-state— supprimée en P4adelonix-mcp→delonix-mgmt— supprimée en P5delonix-oci→delonix-state— supprimée en P4delonix-scanner→delonix-oci— supprimée en P4delonix-sdn→delonix-state— supprimée en P4delonix-vm→delonix-provider-cloud-hypervisor— supprimée en P5delonix-vm→delonix-provider-libvirt— supprimée en P5delonix-vm→delonix-state— supprimée en P5delonix-volume→delonix-state— supprimée en P4
Où en est la restructuration#
L’ADR-0040 est un plan strangler en phases (P0 rails → P1 contrat → P2 contexts → P3 adapters et binaires → P4 providers → P5 API de nœud → P6 CRI → P7 observabilité). Ce que le code montre aujourd’hui :
- La P0 est terminée. Chaque crate vit dans le répertoire de sa couche, les versions sont au niveau du workspace, et le gate de fitness s’exécute en CI.
- La P1 est terminée en tant que contrat, pas en tant que serveur.
proto/delonix/node/v1/*.protoexiste, le document OpenAPIdocs/api/openapi.yamlen est généré, etscripts/contract_gate.pyprotège les deux. Rien ne sert encore le contrat — aucun crate ne référencedelonix.node.v1(l’ADR-0042 D1 dit la même chose). - La P2 a commencé.
delonix-model(l’Errorpartagé et ses codesDX_*, les noms générés, les classes de sortie, le dictionnaire de codes numérotés, le modèle de secrets, et — depuis la #405 — les enregistrements uniquement-donnéesStatus,ContainerFw/FwRuleavec leurs validateurs,default_namespaceet letypestatedu cycle de vie),delonix-stack(table de Kinds, réconciliateur à 3 voies, révisions) etdelonix-compute(la spécification d’exécution uniqueRunOpts,resolve_run,build_record, les cas d’usage réseau et de lancement) existent. La majeure partie de la logique applicative vit encore dansbins/delonix-runtime-bin/src/cmd/. - La P3 est en cours. Les ports de compute sont implémentés dans des adapters
(
HostImages,HostVolumes,HostDevices,HostRuntime,HostNetwork,HostWorkload,HostVmNetwork), la télémétrie a quitté la fondation pourdelonix-telemetry,delonix-vmn’atteint le SDN que via le portVmNetwork, et les serveurs CRI, API de gestion et MCP sont devenus leurs propres exécutables. Quatre adapters portent déjà leurs noms ADR-0040 :delonix-scanner(étaitdelonix-scan),delonix-oci(étaitdelonix-image),delonix-sdn(étaitdelonix-net) etdelonix-linux(étaitdelonix-runtime, le crate du moteur de containers). La #406 a supprimédelonix-runtime-core, le crate de fondation qui contenait auparavant tout ce qui était partagé, en étapes : la #404 a déplacé les stores, les écritures atomiques et le store de secrets chiffré vers l’adapterdelonix-state; la #405 a déplacé les enregistrements uniquement-données (Status,ContainerFw/FwRule,typestate) vers le bas, versdelonix-model; et la #406 a déplacé les enregistrementsContaineretVm(avecMount, les types de santé et de parent-de-cgroup,DELONIX_SLICEetworkload_net) versdelonix-compute, et le journal d’événements,virt,peer_cred,dispatchet les assistants d’hôte/processus (now_unix,is_alive,safe_to_signal,generate_id, …) vers un nouveau context,delonix-node. Aucun ré-export n’a été laissé derrière. Les adapters qui ouvrent des enregistrements ou écrivent des fichiers viadelonix-state(delonix-linux,delonix-vm,delonix-sdn,delonix-oci,delonix-volume) sont des exceptions déclarées jusqu’à ce que la P4 leur donne un portStateRepository(scripts/arch_fitness.py). - La P4 est en cours ; les P5–P7 n’ont pas commencé. L’ADR-0044 (accepté le 2026-09-24)
décide comment la P4 se fait. Le #420 a apporté le port
StateRepository<T>(crates/foundation/delonix-model/src/ports.rs), quedelonix-linuxutilise déjà pourwait_and_record/stop/persist_stop/remove— d’où son exceptionP4adansscripts/arch_fitness.py, qui ne liste que les sites encore ouverts. Le #486 a ajouté le port de provider de VM (VmSpec,Extensions,Provider,VmProviderdanscrates/contexts/delonix-compute/src/vm_provider.rs, P4b tranche 1), etdelonix-vml’implémente pour les deux backends locaux (LocalVmProvider,crates/adapters/delonix-vm/src/provider.rs) en réutilisant soncreate_with/stop/startexistant ; déplacer chaque backend dans son propre crate de provider est la P4b tranche 2. Les exceptions restantes dans le tableau ci-dessus nomment la phase qui supprime chacune.
Enregistrements, assistants de nœud et état persisté, après la #406#
Légende — boîte blanche à bordure rouge : crate du moteur (ou groupe de crates) · cylindre, bleu : fichiers sous la racine d’état · flèche pleine : utilise, avec ce qui est utilisé.
Les types uniquement-données vivent dans la fondation, les enregistrements de workload dans le context Compute, les propres assistants du nœud dans le context Node, et les fichiers qui contiennent les enregistrements dans un seul adapter à travers lequel tout autre adapter atteint ces fichiers.
flowchart TB
CX["other contexts<br/><small>delonix-stack, -security-runtime</small>"]
AD["other adapters<br/><small>delonix-linux, -oci, -sdn, -vm, -volume</small>"]
STATE["delonix-state<br/><small>adapter: Store, JsonStore, write_atomic, SecretStore, CredVault</small>"]
COMPUTE["delonix-compute<br/><small>context: Container, Vm, Mount, DELONIX_SLICE, workload_net</small>"]
NODE["delonix-node<br/><small>context: events, dispatch, peer_cred, virt, safe_to_signal</small>"]
MODEL["delonix-model<br/><small>Error and DX codes, exit classes, secret model, Status, FwRule, typestate</small>"]
NR["delonix-net-rules<br/><small>Cidr, bridge_name — zero dependencies</small>"]
FILES[("state root files<br/><small>containers/, vms/, secrets/, tunnels/</small>")]
CX -->|"events, now_unix"| NODE
CX -->|"Error, Result"| MODEL
AD -->|"Store, JsonStore, write_atomic — declared exceptions until P4"| STATE
AD -->|"Container, Vm, ports, workload_net"| COMPUTE
AD -->|"pid checks, events, in_initial_userns"| NODE
AD -->|"Cidr, bridge_name"| NR
STATE -->|"stores Container"| COMPUTE
STATE -->|"errors convert into Error; re-exports the secret model"| MODEL
COMPUTE -->|"safe_to_signal"| NODE
COMPUTE -->|"Status, ContainerFw, parse_env_file"| MODEL
STATE -->|"flock, temp file + rename"| FILES
class CX,AD,STATE,COMPUTE,NODE,MODEL,NR block
class FILES store
classDef person fill:#191513,stroke:#191513,color:#ffffff
classDef engine fill:#cc2823,stroke:#8f1b17,color:#ffffff
classDef block fill:#ffffff,stroke:#cc2823,color:#191513
classDef external fill:#e1ddda,stroke:#8a817c,color:#191513
classDef store fill:#2390c8,stroke:#17618a,color:#ffffff
Vérifié contre : crates/contexts/delonix-compute/src/record.rs (use
delonix_model::records::{…}, use delonix_node::safe_to_signal) et src/lib.rs (pub use
record::*) ; crates/contexts/delonix-node/src/lib.rs et host.rs ;
crates/foundation/delonix-model/src/records.rs et typestate.rs ;
crates/adapters/delonix-state/src/store.rs (use delonix_compute::Container), secret.rs,
cred_vault.rs, error.rs. delonix-net-rules n’est utilisé que par delonix-sdn et
delonix-vm ; delonix-volume et delonix-scanner nomment aussi delonix-model directement (le
graphe généré ci-dessous a chaque arête).
Ports de Compute et les adapters derrière eux#
Légende — boîte blanche à bordure rouge : composant du moteur (cas d’usage, adapter, binaire) · région encadrée : le crate de context · flèche pleine : un appel via le port nommé.
container run est le chemin de référence : le context décide via des ports, et le binaire
choisit quel adapter répond à chaque port.
flowchart LR
CMD["delonix binary<br/><small>cmd_run and run(): composition root</small>"]
subgraph CX["delonix-compute — context"]
UC["use cases<br/><small>resolve_run, build_record, wire_network, launch::start</small>"]
end
HI["HostImages<br/><small>delonix-oci</small>"]
HV["HostVolumes<br/><small>delonix-volume</small>"]
HD["HostDevices, HostRuntime<br/><small>delonix-linux</small>"]
HW["HostWorkload<br/><small>delonix-linux</small>"]
HN["HostNetwork<br/><small>delonix-sdn</small>"]
VM["delonix-vm<br/><small>VmBackend registry</small>"]
HVN["HostVmNetwork<br/><small>delonix-sdn</small>"]
CMD -->|"calls with the adapters"| UC
UC -->|"ImageStore"| HI
UC -->|"StorageProvider"| HV
UC -->|"DeviceResolver, RunHost"| HD
UC -->|"NetworkProvider"| HN
UC -->|"WorkloadRuntime"| HW
CMD -->|"set_network, register_backend"| VM
VM -->|"VmNetwork"| HVN
class CMD,UC,HI,HV,HD,HW,HN,VM,HVN block
classDef person fill:#191513,stroke:#191513,color:#ffffff
classDef engine fill:#cc2823,stroke:#8f1b17,color:#ffffff
classDef block fill:#ffffff,stroke:#cc2823,color:#191513
classDef external fill:#e1ddda,stroke:#8a817c,color:#191513
classDef store fill:#2390c8,stroke:#17618a,color:#ffffff
Ports : crates/contexts/delonix-compute/src/ports.rs (ImageStore, StorageProvider,
DeviceResolver, RunHost, VmNetwork, NetworkProvider) et launch.rs (WorkloadRuntime).
Implémentations : delonix-oci/src/run_images.rs, delonix-volume/src/lib.rs,
delonix-linux/src/{cdi,run_host,workload}.rs, delonix-sdn/src/{run_network,vm_network}.rs.
Câblage : bins/delonix-runtime-bin/src/cmd/container.rs::cmd_run et
bins/delonix-runtime-bin/src/main.rs::run.
Interfaces et binaires#
Légende — boîte blanche à bordure rouge : crate du moteur (ou groupe de crates) · flèche pleine : un appel Rust direct, avec son usage.
Les serveurs lisent dans leur propre processus et confient chaque fork à la CLI ; la seule arête interface-vers-interface est une exception déclarée.
flowchart TB RB["delonix-runtime-bin<br/><small>executable delonix</small>"] MB["delonix-mgmt-bin<br/><small>executable delonix-mgmt</small>"] PB["delonix-mcp-bin<br/><small>executable delonix-mcp</small>"] CRI["delonix-cri<br/><small>crate and executable delonix-cri</small>"] MG["delonix-mgmt<br/><small>HTTP router, dashstats</small>"] MC["delonix-mcp<br/><small>MCP tools, audit log</small>"] CX["contexts<br/><small>compute, stack, security-runtime</small>"] AD["adapters and providers<br/><small>linux, oci, sdn, vm, volume, scanner, proxmox, truenas</small>"] ST["delonix-state<br/><small>Store, SecretStore</small>"] MB -->|"serve_blocking"| MG PB -->|"serve_stdio"| MC RB -->|"dashstats::collect for dashboard"| MG MC -->|"dashstats — declared exception until P5"| MG RB -->|"use cases, Kind table, policy"| CX RB -->|"wires and calls adapters"| AD CRI -->|"RunOpts"| CX CRI -->|"image pull, reconcile_status, CNI attach"| AD MG -->|"reads volumes, images, networks, VMs"| AD MC -->|"reads VMs, volumes, networks"| AD CRI -->|"container records"| ST MG -->|"container records, secret count"| ST class RB,MB,PB,CRI,MG,MC,CX,AD,ST block classDef person fill:#191513,stroke:#191513,color:#ffffff classDef engine fill:#cc2823,stroke:#8f1b17,color:#ffffff classDef block fill:#ffffff,stroke:#cc2823,color:#191513 classDef external fill:#e1ddda,stroke:#8a817c,color:#191513 classDef store fill:#2390c8,stroke:#17618a,color:#ffffff
Non dessiné, pour garder la figure lisible : chaque binaire et delonix-cri/delonix-mgmt
appellent aussi delonix-telemetry (telemetry::init, métriques), et delonix-mcp et la CLI
lisent aussi delonix-state. Les arêtes du tableau de bord sont
bins/delonix-runtime-bin/src/cmd/dash.rs et crates/interfaces/delonix-mcp/src/lib.rs
(delonix_mgmt::dashstats::collect) ; l’usage de RunOpts par le CRI est start_run_opts dans
runtime_svc/lifecycle.rs.
Chaque arête de crate#
Le graphe des crates, tel que Cargo.toml le déclare. Il est complet et donc dense ; lisez-le
pour répondre « est-ce que A dépend de B », et lisez les figures par couche ci-dessus pour
comprendre pourquoi.
Légende — une boîte par crate, regroupées par couche ; une flèche A --> B signifie A dépend de B. Rouge : binaires · blanc à bordure rouge : interfaces · blanc : contextes et adaptateurs · gris : providers · bleu : fondation.
flowchart TB
subgraph foundation["Foundation"]
delonix_model["delonix-model"]
delonix_net_rules["delonix-net-rules"]
end
subgraph context["Contexts"]
delonix_compute["delonix-compute"]
delonix_networking["delonix-networking"]
delonix_node["delonix-node"]
delonix_security_runtime["delonix-security-runtime"]
delonix_stack["delonix-stack"]
end
subgraph adapter["Adapters"]
delonix_linux["delonix-linux"]
delonix_oci["delonix-oci"]
delonix_scanner["delonix-scanner"]
delonix_sdn["delonix-sdn"]
delonix_state["delonix-state"]
delonix_telemetry["delonix-telemetry"]
delonix_vm["delonix-vm"]
delonix_volume["delonix-volume"]
end
subgraph provider["Providers"]
delonix_opnsense["delonix-opnsense"]
delonix_provider_cloud_hypervisor["delonix-provider-cloud-hypervisor"]
delonix_provider_libvirt["delonix-provider-libvirt"]
delonix_proxmox["delonix-proxmox"]
delonix_truenas["delonix-truenas"]
end
subgraph interface["Interfaces"]
delonix_cri["delonix-cri"]
delonix_mcp["delonix-mcp"]
delonix_mgmt["delonix-mgmt"]
delonix_node_api["delonix-node-api"]
end
subgraph bin["Binaries"]
delonix_mcp_bin["delonix-mcp-bin"]
delonix_mgmt_bin["delonix-mgmt-bin"]
delonix_node_api_bin["delonix-node-api-bin"]
delonix_runtime_bin["delonix-runtime-bin"]
end
delonix_compute --> delonix_model
delonix_compute --> delonix_net_rules
delonix_compute --> delonix_node
delonix_cri --> delonix_compute
delonix_cri --> delonix_linux
delonix_cri --> delonix_model
delonix_cri --> delonix_node
delonix_cri --> delonix_oci
delonix_cri --> delonix_sdn
delonix_cri --> delonix_state
delonix_cri --> delonix_telemetry
delonix_linux --> delonix_compute
delonix_linux --> delonix_model
delonix_linux --> delonix_node
delonix_linux --> delonix_state
delonix_mcp --> delonix_compute
delonix_mcp --> delonix_linux
delonix_mcp --> delonix_mgmt
delonix_mcp --> delonix_model
delonix_mcp --> delonix_node
delonix_mcp --> delonix_sdn
delonix_mcp --> delonix_state
delonix_mcp --> delonix_vm
delonix_mcp --> delonix_volume
delonix_mcp_bin --> delonix_mcp
delonix_mcp_bin --> delonix_node
delonix_mcp_bin --> delonix_telemetry
delonix_mgmt --> delonix_compute
delonix_mgmt --> delonix_linux
delonix_mgmt --> delonix_model
delonix_mgmt --> delonix_node
delonix_mgmt --> delonix_oci
delonix_mgmt --> delonix_scanner
delonix_mgmt --> delonix_sdn
delonix_mgmt --> delonix_state
delonix_mgmt --> delonix_telemetry
delonix_mgmt --> delonix_vm
delonix_mgmt --> delonix_volume
delonix_mgmt_bin --> delonix_mgmt
delonix_mgmt_bin --> delonix_node
delonix_mgmt_bin --> delonix_telemetry
delonix_networking --> delonix_compute
delonix_networking --> delonix_model
delonix_networking --> delonix_net_rules
delonix_node --> delonix_model
delonix_node_api --> delonix_compute
delonix_node_api --> delonix_linux
delonix_node_api --> delonix_model
delonix_node_api --> delonix_node
delonix_node_api --> delonix_opnsense
delonix_node_api --> delonix_proxmox
delonix_node_api --> delonix_sdn
delonix_node_api --> delonix_vm
delonix_node_api --> delonix_volume
delonix_node_api_bin --> delonix_node
delonix_node_api_bin --> delonix_node_api
delonix_node_api_bin --> delonix_telemetry
delonix_oci --> delonix_compute
delonix_oci --> delonix_model
delonix_oci --> delonix_node
delonix_oci --> delonix_state
delonix_opnsense --> delonix_compute
delonix_opnsense --> delonix_model
delonix_opnsense --> delonix_networking
delonix_provider_cloud_hypervisor --> delonix_compute
delonix_provider_cloud_hypervisor --> delonix_model
delonix_provider_cloud_hypervisor --> delonix_node
delonix_provider_libvirt --> delonix_compute
delonix_provider_libvirt --> delonix_model
delonix_provider_libvirt --> delonix_node
delonix_proxmox --> delonix_compute
delonix_proxmox --> delonix_model
delonix_proxmox --> delonix_networking
delonix_runtime_bin --> delonix_compute
delonix_runtime_bin --> delonix_linux
delonix_runtime_bin --> delonix_mgmt
delonix_runtime_bin --> delonix_model
delonix_runtime_bin --> delonix_networking
delonix_runtime_bin --> delonix_node
delonix_runtime_bin --> delonix_oci
delonix_runtime_bin --> delonix_opnsense
delonix_runtime_bin --> delonix_proxmox
delonix_runtime_bin --> delonix_scanner
delonix_runtime_bin --> delonix_sdn
delonix_runtime_bin --> delonix_security_runtime
delonix_runtime_bin --> delonix_stack
delonix_runtime_bin --> delonix_state
delonix_runtime_bin --> delonix_telemetry
delonix_runtime_bin --> delonix_truenas
delonix_runtime_bin --> delonix_vm
delonix_runtime_bin --> delonix_volume
delonix_scanner --> delonix_model
delonix_scanner --> delonix_oci
delonix_sdn --> delonix_compute
delonix_sdn --> delonix_model
delonix_sdn --> delonix_net_rules
delonix_sdn --> delonix_networking
delonix_sdn --> delonix_node
delonix_sdn --> delonix_state
delonix_security_runtime --> delonix_model
delonix_security_runtime --> delonix_node
delonix_stack --> delonix_model
delonix_state --> delonix_compute
delonix_state --> delonix_model
delonix_state --> delonix_node
delonix_truenas --> delonix_model
delonix_vm --> delonix_compute
delonix_vm --> delonix_model
delonix_vm --> delonix_node
delonix_vm --> delonix_provider_cloud_hypervisor
delonix_vm --> delonix_provider_libvirt
delonix_vm --> delonix_state
delonix_volume --> delonix_compute
delonix_volume --> delonix_model
delonix_volume --> delonix_node
delonix_volume --> delonix_state
class delonix_compute block
class delonix_cri iface
class delonix_linux block
class delonix_mcp iface
class delonix_mcp_bin engine
class delonix_mgmt iface
class delonix_mgmt_bin engine
class delonix_model store
class delonix_net_rules store
class delonix_networking block
class delonix_node block
class delonix_node_api iface
class delonix_node_api_bin engine
class delonix_oci block
class delonix_opnsense external
class delonix_provider_cloud_hypervisor external
class delonix_provider_libvirt external
class delonix_proxmox external
class delonix_runtime_bin engine
class delonix_scanner block
class delonix_sdn block
class delonix_security_runtime block
class delonix_stack block
class delonix_state block
class delonix_telemetry block
class delonix_truenas external
class delonix_vm block
class delonix_volume block
classDef engine fill:#cc2823,stroke:#8f1b17,color:#ffffff
classDef iface fill:#ffffff,stroke:#cc2823,stroke-width:2px,color:#191513
classDef block fill:#ffffff,stroke:#8a817c,color:#191513
classDef external fill:#e1ddda,stroke:#8a817c,color:#191513
classDef store fill:#2390c8,stroke:#17618a,color:#ffffff
Comment les crates communiquent#
- Appels Rust directs, dans la direction de la couche. Le cas normal. Par exemple
cmd_run(cmd/container.rs) appelledelonix_compute::run::resolve_runavec les adaptersdelonix_oci::run_images::HostImages,delonix_volume::HostVolumes,delonix_linux::cdi::HostDevicesetdelonix_linux::run_host::HostRuntime, puisdelonix_compute::network::{attach_custom_network, wire_network}avecdelonix_sdn::run_network::HostNetwork, puisdelonix_compute::launch::startavecdelonix_linux::workload::HostWorkload. - Enregistrement à la racine de composition.
run()dansbins/delonix-runtime-bin/src/main.rsenregistre les backends de VM distants configurés (cmd::vmbackends::register_configured→delonix_vm::register_backend) et l’implémentation SDN du port réseau de VM (delonix_vm::set_network(HostVmNetwork)) avant qu’aucune commande ne s’exécute. - Ré-exécuter le binaire du moteur lui-même. Encore courant, et compté par le ratchet
self_exec_sites. Les raisons sont réelles : -clonen’est sûr que dans un processus monothread, et les serveurs CRI, API de gestion et Docker API sont des runtimestokiomultithreads. Ils remettent unRunOptstypé dans un fichier0600à un nouveaudelonix __apirun <spec>(lifecycle.rs::write_run_spec,cmd::dockerapi::run_from_spec_file). - Un processus rootless doit entrer dans les namespaces user et mount du pin réseau avant qu’un container puisse rejoindre une netns nommée là-bas, doncreexec_into_netnsexécutensenter … ip netns exec <netns> delonix netns run <spec>. - Un travail sur des fichiers appartenant à des subuids mappés nécessite un processus à l’intérieur d’un user namespace mappé (delonix_linux::reexec_mapped,reexec_mapped_hold,remove_tree_mapped→ les points d’entrée__rmtree/__ovlhold/…). - Les serveurs construisent encore certaines invocations de CLI (delonix-mgmt, lerun_cli_blockingdedelonix-mcp, l’assistantdelonix()du CRI), résolvant la CLI viadelonix_node::dispatch::cli_bin(DELONIX_BIN, puis undelonixvoisin, puis lePATH) — jamais leur propre exécutable. Le D2.4/D5 de l’ADR-0040 prévoit un exécutabledelonix-launcherrecevant une spec typée, pour que ceux-ci deviennent des appels de cas d’usage plus un spawn. - Le socket de contrôle. Tout ce qui se passe à l’intérieur de la netns rootless de
l’infra est fait par le processus control :
infra::control_send/control_queryécrivent une ligne (attach …,publish …,firewall …) sur un socket unix0600;control_loopn’accepte que les pairs ayant le même uid que le moteur (SO_PEERCRED) et ne sert qu’une connexion à la fois, si bien que les opérations netns/veth/nftables ne s’entrelacent jamais. - Sous-processus vers des outils de l’hôte, dans des adapters :
ip,nft,nsenter,slirp4netns(delonix-sdn),newuidmap/newgidmap(delonix-linux,pin_userns),qemu-img,virsh,cloud-localds(delonix-vm),busctlpour les scopes transitoires systemd (delonix-linux),ssh/scp(cmd/remote.rs). - Le HTTP vers un système de gestion distant ne vit que dans les providers.
delonix-proxmoxetdelonix-truenasdépendent dereqwestpour cela. Deux adapters parlent aussi HTTP, pour d’autres raisons :delonix-ocia son propre client de registre OCI (src/registry.rs,reqwestdans sonCargo.toml), etdelonix-telemetryexporte OTLP sur HTTP. Aucun crate de context ne le fait.
État sur disque#
Il n’y a pas de base de données. L’état, ce sont des fichiers sous une racine d’état :
DELONIX_ROOTquand elle est définie ; sinon$XDG_DATA_HOME/delonixou~/.local/share/delonixpour un utilisateur non privilégié et/var/lib/delonixpour root (bins/delonix-runtime-bin/src/cmd/util.rs::state_root→ImageStore::default_root;infra::base_rootrésout la même règle côté réseau).- Les sockets ne vivent pas sous la racine d’état. Ils se trouvent dans un répertoire de
runtime court par utilisateur (
infra::runtime_dir, redéfinissable avecDELONIX_NET_RUNTIME_DIR) car les cheminsAF_UNIXont une longueur limitée ; une racine non par défaut obtient un suffixe haché (root_suffix) pour que deux racines sur une même connexion ne partagent jamais de sockets. Quand vous exécutez quoi que ce soit en isolation, définissez les deux variables.
| Chemin sous la racine | Quoi | Code |
|---|---|---|
containers/ |
un enregistrement JSON par container | delonix_state::Store (delonix-state/) |
containers/ + overlay-lowers |
la couche inscriptible du container et la liste des couches d’image partagées qu’il monte | ImageStore:: (delonix-oci/) |
images/<id>.json, layers/<hex>/, blobs/ |
métadonnées d’image, couches décompressées partagées par tous les containers, blobs adressés par contenu | ImageStore::open (image.rs), Cas (cas.rs) |
volumes/, volumes/.ns/<ns>/ |
volumes nommés, volumes limités par namespace | VolumeStore (delonix-volume/) |
vms/ |
enregistrements de VM (delonix_state::) et fichiers par VM |
delonix-vm |
vm-images/ |
images de VM (.qcow2 + .json) |
cmd/ |
secrets/ |
secrets chiffrés | SecretStore (delonix-state/) |
tunnels/keyring.key, tunnels/cred/ |
la clé maîtresse de l’hôte et les identifiants chiffrés | CredVault (delonix-state/) |
ingress/ |
pidfiles (holder.pid est le pin), marqueurs refs/, définitions de réseau et de route, logs |
delonix-sdn/ |
hosts-sync |
fichier marqueur : delonix hosts sync a été exécuté, donc les noms de service des containers --expose sont conservés dans le /etc/hosts de l’hôte (il est à la racine, pas sous ingress/) |
hosts_sync_flag dans cmd/ingress_proxy.rs |
ipam/ |
baux d’adresse par préfixe | delonix-sdn/src/ipam.rs |
cri/ |
les propres enregistrements du CRI | delonix-cri/ (sb_dir, ct_dir) |
clusters/ |
kubeconfigs, clés et PKI des clusters | cmd/cluster.rs |
events.jsonl |
journal d’événements ajout-seul | delonix_node::events |
La concurrence est gérée par le système de fichiers, car plusieurs processus (la CLI, le serveur
CRI, un superviseur) mutent les mêmes enregistrements : les écritures sont atomiques (fichier
temporaire + rename, delonix_state::write_atomic), et la lecture-modification-écriture passe
par Store::update / JsonStore::update, qui prennent un flock exclusif et refusent
d’avancer sans lui. Tout cela vit dans l’adapter delonix-state. Les types d’enregistrement
qu’il stocke sont définis ailleurs : Container et Vm dans le context delonix-compute, et les
parties uniquement-données d’un enregistrement (Status, ContainerFw/FwRule) dans le crate de
fondation delonix-model. L’infra réseau a son propre FileLock autour de ensure_up,
teardown, acquire, release et les reapers.
Comme rien de résident ne surveille les processus, un enregistrement disant Running peut être
obsolète. Les lecteurs réconcilient : delonix_linux::reconcile_status vérifie le pid avec son
heure de démarrage (delonix_node::safe_to_signal) pour qu’un pid recyclé ne soit jamais pris
pour le container.
Niveau 4 — Deux flux, en séquences#
Le Niveau 4 n’est dessiné que là où l’ordre des étapes est le point important. Les deux flux ci-dessous sont des séquences plutôt que des figures de structure.
container run -d --net web -p 8080:80 nginx, rootless#
Chaque flèche ci-dessous est un appel dans cmd_run
(bins/delonix-runtime-bin/src/cmd/container.rs) ou dans les fonctions qu’il atteint.
Légende — les participants sont des processus ; les flèches pleines sont des appels, des lignes de socket ou des spawns (l’étiquette dit lequel) ; les flèches pointillées sont des réponses ; une auto-flèche est un travail interne à ce processus ; les notes marquent ce qui reste derrière.
Un réseau personnalisé force un second passage de la CLI à l’intérieur des namespaces du pin, et l’enregistrement n’est publié qu’une fois les montages du container finaux.
sequenceDiagram
participant U as operator
participant P1 as delonix (1st pass)
participant N as delonix-sdn infra
participant C as control process
participant S as single slirp4netns
participant P2 as delonix netns run (2nd pass)
participant SV as supervisor
participant I as container init
U->>P1: container run -d --net web -p 8080:80 nginx
P1->>P1: resolve_run — HostImages.resolve (pull if absent), prepare_overlay writes overlay-lowers
P1->>P1: build_record
P1->>N: attach_custom_network → attach_container
N->>N: ipam::allocate, acquire → ensure_up (pin, control, slirp if absent)
N->>C: control socket: attach netns ip bridge gateway [namespace]
C->>C: do_attach — ip netns add, veth to the bridge, anti-spoofing rule, namespace sets
P1->>P2: reexec_into_netns — spec file 0600, nsenter -t pin -U -m -n ip netns exec
P2->>P2: run_from_spec → cmd_run (second pass reuses the prepared rootfs)
P2->>S: wire_network → publish_port — add_hostfwd 8080 via api socket
P2->>C: control socket: publish tcp 8080 ip 80 (DNAT)
P2->>SV: launch::start → HostWorkload.supervise → fork
SV->>I: spawn → clone — user and net namespaces inherited from the pin
I->>I: mount_overlay_if_marked (fsopen, one lowerdir+ per layer), volumes, pivot_root
I-->>SV: ready byte — the mount namespace is final
SV->>SV: store.save Running
SV-->>P2: first start reported
P2-->>P1: exit 0
I->>I: execvp the image command
Note over P1,I: No process stays behind except the supervisor, the init and its log shim.
Sans réseau personnalisé, le flux n’a pas de second passage : spawn crée son propre user
namespace, et le parent écrit les maps d’id (write_userns_maps), configure le cgroup, exécute
le hook on_started (le slirp_attach par container quand il y a des ports -p) et envoie
seulement ensuite l’octet « go » à l’enfant.
CRI : RunPodSandbox → CreateContainer → StartContainer#
Depuis crates/interfaces/delonix-cri/src/runtime_svc/lifecycle.rs.
Légende — les participants sont des processus, plus la racine d’état comme participant ; les flèches pleines sont des appels gRPC, des appels internes, des sous-processus ou des écritures de fichiers (l’étiquette dit lequel) ; les flèches pointillées sont des réponses ; les boîtes
altsont les modes réseau mutuellement exclusifs.
Le serveur CRI enregistre et décide, mais chaque démarrage de container traverse vers un nouveau
processus delonix.
sequenceDiagram
participant K as kubelet
participant R as delonix-cri
participant D as delonix (child process)
participant N as delonix-sdn
participant ST as state root
K->>R: RunPodSandbox
R->>R: cgroup_parent_of — validated before anything is created
alt hostNetwork
R->>R: no netns of its own
else rootless, native SDN
R->>D: net netns attach cri-id (stderr to a file)
D->>N: attach_container — shared pod netns in the pin
else rootless, DELONIX_CNI=1
R->>N: cni_attach_container — plugins run in the pin
else root
R->>N: cni::attach_named_netns — the node's CNI chain in the host
end
R->>ST: write_rec cri/sandboxes
R-->>K: pod_sandbox_id
K->>R: CreateContainer
R->>R: capability ceiling check, seccomp profile parsed, env file 0600
R->>ST: write_rec cri/containers
R-->>K: container_id
K->>R: StartContainer
R->>R: start_run_opts → RunOpts (pod = cri-sandbox, or net host inside a root CNI netns)
R->>ST: write_run_spec cri/run 0600
R->>D: delonix __apirun spec (nsenter --net for a root CNI sandbox)
D->>D: run_from_spec_file → cmd_run → supervised start
R->>ST: record started
R-->>K: ok
K->>R: ContainerStatus
R->>ST: load_reconciled → reconcile_status against the kernel
Limitations connues#
Note — le contrat de nœud n’est pas servi.
proto/delonix/node/v1est protégé par un gate et génère OpenAPI, mais aucun processus n’y répond. Les intégrations actuelles utilisent la CLI, le CRI, l’API de gestion locale ou le MCP.Note — les serveurs exécutent encore la CLI.
delonix-cri,delonix-mgmtetdelonix-mcpdémarrent les workloads en ré-exécutantdelonix. Cela maintientclonehors des processus multithreads, au prix d’un processus par opération et d’un texte d’erreur traversant une frontière de processus.Note — les adapters atteignent encore directement les fichiers d’état.
delonix-linux,delonix-vm,delonix-sdn,delonix-ocietdelonix-volumedépendent dedelonix-statecomme exceptions déclarées. Le portStateRepositoryqui les supprime existe depuis le #420 (delonix-model/src/ports.rs, ADR-0044 D6), et pour l’instant seuldelonix-linuxpasse par lui pour une partie de son cycle de vie ; les quatre autres ouvrent les stores directement jusqu’à l’arrivée de leur tranche de la P4.Note —
macvlan/ipvlansont déclarés, pas réalisés.network createles enregistre et rapporteRealized=Falseavec la raisonDriverNotImplemented(bins/delonix-runtime-bin/src/cmd/network.rs) : leur plan physique nécessiteCAP_NET_ADMINdans le network namespace initial de l’hôte.Note — la récupération après la mort du pin se fait par redémarrage. Si le processus control meurt,
ensure_upne redémarre que lui et aucun workload ne bouge. Si le pin meurt, la netns est reconstruite etdelonix net netns upredémarre les containers et membres de pod échoués (cmd/netns.rs::reconcile_after_respawn, qui ne lit que le store de containers — les VM ne sont pas récupérées de cette façon).Note — l’IPv6 dans le SDN est désactivé par défaut. Le pare-feu d’ingress est
table ip; le holder installe unetable ip6qui rejette tout (infra::ingress_v6_refusal_ruleset) et désactive l’IPv6 à l’intérieur des netns de container sauf siDELONIX_ENABLE_IPV6=1(ipv6_sdn_enabled).Note — un appelant qui ne peut pas fork démarre sans supervision.
launch::should_superviseexigedetach && forkable; sans superviseur, personne n’est le parent du processus et le vrai code de sortie ne peut pas être collecté.
Par où commencer à lire#
| Domaine | Commencez ici |
|---|---|
| Entrée de la CLI et points d’entrée de ré-exécution cachés | bins/ (main, run) |
container run de bout en bout |
cmd/, puis delonix-compute/ |
| Création de processus, namespaces, rootfs, seccomp, cgroups | delonix-linux/ (spawn, container_init, setup_rootfs, setup_cgroup), supervise.rs, launch_spec.rs |
| Réseau rootless | delonix-sdn/ (ensure_up, control_main, attach_container, publish_port, ingress_table_ruleset, fw_chain_body), pin_userns.rs, ipam.rs |
| Images | delonix-oci/ |
| VM | delonix-vm/src/lib.rs (VmBackend, builtin_backends, register_backend, select_backend), cloudinit.rs ; cmd/vm.rs, cmd/vmimage.rs |
| Apply déclaratif | delonix-stack/ ; cmd/stack.rs, cmd/manifest.rs |
| Enregistrements, erreurs, état persisté | delonix-compute/ (Container, Vm), delonix-model/, delonix-state/ |
| CRI | delonix-cri/, runtime_svc.rs, runtime_svc/ |
| API de gestion / MCP | delonix-mgmt/src/lib.rs, delonix-mcp/src/lib.rs |
| Contrat de nœud | proto/delonix/node/v1/, scripts/, docs/api/openapi.yaml |
| Règles d’architecture | scripts/arch_fitness.py, ADR-0040 |
Suivant : Les crates — une section par crate : ce qu’il possède, ses types principaux, par où commencer à lire et les pièges pour lesquels il a déjà payé.