Cette série est également disponible en anglais.
Read in English
Chapitre 7
Home LabTutorialsDevOps & Infrastructure

Comment auto-héberger un registre de conteneurs sur K3s avec Zot

19 min de lecture
Comment auto-héberger un registre de conteneurs sur K3s avec Zot
Apprenez à auto-héberger un registre de conteneurs privé et prêt pour la production sur K3s en utilisant Zot. Ce guide couvre l'installation avec Helm, l'intégration CI/CD, la signature d'images avec Cosign et l'analyse de vulnérabilités avec Trivy.

Auto-héberger un registre de conteneurs est l'un des moyens les plus simples de rendre un serveur maison plus rapide, plus privée et plus proche d'un véritable environnement de production. Au lieu de tirer chaque image depuis Docker Hub ou GitHub Container Registry, vous pouvez héberger votre propre registre directement dans K3s et garder vos images à proximité de l'endroit où elles sont construites et déployées.

Dans ce guide, je vais vous montrer comment auto-héberger un registre de conteneurs avec Zot sur K3s, incluant :

  • L'installation avec Helm
  • Les pushes d'images locales et les pulls Kubernetes
  • L'intégration CI/CD
  • La signature d'images avec Cosign
  • L'analyse de vulnérabilités avec Trivy
  • Les politiques de rétention
  • L'authentification
  • Et des améliorations de sécurité de niveau production.

L'objectif n'est pas seulement d'installer un registre, mais de construire une configuration de registre de conteneurs privé et pratique, suffisamment légère pour un cluster de Raspberry Pi, tout en enseignant les mêmes concepts utilisés dans de vraies plateformes de production.

Pourquoi auto-héberger un registre de conteneurs ?

La principale raison pour laquelle j'ai décidé d'auto-héberger mon registre de conteneurs est que cela conduit à des pushes et pulls plus rapides, ce qui peut considérablement accélérer les pipelines CI/CD. Dans mon cas, cela a entraîné une augmentation de vitesse de 2x car je n'ai pas à passer par le réseau pour récupérer mes conteneurs. Je les construis et les utilise déjà sur la même machine, alors pourquoi ne pas les héberger là aussi.

En stockant et en utilisant des images locales, nous assurons également une plus grande sécurité et confidentialité. Un autre avantage est que nous ne dépendons pas d'un fournisseur externe et ne sommes donc pas soumis aux limites de débit, aux limites de stockage ou aux pannes de services externes (n'est-ce pas, GitHub).

Néanmoins, l'auto-hébergement n'est peut-être pas la solution qui vous convient. Cela demande un certain travail de mise en place et de maintenance. Il ne sera pas non plus accessible globalement à moins que vous ne le rendiez tel, mais cela nécessitera une configuration plus complexe pour garantir une sécurité adéquate.

Cependant, si vous souhaitez en savoir plus sur Kubernetes et les registres de conteneurs, l'auto-hébergement est un excellent moyen de le faire.

Quelles sont nos options pour auto-héberger un registre ?

Il existe de nombreuses solutions pour l'auto-hébergement, chacune avec ses propres avantages et inconvénients.

Docker Registry est l'option la plus simple : il est léger, bien connu et facile à exécuter si tout ce dont vous avez besoin est "pousser une image, tirer une image". L'inconvénient est qu'il est très basique. Il est bon pour les configurations minimales, mais vous ressentez rapidement ses limites dès que vous voulez des opérations appropriées autour de vos images.

Harbor est le contraire : c'est une plateforme complète de registre de conteneurs. Il vous offre une interface utilisateur, des projets, des utilisateurs, du RBAC, de l'analyse de vulnérabilités, de la réplication, des politiques de rétention, des comptes robots, des journaux d'audit et une gouvernance de style entreprise. L'inconvénient est qu'il est lourd pour un homelab ou un petit cluster k3s. Il a plus de composants, une plus grande consommation de ressources, plus de configuration et plus de maintenance. Idéal pour les équipes ; excessif pour une seule personne.

Gitea/Forgejo est pratique car il peut héberger le code et les images de conteneurs au même endroit. Le compromis est que le registre n'est pas le produit principal. C'est suffisant pour des workflows simples, mais il est moins spécialisé que Harbor ou Zot pour les fonctionnalités spécifiques au registre.

Zot est un solide compromis pour l'auto-hébergement. Il est léger, natif OCI, moderne et plus ciblé que Docker Registry sans devenir aussi lourd que Harbor. Il est écrit en Go, ce qui le rend très rapide, et dispose d'une interface utilisateur de base. Il s'adapte bien aux homelabs, aux clusters en périphérie, à k3s, aux appareils ARM et aux workflows d'images privées. L'inconvénient est qu'il est moins familier que Docker Registry ou Harbor et son écosystème est plus petit. C'est un bon choix lorsque vous voulez un vrai registre sans exécuter une plateforme d'entreprise complète.

Dans ma configuration et cet article, nous utiliserons Zot car son aspect léger est très attrayant sans être trop basique.

Comment installer Zot

Comme tout bon utilisateur de Kubernetes, nous allons installer Zot via Helm. D'abord, nous aurons besoin d'un petit fichier de configuration pour activer l'interface utilisateur, la persistance et l'ingress. Appelons-le "values.yaml" :

replicaCount: 1
 
resources:
  requests:
    cpu: 5m
  limits:
    cpu: 200m
    memory: 256Mi
 
image:
  repository: ghcr.io/project-zot/zot-linux-arm64
  tag: "v2.1.17"
  pullPolicy: IfNotPresent
 
service:
  type: ClusterIP
  port: 5000
 
ingress:
  enabled: true
  className: traefik
  hosts:
    - host: zot.home.arpa
      paths:
        - path: /
          pathType: Prefix
 
persistence:
  enabled: true
  storageClass: "local-path"
  size: 10Gi
 
mountConfig: true
configFiles:
  config.json: |-
    {
      "storage": {
        "rootDirectory": "/var/lib/registry"
      },
      "http": {
        "address": "0.0.0.0",
        "port": "5000",
        "externalURL": "http://zot.home.arpa"
      },
      "log": {
        "level": "debug"
      },
      "sync": {
        "enable": true,
        "registries": [
          {
            "urls": ["https://registry-1.docker.io"],
            "onDemand": true,
            "content": [{ "prefix": "/**" }]
          }
        ]
      }
    }

Notez que, bien sûr, nous devrons créer un volume persistant pour stocker ces conteneurs. Dans ce cas, nous utilisons le local-path de k3s et allouons 10Gi. Nous définissons également où ce stockage est situé avec la configuration Zot "storage". Cela peut être différent selon votre configuration.

Lorsque vous utilisez le stockage local-path sur un Raspberry Pi, assurez-vous que votre rootDirectory dans la configuration de Zot correspond à l'endroit où le PVC est monté. Dans cette configuration, nous l'avons mappé sur /var/lib/registry pour garder les choses organisées et séparées des chemins de registre système par défaut.

Dans cette configuration, j'ai également défini Zot comme un cache/miroir pull-through. Chaque fois qu'une nouvelle construction nécessite une image de base (comme node:20 ou python:3.11), elle la tire normalement de Docker Hub. Le paramètre "sync" configure Zot pour agir comme un cache proxy pour Docker Hub, de sorte que ces images sont tirées une fois puis stockées dans Zot pour la prochaine fois, assurant des constructions plus rapides et une utilisation de bande passante plus faible. Vous évitez également ces satanées limites de débit de Docker Hub.

Comme cela fait partie de l'infrastructure globale de votre cluster, je vous suggère de stocker ce fichier de configuration dans un dépôt. J'ai un dépôt appelé "pie-k3s" qui contient de nombreux fichiers de configuration et scripts de déploiement pour de nombreux aspects de mon infrastructure.

Une liste verticale de répertoires de projet où le dossier du bas nommé 'zot' est en surbrillance.

Une fois que vous êtes prêt, installez-le avec Helm :

helm repo add zot https://zotregistry.dev/helm-charts
helm install zot zot/zot -n zot --create-namespace -f values.yaml

Si vous avez déjà configuré la redirection DNS locale pour .home.arpa comme nous l'avons fait dans un article précédent, vous devriez pouvoir visiter http://zot.home.arpa pour accéder à l'interface utilisateur. Nos images y seront affichées une fois que nous les aurons poussées.

Une interface de recherche avec des résultats pour divers dépôts, y compris 'thethoughtprocess', 'portfolio' et 'tts-backend'.

Comment configurer Docker et K3s pour Zot

Configuration de Docker local

Comme j'ai décidé de configurer Zot comme un service local uniquement, le https n'est pas configuré. Ainsi, pour pousser des images avec Docker depuis votre machine de développement, vous devez ajouter Zot comme un registre non sécurisé. Ajoutez ceci à votre "/etc/docker/daemon.json" (ou dans les paramètres de Docker Desktop) :

{
  "insecure-registries": ["zot.home.arpa"]
}

Par exemple :

L'écran des paramètres du moteur Docker dans Docker Desktop affichant le fichier de configuration JSON pour le démon Docker.

Vous pouvez ensuite pousser une image comme ceci :

docker tag my-app:latest zot.home.arpa/my-app:latest
docker push zot.home.arpa/my-app:latest

Configuration de K3s

Maintenant, nous devons dire à k3s d'utiliser Zot pour tirer les images ainsi que pour agir comme un proxy pull-through pour Docker. Ajoutez ceci à /etc/rancher/k3s/registries.yaml :

mirrors:
  "zot.home.arpa":
    endpoint:
      - "http://zot.home.arpa"
  "docker.io":
    endpoint:
      - "http://zot.home.arpa"

Puis redémarrez k3s avec

sudo systemctl restart k3s

Nous sommes maintenant prêts à utiliser Zot dans nos déploiements k3s.

Utiliser Zot dans les workflows CI/CD

Puisque Zot est auto-hébergé à l'intérieur du cluster, il est logique d'utiliser également un runner auto-hébergé. Sinon, votre pipeline CI/CD doit toujours pousser des images depuis les runners hébergés par GitHub sur l'Internet public. Cela supprimerait une grande partie de l'avantage de vitesse de garder le registre local et rendrait la configuration plus complexe. J'expliquerai comment configurer un runner auto-hébergé dans le prochain article.

Dans nos workflows Git, nous pouvons configurer Buildx pour utiliser Zot comme ceci :

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3
  with:
    buildkitd-config-inline: |
      [registry."zot.home.arpa"]
        http = true

Et taguez vos images avec zot.home.arpa/... pour le push.

 
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          network: host
          platforms: linux/arm64
          push: true
          tags: |
            zot.home.arpa/my-app:latest
		  cache-from: type=registry,ref=zot.home.arpa/my-app:buildcache
          cache-to: type=registry,ref=zot.home.arpa/my-app:buildcache,mode=max

N'oubliez pas, utiliser latest n'est pas une bonne idée pour de vraies applications de production. Ce n'est qu'un exemple.

Comme nous avons activé la mise en cache lors de l'installation de Zot, nous en profitons également en ajoutant les paramètres cache-to et cache-from à l'étape de construction.

Signer les images

Pourquoi devriez-vous signer les images

Lorsque vous regardez l'interface utilisateur de Zot, vous remarquerez peut-être que vos images sont marquées comme "non signées". La signature d'images est un élément essentiel de la sécurité de la chaîne d'approvisionnement, prouvant que l'image exécutée dans votre cluster a été construite par votre pipeline CI/CD et n'a pas été altérée. C'est important pour s'assurer que des acteurs malveillants ne peuvent pas exécuter sur votre cluster des images que vous n'avez pas signées.

Une interface utilisateur affichant les détails d'une image nommée boinc, avec une info-bulle sur une icône de bouclier rouge indiquant qu'elle n'est 'Pas signée'.

Créer des clés publiques-privées avec Cosign

Tout d'abord, téléchargez Cosign localement sur https://github.com/sigstore/cosign/releases et sélectionnez le bon exécutable en fonction de votre OS et de votre architecture. Par exemple cosign-windows-amd64.exe.

Ensuite, générez les clés. On vous demandera de taper un mot de passe, gardez-le précieusement, nous en aurons besoin :

cosign-windows-amd64.exe generate-key-pair

Cela créera deux fichiers :

cosign.key   # private key: keep secret
cosign.pub   # public key: safe to share

Dans les paramètres de votre dépôt GitHub, créez un secret appelé COSIGN_PRIVATE_KEY et COSIGN_PASSWORD et collez le contenu de cosign.key dans le premier et votre mot de passe dans le second.

La page des paramètres des secrets et variables de GitHub Actions affichant une liste des secrets de dépôt configurés.

Bien qu'il soit également possible d'utiliser la signature sans clé avec Cosign, celle-ci repose sur des services externes pour émettre et vérifier les certificats. Pour un homelab privé, une paire de clés traditionnelle est souvent plus simple car elle fonctionne entièrement hors ligne.

Signer les images avec Cosign

Tout d'abord, assurez-vous que l'extension de confiance est activée dans votre values.yaml pour Zot. Ensuite, redéployez Zot avec le nouveau fichier de configuration avec la confiance activée :

configFiles:
  config.json: |-
    {
      "extensions": {
        "trust": {
          "enable": true,
          "cosign": true
        }
      }
    }

Ensuite, téléversez votre clé cosign.pub directement sur l'API de Zot en utilisant cette commande. Zot stockera cette clé en toute sécurité dans son répertoire de stockage :


curl --data-binary @cosign.pub "http://zot.home.arpa/v2/_zot/ext/cosign"


Invoke-WebRequest `
  -Uri "http://zot.home.arpa/v2/_zot/ext/cosign" `
  -Method Post `
  -InFile "cosign.pub" `
  -ContentType "application/octet-stream"

Enfin, nous sommes prêts à signer nos images. Ajoutez ces étapes après avoir poussé votre image sur Zot :

- name: Install Cosign
  uses: sigstore/[email protected]
  
- name: Sign image with Cosign
  env:
    COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
    COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
        run: |
            export TLS_INSECURE_SKIP_VERIFY=true
            cosign sign --yes --key env://COSIGN_PRIVATE_KEY zot.home.arpa/my-app:${{ github.sha }}

Attention : Nous utilisons TLS_INSECURE_SKIP_VERIFY=true car notre registre Zot est servi en HTTP en interne. Dans un environnement d'entreprise de production utilisant HTTPS/TLS, vous ne devriez jamais ignorer la vérification. Assurez-vous plutôt que votre registre dispose de certificats valides.

Désormais, chaque fois que Zot analysera une nouvelle signature poussée par Cosign, il la vérifiera par rapport à votre clé publique téléversée et affichera fièrement votre image comme "De confiance" dans l'interface utilisateur !

Image conteneur vérifiée et signée avec Cosign

S'assurer que seules les images signées sont déployées avec Kyverno

À ce stade, Zot peut vérifier et afficher les signatures d'images, mais Kubernetes autorisera toujours le déploiement d'images non signées. Pour réellement appliquer la sécurité de la chaîne d'approvisionnement, nous avons besoin d'un contrôleur d'admission qui vérifie les signatures d'images avant même la création d'un Pod.

Pour cela, nous utiliserons Kyverno qui est léger, facile à installer et s'intègre directement avec les signatures Cosign.

Installer Kyverno

Ajoutez le dépôt Helm de Kyverno et installez-le :

helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
 
kubectl create namespace kyverno
 
helm install kyverno kyverno/kyverno \
  --namespace kyverno

Vérifiez que Kyverno est en cours d'exécution :

kubectl get pods -n kyverno

Créer une politique de vérification d'image

Créez un fichier appelé verify-images.yaml et enregistrez-le dans votre dépôt d'infrastructure :

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-zot-images
spec:
  validationFailureAction: Enforce
  background: false
 
  rules:
    - name: verify-signatures
      match:
        any:
          - resources:
              kinds:
                - Pod
 
      verifyImages:
        - imageReferences:
            - "zot.home.arpa/*"
          mutateDigest: true
          verifyDigest: true
          attestors:
            - entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      COLLEZ_VOTRE_CLE_PUBLIQUE_COSIGN_ICI
                      -----END PUBLIC KEY-----

Remplacez la clé publique par le contenu de votre fichier cosign.pub. Vous pourriez automatiser cela, je vous laisse cet exercice.

Enfin, appliquez la politique :

kubectl apply -f verify-images.yaml

Tester la politique

Le déploiement d'une image correctement signée devrait fonctionner normalement.

Cependant, si vous tentez de déployer une image non signée :

containers:
  - name: app
    image: zot.home.arpa/my-app:unsigned

Kyverno rejettera le déploiement et Kubernetes renverra une erreur similaire à :

failed policy verification:
image signature verification failed

Avec Kyverno qui impose les signatures, Kubernetes lui-même refuse d'exécuter des images non signées ou signées avec une clé non fiable.

Vérifier par digest

Avant de conclure sur Kyverno, parlons des digests.

Les tags comme ceux-ci sont mutables :

image: zot.home.arpa/my-app:latest

Aujourd'hui, latest peut pointer vers une image sûre A. Demain, quelqu'un peut faire pointer latest vers l'image B. Même si Kyverno vérifie le tag au moment de l'admission, le tag lui-même n'est pas une identité stable.

Un digest est immuable :

image: zot.home.arpa/my-app@sha256:abc123...

Cela pointe vers un manifeste d'image exact qui ne peut pas changer.

Donc, dans Kyverno, vous voudrez généralement :

mutateDigest: true # force k3s à utiliser le digest en mutant les tags vers le digest résolu
verifyDigest: true # exige que la référence d'image finale inclue un digest.

Ainsi, ceci :

zot.home.arpa/my-app:prd-latest

Devient automatiquement un digest une fois déployé :

zot.home.arpa/my-app@sha256:abc123...

Cela rend le déploiement reproductible et prévient les attaques par déplacement de tag.

Activer l'analyse de vulnérabilités (CVE)

Une image peut être signée, mais cela ne signifie pas qu'elle est sûre. Les vulnérabilités logicielles peuvent fournir aux acteurs malveillants des points d'entrée pour attaquer votre cluster. Pour augmenter votre sécurité, vous devez également rechercher les vulnérabilités.

Zot a une intégration intégrée avec Trivy, lui permettant d'analyser automatiquement toutes vos images de conteneurs pour les vulnérabilités de sécurité (CVE) et d'afficher les résultats directement dans l'interface utilisateur.

Comment activer l'analyse de vulnérabilités

Pour activer l'analyse des CVE, vous devez ajouter le bloc cve à l'intérieur de l'extension search dans votre fichier values.yaml :

configFiles:
  config.json: |-
    {
      "extensions": {
        "search": {
          "enable": true,
          "cve": {
            "updateInterval": "2h"
          }
        }
      }
    }

C'est tout, c'est aussi simple que ça !

Comment ça marche

Lorsque vous activez pour la première fois l'analyse des CVE (ou redémarrez Zot), Zot lancera immédiatement une tâche en arrière-plan pour télécharger les dernières bases de données de vulnérabilités depuis ghcr.io/aquasecurity/trivy-db. C'est une base de données de vulnérabilités connues, donc les nouvelles pourraient ne pas être trouvées, mais elle trouvera 99% de celles qui comptent.

Parce que ces bases de données peuvent être assez volumineuses, le téléchargement prendra quelques minutes. Pendant ce temps, si vous parcourez vos images dans l'interface utilisateur, vous pourriez les voir marquées comme "échec de l'analyse". C'est tout à fait normal ! Attendez simplement que le téléchargement en arrière-plan se termine, et Zot analysera automatiquement vos images et affichera les badges de gravité.

Une fois terminé, vous pouvez jeter un œil à vos images et corriger les vulnérabilités trouvées.

Un tableau de bord de sécurité affichant les résultats de l'analyse de vulnérabilités pour l'image de conteneur boinc:prd-latest, listant plusieurs CVE de gravité moyenne.

Bloquer les images vulnérables avant qu'elles n'atteignent Kubernetes

Tout comme les images signées, Zot peut afficher les CVE dans l'interface utilisateur, mais il ne vous empêche pas automatiquement de pousser ou de déployer des images vulnérables. Pour cela, vous avez besoin d'une porte de politique (policy gate).

La protection la plus simple est de faire échouer votre pipeline CI/CD avant de pousser l'image :

- name: Scan image with Trivy
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: zot.home.arpa/my-app:${{ github.sha }}
    severity: MEDIUM,HIGH,CRITICAL
    exit-code: 1
    ignore-unfixed: true

exit-code: 1 fait échouer le workflow lorsque des vulnérabilités correspondantes sont trouvées. Par défaut, Trivy se termine avec succès même s'il trouve des problèmes, donc ce drapeau est important.

Nous avons maintenant un pipeline de déploiement en production plus sécurisé. Un exemple complet est montré à la fin de l'article :

  1. Construire l'image.
  2. Signer l'image avec Cosign.
  3. Pousser uniquement les images signées.
  4. L'analyser avec Trivy.
  5. Faire échouer le pipeline si des CVE de niveau MEDIUM/HIGH/CRITICAL sont trouvées.
  6. Utiliser Kyverno pour rejeter les images non signées dans Kubernetes.

Aller plus loin

Stockage, nettoyage et rétention

Les registres de conteneurs peuvent croître rapidement. Chaque fois que vous poussez un nouveau tag, le registre stocke des manifestes et des couches. Les couches sont partagées lorsque c'est possible, mais les anciens tags et les objets non référencés peuvent toujours s'accumuler avec le temps.

C'est pourquoi vous devriez surveiller l'utilisation du disque sur le PVC du registre. Pour vérifier l'utilisation actuelle d'un stockage, Kubernetes ne fournit aucun bon outil (Bizarre, non ?). La meilleure façon que j'ai trouvée est d'utiliser Prometheus et de l'interroger avec :

100 *
kubelet_volume_stats_used_bytes{namespace="zot"}
/
kubelet_volume_stats_capacity_bytes{namespace="zot"}

Ajouter une politique de rétention

Une fois que votre registre commence à accumuler des centaines de tags d'images, la suppression manuelle des anciennes images devient fastidieuse. Zot prend en charge les politiques de rétention qui peuvent préserver automatiquement les tags importants tout en supprimant les plus anciens.

Par exemple, vous pourriez vouloir :

  • Toujours conserver latest, prd-latest, et preprod-latest
  • Conserver toutes les versions publiées telles que v1.2.3
  • Conserver les 30 tags SHA les plus récemment poussés
  • Supprimer automatiquement les anciens tags SHA

Une telle politique de rétention peut être configurée directement dans le fichier values.yaml de Zot :

{
  "storage": {
    "retention": {
      "dryRun": false,
      "delay": "24h",
      "policies": [
        {
          "repositories": ["**"],
          "keepTags": [
            {
              "patterns": ["latest", "prd-latest", "preprod-latest"]
            },
            {
              "patterns": ["v.*"]
            },
            {
              "mostRecentlyPushedCount": 30
            }
          ]
        }
      ]
    }
  }
}

Comme avec tout système de nettoyage automatisé, commencez avec une politique conservatrice et vérifiez les résultats avant d'activer la suppression automatique en production.

Une fonctionnalité particulièrement intéressante dans les versions récentes de Zot est un mode de simulation de rétention (dry-run), qui vous permet de prévisualiser ce qui serait supprimé avant d'activer la politique pour de vrai. Mettez "dryRun": true pour tester votre politique avant de l'appliquer réellement. Sinon, vous pourriez supprimer par accident des images actuellement déployées.

Authentification et secrets du registre

La configuration actuelle de Zot que nous avons faite est publique à l'intérieur du réseau local et du cluster : pas de mot de passe, pas d'utilisateur de registre, et pas de secret de pull. Cela peut être acceptable pour un petit homelab, mais nous pouvons faire mieux.

Comment créer des mots de passe secrets pour Zot avec htpasswd

Une meilleure approche consiste à activer l'authentification Zot avec htpasswd, à stocker les informations d'identification en tant que Secret Kubernetes, et à ne donner à Kubernetes que les informations d'identification minimales dont il a besoin pour tirer les images.

Zot prend en charge htpasswd, et les hachages bcrypt peuvent être générés avec :

htpasswd -bBn ci-user "your-ci-password"
htpasswd -bBn cluster-user "your-cluster-password"

Dans ce scénario, je crée deux utilisateurs distincts :

  • ci-user : utilisé par le CI/CD pour pousser les images
  • cluster-user : utilisé par Kubernetes pour tirer les images

Ne commitez pas de vrais mots de passe ou de vrais hachages htpasswd dans Git. Même les mots de passe hachés peuvent être forcés par brute-force hors ligne si votre dépôt fuite. Pour la production, utilisez les Secrets de GitHub ou de Kubernetes.

Pour une configuration simple avec K3s, vous pouvez créer le secret htpasswd de Zot directement dans le cluster :

kubectl create secret generic zot-htpasswd \
  --namespace zot \
  --from-file=htpasswd=./htpasswd

Ensuite, configurez Zot pour monter ce fichier secret et l'utiliser pour l'authentification :

mountSecret: true
existingSecret: zot-htpasswd

Et dans la configuration de Zot :

"http": {
  "address": "0.0.0.0",
  "port": "5000",
  "externalURL": "http://zot.home.arpa",
  "auth": {
    "htpasswd": {
      "path": "/secret/htpasswd"
    }
  }
}

Comment configurer K3s pour utiliser les mots de passe Zot

Créez un secret de pull Kubernetes pour les charges de travail qui doivent tirer depuis Zot :

kubectl create secret docker-registry zot-registry \
  --namespace my-app \
  --docker-server=zot.home.arpa \
  --docker-username=cluster-user \
  --docker-password='your-cluster-password'

Utilisez ce secret de pull dans votre Déploiement :

spec:
  template:
    spec:
      imagePullSecrets:
        - name: zot-registry
      containers:
        - name: my-app
          image: zot.home.arpa/my-app:latest

Si de nombreuses charges de travail dans le même namespace ont besoin de tirer depuis Zot, vous pouvez attacher le secret de pull au ServiceAccount par défaut du namespace :

kubectl patch serviceaccount default \
  --namespace my-app \
  -p '{"imagePullSecrets":[{"name":"zot-registry"}]}'

Maintenant, chaque Pod utilisant le ServiceAccount par défaut dans ce namespace peut tirer depuis Zot sans répéter imagePullSecrets dans chaque Déploiement.

RBAC affiné avec Zot

Vous pouvez également restreindre ce que chaque utilisateur de Zot peut faire. Par exemple, ci-user peut pousser et supprimer des images, tandis que cluster-user ne peut que les tirer :

"accessControl": {
  "repositories": {
    "**": {
      "anonymousPolicy": [],
      "policies": [
        {
          "users": ["ci-user"],
          "actions": ["create", "read", "update", "delete"]
        },
        {
          "users": ["cluster-user"],
          "actions": ["read"]
        }
      ]
    }
  }
}

Pour une isolation plus forte, créez différents utilisateurs de pull par namespace ou par application. Par exemple :

blog-puller
portfolio-puller
monitoring-puller

Ensuite, restreignez chaque utilisateur aux seuls dépôts dont il a besoin. Cela empêche qu'un namespace compromis puisse tirer toutes les images privées de votre registre.

Pour un véritable environnement de production, vous vous éloigneriez généralement du htpasswd statique et intégreriez le registre avec un fournisseur d'identité centralisé tel que LDAP, Active Directory ou OIDC. De plus, le chiffrement des secrets Kubernetes devrait être activé et les informations d'identification renouvelées automatiquement.

Utilisation externe de Zot via https

Notez également que nous utilisons Zot avec http et non https. C'est parce qu'il ne sera utilisé que localement par notre cluster et notre runner auto-hébergé. Si vous utilisez un runner hébergé par GitHub, vous devrez déployer Zot avec https en utilisant un nom de domaine. Vous pourriez acheter un domaine pour votre cluster et utiliser un sous-domaine pour Zot comme https://zot.my-pie.com.

Cependant, vous devrez vous assurer qu'il est sécurisé. Pour un homelab ou une petite configuration privée, j'éviterais d'exposer Zot directement à Internet. Si un accès externe est nécessaire, un tunnel Cloudflare est généralement plus sûr et plus simple que d'ouvrir des ports sur le routeur, de gérer les changements d'IP publique et d'exposer Traefik directement. Vous obtenez toujours le HTTPS et un vrai domaine, mais votre réseau domestique n'a pas besoin d'avoir des ports entrants ouverts.

Processus de sauvegarde et de reprise après sinistre

Pour être résilient aux pannes ou autres sinistres, sauvegardez régulièrement le PVC de Zot. Le registre est avec état (stateful) ; perdre le PVC signifie perdre les images, les signatures, les métadonnées et les clés de confiance téléversées. Vous devez sauvegarder :

  • Le PVC
  • La configuration de Zot
  • htpasswd / les secrets
  • La clé publique Cosign / la gestion des clés de signature

Vous pourriez également tester périodiquement la restauration du PVC de Zot dans un nouveau namespace ou un nouveau cluster. Un bon processus de reprise après sinistre doit toujours être testé régulièrement, pas lorsqu'un sinistre survient.

Séparer le registre de développement et de production

Enfin, un petit commentaire sur la séparation des environnements. Dans un véritable environnement de production, vous verrez souvent un registre de conteneurs distinct pour les images de production et de non-production. Cela aide à bien des égards, comme tester de nouvelles politiques de rétention, le RBAC et plus encore sans affecter le registre de production. Ce n'est qu'une fois que vous êtes sûr des changements que vous pouvez appliquer les mêmes à la production.

Une fois que vous avez des registres d'environnement séparés, envisagez de définir un workflow de promotion où les applications sont construites et testées dans le registre de non-production, puis promues vers le registre de production.

Alertes et surveillance

Il y a beaucoup plus que nous pourrions faire pour améliorer cette configuration. Par exemple, ajouter des alertes et de la surveillance pour :

  • Utilisation du PVC > 80%
  • Pod Zot en panne
  • Erreurs 5xx
  • Échecs de pull/push
  • Échecs de mise à jour de la base de données CVE
  • Échecs de rétention/GC
  • Expiration du certificat si HTTPS Mais cela devient trop long, alors je vous laisse le soin de le faire.

Conclusion

Voilà, je pensais que ce serait un article plus court, mais il s'avère que les registres de conteneurs sont complexes. Surtout si vous voulez les rendre sûrs et solides pour une utilisation réelle en production. Si vous avez activé tous les mécanismes de sécurité, vous devriez avoir quelque chose de solide, mais pour un homelab, vous n'aurez peut-être pas besoin de tout cela. C'est à vous de décider.

Notez que cette configuration est parfaitement adaptée à un cluster à un seul nœud. Cependant, dans un cluster à plusieurs nœuds, le stockage local-path lie les données du registre à un seul nœud, créant un point de défaillance unique. Pour une haute disponibilité, exécutez plusieurs répliques de Zot et utilisez un stockage partagé et tolérant aux pannes tel que Longhorn, afin que le registre reste disponible même si un nœud tombe en panne.

Annexe : Exemple complet de workflow CI/CD avec Zot

Voici un exemple complet de workflow GitHub Actions utilisant Zot comme registre de conteneurs local.

Cet exemple :

  • construit une image ARM64
  • la pousse sur Zot
  • utilise Zot comme cache de registre
  • analyse l'image avec Trivy
  • signe l'image avec Cosign
  • la déploie sur K3s
name: Build and deploy with Zot
 
on:
  push:
    branches:
      - main
 
env:
  REGISTRY: zot.home.arpa
  IMAGE_NAME: my-app
  NAMESPACE: my-app
 
jobs:
  build-and-deploy:
    runs-on: self-hosted
 
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
 
      - name: Set image tags
        run: |
          echo "IMAGE=${REGISTRY}/${IMAGE_NAME}" >> $GITHUB_ENV
          echo "SHA_TAG=${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}" >> $GITHUB_ENV
          echo "LATEST_TAG=${REGISTRY}/${IMAGE_NAME}:prd-latest" >> $GITHUB_ENV
 
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          buildkitd-config-inline: |
            [registry."zot.home.arpa"]
              http = true
 
      - name: Login to Zot
        uses: docker/login-action@v3
        with:
          registry: zot.home.arpa
          username: ci-user
          password: ${{ secrets.ZOT_CI_PASSWORD }}
 
      - name: Build and push image
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/arm64
          push: true
          tags: |
            ${{ env.SHA_TAG }}
            ${{ env.LATEST_TAG }}
          cache-from: type=registry,ref=zot.home.arpa/my-app:buildcache
          cache-to: type=registry,ref=zot.home.arpa/my-app:buildcache,mode=max
 
      - name: Scan image with Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.SHA_TAG }}
          severity: MEDIUM,HIGH,CRITICAL
          exit-code: 1
          ignore-unfixed: true
 
      - name: Install Cosign
        uses: sigstore/[email protected]
 
      - name: Sign image with Cosign
        env:
          COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
          COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
        run: |
          cosign sign \
            --yes \
            --key env://COSIGN_PRIVATE_KEY \
            $SHA_TAG
 
      - name: Deploy to K3s
        run: |
          kubectl -n $NAMESPACE set image deployment/my-app \
            my-app=$SHA_TAG
 
          kubectl -n $NAMESPACE rollout status deployment/my-app
#kubernetes#home-server#networking#devops
Judicael Poumay (Ph.D.)

Judicael Poumay (Ph.D.)

Suivez-moi sur LinkedIn pour du contenu hebdomadaire Judicaël Poumay

En tant que chercheur/développeur IA indépendant spécialisé en Traitement du Langage Naturel (NLP), j'ai une expertise complète dans le développement et l'intégration de systèmes d'IA, ainsi que l'analyse de données.

Votre entreprise cherche à intégrer des solutions IA, analyser des données ou renforcer son développement back-end ? Contactez-moi !

Offrez-moi une bière 🍺

Articles Similaires