Connectes-toi pour publier un commentaire.
Publié le 25/04/2021 à 19:50.
Ou comment contourner les limites d'utilisation de Docker Hub. Et sans oublier Docker-in-Docker !
Publié le 25/04/2021 à 19:50.
Ou comment contourner les limites d'utilisation de Docker Hub. Et sans oublier Docker-in-Docker !
Photo de Ludovic Charlet sur Unsplash.
Comme pour mon tout premier article, je décide de faire face à de nouvelles restrictions: cette fois ce sont les limites de téléchargement d'images sur Docker Hub:
J'ai pensé moi aussi que ce serait suffisant, jusqu'à ce que ma pipeline s'arrête lors d'un déploiement en production.
Ce problème ne se pose pas si vous passez par les shared runners de GitLab, ce qui n'est pas mon cas puique j'ai mon propre runner.
L'objectif est d'utiliser mon propre registre Docker. Pour ceux qui ne verraient pas de quoi il s'agit, c'est là où sont stockées les images. Il existe Docker Hub, votre registre local, mais vous pouvez faire le vôtre si ça vous chante: il existe l'image registry !
Sauf que je ne vais pas simplement créer mon registre mais en faire un mirroir de Docker Hub, un "pull through cache":
Pour des raisons de simplicité, le registre sera installé sur le même serveur que le runner. Rien ne vous empêche de les séparer.
Mais pour pouvoir m'adapter aux éventuelles limitations futures et modifier facilement ma configuration, j'ai créé un dépôt GitLab avec une pipeline, spécialement pour le GitLab Runner (ce qui est complètement facultatif).
Le but du dépôt peut être de mettre à jour le GitLab Runner, on peut y enregistrer le fichier config.toml qui sera mis à jour.
Aussi, puisque la pipeline peut potentiellement faire redémarrer le runner, elle ne doit pas être exécutée par celui-ci mais par les "shared runners".
Pour cela il faudra aller dans Settings > CI/CD > Runners. Dans la colonne Shared runners, cocher la case « Enable shared runners for this project » et cliquer sur « Disable group runners ».
Note: côté performances, la pipeline dure en moyenne environ 30 secondes, pas de quoi s'inquiéter.
Pour que la pipeline puisse accéder au serveur, on configure une paire de clés SSH.
Pour utiliser la clé privée, on l'enregistre en tant que variable dans Settings > CI/CD > Variables. Chez moi ce sera SSH_PRIVATE_KEY
.
On peut ensuite commencer la création de la pipeline. Aucun besoin spécifique, j'utilise donc une image d'Alpine Linux. Mais puisque cette image ne dispose pas d'un client SSH, il faudra en installer un:
image: alpine:3
variables:
RUNNER_IP: "123.1.2.3"
stages:
- deploy
deploy-runner:
stage: deploy
only:
- master
before_script:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
On a donc une pipeline avec un unique job. Notez également qu'il ne s'exécute que pour notre branche master
et que l'adresse du runner est enregistrée dans une variable à utiliser plus tard.
La première étape du job deploy-runner
sera de mettre à jour le fichier de configuration du runner. Si le fichier a été modifié, on le met à jour et on relance le runner:
deploy-runner:
...
script:
- cat ./config.toml | ssh user@$RUNNER_IP "cat > ./config.toml"
- ssh user@$RUNNER_IP "[[ ./config.toml -ef /etc/gitlab-runner/config.toml ]] || { mv ./config.toml /etc/gitlab-runner/config.toml && gitlab-runner restart; }"
Note: Pour la mise en place du registre, vous n'aurez pas forcément besoin de changer la configuration du runner.
Dans les différentes documentations que j'ai pu lire pour réaliser ce projet, il est mentionné que des identifiants Docker Hub sont utiles pour pouvoir télécharger des images privées. Cependant je n'ai réussi à faire fonctionner le registre sans, donc je vous conseille d'en générer.
Dans un premier temps, créez un compte sur Docker Hub ou connectez-vous.
Une fois connecté, rendez-vous dans la section sécurité des paramètres du compte: Account Settings > Security.
Vous pourrez créer un jeton d'accès en cliquant sur « New Access Token ». Indiquez une description et gardez de côté le jeton fourni pour la suite.
Le registre s'exécute en tant que conteneur. Pour faciliter sa gestion, j'ai choisi de passer par Docker Compose que j'ai donc installé sur la machine.
Et voici le docker-compose.yml:
version: "3.7"
services:
registry:
image: registry:2.7
restart: always
ports:
- 5000:5000
volumes:
- "./registry-config.yml:/etc/docker/registry/config.yml"
On expose le port 5000 et on spécifie un volume pour le fichier de configuration du registre.
La section qui nous intéresse dans ce fichier est proxy
, qu'on rajoute donc après la configuration par défaut (que vous pourrez trouver dans l'image de base):
version: 0.1
log:
fields:
service: registry
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
proxy:
remoteurl: https://registry-1.docker.io
username: USERNAME
password: DOCKER_HUB_ACCESS_TOKEN
La clé proxy.remoteurl
va configurer le registre pour qu'il agisse comme un "pull through cache" en se basant sur celui de Docker Hub.
Remplacez la valeur de proxy.username
par votre nom d'utilisateur Docker Hub, et proxy.password
par le jeton d'accès précédemment créé.
Dans notre job GitLab CI, on rajoute quelques commandes pour le démarrage du registre:
...
deploy-runner:
...
script:
...
- cat ./docker-compose.yml | ssh user@$RUNNER_IP "cat > ./docker-compose.yml"
- cat ./registry-config.yml | ssh user@$RUNNER_IP "cat > ./registry-config.yml"
- ssh user@$RUNNER_IP "docker-compose up -d"
La commande docker-compose up
démarre ou met à jour des services, parfait pour notre besoin.
Il faut maintenant indiquer au Docker daemon d'utiliser le registre.
Dans une configuration simple utilisant le shell executor ou le docker executor avec docker socket binding il s'agit du daemon de la machine hôte.
On peut configurer ce dernier avec un fichier de configuration JSON dans lequel on indiquera le registre mirroir à utiliser. Il faudra ensuite relancer le Docker daemon, il est donc conseillé d'activer le Live Restore pour ne pas arrêter les conteneurs démarrés:
{
"registry-mirrors": ["http://123.1.2.3:5000"],
"live-restore": true
}
Note: Veillez à bien indiquer le protocole HTTP si vous n'avez pas configuré le HTTPS, et à ne pas oublier le numéro de port.
Dans le job GitLab CI, on indiquera de relancer le Docker daemon si le fichier de configuration a changé:
...
deploy-runner:
...
script:
...
- cat ./docker-daemon.json | ssh user@$RUNNER_IP "cat > ./docker-daemon.json"
- ssh user@$RUNNER_IP "[[ ./docker-daemon.json -ef /etc/docker/daemon.json ]] || { mv ./docker-daemon.json /etc/docker/daemon.json && systemctl reload docker; }"
Dans mon cas, j'utilise le DinD pour que les jobs puissent exécuter des commandes docker
. Le DinD est configuré en tant que service dans les pipelines, on va donc ajouter une option au service pour qu'il utilise le registre.
Cela peut se faire directement dans le fichier de configuration du runner, ou bien dans le fichier gitlab-ci.yml:
services:
- name: docker:20.10.6-dind
command: ["--registry-mirror", "http://123.1.2.3:5000"]
alias: docker
Attention il ne s'agit pas du fichier de la pipeline du runner mais bien d'un projet dont les pipelines s'exécuteront sur celui-ci.
Puisque l'utilisation du registre est transparente, on peut se demander si tout fonctionne comme prévu. Exécuter la commande docker image ls
n'est pas suffisant car vous n'aurez aucune certitude du registre utilisé.
Téléchargez des images avec un simple docker pull
ou en exécutant une pipeline si vous utilisez le DinD.
Un registre peut exposer les images qu'il possède via une route HTTP, dans du JSON:
curl http://123.1.2.3:5000/v2/_catalog
[output]{"repositories":["library/docker", "library/alpine"]}
Si vous retrouvez les images utilisées dans votre pipeline, c'est tout bon !
Beaucoup de manipulations pour contourner une limitation, mais ça peut vous permettre d'en découvrir un peu plus sur le fonctionnement de Docker. Pour l'instant je ne saurai dire si ce registre aura un impact sur la performance des pipelines.
Une chose est sûre, ça permet d'avoir un peu plus le contrôle sur l'environnement de la CI.
Connectes-toi pour publier un commentaire.