Installer un registre privé d’images docker (registry)

Cet article décrit comment mettre en place un docker registry local, exposé en HTTPS avec un certificat TLS officiel grâce à Let’s Encrypt, permettant de déployer des conteneurs à partir d’images hébergées sur son réseau local.

J’ai un NAS (QNAP) sur lequel tourne un mini cluster Kubernetes (K3s) qui héberge des applications internes qui tournent en permanence sur mon réseau local. J’ai aussi un ordinateur de travail sur lequel j’effectue ponctuellement des tests avec un autre cluster Kubernetes plus standard, qui me sert pour de la formation et de la veille. Ce cluster n’est pas allumé en permanence. Et il est possible que ponctuellement je veuille utiliser Docker depuis d’autres mini-pc (voire sur un Raspberry).

J’ai donc besoin d’un registry pour les images docker à partir desquelles déployer des applications internes, que je ne souhaite pas forcément rendre publique, ni payer pour les héberger dans un dépôt privé du cloud. J’aurai pu choisir d’héberger cette registry dans le cluster K3s du NAS mais il semble que seuls les ports 61000-62000 soient disponibles, donc cela posera un problème avec le port standard 5000 de la registry. Je n’ai pas voulu m’embêter, et je trouve finalement « pratique » de faire tourner la registry sur un autre mini-pc qui ne sera pas allumé en permanence (nécessaire que lorsqu’une nouvelle image doit être déployée).

A ma connaissance, il n’est pas possible ou en tout cas il n’est pas supporté par QNAP d’utiliser un « Insecure Registry », c’est-à-dire un registry sans certificat TLS (https). Quasiment rien n’est paramétrable dans le cluster K3s mis à disposition par QNAP et je préfère ne pas chercher à le faire pour éviter des problèmes lors de futures mises à jour de QNAP. J’ai donc besoin d’un registry exposé en https, raison pour laquelle j’utilise Let’s Encrypt pour obtenir un certificat TLS.

Dans mon cas, j’héberge ce registre sur un « mini pc » dans mon réseau local, avec CentOS.

Il faut y avoir installé Docker. Je n’ai installé que les packages docker-ce, docker-ce-cli, containerd.io mais vos besoins peuvent varier.

En supposant que vous avez Docker qui tourne, prêt à l’emploi, il reste deux étapes:

  • Générer un certificat TLS pour votre future registry.
  • Déployer la registry avec les paramètres adéquats.

Générer un certificat via Let’s Encrypt en mode manuel

Il n’est pas supporté par Let’s Encrypt de générer un certificat pour un domaine non publique. C’est pourtant un cas d’utilisation tout à fait viable, et il faut donc user d’un peu d’astuce: il faut que vous possédiez un nom de domaine publique, même si au final, vous n’utiliserez ce nom de domaine que depuis un réseau local. Dans mon cas, je possédais déjà un nom de domaine et un hébergement. Le plus simple a donc été de créer un sous-domaine de mon domaine déjà détenu (c’est généralement gratuit), puis d’y stocker un fichier généré par l’outil certbot de Let’s Encrypt. Les serveurs de Let’s Encrypt doivent pouvoir y accéder via Internet, temporairement pour générer le certificat.

Le plus simple est d’utiliser certbot depuis le serveur qui va héberger le registry. Pour l’installer:


sudo yum -y install epel-release
sudo yum -y install certbot

En supposant que vous ayez mis en place votre nom de domaine « registry.mon-domaine.com », et que celui pointe un espace d’hébergement, la commande certbot est la suivante:


export DOMAIN="registry.mon-domaine.com"
sudo certbot certonly --manual -d $DOMAIN --preferred-challenges http

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Create a file containing just this data:

xyz...

And make it available on your web server at this URL:

http://registry.mon-domaine.com/.well-known/acme-challenge/abcdef

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

L’outil en mode interactif va vous indiquer de créer un fichier texte (sans extension) sur votre hébergement web, avec un contenu. Il suffit de respecter ses instruction. On pourra tester depuis un navigateur web que l’URL indiquée retourne bien la valeur indiquée. Une fois que vous avez confirmé que la valeur est bien retournée, il suffit de confirmer dans le terminal certbot. Le résultat doit être un succès, les fichiers *.pem seront stockés dans /etc/letsencrypt/live/$DOMAIN/.


Waiting for verification...
Resetting dropped connection: acme-v02.api.letsencrypt.org
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/registry.mon-domaine.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/registry.mon-domaine.com/privkey.pem
   Your certificate will expire on 2024-03-23. To obtain a new or
   tweaked version of this certificate in the future, simply run
   certbot again. To non-interactively renew *all* of your
   certificates, run "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

Déployer la registry

Il faudra copier les certificats dans un répertoire qu’on montera ensuite comme volume avec docker:


sudo mkdir /certs/$DOMAIN
sudo cp /etc/letsencrypt/live/$DOMAIN/fullchain.pem /certs/$DOMAIN/fullchain.pem
sudo cp /etc/letsencrypt/live/$DOMAIN/privkey.pem /certs/$DOMAIN/privkey.pem
sudo cp /etc/letsencrypt/live/$DOMAIN/cert.pem /certs/$DOMAIN/cert.pem

On crée également le répertoire de données sur le serveur, qu’on montera comme un volume quand on déploiera la registry. Puis on déploie via l’image officielle:


sudo mkdir /var/lib/docker/registry
docker run -d --name docker-registry \
--restart=always -p 5000:5000 \
-e REGISTRY_HTTP_ADDR=0.0.0.0:5000 
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/$DOMAIN/fullchain.pem \
-e REGISTRY_HTTP_TLS_KEY=/certs/$DOMAIN/privkey.pem \
-v /certs/$DOMAIN:/certs \
-v /var/lib/docker/registry:/var/lib/registry registry:2 

docker ps

CONTAINER ID   IMAGE        COMMAND                  CREATED              STATUS              PORTS                    NAMES
76b9309cd063   registry:2   "/entrypoint.sh /etc…"   About a minute ago   Up About a minute   0.0.0.0:5000->5000/tcp   docker-registry

Maintenant, pour que cela fonctionne, il faut pouvoir accéder depuis le LAN à l’adresse qui correspond au certificat, qui est donc une adresse publique, déclarée dans les DNS publics. Il y a différentes solutions. Dans mon cas, j’ai également un serveur pfSense dans mon réseau local, avec un service DNS Resolver qui me permet de forcer la résolution de n’importe quel domaine vers n’importe quelle adresse IP. C’est donc très simple et très efficace car ça marche sur toute machine du réseau local. Sans cela, il faut modifier le fichier hosts de chaque machine du réseau qui a besoin d’accéder au registry local.

On peut tester que ça fonctionne depuis une autre machine du réseau:


curl https://registry.mon-domaine.com:5000/v2/_catalog

{"repositories":[]}

On voit dans le résultat précédent que la registry ne contient aucune image, mais ça marche, sans avertissement, donc le certificat TLS est bien mis en place.

L’inconvénient de la méthode « manual » avec certbot est qu’il faudra faire les mêmes manipulations pour chaque renouvellement du certificat, apparemment tous les 3 mois. Mais son avantage est qu’elle est plus souple si votre hébergeur ne propose pas une intégration avec Let’s Encrypt, et si vous n’avez pas un accès SSH direct à votre hébergement web (ce qui n’est généralement pas le cas pour les hébergements les moins chers).