Arquitectura
Arquitectura
Antes de leres: Estrutura do projecto (onde as coisas estão), IaaS e cloud native (o lugar e os princípios do motor) e Introdução ao cloud native (os mecanismos que as figuras nomeiam).
Esta página é o mapa de que um contribuidor precisa antes de tocar no backend: o que é o motor, quem fala com ele, que processos existem em runtime, como os crates estão em camadas e se chamam uns aos outros, e onde vive o estado em disco. Segue o modelo C4 por ordem — Nível 1 contexto de sistema, Nível 2 containers (executáveis e processos), Nível 3 componentes (crates), e Nível 4 fluxos ao nível do código como sequências. Todo nó e seta numa figura nomeia, no texto ao lado, o ficheiro e o símbolo contra o qual foi verificado. Depois dela consegues dizer em que processo corre um trabalho, a que camada pertence um crate e que dependências pode ter, e onde em disco vive o seu estado.
O documento canónico, mais longo, é o ARCHITECTURE.md na raiz do
repositório; as decisões por trás da estrutura estão em docs/adr/, sobretudo o
ADR-0040. Se um termo for novo
para ti, lê primeiro IaaS e cloud native e
Fundações de Linux; para a árvore em si (o que é cada directório de topo)
ver Estrutura do projecto.
Duas metades nesta página. A tabela de camadas, a lista de ratchets e o grafo completo de crates são gerados por
python3 scripts/dev_docs.pya partir doCargo.tomle doscripts/arch_fitness.py— não os edites à mão. O resto é narrativa e é revisto depois de cada mudança estrutural.
Como ler as figuras. Toda figura usa as mesmas formas e cores, e a sua legenda vem primeiro:
| Forma | Significado |
|---|---|
| caixa arredondada, escura | pessoa ou actor externo (operador, kubelet, programa local) |
| caixa, vermelha | o motor Delonix como um todo (este repositório) |
| caixa, branca com borda vermelha | um bloco de construção do motor: executável, processo ou crate |
| caixa, cinzenta | um sistema externo (kernel, systemd, registo, hipervisor, API remota) |
| cilindro, azul | estado em disco |
| seta sólida | uma chamada ou um fluxo de dados; a etiqueta diz o que flui |
| seta tracejada | arranca, faz exec de, ou supervisiona um processo |
| região contornada | uma fronteira de confiança ou de processo |
Identidade e fronteiras do motor#
O texto canónico é a secção «Identidade e fronteira do motor» no topo do
AGENTS.md. O que o motor é, o que deixa para um control plane, e como cada
princípio cloud native aparece no código são o contexto, explicado em
IaaS e cloud native.
Esta secção guarda só as partes que moldam a estrutura abaixo:
- Os providers ficam atrás de portas. O kernel Linux, o Cloud Hypervisor e o libvirt, o
Proxmox VE e o CRI do Kubernetes são alcançados através de um trait, nunca através de
if provider == …espalhado pelo código. As portas de hoje:VmBackend(crates/adapters/delonix-vm/src/lib.rs) e as portas de compute emcrates/contexts/delonix-compute/src/ports.rselaunch.rs(ImageStore,StorageProvider,DeviceResolver,RunHost,NetworkProvider,VmNetwork,WorkloadRuntime). Um backend OpenStack está desenhado (ADR-0039, Proposed) mas ainda não tem crate. - Um conjunto de operações, várias interfaces — a CLI, o CRI, a API de gestão local, o MCP e
uma fatia da API Docker Engine, com o contrato de nó como a API única pretendida (ver
abaixo); a observabilidade passa por
crates/adapters/delonix-telemetry. - Daemonless e rootless-first decidem o modelo de processos — o que tem de persistir pertence
ao systemd ou a um processo por workload com um dono claro (o supervisor de um container, o pin
de rede), e o privilégio é um opt-in explícito (
--privileged,vm bridge). O Nível 2 mostra esses processos. - Não conhece nenhum consumidor. Nenhuma plataforma, control plane, consola ou agente é
nomeado em
crates/,bins/,proto/ou nos manifestos, e não há noção de inquilino, conta, plano ou facturação. O namespace que vais ver em todo o lado é o namespace de isolamento próprio do motor, não um inquilino.
Estas não são convenções; o scripts/arch_fitness.py impõe a metade estrutural na CI:
| Verificação | Onde no arch_fitness.py |
|---|---|
| Uma dependência contra a direcção das camadas falha, a não ser que seja uma excepção declarada que nomeia a fase do ADR-0040 que a remove | LAYERS, ALLOWED, EXCEPTIONS, rule_failures |
Um crate de fundação ou de contexto não pode levar uma dependência de runtime/servidor/CLI (tokio, tonic, reqwest, clap, …) |
HEAVY |
| Um binário compõe uma interface | rule_failures (a verificação roles) |
| Um crate tem de viver no directório da sua camada | LAYER_DIR, misplaced |
O nome de um consumidor em qualquer sítio debaixo de crates/, bins/, proto/ (comentários incluídos) falha |
CONSUMER_NAMES, consumer_mentions |
As versões de dependência vivem só no [workspace. raiz |
inline_versions |
Ratchets que só podem descer (listados abaixo) — ex.: crates de biblioteca a voltar a correr o próprio binário do motor, println! em bibliotecas, escritas no ambiente do processo, adapters a importar o Error partilhado como se fosse seu |
os padrões de ratchet (SELF_EXEC, PRINTS, ENV_WRITES, SHARED_ERROR, …), linha de base em scripts/ |
O scripts/arch_fitness.py mantém 6 ratchets de dívida (linha de base em scripts/arch_baseline.json):
self_exec_siteslibrary_printsenv_writesshared_error_importsraw_error_variant_matchescontext_spawns
O python3 scripts/arch_fitness.py --list mostra o que cada ratchet conta hoje, ficheiro a
ficheiro.
Nível 1 — Contexto de sistema#
Legenda — caixa arredondada escura: pessoa ou actor externo · caixa vermelha: o motor Delonix · caixa cinzenta: sistema externo · seta sólida: chamada ou fluxo de dados, com etiqueta.
O motor fica entre quatro tipos de chamador e os sistemas de um nó Linux; não tem nenhuma API própria virada para a rede, e tudo o que alcança remotamente é alcançado para fora.
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
Onde cada seta está no código:
| Seta | Código |
|---|---|
| operador → motor | bins/ (main, run); classes de saída em crates/ |
| kubelet → motor | crates/ (serve_blocking) |
| programa local → motor | crates/ (serve_blocking, o router axum) |
| cliente de IA → motor | crates/ (serve_stdio) |
| motor → kernel | crates/ (spawn, container_init); crates/ (subprocessos ip, nft, nsenter) |
| motor → systemd | scopes transitórios busctl em crates/; units de arranque em bins/ |
| motor → registos | crates/ (resolve_or_pull, push_to_registry) |
| motor → hipervisores | crates/ (CloudHypervisorBackend, LibvirtBackend) |
| motor → APIs de gestão remotas | crates/, crates/ |
| motor → hosts remotos | bins/ (ssh, scp), usado por cmd/cluster.rs |
| motor ↔ observabilidade | crates/ (OTLP), rotas /metrics em delonix-mgmt e delonix-cri |
Nível 2 — Containers: executáveis e processos#
No C4 um container é algo que corre. O build produz quatro executáveis (ver a contagem gerada no README do manual); vários processos mais aparecem por workload ou por nó, cada um com um dono. As três figuras abaixo dividem essa imagem por preocupação: quem entra no motor, o que um container custa em processos, e a infra-estrutura de rede rootless.
Pontos de entrada#
Legenda
| Forma | Significado |
|---|---|
| caixa arredondada, escura | chamador |
| caixa, branca com borda vermelha | executável do motor |
| cilindro, azul | estado em disco |
| seta sólida | pedido ou acesso a ficheiro, com etiqueta |
| seta tracejada | exec ou arranque de um processo |
| região contornada | fronteira de processo |
Quatro portas levam ao motor, mas só o processo delonix de disparo único chega a criar um
container: os servidores multi-thread voltam a chamar a CLI para isso.
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
O exec é cmd/serve.rs::exec_server (e cmd/mcp.rs); a chamada de volta é o helper delonix()
do CRI e write_run_spec em crates/interfaces/delonix-cri/src/runtime_svc/lifecycle.rs,
run_cli no delonix-mgmt e run_cli_blocking no delonix-mcp, todos a resolver a CLI através
de delonix_node::dispatch::cli_bin. O CRI também escreve os seus próprios registos debaixo de
cri/ — ver Estado em disco.
Um container destacado#
Legenda
| Forma | Significado |
|---|---|
| caixa, branca com borda vermelha | processo do motor |
| caixa, cinzenta | sistema externo |
| cilindro, azul | ficheiro em disco |
| seta sólida | fluxo de dados ou escrita, com etiqueta |
| seta tracejada | fork, clone ou spawn |
| região contornada | vive enquanto o workload viver |
Um run -d deixa para trás exactamente três ou quatro processos, e o supervisor — não um daemon —
é o pai do 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
O supervisor é crates/adapters/delonix-linux/src/supervise.rs::run_supervised, escolhido por
delonix_compute::launch::start através de HostWorkload::supervise
(crates/adapters/delonix-linux/src/workload.rs); lá dentro create_with → spawn faz o
clone, o write_userns_maps, o cgroup, o hook on_started (preenchido com
delonix_sdn::slirp_attach por cmd/container.rs::with_host_workload) e faz fork do log_shim,
tudo em crates/adapters/delonix-linux/src/lib.rs. O registo é escrito através do
delonix_state::Store. Um run em primeiro plano faz o mesmo sem o supervisor.
Infra-estrutura de rede rootless#
Legenda
| Forma | Significado |
|---|---|
| caixa, branca com borda vermelha | processo do motor |
| caixa, cinzenta | sistema externo |
| cilindro, azul | estado em disco |
| seta sólida | pedido ou tráfego, com etiqueta |
| seta tracejada | spawn (a CLI arranca o processo) |
| região contornada | os namespaces de user, network e mount do pin |
Tudo o que a rede rootless precisa vive dentro de um conjunto de namespaces segurado por um processo que só dorme; o resto pode morrer e ser reiniciado à volta dele.
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
O pin, o control e o uplink são start_pin/pin_main, start_control/control_main e
start_slirp em crates/adapters/delonix-sdn/src/infra.rs; os namespaces do pin são criados em
pin_userns.rs. O proxy é cmd/ingress_proxy.rs::spawn_proxy; o arranque do VMM é
delonix_vm::launch_vmm, ao qual a porta VmNetwork dá o argv de junção. Um container junta-se a
uma rede personalizada por a CLI se re-executar para dentro dos namespaces (reexec_into_netns,
ver a sequência de run no Nível 4 abaixo). Uma VM libvirt vive fora do holder, na virbr0 no
network namespace do host.
Tabela de processos#
| Processo | Nasce em | Vive por |
|---|---|---|
delonix |
bins/ (main, run) |
um comando. O main intercepta os pontos de entrada escondidos (netns pin, netns control, netns run, __rmtree, __volsnap, __ovlmigrate, __ovlhold, __duusage, __buildtar, __apirun, __netnsconnect) antes de o clap fazer o parse |
delonix-cri |
crates/ → delonix_cri:: |
um serviço (tipicamente uma unit systemd). O delonix serve cri faz exec dele (cmd/) |
delonix-mgmt |
bins/ → delonix_mgmt:: |
um serviço; o delonix serve api faz exec dele |
delonix-mcp |
bins/ → delonix_mcp:: |
uma sessão de cliente de IA (um processo filho sobre stdio); o delonix mcp faz exec dele |
| Fatia da API Docker | cmd/serve.rs → cmd::dockerapi::run, dentro do processo delonix |
enquanto o delonix serve docker-api corre |
| supervisor | delonix_linux::, escolhido por delonix_compute:: para todo arranque destacado que o chamador consiga fazer fork |
a vida do container; é o pai real, por isso colhe o estado de saída e aplica o --restart |
| init do container | delonix_linux::spawn → clone → container_init |
o container |
| shim de logs | fork dentro do spawn, a correr log_shim |
o container |
slirp4netns por-container |
delonix_sdn::, chamado como o hook on_started |
a netns do container; órfãos colhidos por reap_orphan_slirp |
| pin | infra::start_pin arranca delonix netns pin; infra::pin_main cria os namespaces de user, net e mount dentro do processo (crates/) e dorme |
a infra; o seu pid é ingress/holder.pid e nunca muda |
| control | infra::start_control (nsenter -t <pin> -U -m -n -- delonix netns control) → infra::control_main |
reiniciável; serve o socket de controlo, o DNS (dns_server_main), Router Advertisements (ra_sender_main) e DHCP por-bridge (dhcp_serve) |
slirp4netns único |
infra::start_slirp (tap0 para dentro da netns do pin, --api-socket) |
a infra |
| proxy de ingress L7 | cmd/ através de infra::infra_join_argv |
enquanto existir um HTTPRoute/Ingress ou uma rota --expose; recarrega rotas em SIGHUP |
cloud-hypervisor |
delonix_vm::launch_vmm, corrido através do argv de junção da infra |
a VM |
| domínio libvirt | LibvirtBackend a conduzir o virsh |
a VM (o domínio vive no libvirt) |
O ensure_up (crates/adapters/delonix-sdn/src/infra.rs) é a única função que levanta a infra de
rede, debaixo de um file lock por-raiz, e distingue três casos: pin e control vivos (nada a
fazer); pin vivo e control desaparecido (reinicia só o control plane — nenhum fio se mexe);
pin desaparecido (desmonta e reconstrói).
Um conjunto de operações, várias interfaces#
| Interface | Transporte | Entrada | Estado |
|---|---|---|---|
| CLI | argv | bins/ |
a superfície completa |
CRI (runtime.v1) |
gRPC sobre um socket unix, 0600 + SO_PEERCRED |
delonix_cri:: |
serve o kubelet |
| API de gestão | HTTP+JSON sobre um socket unix, só o mesmo uid | delonix_mgmt:: (rotas como /v1/containers, /v1/volumes, /metrics) |
só local (ADR-0010 rejeitou uma API remota); a ser substituída pelo contrato de nó |
| MCP | stdio | delonix_mcp:: |
local, sem inquilino (ADR-0025) |
| Fatia da Docker Engine API | HTTP sobre um socket unix | cmd::dockerapi::run |
uma fatia de compatibilidade, dentro do delonix |
Contrato de nó delonix.node.v1 |
gRPC e HTTP/JSON num só socket unix | proto/delonix/node/v1/ |
só contrato — ainda sem servidor |
O contrato de nó é a API única pretendida
(ADR-0040 D4,
ADR-0042). Os ficheiros .proto são a fonte de
verdade; o docs/api/openapi.yaml é gerado a partir deles e nunca editado à mão. O
scripts/contract_gate.py falha em: buf format, buf lint, buf breaking contra a última tag
que traz proto/, um RPC sem mapeamento HTTP (ou um stream bidireccional com um), um documento
OpenAPI que difere do gerado, e dois caminhos que sejam o mesmo URL sob nomes de variável
diferentes. Três regras que protege: um pedido por RPC, identidade explícita
(namespace/name) no pedido, e imagens endereçadas por parâmetro de query.
Nível 3 — Componentes: crates por camada#
Camadas e a direcção permitida#
Legenda — caixa branca com borda vermelha: uma camada de crates · seta sólida: pode depender de, com o que a dependência é usada para.
O D1 do ADR-0040 fixa uma direcção de dependência: os contexts são dependidos, nunca ao contrário, e os binários são o único sítio onde tudo se encontra.
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/) — tipos partilhados, mais ou menos puros, que qualquer camada pode nomear. - Contexts (
crates/contexts/) — um crate por contexto delimitado, nomeado a partir dos grupos de API publicados: os casos de uso e as portas de que precisam. Sem HTTP, sem provider, e sem mounts, processos ou configuração de rede. Odelonix-nodeé o único context que lê o host directamente —/proc,/sys,kill(pid, 0),SO_PEERCRED— porque essas perguntas são a razão de ele existir, para as responder uma vez. - Adapters (
crates/adapters/) e providers (crates/providers/) — implementam portas: kernel, SDN, store OCI, backends de VM, estado persistido; os providers trazem um cliente HTTP para um alvo remoto. - Interfaces (
crates/interfaces/) — CRI, API de gestão, MCP: analisam um pedido, chamam o motor, apresentam. - Binaries (
bins/) — raízes de composição.
A camada a que cada crate pertence, e a direcção em que pode depender:
| Camada | Pode depender de |
|---|---|
| Foundation | foundation |
| Contexts | foundation, contexts |
| Adapters | foundation, contexts |
| Providers | foundation, contexts |
| Interfaces | foundation, contexts, adapters, providers |
| Binaries | foundation, contexts, adapters, providers, interfaces |
Excepções declaradas (cada uma nomeia a fase do ADR-0040 que a remove):
delonix-linux→delonix-state— removida na P4adelonix-mcp→delonix-mgmt— removida na P5delonix-oci→delonix-state— removida na P4delonix-scanner→delonix-oci— removida na P4delonix-sdn→delonix-state— removida na P4delonix-vm→delonix-provider-cloud-hypervisor— removida na P5delonix-vm→delonix-provider-libvirt— removida na P5delonix-vm→delonix-state— removida na P5delonix-volume→delonix-state— removida na P4
Onde está a restruturação#
O ADR-0040 é um plano strangler por fases (P0 carris → P1 contrato → P2 contexts → P3 adapters e binários → P4 providers → P5 API de nó → P6 CRI → P7 observabilidade). O que o código mostra hoje:
- A P0 está feita. Todo crate vive no directório da sua camada, as versões são ao nível do workspace, e o gate de fitness corre na CI.
- A P1 está feita como contrato, não como servidor. O
proto/delonix/node/v1/*.protoexiste, o documento OpenAPIdocs/api/openapi.yamlé gerado a partir dele, e oscripts/contract_gate.pyguarda os dois. Nada serve ainda o contrato — nenhum crate referenciadelonix.node.v1(o ADR-0042 D1 diz o mesmo). - A P2 começou. O
delonix-model(oErrorpartilhado e os seus códigosDX_*, nomes gerados, classes de saída, o dicionário de códigos numerado, o modelo de segredos, e — desde a #405 — os registos só-de-dadosStatus,ContainerFw/FwRulecom os seus validadores,default_namespacee otypestatedo ciclo de vida), odelonix-stack(tabela de Kinds, reconciliador de 3 vias, revisões) e odelonix-compute(a única especificação de execuçãoRunOpts,resolve_run,build_record, os casos de uso de rede e de arranque) existem. A maior parte da lógica de aplicação ainda vive embins/delonix-runtime-bin/src/cmd/. - A P3 está em curso. As portas de compute são implementadas em adapters (
HostImages,HostVolumes,HostDevices,HostRuntime,HostNetwork,HostWorkload,HostVmNetwork), a telemetria saiu da fundação para odelonix-telemetry, odelonix-vmsó alcança a SDN através da portaVmNetwork, e os servidores CRI, API de gestão e MCP tornaram-se executáveis próprios. Quatro adapters levam já os seus nomes do ADR-0040:delonix-scanner(eradelonix-scan),delonix-oci(eradelonix-image),delonix-sdn(eradelonix-net) edelonix-linux(eradelonix-runtime, o crate do motor de containers). A #406 removeu odelonix-runtime-core, o crate de fundação que costumava guardar tudo o partilhado, em passos: a #404 moveu os stores, as escritas atómicas e o store de segredos cifrado para o adapterdelonix-state; a #405 moveu os registos só-de-dados (Status,ContainerFw/FwRule,typestate) para baixo, para odelonix-model; e a #406 moveu os registosContainereVm(comMount, tipos de saúde e de pai-de-cgroup,DELONIX_SLICEeworkload_net) para odelonix-compute, e o registo de eventos,virt,peer_cred,dispatche os helpers de host/processo (now_unix,is_alive,safe_to_signal,generate_id, …) para um context novo, odelonix-node. Não ficou nenhum re-export para trás. Os adapters que abrem registos ou escrevem ficheiros através dodelonix-state(delonix-linux,delonix-vm,delonix-sdn,delonix-oci,delonix-volume) são excepções declaradas até a P4 lhes dar uma portaStateRepository(scripts/arch_fitness.py). - A P4 está em curso; as P5–P7 não começaram. O ADR-0044 (aceite a 2026-09-24) decide como a
P4 se faz. O #420 trouxe a porta
StateRepository<T>(crates/foundation/delonix-model/src/ports.rs), que odelonix-linuxjá usa emwait_and_record/stop/persist_stop/remove— por isso a sua excepção noscripts/arch_fitness.pydizP4ae lista só os sítios ainda abertos. O #486 acrescentou a porta de provider de VM (VmSpec,Extensions,Provider,VmProvideremcrates/contexts/delonix-compute/src/vm_provider.rs, P4b fatia 1), e odelonix-vmimplementa-a para os dois backends locais (LocalVmProvider,crates/adapters/delonix-vm/src/provider.rs) reaproveitando ocreate_with/stop/startque já tinha; mover cada backend para o seu crate de provider é a P4b fatia 2. As excepções restantes na tabela acima nomeiam a fase que remove cada uma.
Registos, helpers de nó e estado persistido, depois da #406#
Legenda — caixa branca com borda vermelha: crate do motor (ou grupo de crates) · cilindro, azul: ficheiros debaixo do state root · seta sólida: usa, com o que é usado.
Os tipos só-de-dados vivem na fundação, os registos de workload no context Compute, os próprios helpers do nó no context Node, e os ficheiros que guardam registos num só adapter através do qual todo outro adapter alcança esses ficheiros.
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
Verificado contra: crates/contexts/delonix-compute/src/record.rs (use
delonix_model::records::{…}, use delonix_node::safe_to_signal) e src/lib.rs (pub use
record::*); crates/contexts/delonix-node/src/lib.rs e host.rs;
crates/foundation/delonix-model/src/records.rs e typestate.rs;
crates/adapters/delonix-state/src/store.rs (use delonix_compute::Container), secret.rs,
cred_vault.rs, error.rs. O delonix-net-rules só é usado por delonix-sdn e delonix-vm; o
delonix-volume e o delonix-scanner também nomeiam delonix-model directamente (o grafo gerado
abaixo tem toda aresta).
Portas de Compute e os adapters por trás delas#
Legenda — caixa branca com borda vermelha: componente do motor (casos de uso, adapter, binário) · região contornada: o crate de context · seta sólida: uma chamada através da porta nomeada.
O container run é o caminho de referência: o context decide através de portas, e o binário
escolhe que adapter responde a cada porta.
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
Portas: crates/contexts/delonix-compute/src/ports.rs (ImageStore, StorageProvider,
DeviceResolver, RunHost, VmNetwork, NetworkProvider) e launch.rs (WorkloadRuntime).
Implementações: 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.
Ligação: bins/delonix-runtime-bin/src/cmd/container.rs::cmd_run e
bins/delonix-runtime-bin/src/main.rs::run.
Interfaces e binários#
Legenda — caixa branca com borda vermelha: crate do motor (ou grupo de crates) · seta sólida: uma chamada directa de Rust, com o que é usada para.
Os servidores leem dentro do processo e entregam todo fork à CLI; a única aresta interface-para-interface é uma excepção declarada.
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
Não desenhado, para a figura ficar legível: todo binário e delonix-cri/delonix-mgmt também
chamam delonix-telemetry (telemetry::init, métricas), e o delonix-mcp e a CLI também leem o
delonix-state. As arestas do dashboard são bins/delonix-runtime-bin/src/cmd/dash.rs e
crates/interfaces/delonix-mcp/src/lib.rs (delonix_mgmt::dashstats::collect); o uso de
RunOpts pelo CRI é start_run_opts em runtime_svc/lifecycle.rs.
Toda aresta de crate#
O grafo de crates, tal como o Cargo.toml o declara. É completo e por isso denso; lê-o para
responder «será que A depende de B», e lê as figuras por-camada acima para perceber porquê.
Legenda — uma caixa por crate, agrupada por camada; uma seta A --> B quer dizer A depende de B. Vermelho: binários · branco com borda vermelha: interfaces · branco: contextos e adaptadores · cinzento: providers · azul: fundação.
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
Como os crates comunicam#
- Chamadas Rust directas, na direcção da camada. O caso normal. Por exemplo o
cmd_run(cmd/container.rs) chamadelonix_compute::run::resolve_runcom os adaptersdelonix_oci::run_images::HostImages,delonix_volume::HostVolumes,delonix_linux::cdi::HostDevicesedelonix_linux::run_host::HostRuntime, depoisdelonix_compute::network::{attach_custom_network, wire_network}comdelonix_sdn::run_network::HostNetwork, depoisdelonix_compute::launch::startcomdelonix_linux::workload::HostWorkload. - Registo na raiz de composição. O
run()embins/delonix-runtime-bin/src/main.rsregista os backends de VM remotos configurados (cmd::vmbackends::register_configured→delonix_vm::register_backend) e a implementação SDN da porta de rede de VM (delonix_vm::set_network(HostVmNetwork)) antes de qualquer comando correr. - Re-executar o próprio binário do motor. Ainda comum, e contado pelo ratchet
self_exec_sites. As razões são reais: - Oclonesó é seguro num processo de uma só thread, e os servidores CRI, API de gestão e Docker API são runtimestokiomulti-thread. Entregam umRunOptstipado num ficheiro0600a umdelonix __apirun <spec>novo (lifecycle.rs::write_run_spec,cmd::dockerapi::run_from_spec_file). - Um processo rootless tem de entrar nos namespaces de user e mount do pin de rede antes de um container se poder juntar a uma netns nomeada lá, por issoreexec_into_netnscorrensenter … ip netns exec <netns> delonix netns run <spec>. - Trabalho sobre ficheiros que pertencem a subuids mapeados precisa de um processo dentro de um user namespace mapeado (delonix_linux::reexec_mapped,reexec_mapped_hold,remove_tree_mapped→ os pontos de entrada__rmtree/__ovlhold/…). - Os servidores ainda constroem algumas invocações da CLI (delonix-mgmt, orun_cli_blockingdodelonix-mcp, o helperdelonix()do CRI), resolvendo a CLI através dedelonix_node::dispatch::cli_bin(DELONIX_BIN, depois umdelonixao lado, depois oPATH) — nunca o seu próprio executável. O D2.4/D5 do ADR-0040 planeia um executáveldelonix-launcherque recebe uma spec tipada, para estes se tornarem chamadas de caso de uso mais um spawn. - O socket de controlo. Tudo dentro da netns rootless da infra é feito pelo processo
control:
infra::control_send/control_queryescrevem uma linha (attach …,publish …,firewall …) num socket unix0600; ocontrol_loopsó aceita peers com o mesmo uid do motor (SO_PEERCRED) e serve uma ligação de cada vez, por isso as operações de netns/veth/nftables nunca se intercalam. - Subprocessos a ferramentas do host, em adapters:
ip,nft,nsenter,slirp4netns(delonix-sdn),newuidmap/newgidmap(delonix-linux,pin_userns),qemu-img,virsh,cloud-localds(delonix-vm),busctlpara scopes transitórios do systemd (delonix-linux),ssh/scp(cmd/remote.rs). - HTTP para um sistema de gestão remoto só vive em providers. O
delonix-proxmoxe odelonix-truenasdependem doreqwestpara isso. Dois adapters também falam HTTP, por outras razões: odelonix-ocitem o seu próprio cliente de registo OCI (src/registry.rs,reqwestno seuCargo.toml), e odelonix-telemetryexporta OTLP sobre HTTP. Nenhum crate de context o faz.
Estado em disco#
Não há base de dados. O estado são ficheiros debaixo de um state root:
DELONIX_ROOTquando definida; senão$XDG_DATA_HOME/delonixou~/.local/share/delonixpara um utilizador sem privilégio e/var/lib/delonixpara root (bins/delonix-runtime-bin/src/cmd/util.rs::state_root→ImageStore::default_root;infra::base_rootresolve a mesma regra do lado da rede).- Os sockets não vivem debaixo do state root. Estão num directório de runtime curto por
utilizador (
infra::runtime_dir, sobreponível comDELONIX_NET_RUNTIME_DIR) porque os caminhosAF_UNIXtêm comprimento limitado; uma raiz que não seja a de omissão ganha um sufixo com hash (root_suffix) para duas raízes num mesmo login nunca partilharem sockets. Quando correres qualquer coisa isolada, define as duas variáveis.
| Caminho debaixo da raiz | O quê | Código |
|---|---|---|
containers/ |
um registo JSON por container | delonix_state::Store (delonix-state/) |
containers/ + overlay-lowers |
a layer escrevível do container e a lista de layers de imagem partilhadas que monta | ImageStore:: (delonix-oci/) |
images/<id>.json, layers/<hex>/, blobs/ |
metadados de imagem, layers desempacotadas partilhadas por todo container, blobs endereçados por conteúdo | ImageStore::open (image.rs), Cas (cas.rs) |
volumes/, volumes/.ns/<ns>/ |
volumes nomeados, volumes por-namespace | VolumeStore (delonix-volume/) |
vms/ |
registos de VM (delonix_state::) e ficheiros por-VM |
delonix-vm |
vm-images/ |
imagens de VM (.qcow2 + .json) |
cmd/ |
secrets/ |
segredos cifrados | SecretStore (delonix-state/) |
tunnels/keyring.key, tunnels/cred/ |
a chave mestra do host e credenciais cifradas | CredVault (delonix-state/) |
ingress/ |
pidfiles (holder.pid é o pin), marcadores refs/, definições de rede e rota, logs |
delonix-sdn/ |
hosts-sync |
ficheiro marcador: o delonix hosts sync foi corrido, por isso os nomes de serviço dos containers --expose são mantidos no /etc/hosts do host (fica na raiz, não em ingress/) |
hosts_sync_flag em cmd/ingress_proxy.rs |
ipam/ |
leases de endereço por-prefixo | delonix-sdn/src/ipam.rs |
cri/ |
os próprios registos do CRI | delonix-cri/ (sb_dir, ct_dir) |
clusters/ |
kubeconfigs, chaves e PKI de clusters | cmd/cluster.rs |
events.jsonl |
registo de eventos só-de-acrescentar | delonix_node::events |
A concorrência é tratada pelo sistema de ficheiros, porque vários processos (a CLI, o servidor
CRI, um supervisor) mutam os mesmos registos: as escritas são atómicas (ficheiro temporário +
rename, delonix_state::write_atomic), e o read-modify-write passa por Store::update /
JsonStore::update, que tomam um flock exclusivo e recusam avançar sem ele. Tudo isso vive
no adapter delonix-state. Os tipos de registo que ele guarda são definidos noutro sítio:
Container e Vm no context delonix-compute, e as partes só-de-dados de um registo (Status,
ContainerFw/FwRule) no crate de fundação delonix-model. A infra de rede tem o seu próprio
FileLock à volta de ensure_up, teardown, acquire, release e os reapers.
Como nada residente vigia processos, um registo a dizer Running pode estar desactualizado. Os
leitores reconciliam: o delonix_linux::reconcile_status verifica o pid junto com a sua hora de
arranque (delonix_node::safe_to_signal) para um pid reciclado nunca ser confundido com o
container.
Nível 4 — Dois fluxos, como sequências#
O Nível 4 só é desenhado onde a ordem dos passos é o ponto. Os dois fluxos abaixo são sequências em vez de figuras de estrutura.
container run -d --net web -p 8080:80 nginx, rootless#
Toda seta abaixo é uma chamada no cmd_run (bins/delonix-runtime-bin/src/cmd/container.rs) ou
nas funções que ele alcança.
Legenda — os participantes são processos; as setas sólidas são chamadas, linhas de socket ou spawns (a etiqueta diz qual); as setas tracejadas são respostas; uma auto-seta é trabalho dentro desse processo; as notas marcam o que fica para trás.
Uma rede personalizada força uma segunda passagem da CLI dentro dos namespaces do pin, e o registo só é publicado depois de os mounts do container serem finais.
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.
Sem uma rede personalizada o fluxo não tem segunda passagem: o spawn cria o seu próprio user
namespace, e o pai escreve os mapas de id (write_userns_maps), configura o cgroup, corre o hook
on_started (o slirp_attach por-container quando há portas -p) e só então envia o byte "go"
ao filho.
CRI: RunPodSandbox → CreateContainer → StartContainer#
De crates/interfaces/delonix-cri/src/runtime_svc/lifecycle.rs.
Legenda — os participantes são processos, mais o state root como participante; as setas sólidas são chamadas gRPC, chamadas dentro do processo, subprocessos ou escritas de ficheiro (a etiqueta diz qual); as setas tracejadas são respostas; as caixas
altsão os modos de rede mutuamente exclusivos.
O servidor CRI regista e decide, mas todo arranque de container atravessa para um processo
delonix novo.
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
Limitações conhecidas#
Nota — o contrato de nó não é servido. O
proto/delonix/node/v1é guardado por gate e gera OpenAPI, mas nenhum processo lhe responde. As integrações de hoje usam a CLI, o CRI, a API de gestão local ou o MCP.Nota — os servidores ainda correm a CLI. O
delonix-cri, odelonix-mgmte odelonix-mcparrancam workloads voltando a executar odelonix. Isto mantém oclonefora de processos multi-thread, ao custo de um processo por operação e de o texto de erro atravessar uma fronteira de processo.Nota — os adapters ainda alcançam os ficheiros de estado directamente. O
delonix-linux, odelonix-vm, odelonix-sdn, odelonix-ocie odelonix-volumedependem dodelonix-statecomo excepções declaradas. A portaStateRepositoryque os remove existe desde o #420 (delonix-model/src/ports.rs, ADR-0044 D6), e por agora só odelonix-linuxpassa por ela em parte do seu ciclo de vida; os outros quatro abrem os stores directamente até a sua fatia da P4 entrar.Nota —
macvlan/ipvlanestão declaradas, não realizadas. Onetwork createregista-as e reportaRealized=Falsecom a razãoDriverNotImplemented(bins/delonix-runtime-bin/src/cmd/network.rs): o seu plano físico precisa deCAP_NET_ADMINna network namespace inicial do host.Nota — a recuperação depois de o pin morrer é por reinício. Se o processo control morrer, o
ensure_upsó o reinicia a ele e nenhum workload se move. Se o pin morrer, a netns é reconstruída e odelonix net netns upreinicia os containers e membros de pod encalhados (cmd/netns.rs::reconcile_after_respawn, que só lê o store de containers — as VMs não são recuperadas desta forma).Nota — o IPv6 na SDN está desligado por omissão. A firewall de ingress é
table ip; o holder instala umatable ip6que dropa tudo (infra::ingress_v6_refusal_ruleset) e desliga o IPv6 dentro das netns de container a não ser queDELONIX_ENABLE_IPV6=1(ipv6_sdn_enabled).Nota — um chamador que não consegue fazer fork arranca sem supervisor. O
launch::should_superviseexigedetach && forkable; sem supervisor ninguém é o pai do processo e o código de saída real não pode ser colhido.
Onde começar a ler#
| Área | Começa aqui |
|---|---|
| Entrada da CLI e pontos de entrada de re-exec escondidos | bins/ (main, run) |
container run de ponta a ponta |
cmd/, depois delonix-compute/ |
| Criação de processos, namespaces, rootfs, seccomp, cgroups | delonix-linux/ (spawn, container_init, setup_rootfs, setup_cgroup), supervise.rs, launch_spec.rs |
| Rede rootless | delonix-sdn/ (ensure_up, control_main, attach_container, publish_port, ingress_table_ruleset, fw_chain_body), pin_userns.rs, ipam.rs |
| Imagens | delonix-oci/ |
| VMs | delonix-vm/src/lib.rs (VmBackend, builtin_backends, register_backend, select_backend), cloudinit.rs; cmd/vm.rs, cmd/vmimage.rs |
| Apply declarativo | delonix-stack/; cmd/stack.rs, cmd/manifest.rs |
| Registos, erros, estado persistido | delonix-compute/ (Container, Vm), delonix-model/, delonix-state/ |
| CRI | delonix-cri/, runtime_svc.rs, runtime_svc/ |
| API de gestão / MCP | delonix-mgmt/src/lib.rs, delonix-mcp/src/lib.rs |
| Contrato de nó | proto/delonix/node/v1/, scripts/, docs/api/openapi.yaml |
| Regras de arquitectura | scripts/arch_fitness.py, ADR-0040 |
Seguinte: Os crates — uma secção por crate: o que possui, os seus tipos principais, por onde começar a ler e as armadilhas por que já pagou.