Cet article est une mise à jour du déploiement continu avec drone. Cette fois-ci l’idée est d’utiliser des serveurs en ARM64 disponible chez sacleway. J’utilise toujours une infra docker swarm comme présenté dans cet article auto-hébergement hybride mais à cause de certaines spécifités de l’ARM64 je vais repartir de zéro.
J’utilise des serveurs baremetal ARM64-2GB car pour le même prix qu’un VPS on a ici un quad core physique ce qui est bien plus efficace qu’une VM à mon sens. L’autre intérêt est de pouvoir se faire la main sur cette architecture afin de pouvoir y basculer chez soi sur des raspberry pi 3 ou équivalent en 64bits.
J’ai installé des CentOS 7 avec le kernel docker proposé. En effet il vaut mieux utiliser ce kernel plutôt que celui par défaut dans lequel il risque de manquer des modules nécessaires à Docker.
Mon infra est composée de 2 serveurs ARM64 et d’un X86-64 à la maison. Le premier ARM64 sera le manager docker, les 2 autres des workers. Avoir un noeud en x86-64 me semble indispensable car certaines images ne sont pas disponible sur ARM64 et sont difficilement portable sur une autre architecture, cela peut donc dépanner.
Sur la CentOS 7 la version de docker est très ancienne, il faut malgré tout l’installer avant de mettre à jour manuellement la dernière version. Cet article , Get started with Docker on 64-bit ARM, m’a servi de base pour mettre à jour Docker dans une version descente (à ce jour 17.05.0-ce).
mkdir -p sources/gits
cd sources/gits
git clone https://github.com/moby/moby
git checkout tags/v17.05.0-ce
make tgz
sortir prendre un café :)
La compilation de docker nécessite docker (sic), d’où l’intérêt d’avoir installé auparavant la version de CentOS. Un tgz a été généré ici
bundles/17.05.0-ce/tgz/linux/arm64/docker-17.05.0-ce.tgz
à décompresser dans un repertoire temporaire pour y trouver les binaires :
completion docker-containerd docker-containerd-shim docker-init docker-runc
docker docker-containerd-ctr dockerd docker-proxy
il vont remplacer la version en cours. Avant on purge docker de l’OS :
systemctl stop docker
yum remove docker*
rm -rf /var/lib/docker/
vérifier que tous les binaires ont bien été supprimé puis copier les nouveaux :
ls /usr/bin/docker*
cp bundles/17.05.0-ce/tgz/linux/arm64/docker-17.05.0-ce.tgz /tmp
cd /tmp && tar xvzf docker-17.05.0-ce.tgz
cd docker
cp docker* /usr/bin/
installer les 2 fichiers service pour systemd :
/usr/lib/systemd/system/docker-storage-setup.service
[Unit]
Description=Docker Storage Setup
After=cloud-init.service
Before=docker.service
[Service]
Type=oneshot
ExecStart=/usr/bin/docker-storage-setup
EnvironmentFile=-/etc/sysconfig/docker-storage-setup
[Install]
WantedBy=multi-user.target
/usr/lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network.target vpncloud@fredix.service
[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd
ExecReload=/bin/kill -s HUP $MAINPID
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
# Uncomment TasksMax if your systemd version supports it.
# Only systemd 226 and above support this version.
#TasksMax=infinity
TimeoutStartSec=0
# set delegate yes so that systemd does not reset the cgroups of docker containers
Delegate=yes
# kill only the docker process, not all processes in the cgroup
KillMode=process
[Install]
WantedBy=multi-user.target
Ce fichier fait référence à vpncloud que j’utilise pour relier le swarm docker. Voir mon article précédent pour la config sauf si vous utilisez une autre solution. A savoir qu’il est nécessaire de désactiver le service firewalld qui n’est pas compatible avec swarm.
systemctl stop firewalld
systemctl disable firewalld
On peut maintenant lancer Docker et vérifier que tout fonctionne :
systemctl daemon-reload
systemctl start docker
systemctl status docker
docker version
Client:
Version: 17.05.0-ce
API version: 1.29
Go version: go1.7.5
Git commit: 89658be
Built: Thu Jun 29 19:21:31 2017
OS/Arch: linux/arm64
Server:
Version: 17.05.0-ce
API version: 1.29 (minimum version 1.12)
Go version: go1.7.5
Git commit: 89658be
Built: Thu Jun 29 19:21:31 2017
OS/Arch: linux/arm64
Experimental: false
l’indispensable hello world
docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
efe909565661: Pull complete
Digest: sha256:07d5f7800dfe37b8c2196c7b1c524c33808ce2e0f74e7aa00e603295ca9a0972
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://cloud.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/
L’installation est à faire également sur chaque noeud. Pour celui en x86-64 il suffira d’installer le dépôt fourni par Docker pour la distribution. Chaque noeud doit pouvoir être relié en VPN. Pour relier les noeuds on utilisera les IPs privés.
192.168.254.1 (manager)
192.168.254.2 (worker 1 ARM64)
192.168.254.10 (worker 2 x86-64)
initialisation du swarm sur le manager
docker swarm init \
--listen-addr 192.168.254.1 \
--advertise-addr 192.168.254.1
on demande la token pour connecter les workers
docker swarm join-token worker
To add a worker to this swarm, run the following command:
docker swarm join \
--token unetoken\
192.168.254.1:2377
il suffit ensuite de lancer cette commande sur les workers pour connecter les noeuds.
on vérifie que tout est ok
docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
kkzcm1j43i8z2sdl4c6cimnwd worker2 Ready Active
qiens4qo3og9bwmzby7vaqdyj worker1 Ready Active
xy1riie2l28w9jamansynaxqb * manager Ready Active Leader
Enfin on tag les workers afin de lancer les images au bon endroit. Il serait génant de lancer une image ARM64 sur un noeud en x86-64…
docker node update --label-add location=cloud-x86 worker2
docker node update --label-add location=cloud-arm64 worker1
l’infra est prête à recevoir notre outil de déploiement continu. Pour cela on va utiliser docker stack ; il permet de regrouper des services entre eux. On écrit le dockerfile qui va permettre de déployer notre stack drone :
drone-arm64v8.yml
version: '3'
services:
drone-server:
image: fredix/arm64v8-alpine-drone-server
# image: drone/drone
restart: always
env_file: .env.production-server
ports:
- 8000:8000
- 9000:9000
- 80
- 443
volumes:
- /docker_volumes/drone_server:/var/lib/drone/
networks:
- drone-infra
- traefik-net
deploy:
placement:
constraints:
- node.labels.location == cloud-arm64
labels:
- "traefik.port=8000"
- "traefik.docker.network=traefik-net"
- "traefik.frontend.rule=Host:drone.fredix.xyz"
drone-agent:
# image: fredix/arm64v8-alpine-drone-agent:0.8.2
image: drone/agent:linux-arm64
restart: always
env_file: .env.production-agent
command: agent
depends_on:
- drone-server
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /docker_volumes/drone_agent/drone.key:/drone.key
networks:
- drone-infra
deploy:
placement:
constraints:
- node.labels.location == cloud-arm64
drone-wall:
image: drone/drone-wall
restart: always
ports:
- "80"
networks:
- drone-infra
- traefik-net
deploy:
placement:
constraints:
- node.labels.location == cloud-x86
labels:
- "traefik.port=80"
- "traefik.docker.network=traefik-net"
- "traefik.frontend.rule=Host:drone-wall.fredix.xyz"
networks:
traefik-net:
external: true
drone-infra:
external: true
On retrouve 2 informations vues précédement : cloud-x86 et cloud-arm64. Ici j’impose que les conteneurs qui vont lancer les services drone-server et drone-agent soient lancés sur le noeud worker1 (tagué cloud-arm64). Le service drone-wall sera lancé sur un x86-64 car j’ai eu la flemme de faire une image ARM64.
A noter que j’utilise une image personnelle du drone serveur car à ce jour il n’existe pas de version officielle pour ARM64. Vous pouvez cependant utiliser l’image drone/drone sur un serveur amd64, car le serveur ne fait qu’afficher l’interface web et lancer les travaux au noeud agent. Pour ce dernier j’utilise l’image officielle drone/agent:linux-arm64 car mon image plante toutes les 60s pour une raison inconnue (le dockerfile https://github.com/fredix/dockerfile/blob/master/drone/Dockerfile.agent.alpine.arm64v8).
drone-agent va recevoir les tâches du drone-server. Ces tâches sont écrites dans un fichier .drone.yml déposé à la racine du dépôt git du projet à déployer. Le fichier décrit un pipeline que l’agent devra dérouler :
platform: linux/arm64
clone:
default:
image: plugins/git:linux-arm64
depth: 50
pipeline:
publish:
image: plugins/docker:linux-arm64
repo: fredix/arm64v8-blog
tags: latest
dockerfile: Dockerfile.arm64
secrets: [ docker_username, docker_password ]
ssh:
image: fredix/arm64v8-alpine-drone-ssh
host: 192.168.254.1
port: 22
username: drone
volumes:
- /docker_volumes/drone_agent/drone.key:/root/ssh/drone.key
key_path: /root/ssh/drone.key
script:
- "sudo docker service update --image fredix/arm64v8-blog hugo-arm64"
when:
status: success
telegram:
image: fredix/arm64v8-alpine-drone-telegram
token: $PLUGIN_TOKEN
to: $PLUGIN_TO
secrets: [ plugin_token, plugin_to ]
message: >
{{#success build.status}}
build {{build.number}} succeeded on {{repo.name}}. Good job {{build.author}} {{build.link}}
{{else}}
build {{build.number}} failed on {{repo.name}}. Fix me please {{build.author}} {{build.link}}
{{/success}}
when:
status: [ success, failure ]
Après de nombreux tatonnement dus entre autre à la version de drone 0.8.2 et des plugins, ce pipeline fonctionne. La première ligne demande à drone de déployer la tâche à un agent linux/arm64. En l’abscence de cette ligne il mettra par défaut linux/amd64 et la tâche restera indéfiniement en pending.
Ensuite on demande à l’agent de faire un clone git du projet. Pour cela il utilise un plugin drone. Il faut bien comprendre que l’agent drone tournant sur un ARM64 il aura besoin de lancer des plugins compilés pour cette architecture. Le dépôt https://hub.docker.com/r/plugins/ propose un ensemble de plugins drone mais ne sont pas forcement tous disponible pour ARM64. Par chance les 2 premiers le sont.
Une fois le clone du dépôt effectué, on demande à l’agent de builder l’image et de la publier. On utilise un plugin docker. L’agent se connecte à la socket du serveur docker local. Grâce à ce plugin il va créer une image docker alors qu’il tourne lui même dans Docker (DockerInDocker). On lui donne le nom du dépôt, le tag, le dockerfile à utiliser et les login/pass du compte hub.docker pour la publication ( à ne pas mettre en clair dans ce fichier, pour cela on utilise les secrets de drone, voir plus bas). Pendant cette étape on voit un conteneur en cours d’execution :
7d0b242a2861 plugins/docker:linux-arm64
avec un docker logs 7d0b242a2861
on peut observer la contruction de l’image et sa publication.
L’agent passe ensuite à l’étape ssh. J’ai construit ma propre image du plugin https://github.com/appleboy/drone-ssh. Cette étape permet de forcer la mise à jour de l’image en cours d’exécution sur le swarm. Docker télécharge alors depuis le hub.docker.com l’image que l’agent vient de publier, puis met à jour à chaud cette image. Pour cela j’ai créé un utilisateur drone sur le manager Docker, qui a les droits d’executer la commande docker via sudo. J’ai déposé sa clé privée sur le serveur ARM64 ou tourne l’agent, la drone.key. Le plugin peut ainsi se connecter en ssh sur le manager et lancer la commande sudo docker service update –image fredix/arm64v8-blog hugo-arm64.
Dockerfile : https://github.com/fredix/dockerfile/tree/master/drone-ssh
pour finir j’ai contruis ma propre image du plugin telegram https://github.com/appleboy/drone-telegram. Grâce à ce plugin je peux recevoir en temps réel le statut du déploiement (il faut ajouter dans les secrets de drone la token et l’id du destinaire, voir plus bas).
Dockerfile : https://github.com/fredix/dockerfile/tree/master/drone-telegram
on notera que j’utilise 2 réseaux Docker. Le premier est lié à traefik, mon reverse proxy. Il est nécessaire afin que traefik puisse relier le domaine vers le conteneur du drone serveur. Le deuxième qui est drone-infra, est un réseau créé par docker spécifique à drone, ce qui permet à la stack drone d’avoir son propre réseau interne dédié. Pour le créer il suffit de lancer sur le manager :
docker network create --driver=overlay --attachable drone-infra
Pour mettre en place votre propre infra drone vous pouvez soit utiliser mes images, soit construire les votres, dans ce cas voici mes dockerfile : https://github.com/fredix/dockerfile
Le fichier drone-arm64v8.yml utilise 2 fichiers de variables d’environnement :
env_file: .env.production-server
env_file: .env.production-agent
Il faut créer ces 2 fichiers dans le répertoire où vous allez lancer le docker stack deploy et indiquer différentes informations, tel que le serveur git à utiliser. ces 2 fichiers sont disponible ici en version d’exemple : https://github.com/fredix/swarm/tree/master/drone
Dans le fichier .env.production-server on indique l’url de notre serveur drone, le compte admin, le serveur git à utiliser (github, gogs, gitea, …) avec les tokens, ainsi qu’un secret à partager avec l’agent.
Dans le fichier .env.production-agent le drone secret identique au serveur ainsi que la plateform sur lequel il tourne.
A la première connexion sur l’interface web du serveur drone (indiquée dans DRONE_HOST) le serveur fait une connexion oauth2 vers le serveur git choisis. On autorise l’accès à ses dépôts puis on sélectionne les dépôts que l’on souhaite être géré par drone. A ce moment là drone ajoute une webkook dans notre projet sur github.
Dans l’interface web de drone il faut écrire les secrets qui sont utilisés dans le fichier .drone.yml :
docker_username, docker_password
plugin_token, plugin_to
La première ligne permet au drone agent de se connecter au hub.docker.com pour y publier l’image quil a construite.
plugin_token correspond à une token fourni par Telegram lorsque vous créez un bot. Le bot @botfather vous indiquera comment faire. plugin_to correspond à l’ID telegram du destinaire qui recevra les messages de build. Pour obtenir le votre il suffit d’interroger le bot @idbot.
On peut maintenant déployer notre stack :
docker stack deploy --compose-file=drone-arm64v8.yml drone-arm64
et vérifier sa bonne exécution :
docker stack ps drone-arm64
la supprimer si nécessaire :
docker stack rm drone-arm64
Le workflow est le suivant. A chaque git push vers github (ou autre) une webhook vers l’api de drone serveur va se déclencher. Ce dernier parse le fichier .drone.yml puis transmet le pipeline à l’agent drone. Celui-ci informe constament le serveur de son avancé ce qui permet de surveiller sur l’interface web les différents steps.
En résumé l’agent drone exécute :
Drone propose en plus de l’interface web un client en console : http://docs.drone.io/cli-installation/
Ce dernier permet de consulter les builds en cours et de les stopper, ce que ne permet pas l’interface web (sic).
Malgré mes nombreux tests je constate que des jobs risquent de rester en pending indéfiniment.. Le contournement est pour l’instant de relancer la stack docker, (docker stack rm drone-arm64 / docker stack deploy –compose-file=drone-arm64v8.yml drone-arm64). Je ne sais pas si c’est un problème de l’architecture ARM64, mais cela pourrait être lié à la recente migration du code de drone qui utilise maintenant grpc pour la communication entre le serveur et les agents ( issue GRPC Health Checks ).
Pour l’instant j’utilise drone pour déployer automatiquement une mise à jour de mon blog. J’utilise ce dockerfile https://github.com/fredix/fredix.xyz/blob/master/Dockerfile.arm64 qui télécharge tous les fichiers statiques de mon blog. Une autre solution plus élégante serait d’utiliser ansible pour synchroniser mes fichiers markdown de mon blog vers un volume docker de mon serveur. Ainsi la mise à jour du conteneur ne se ferait uniquement que pour une mise à jour de hugo et non pas pour chaque modification/ajout de texte.
Je souhaite utiliser drone sur d’autres projets personnels dans lesquels j’utiliserais sans doute des fonctionnalités plus avancées.