Aller au contenu

Fondamentaux du registre d'artefacts

Spécification OCI

L'Open Container Initiative (OCI) définit trois standards qui gouvernent le stockage et la distribution d'artefacts conteneur :

Standard Rôle
OCI Image Spec Format de l'image : layers, config, manifest
OCI Distribution Spec API HTTP pour push/pull entre client et registre
OCI Runtime Spec Comment exécuter un conteneur (hors scope registre)
OCI Artifacts Extension pour stocker des artefacts non-image (Helm, SBOM)

Un registre conforme OCI accepte tout artefact qui respecte le format manifest + blobs, pas uniquement des images conteneur.

Anatomie d'une image OCI

Layers

Une image est composee de couches (layers) empilees. Chaque instruction du Dockerfile qui modifie le système de fichiers produit un nouveau layer.

graph TD
    L4["Layer 4 : COPY app.jar<br/>Votre application"] --- L3
    L3["Layer 3 : RUN apt install<br/>Dependances"] --- L2
    L2["Layer 2 : ENV JAVA_HOME<br/>Config (metadata)"] --- L1
    L1["Layer 1 : Base image<br/>debian:bookworm-slim"]

Chaque layer est identifié par son hash SHA-256. Deux images partageant la même base reutilisent les mêmes layers (deduplication côté registre).

Manifest

Le manifest est un document JSON qui référence les layers et la configuration de l'image :

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:abc123...",
    "size": 7023
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:def456...",
      "size": 32654
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:789ghi...",
      "size": 16724
    }
  ]
}

Index (manifest list)

Un index OCI regroupe plusieurs manifests pour différentes architectures sous une même référence :

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:aaa...",
      "platform": { "architecture": "amd64", "os": "linux" }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:bbb...",
      "platform": { "architecture": "arm64", "os": "linux" }
    }
  ]
}

Tags vs Digests

Tags

Un tag est un alias mutable qui pointe vers un digest :

# Pousser une image avec un tag
podman push registry.example.com/myapp:v1.2.3

# Le tag v1.2.3 pointe vers sha256:abc123...
# Si on repousse avec le meme tag, le digest change !
podman push registry.example.com/myapp:v1.2.3  # → sha256:def456...

Ne jamais déployer via tag mutable en production

Les tags latest, stable, main changent de contenu sans prevenir. Un déploiement reproductible exige un digest ou un tag immutable.

Digests

Un digest est l'identifiant immuable d'un manifest, calcule par hash SHA-256 :

# Tirer une image par digest — toujours le meme contenu
podman pull registry.example.com/myapp@sha256:abc123def456...

# Obtenir le digest d'une image locale
podman inspect --format='{{.Digest}}' myapp:v1.2.3

Stratégie de tagging recommandee

Tag Usage Mutable Production
v1.2.3 Version semantique Non* Oui
sha-a1b2c3d Hash du commit Git Non Oui
latest Dernière version Oui Non
main Branche principale Oui Non

*Immutabilite forcee par le registre (Harbor tag immutability rule).

Images multi-architecture

Construire des images multi-arch

# Avec Podman (ou Docker buildx)
podman manifest create registry.example.com/myapp:v1.2.3

podman build --platform linux/amd64 -t myapp:v1.2.3-amd64 .
podman build --platform linux/arm64 -t myapp:v1.2.3-arm64 .

podman manifest add registry.example.com/myapp:v1.2.3 \
  myapp:v1.2.3-amd64
podman manifest add registry.example.com/myapp:v1.2.3 \
  myapp:v1.2.3-arm64

podman manifest push registry.example.com/myapp:v1.2.3

Le registre stocke un index OCI qui pointe vers les deux manifests. Le client tire automatiquement la bonne architecture.

Helm charts comme artefacts OCI

Depuis Helm 3.8, les charts peuvent etre stockes dans un registre OCI au lieu d'un repository HTTP classique :

# Empaqueter le chart
helm package mychart/

# Se connecter au registre
helm registry login registry.example.com -u robot-ci

# Pousser le chart
helm push mychart-1.0.0.tgz oci://registry.example.com/charts

# Tirer le chart
helm pull oci://registry.example.com/charts/mychart --version 1.0.0

# Installer directement
helm install myrelease oci://registry.example.com/charts/mychart \
  --version 1.0.0

Un seul registre pour tout

Stocker images conteneur et charts Helm dans le même registre OCI simplifie la gestion des credentials, la réplication et le RBAC.

Attachements : SBOM et attestations

SBOM (Software Bill of Materials)

Un SBOM liste toutes les dependances d'une image. Il est attache a l'image dans le registre via les artefacts OCI.

# Generer un SBOM avec Syft
syft registry.example.com/myapp:v1.2.3 -o spdx-json > sbom.json

# Attacher le SBOM a l'image via ORAS
oras attach registry.example.com/myapp:v1.2.3 \
  --artifact-type application/spdx+json \
  sbom.json

# Ou via Cosign
cosign attach sbom --sbom sbom.json \
  registry.example.com/myapp:v1.2.3

Attestations de build (SLSA)

Les attestations SLSA (Supply-chain Levels for Software Artifacts) prouvent comment une image a ete construite :

# Attacher une attestation de provenance
cosign attest --predicate provenance.json \
  --type slsaprovenance \
  --key cosign.key \
  registry.example.com/myapp:v1.2.3

Signature d'images

Cosign (Sigstore)

Cosign est l'outil de référence pour signer les images conteneur. Il stocke les signatures directement dans le registre OCI.

# Generer une paire de cles
cosign generate-key-pair

# Signer une image
cosign sign --key cosign.key registry.example.com/myapp:v1.2.3

# Verifier une signature
cosign verify --key cosign.pub registry.example.com/myapp:v1.2.3

Signature keyless (Fulcio + Rekor)

En mode keyless, Cosign utilise un certificat ephemere via Fulcio et enregistre la signature dans un log de transparence (Rekor) :

# Signature keyless (OIDC)
COSIGN_EXPERIMENTAL=1 cosign sign registry.example.com/myapp:v1.2.3

# Verification keyless
cosign verify \
  --certificate-identity fabien@example.com \
  --certificate-oidc-issuer https://keycloak.example.com/realms/org \
  registry.example.com/myapp:v1.2.3

Notary v2 (Notation)

Notation (anciennement Notary v2) est une alternative a Cosign, portee par le projet CNCF Notary. Harbor supporte les deux.

Garbage collection

Les registres accumulent des layers orphelins (blobs non références par aucun manifest). Le garbage collection libere cet espace.

graph LR
    subgraph Avant["Avant GC"]
        M1a["manifest:v1.0"] --> LA["layer-A"]
        M1a --> LBa["layer-B"]
        M2a["manifest:v1.1"] --> LBa
        M2a --> LCa["layer-C"]
        Orphelin["(orphelin)"] -.-> LD["layer-D<br/>n'est plus reference"]
    end
    subgraph Apres["Apres GC"]
        M1b["manifest:v1.0"] --> LA2["layer-A"]
        M1b --> LBb["layer-B"]
        M2b["manifest:v1.1"] --> LBb
        M2b --> LCb["layer-C"]
    end

GC et disponibilité

Sur Docker Distribution, le GC necessite un arrêt du registre (mode lecture seule). Harbor effectue le GC en ligne sans interruption de service.

Réplication

La réplication synchronise les artefacts entre deux registres distants.

Push vs Pull

Mode Déclencheur Cas d'usage
Push Le registre source pousse vers la cible Réplication vers site DR, edge
Pull Le registre cible tire depuis la source Import depuis registre externe

Filtres de réplication

Filtre Exemple Effet
Nom library/** Replique uniquement le projet library
Tag v* Replique uniquement les tags de version
Label production=true Replique uniquement les images labellisees
Ressource image, chart, artifact Replique uniquement un type d'artefact

Topologies

graph TD
    Principal["Harbor<br/>(actif)<br/>Site principal"] -->|push| DR["Harbor<br/>(passif)<br/>Site DR"]
    Principal -->|push| Edge1["Harbor<br/>(edge-1)"]
    Principal -->|push| Edge2["Harbor<br/>(edge-2)"]

La réplication permet aussi d'importer des images depuis des registres publics (Docker Hub, Quay, GCR) vers le registre interne, creant ainsi un miroir contrôle.