Retriever de zone et cascade
Une zone est pilotée par un seul document : son Retriever. Il liste,
par nom et par contrainte de version, les recipes que la zone doit
détenir, et nomme le cookbook où les résoudre. L’instance le relit à
chaque synchronisation ; changer ce que contient une zone, c’est changer
ce document — ou publier une nouvelle version de recipe qu’une contrainte
existante couvre déjà. Le format du document est normatif sur le site
recipe-spec : voir la
spécification du Retriever
(le kind Retriever, recipe.tobby.dev/v1alpha1). Un exemple complet et
commenté est livré dans le dépôt, sous
examples/retriever.yaml.
Trois sources
Section intitulée « Trois sources »retriever.source accepte trois formes (FR-010) :
| Forme | Exemple | Quand |
|---|---|---|
| Fichier local | /etc/tobby/retriever.yaml |
Le document est géré avec la configuration de l’instance. |
| URL HTTP(S) | https://git.example.com/platform/retriever.yaml |
Le document vit dans un dépôt Git ou sur n’importe quel serveur web — le montage GitOps habituel. |
| Référence OCI | oci://registry.example.com/config/retriever:v1 |
Le document voyage comme le contenu qu’il décrit — y compris d’une zone à l’autre, porté par Tobby lui-même. |
La source configurée est affichée telle quelle sur l’écran
d’administration Retriever (/admin/retriever, rôle admin) et sur
son miroir d’API GET /api/v1/retriever — aux côtés des périmètres de
confiance relâchés déclarés et de l’intervalle de synchronisation
effectif. La surcharge d’intervalle vit sur le même écran
(PUT /api/v1/retriever/interval, et DELETE pour revenir à la valeur
configurée) ; elle persiste dans le répertoire d’état, survit aux
redémarrages, l’emporte sur sync.interval, et est auditée comme un
changement de configuration sensible (FR-094).

À chaque cycle, l’instance résout depuis le cookbook chaque recipe listée, vérifie sa signature contre les trust roots configurées (FR-033), puis réconcilie. Les contraintes de version sont résolues à chaque passe ; si aucune version publiée ne satisfait une contrainte, cette entrée échoue et le dit — les autres entrées poursuivent. Une seule recipe non résolvable ne bloque jamais la zone.
La cascade : connectée → restreinte → plus restreinte
Section intitulée « La cascade : connectée → restreinte → plus restreinte »Les topologies réelles enchaînent les zones. La zone amont promeut dans
son registre ; le Tobby de la zone aval récupère depuis ce registre,
alors même que les recipes — immuables, signées, au bit près —
continuent de nommer les hôtes d’origine (docker.io/...). Le pont,
c’est la substitution de source (FR-036) :
# Instance avalregistries: substitutions: docker.io: registry.upstream.example ghcr.io: registry.upstream.example
retriever: source: oci://registry.upstream.example/cookbook/retriever:v1La substitution change uniquement le point de terminaison réseau
contacté — jamais le chemin de destination calculé (FR-035). C’est
cette invariance qui rend la cascade composable :
docker.io/bitnami/wordpress se relocalise en
<registry>/docker.io/bitnami/wordpress dans chaque zone, quel que
soit le nombre de sauts franchis, et ne dégénère jamais en
reg.zone2/reg.zone1/docker.io/.... Le registre de chaque zone détient
les mêmes chemins relocalisés sous son propre hôte, et le cookbook de
chaque zone — alimenté par la propagation des recipes (FR-034) — est ce
que désigne le Retriever de la zone suivante. La règle et sa
justification sont dans ADR-0013 ; la
grammaire normative (forme canonique des hôtes, encodage des ports,
sémantique de substitution) est dans
RECIPE-SPEC §11.5.
Ce que doit contenir le registre substitué
Section intitulée « Ce que doit contenir le registre substitué »Une substitution associe un hôte d’origine à une base. L’instance y ajoute ensuite le chemin canonique complet, hôte d’origine compris :
effectif = <base>/<hôte-canonique>[_<port>]/<chemin-du-dépôt>
substitutions: docker.io: registry.upstream.example
docker.io/library/nginx → registry.upstream.example/docker.io/library/nginxghcr.io/dexidp/dex → ghcr.io/dexidp/dex (non substitué : tel quel)L’hôte d’origine reste dans le chemin à dessein : c’est là qu’un Tobby amont range le contenu, et l’instance aval l’y retrouve sans aucune traduction. La valeur est donc un hôte, suivi au plus du chemin de base sous lequel l’amont a promu. Elle ne répète jamais l’hôte d’origine.
Ce qu’on écrit dépend de ce qui alimente le registre amont :
| Registre amont | Alimenté par | Valeur de la substitution | Ce que fait l’exploitant |
|---|---|---|---|
Le registre embarqué (/v2/) d’un Tobby amont |
Les synchronisations de cette instance | <hôte amont>, ou <hôte amont>/<storage.basePrefix> s’il y en a un |
Rien : il porte déjà la disposition canonique. |
| Un registre de zone dans lequel un Tobby amont promeut | destination.registry |
<hôte du registre>, ou <hôte du registre>/<destination.basePath> s’il y en a un |
Rien de plus que pour la promotion elle-même : Tobby pousse docker.io/library/nginx en <registre>/docker.io/library/nginx. Sur Harbor, le projet de premier niveau qui porte le nom de l’hôte d’origine doit exister (ADR-0013). |
| Un proxy de tirage : projet proxy-cache Harbor, dépôt distant Artifactory ou Nexus | Le proxy lui-même, à la demande, depuis l’origine | <hôte du proxy> |
Créer un proxy par hôte d’origine, nommé exactement comme l’hôte canonique (ci-dessous). |
Les trois cas convergent vers la même disposition. Un registre de zone alimenté par un Tobby amont l’obtient gratuitement : Tobby source depuis l’origine, sans substitution, et pousse sous le préfixe d’origine. Un proxy l’obtient par son nom.
Proxys de tirage : un projet par hôte d’origine
Section intitulée « Proxys de tirage : un projet par hôte d’origine »Un proxy de tirage réécrit dans l’autre sens : le nom de son projet
remplace l’hôte d’origine. Un projet proxy-cache Harbor nommé
dockerhub sert harbor.example/dockerhub/library/nginx depuis le
library/nginx de Docker Hub. Une substitution pointée sur ce projet
demanderait à Docker Hub docker.io/library/nginx, qui n’existe pas.
La correction revient au mainteneur du proxy, et c’est une simple question de nom. Il suffit de nommer le projet d’après l’hôte d’origine canonique : le chemin que demande Tobby est alors exactement celui qu’attend le proxy.
Tobby demande harbor.example/docker.io/library/nginxHarbor lit projet « docker.io », dépôt « library/nginx »Harbor récupère library/nginx sur Docker HubL’hôte canonique est à la fois la clé de substitution et le nom du
projet, et le chemin demandé au proxy est toujours <proxy>/ suivi du
ref de la recipe (un port s’écrivant _<port>) :
| Registre d’origine | Clé de substitution / projet du proxy | ref dans la recipe |
|---|---|---|
| Docker Hub | docker.io |
docker.io/library/nginx |
| GitHub Container Registry | ghcr.io |
ghcr.io/dexidp/dex |
| Quay | quay.io |
quay.io/jetstack/cert-manager-controller |
| Kubernetes | registry.k8s.io |
registry.k8s.io/kube-state-metrics/kube-state-metrics |
| Google Container Registry | gcr.io |
gcr.io/distroless/static |
| Google Artifact Registry | europe-docker.pkg.dev |
europe-docker.pkg.dev/acme/apps/api |
| Microsoft Artifact Registry | mcr.microsoft.com |
mcr.microsoft.com/dotnet/runtime |
| Amazon ECR Public | public.ecr.aws |
public.ecr.aws/docker/library/alpine |
| Azure Container Registry | acme.azurecr.io |
acme.azurecr.io/team/app |
| GitLab.com | registry.gitlab.com |
registry.gitlab.com/acme/app/api |
| Forge auto-hébergée sur un port dédié | clé forge.example.com:5050, projet forge.example.com_5050 |
forge.example.com:5050/acme/app |
- Docker Hub est le seul cas particulier, et seulement côté clients.
docker pull nginxveut diredocker.io/library/nginx. Une recipe écrit toujours l’hôte, etlibrary/pour les images officielles.index.docker.ioetregistry-1.docker.iose replient surdocker.io(liste fermée, ADR-0013) : le seul projetdocker.iocouvre les trois graphies. - Un port dédié. C’est courant sur les forges auto-hébergées comme
GitLab ou Froggit, dont le registre écoute souvent sur 5050. La clé
garde le
:(forge.example.com:5050). Le chemin l’encode en_(forge.example.com_5050), car un nom de dépôt ne peut pas contenir:. Le projet du proxy s’appelle doncforge.example.com_5050. - Un hôte, un projet. C’est vrai même chez un même éditeur :
gcr.ioetus.gcr.io, ou deux régions d’Artifact Registry, font deux projets. - Vérifier les noms. Ils contiennent des points, et un tiret bas quand il y a un port. Vérifiez que votre proxy les accepte avant de bâtir sur cette disposition.
- La confiance ne se déplace pas. Le proxy s’authentifie auprès de l’origine avec ses propres identifiants. Tobby s’authentifie auprès du proxy : le fichier de credentials et la liste blanche nomment l’hôte du proxy. Le proxy n’est pas non plus une ancre de confiance : signatures et digests épinglés sont vérifiés exactement comme si les octets venaient de l’origine.
Deux politiques lisent délibérément des références différentes :
- La liste blanche de registries (FR-030) et la recherche de credentials portent sur l’hôte effectif réellement contacté — le substitut. C’est de là que viennent les octets, c’est donc ce que la politique réseau doit nommer.
- Les périmètres des trust roots (FR-033) s’appliquent au
refnominal écrit dans la recipe. Une provenance signée ne change pas parce que le contenu a été récupéré depuis une copie plus proche.
Les journaux enregistrent la correspondance nominal→effectif à chaque récupération substituée : un audit peut donc toujours répondre aux deux questions — « quel était ce contenu » et « d’où viennent ces octets ».
Credentials entre instances
Section intitulée « Credentials entre instances »Une instance aval s’authentifie auprès de son amont comme n’importe quel client de registre. Créez sur l’instance amont un compte (ou un jeton) dédié — voir Authentification et RBAC — dont le seul besoin est la lecture, et transmettez-le à l’instance aval par son fichier de credentials (FR-004) :
registries: credentialsFile: /etc/tobby-credentials/config.jsonLe fichier est une charge dockerconfigjson standard ; les entrées sont
recherchées par hôte effectif, l’entrée nomme donc le registre amont
— registry.upstream.example — et non docker.io. Il doit vivre en
dehors du store (les secrets ne voyagent jamais sur un média
transportable), et sur Kubernetes c’est un Secret monté que le chart
câble pour vous. Les credentials en écriture suivent le même chemin : le
push vers le registre de destination de la zone utilise le même fichier
de credentials, indexé par l’hôte de destination.
Le versant destination de la même instance a sa propre section de
configuration — destination.registry, plus destination.basePath et
destination.cookbook — délibérément séparée des substitutions : l’une
répond à « où est-ce que je lis », l’autre à « où est-ce que je
promeus », et appliquer une réécriture côté lecture à une écriture
publierait dans un registre que personne n’a nommé.
Ensuite : vos clusters et vos hôtes consomment ce qui est arrivé — Brancher vos clients.