Monter un registry docker privé

Si vous n’avez pas de compte payant sur docker hub mais que vous ne voulez pas publier publiquement une image docker il va falloir trouver des solutions pour pouvoir héberger vos images docker. Par exemple en montant un registry docker privé pour héberger vos images docker.

Dans cet article nous allons voir comment monter un registry docker et comment y accéder en HTTPs sans avoir à déployer de certificat. 

Pour cet exemple je vais utiliser un petit serveur START1-S chez scaleway à moins de 2 euros par mois.

Je préfère avoir de toutes petites instances de serveurs, plutôt que tout avoir sur la même machine. Si jamais une des machines se fait pirater, ou si elle a une panne matérielle, il n’y aura qu’un de mes projets perso qui sera hors ligne.

Installer docker

Dans un premier temps il va falloir installer docker sur la machine. Pour cette étape le mieux est de suivre le tuto présent sur la documentation officielle en fonction de votre OS.

Mettre en place une connexion https

Plusieurs méthodes sont possibles, de la plus simple à la plus complexe. Vous pourrez trouver des tutos pour le faire via du let’s encrypt mais je suis parti sur quelque chose de plus simple. Comme je gère mes DNS avec cloudflare je vais utiliser le SSL flexible.

Ce n’est pas un SSL de bout en bout mais c’est suffisant pour ce que je fais sur domaine lahaxe.fr. Il n’y a en effet aucune donnée critique qui transite dessus.

Créer un sous domaine

Dans le menu DNS il va falloir ajouter une entrée, par exemple dans mon cas « registry.lahaxe.fr« , et la faire pointer vers votre serveur.

Activer le SSL flexible dans cloudflare

Dans le menu Crypto il y a une section SSL. Dans le menu déroulant il faut sélectionner Flexible.

Encore une fois, c’est satisfaisant pour un projet perso, mais ça ne l’est pas pour de la vraie production.

Lancer le registry

Il existe une image officielle pour monter son propre registry. Elle est très sobrement nommé registry

L’image expose le port 5000 mais on va le mapper sur le port 80 afin de profiter du SSL flexible de Cloudflare. 

docker run -d -p 80:5000 
  --restart=always 
  --name registry 
  -v /opt/docker-registry:/var/lib/registry  
  registry:2

Vous pouvez vérifier le bon fonctionnement en consultant l’url https://registry.lahaxe.fr/v2/_catalog (dans mon cas).

A partir de ce moment vous avez un registry fonctionnel mais public. N’importe qui peut pousser et télécharger une image dessus. Nous verrons dans la section « sécuriser le registry » comment mettre en place la sécurité sur le registry.

Uploader une image

Maintenant que nous avons notre registry de disponible nous allons pouvoir lui pousser une image pour nous assurer que tout est bien configuré.

# pull d'une image sur le hub
docker pull alpine

# on re-tag l'image 
docker image tag alpine registry.lahaxe.fr/alpine

# push de l'image sur notre registry
docker push registry.lahaxe.fr/alpine

Sécuriser le registry

Avoir un registry ouvert est une très mauvaise pratique. Afin de le sécuriser un minimum nous pouvons mettre en place deux sécurités de base. 

Filtre par IP

L’idée est de donner les accès seulement à vos serveurs et aux personnes qui doivent interagir avec. Il va donc falloir sécuriser le port 80 de serveur (ou le 443 si vous faites du SSL de bout en bout).

De mon côté je vais utiliser les security policies de scaleway pour bloquer le port. Attention: Il faut bien penser à redémarrer le serveur après avoir changé les règles de firewall scaleway. Mais sinon vous pouvez mettre en place un filtrage via iptable ou via shorewall.

Mettre en place de l’authentification

Dans un premier temps il va falloir générer un fichier htpasswd avec le ou les logins et mots de passes.

mkdir auth

docker run 
   --entrypoint htpasswd 
   registry:2 -Bbn username MySuperP4ssw0rd123 > auth/htpasswd

Il suffit de dire à l’image docker de prendre ce fichier et d’activer l’authentification basique. Il est absolument nécessaire d’être en HTTPS pour cette authentification sinon le mot de passe passera en clair dans les trames HTTP. 

# si vous avez un container qui tourne déjà
docker rm -f registry

docker run -d -p 80:5000 
  --restart=always 
  --name registry 
  -v `pwd`/auth:/auth 
  -e "REGISTRY_AUTH=htpasswd" 
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" 
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd 
  -v /opt/docker-registry:/var/lib/registry  
  registry:2

Sur les pc « clients », votre desktop par exemple, il faudra faire un docker login pour pouvoir utiliser le registry.

docker login registry.lahaxe.fr

Je ne mets pas en place de system de backup sur le registry car je n’en ai pas spécialement le besoin. Je préfère stocker l’ensemble de mes dockerfiles afin de pouvoir les reconstruire en cas de besoin.

Gérer ses tunnels SSH avec Mole

Mole est une application en ligne de commande qui permet de simplifier la création de tunnels SSH.

Il est parfois utile de monter un tunnel pour accéder à un service qui n’est pas ouvert sur le WEB. Par exemple si votre serveur MYSQL, n’est accessible que depuis votre serveur web ou votre serveur de backup. Ou encore pour contourner un firewall qui bloquerait un port en particulier.

Sur cet exemple le serveur bleu ne peut pas accéder au port 80 (application web) du serveur rouge car un firewall le bloque. Mais le port 22 (ssh) est accessible. Il est donc possible de créer un tunnel qui forward le port 80 sur serveur rouge sur le port 8080 du serveur bleu. Une fois le tunnel mis en place il sera possible d’accéder à l’application web du serveur rouge via l’url: http://blue:8080.

Installation

Deux méthodes d’installation sont possibles. Via le script d’installation ou via homebrew pour les utilisateurs de mac.

L’installation via le script :

bash <(curl -fsSL https://raw.githubusercontent.com/davrodpin/mole/master/tools/install.sh)

Je vous invite à vérifier le contenu du script avant de le lancer.

L’installation via homebrew :

brew tap davrodpin/homebrew-mole && brew install mole

Mole est utile car il nous simplifie la syntaxe pour créer un tunnel ssh. En effet il n’apporte rien de plus que de la simplification.

Par exemple la commande suivante, qui permet d’accéder au port 80 local d’une machine :

➜ ssh -L 8080:xxxxx@wordpress:80 root@wordpress

Se transforme en quelque chose de plus simple via mole :

➜ mole -remote :80 -local :8080 -server xxxxx@wordpress

Utilisation

Pour se connecter à un serveur mysql qui n’est pas ouvert sur le web nous allons ouvrir un tunnel entre le port 3306 local du serveur et le port 3307 de mon mac :

➜ mole -v -remote :3306 -local :3307 -server xxxxx@wordpress

Je peux maintenant me connecter au serveur mysql du serveur sur le port 3307 de ma machine :

➜ mysql connect -h 127.0.0.1 -P 3307 -uxxxxxxx -p

Gestion des alias

Le dernier point qui est bien pratique si vous avez à jongler avec plusieurs serveurs c’est la gestion des alias. Il est possible d’enregister des alias pour les tunnels que l’on a fréquemment à créer.

➜ mole -alias wp-mysql -remote :3306 -local :3307 -server xxxxx@wordpress
➜  mole -start wp-mysql

Explorer vos json dans le terminal avec fx

Fx est une application node installable via npm ou yarn qui permet de visualiser et d’interagir avec un json directement dans le terminal.

Ce qui est intéressant c’est qu’il est possible d’explorer le json en précisant un path à afficher.

Voici un exemple sur un fichier package.json :

Il est même possible d’aller un peu plus loin et de lui donner une ou plusieurs fonctions anonymes en paramètre.

Un petit outil sympa qui va vous éviter de copier/ coller votre json dans un IDE pour le remettre en forme et naviguer dedans.

Monter un cluster swarm avec des Raspberry pi et HypriotOS

HypriotOs est une version de raspbian dans laquelle docker, et docker swarm sont pré installés. La particularité est que HypriotOs tourne sur des architectures ARM telles que les Raspberry, le Nvidia shield et quelques autres cartes comme les ODROID C2.

Dans cet article, nous allons voir comment monter un petit cluster swarm avec trois noeuds (1 manager et 2 workers). Une fois la partie serveur montée, nous ferons tourner des services dessus à la fois en lignes de commandes et via une interface graphique.

Swarm

Swarm est l’orchestrateur historiquement intégré dans docker. Il permet de distribuer automatiquement les containers sur l’un ou l’autre des noeuds du cluster en fonction de l’indisponibilité ou de la charge d’un noeud.  Docker inc a été testé pour scal

ler jusqu’à 1 000 nœuds et  50 000 conteneurs sans dégradation de performance. A mon avis nous allons avoir du mal à faire tourner autant de conteneurs sur nos Raspberry.

Il existe deux types de noeuds, les managers et les workers. Pour faire simple, les managers peuvent administrer ce qui tourne sur le cluster et répartir le travail sur les workers tandis que les workers sont de simples exécutants qui lancent des containers.

De base, le cluster va faire du mesh routing. Cela signifie, par exemple, que si vous lancez un serveur web sur le port 80 quelque part sur votre cluster, vous pourrez y accéder en attaquant n’importe quelle machine (worker ou manager) qui compose le cluster. Swarm va aussi nous proposer un système de load balancing afin de pouvoir distribuer la charge entre différentes instances d’un service. 

https://docs.docker.com/engine/swarm/ingress/#publish-a-port-for-a-service

Les deux points précédents sont disponibles, sans configuration, dès la création de votre cluster.

Choix des matériaux

Pour monter mon cluster avec trois noeuds il va me falloir le matériel suivant :

Matériel pour un cluster swarm de raspberry pie

Installer HypriotOs sur les cartes

Pour les utilisateurs de Linux et MacOs il existe un outil proposé par la communauté de Hypriot pour flasher les cartes. Malheureusement, pour les utilisateurs de Windows, les choses sont un peu plus compliquées.

Pour linux et mac

Hypriot flash est un utilitaire en lignes de commandes qui va s’occuper de tout, de la décompression de l’image à l’installation sur la carte SIM. Pour l’installation, tout est précisé dans le readme du dépôt.

Télécharger la dernière image de hypriotos

wget https://github.com/hypriot/image-builder-rpi/releases/download/v1.9.0/hypriotos-rpi-v1.9.0.img.zip

Vérifier le checksum de l’image

➜  cat ../Downloads/hypriotos-rpi-v1.9.0.img.zip.sha256
be21a702887817d8c84c053f9ee4c54e04fd55e9fb9692edc3904801b14c33a8  hypriotos-rpi-v1.9.0.img.zip

# sur linux sha256sum / sur mac gsha256sum

➜  gsha256sum hypriotos-rpi-v1.9.0.img.zip
be21a702887817d8c84c053f9ee4c54e04fd55e9fb9692edc3904801b14c33a8  hypriotos-rpi-v1.9.0.img.zip

Flasher les cartes SD

Pour notre manager:

flash hypriotos-rpi-v1.9.0.img.zip --hostname manager-01

Pour nos workers :

flash hypriotos-rpi-v1.9.0.img.zip --hostname worker-01
flash hypriotos-rpi-v1.9.0.img.zip --hostname worker-02

Votre mot de passe root vous sera demandé pendant l’installation de l’image sur la carte.

Vous devriez avoir une sortie console proche de celle ci:

flash --hostname worker-01 hypriotos-rpi-v1.9.0.img.zip
Using cached image /tmp/hypriotos-rpi-v1.9.0.img

Is /dev/disk2 correct? yes
Unmounting /dev/disk2 ...
Unmount of all volumes on disk2 was successful
Unmount of all volumes on disk2 was successful
Flashing /tmp/hypriotos-rpi-v1.9.0.img to /dev/rdisk2 ...
No 'pv' command found, so no progress available.
Press CTRL+T if you want to see the current info of dd command.
Password:
930+1 records in
930+1 records out
975765504 bytes transferred in 111.983538 secs (8713473 bytes/sec)
Mounting Disk
Mounting /dev/disk2 to customize...
Set hostname=worker-01
Unmounting /dev/disk2 ...
"disk2" ejected.
Finished.

Pour windows

Je n’ai jamais flashé mes cartes SD depuis Windows. Mais vous pourrez trouver une procédure sur le site de hypriot. 

Dans le cas de Windows il faudra, si possible, éditer le fichier user-data  (c’est un yml sans extension) à la racine de la carte SD pour changer le hostname qui est présent dedans. Il faudra en nommer un manager-01 et les deux autres worker-01 et worker-02 histoire de pouvoir suivre dans la suite de l’article.

Les branchements

Rien de bien complexe, les câbles ethernet dans les prises ethernets, les cables d’alimentation dans les micro usb et les cartes SD dans les slots prévus à cet effet. Et hop, le tour est joué. Vous devriez pouvoir détecter les machines sur votre réseau local.

Branchements raspberry pi pour un cluster swarm

Se connecter en SSH sur les différents noeuds

Sur mac, j’utilise l’application LanScan Pro depuis des années pour scanner mon réseau local.

Détection des machines du cluster swarm sur le réseau

J’ai bien mes trois Raspberry qui sont présents sur le réseau avec les ip 192.168.1.22, 23 et 24.

Pour se connecter en ssh sur les Raspberry il faut utiliser le login/ mot de passe par défaut de hypriotOS. A savoir pirate/ hypriot.

Première connexion SSH sur hypriot

A cette étape je vous conseille de faire les mises à jour avant d’aller plus loin. Ce serait dommage de faire toute sa configuration sur une version qui a des failles de sécu. De plus il est préférable d’avoir les versions de docker sur l’ensemble du cluster.

Le temps des mises à jour vous avez le temps d’aller prendre un café 😀

Les versions de docker

Création du cluster swarm

Creation du manager

Sur le rasp nommé manager-01 je vais créer le cluster swarm :

docker swarm init

La commande va créer le cluster, vous enregistrer en temps que leader et vous donner la commande pour ajouter des workers.

La création du cluster swarm
La liste des noeuds du cluster après création

Ajout des workers

Pour ajouter les workers on va utiliser la commande donnée au moment de la création du cluster en lançant sur les Raspberry worker-01 et worker-02.

Si vous avez perdu la commande vous pouvez la récupérer en vous connectant sur un manager et en exécutant la ligne de commande :

docker swarm join-token worker
Récupération de la commande pour joindre un cluster swarm

On va donc se connecter en ssh sur worker-01 et worker-02 et lancer la commande de join. Vous devriez avoir le résultat suivant :

Joindre un cluster swarm

Sur le manager on va voir apparaitre les noeuds en tant que worker si on utilise la commande docker node ls

La liste des noeuds d’un cluster swarm

Installation et utilisation de Portainer

Portainer est un outil de visualisation et d’administration et de visualisation open source pour docker. Il est assez intéressant dans notre cas car il va nous permettre de visualiser ce qu’il se passe sur l’ensemble de notre cluster depuis une interface WEB. 

Installation de portainer

On va lancer un container Portainer sur le noeud manager-01.

docker run -d -p 9000:9000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

Portainer est maintenant compatible avec notre architecture arm, il est donc possible d’utiliser l’image officielle. 

Une fois le container lancé, vous pouvez vous rendre, via votre navigateur, sur le port 9000 de votre manager. Pour moi http://192.168.1.22:9000.

Vous allez être redirigé sur la page de création du compte administrateur. Il  est préférable de ne pas conserver le nom d’utilisateur par défaut.

Installation portainer

La seconde étape de l’installation est le choix du type de connexion à docker. Nous allons choisir local car nous avons monté le socket docker lors de la création du container.

Installation portainer

Maintenant que Portainer est configuré nous allons pouvoir consulter notre cluster swarm, mais aussi lancer des services si nous le souhaitons. 

On remarque déjà, que notre cluster est présent et que nos 3 noeuds sont bien détectés.

Cluster swarm dans portainer

En l’état notre cluster est composé de 12 coeurs et de 3 Go de ram.

Cluster visualizer

Lancer un service sur swarm

Pour lancer un service, il y a deux possibilité. Soit passer par portainer soit le faire en lignes de commandes. A vous de voir ce que vous préférez. J’ai préparé une petite image pour faire cette démo. Je m’excuse d’avance du poids de l’image (120Mo) pour faire un hello world…

J’ai buildé une image apache/ php qui affiche le hostname courant (celui du container). L’idée est de déployer un service sur le cluster et de voir comment il réagit et quel est le comportement que l’on obtient.

Déployer un service en lignes de commandes

Nous allons lancer une instance de notre image sur le cluster. Nous n’avons pas à nous soucier du choix de la machine sur laquelle l’application va tourner car le routing mesh nous permettra d’y accéder depuis n’importe quel noeud.  Mais vous pouvez le savoir dans portainer en retournant sur la vue Cluster visualisation.

docker service create -p 80:80 --name hello-world docker.io/lahaxearnaud/swarm-hello-world
Création d’un service dans swarm

Il est possible de préciser beaucoup d’options pour limiter les ressources que le container pourra utiliser, la façon de vérifier que l’application est toujours up (health check)…

Déployer un service avec Portainer

Toutes les options que l’on peut donner via le cli sont disponibles via portainer pour la création de service. La mise en place de nouveau service est très simplifié grâce à l’interface graphique.

Creation d’un service dans portainer

Pour notre test nous n’avons pas besoin des configurations avancées.

Tester le load balancing

Nous avons maintenant un service qui tourne sur notre cluster et qui sert une « page » web contenant le nom du container qui le fait tourner. Pour tester le load balancing on fait une série d’appels et on va regarder si on obtient toujours le même nom de container.

for ((i=1;i<=10;i++)); do curl "http://192.168.1.22"; echo ""; done

Sans surprise pour le moment je vais obtenir 10 fois la même valeur, car nous n’avons qu’une instance qui tourne :

Dans un premier temps nous allons faire scaller notre application pour faire tourner 9 instances.

docker service scale hello-world=9

Vous pouvez aussi le faire via portainer en cliquant sur le service hello-world dans la section service et en modifiant la valeur dans le champs Replicas.

Scaller un service avec portainer

Il va falloir attendre un peu que l’image soit téléchargée sur les différents Raspberry et que les containers soient lancés. Mais une fois que tout est prêt, on va pouvoir relancer notre commande.

On se rend bien compte que la charge est distribuée entre les différentes machines. Sur les 10 hits on a touché les 9 instances.

Consulter les logs

Sur le manager vous pouvez faire un tail sur les logs d’un service via la commande docker service log -ft hello-world.

Visualisation des logs d’un service swarm

Dans portainer par contre il semblerait qu’il y ait un bug et qu’il ne remonte que les logs des instances présentes sur le manager. 

Que mettre sur le cluster ?

Pour ma part, j’utilise swarm depuis au moins deux ans pour ma domotique. Comme je voulais jouer un peu avec l’IoT j’ai mis des capteurs un peu partout dans l’appartement (luminosité, température, mouvements, capteurs d’ouverture de porte et de fenêtre…) et chaque élément envoie des messages dans un rabbitmq. Mon cluster contient l’ensemble des consommateurs et l’application symfony qui sert l’interface graphique. Les consommateurs sont des workers symfony 4 qui utilisent le composant symfony messenger

Avoir cette architecture en cluster permet de perdre ou d’ajouter un ou plusieurs serveurs sans devoir déplacer des applications à la main. Là où il faut être attentif c’est que swarm peut continuer de fonctionner en perdant un worker, mais la perte des managers est plus problématique. Il est préférable d’avoir plusieurs managers pour éviter que le cluster se retrouve sans manager en cas de panne. Un nombre impair de managers est préférable pour s’assurer que l’algorithme (Raft) arrive à élire un nouveau leader.

Retour d’expérience

La technologie est sympa et la fiabilité est au rendez-vous sur tout ce qui est worker et application web. Par contre, j’ai rencontré pas mal de problèmes en voulant mettre un rabbitmq, un elastic search ou encore un mysql dans le cluster. Après plusieurs crashes je les ai installés sur une machine séparée. Je ne sais pas à quel niveau se situe le problème, mais depuis que c’est sur un rasp séparé il n’y a plus de problèmes.

L’autre point est que j’ai une livebox à la maison, c’est elle qui me sert de DHCP (c’est dans ma todo de faire autrement). Elle a tendance à sortir du réseau les machines avec des ips fixes au bout de quelques jours. Je me suis retrouvé à plusieurs reprises avec un cluster qui était HS car toutes les machines étaient déconnectées.

Le fait d’être sur une architecture ARM nous empêche d’utiliser la plupart des images disponibles sur les hubs. Il faut donc passer du temps à faire ses propres images. Ce n’est pas forcement un mal !

Faq

Comment retirer proprement un noeud d’un cluster swarm ?

Si c’est un manager il faut se poser la question de promouvoir un worker en manager avant de commencer docker node promote worker-02 puis vous devez retirer le role manager au noeud à supprimer docker node demote manager-01. La seconde étape est de dire à l’orchestrateur d’enlever tout ce qui tourne sur le noeud docker node update --availability drain worker-01. Une fois qu’il n’y a plus rien sur le noeud on peut le sortir du cluster via la commande docker node rm worker-01. De cette manière il n’y aura pas d’interruption de service.

Comment supprimer le cluster swarm ?

Dans un premier temps il faut supprimer les services qui tournent dessus : docker service rm mon-service. Une fois que c’est fait vous pouvez supprimer les noeuds un à un via docker node rm -f node-name. Puis sur le manager faire un docker swarm leave -f.

Est-ce qu’il est possible de deployer une image locale ?

Non, il n’est pas possible de déployer une image qui n’est pas dans un registry. Mais vous pouvez monter votre propre registry et l’utiliser pour déployer vos images sur vos workers.

Est-il possible de déployer un fichier docker-compose sur swarm ?

Oui c’est possible via docker stack deploy

Est-ce que l’on peut monter des volumes dans les services ?

Oui vous pouvez, maintenant il y aura autant de volume que de noeuds sur lesquels tourneront vos applications.

Analyse de code statique avec phpstan

Phpstan est un outil en ligne de commande qui va vous permettre de détecter automatiquement les erreurs les plus simples en scannant l’intégralité de votre projet.

Démonstration phpstan

De base, l’outil va analyser une application PHP sans tenir compte des spécificités des différents frameworks et librairies. C’est pourquoi il existe des extensions comme PHPstan Symfony pour affiner l’analyse.

Si comme moi vous voulez l’utiliser sur un projet qui a beaucoup de legacy (plus de 10 ans) vous allez avoir un rapport gigantesque. Il faut trouver des solutions pour réduire le rapport à la pull request (merge request) que l’on doit relire. 

Il existe justement un outil qui permet de faire cette corrélation entre le diff et le rapport de PHPstan. Reviewdog est une application en go qui permet de prendre en entrée le rapport d’un outil d’analyse de code comme eslint, golint ou dans notre cas PHPstan.

Sans passer par reviewdog :

➜  vendor/bin/phpstan analyse src -l 4 --no-progress --error-format=raw | wc -l

     292

292 anomalies sur la globalité du projet.

En passant par reviewdog :

➜ vendor/bin/phpstan analyse src -l 4 --no-progress --error-format=raw | reviewdog -diff="git diff master" -f=phpstan | wc -l

	2

Il se reste que deux anomalies, et elles sont directement liées au diff entre la branche courante et master.

Reviewdog peut se connecter directement sur votre gitlab ou github pour ajouter des commentaires dans les pull requests.

Tout ceci ne remplace pas une relecture par un « humain » mais cela va nous donner des indicateurs de qualité et va vous délester des vérifications les plus simples (Est-ce que cette méthode/ classe existe ?). Selon le niveau de vérification que l’on donne à PHPstan on va pouvoir vérifier plus ou moins de choses, comme les dockblocks par exemple.

Ce qui est assez intéressant c’est que PHPstan est extensible et qu’il est donc possible de créer nos propres vérifications. Il serait donc possible, par exemple, de remonter une alerte quand on utilise une classe ou méthode marquée comme dépréciée dans notre code.