déploiement continu avec drone

Suite de l’article sur l’auto hébergement hybride , voici une présentation de l’outil de déploiement continu drone.io idéal pour déployer dans une infrastructure docker. Je vais prendre comme exemple de déploiement mon blog qui utilise hugo un serveur de blog statique (pas de sgbd) en Golang. Outre les avantages de go (un binaire statique, rapide, léger), il permet d’écrire ses pages au format makdown. La première partie avec un déploiement manuel puis celle automatisée avec drone.

Création d’une image docker

J’utilise mon infra auto hébergée qui utilise un docker swarm. Mon blog est donc déployé sous forme de conteneur ; de plus je n’utilise pas de volume pour monter les données le conteneur contient toutes les données du blog. Cela m’évite à gérer la synchro du volume qui ne contiendrait de toute manière que des fichiers textes pas volumineux.

Hugo

Pour créer un site avec hugo, après l’avoir installé en local, on génère la structure

hugo new site test
Congratulations! Your new Hugo site is created in /home/fredix/tmp/test.

Just a few more steps and you're ready to go:

1. Download a theme into the same-named folder.
   Choose a theme from https://themes.gohugo.io/, or
   create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
   with "hugo new <SECTIONNAME>/<FILENAME>.<FORMAT>".
3. Start the built-in live server via "hugo server".

Visit https://gohugo.io/ for quickstart guide and full documentation.

on rentre ensuite dans le répertoire pour éditer son fichier de configuration

cd test
vim config.toml

Je vous laisse voir la documentation à ce sujet. On peut créer ensuite de nouvelles pages

hugo new post/test.md
/home/fredix/tmp/test/content/post/test.md created

Hugo a créé la structure de base du fichier

cat content/post/test.md 
+++
date = "2017-05-28T12:58:49+02:00"
draft = true
title = "test"

+++

On peut écrire à la fin de ce fichier. On doit ajouter un thème pour ne pas avoir une page vide

cd themes
git clone https://github.com/dim0627/hugo_theme_robust.git
cd ..

puis on lance le serveur pour voir le rendu

hugo server –theme=hugo_theme_robust –buildDrafts

Started building sites ...
Built site for language en:
1 of 1 draft rendered
0 future content
0 expired content
1 regular pages created
8 other pages created
0 non-page files copied
2 paginator pages created
0 tags created
0 categories created
total in 8 ms
Watching for changes in /home/fredix/tmp/test/{data,content,layouts,static,themes}
Serving pages from memory
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop

Tant que le serveur est lancé on peut modifier ses textes, le rendu dans le navigateur sera réactualisé automatiquement. Plus d’infos sur le quickstart.

Github

On doit créer un dépôt sur github dans lequel on stockera tout le contenu du répertoire hugo. Ainsi à chaque modification/ajout d’un texte, il faudra les commit et les push vers github. Exemple le mien fredix.xyz.
Cela permet par ailleurs d’avoir une sauvegarde complète de son site sur github.

Docker

On va créer un Dockerfile pour pouvoir créer un conteneur qui contiendra hugo mais aussi tout le site web depuis github.

cat Dockerfile 
FROM debian:jessie
MAINTAINER fredix@protonmail.com

# Install pygments (for syntax highlighting) 
RUN apt-get -qq update \
    && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y --no-install-recommends python-pygments unzip \
    && rm -rf /var/lib/apt/lists/*

# Download and install hugo
ENV HUGO_VERSION 0.21
ENV HUGO_BINARY hugo_${HUGO_VERSION}_Linux-64bit

ADD https://github.com/spf13/hugo/releases/download/v${HUGO_VERSION}/${HUGO_BINARY}.tar.gz /usr/local/
RUN tar xzf /usr/local/${HUGO_BINARY}.tar.gz -C /tmp/ \
    && cp /tmp/hugo /usr/local/bin/hugo \
    && rm /usr/local/${HUGO_BINARY}.tar.gz

# Create working directory
RUN mkdir /usr/share/blog
ADD https://github.com/fredix/fredix.xyz/archive/master.zip /tmp/
RUN unzip /tmp/master.zip -d /usr/share/blog/ && rm /tmp/master.zip
WORKDIR /usr/share/blog/fredix.xyz-master

# Expose default hugo port
EXPOSE 1313

# Automatically build site
ONBUILD ADD . /usr/share/blog/fredix.xyz-master

# By default, serve site
ENV HUGO_BASE_URL http://fredix.xyz
# CMD /usr/local/bin/hugo server --baseUrl=${HUGO_BASE_URL} --theme=fredix --watch --appendPort=false
CMD /usr/local/bin/hugo server -s /usr/share/blog/fredix.xyz-master --baseUrl=${HUGO_BASE_URL} --watch --appendPort=false --bind=0.0.0.0

Ce fichier est à déposer dans le répertoire racine du projet hugo.

archetypes  config.toml  content  Dockerfile  layouts  public  static  themes

Que fait-il ?
On utilse une image debian jessie, puis on installe les paquets nécessaires à hugo et unzip pour décompresser le zip généré par github. Le premier ADD permet de télécharger le binaire hugo selon la version précisé, ici 0.21, on le décompresse et on l’installe dans /usr/local/bin
On créé ensuite le répertoire du blog, le 2ème ADD télécharge le zip du master généré par github après chaque git push. Enfin on lance hugo avec les options souhaitées.

On build notre image en local et on l’envoie sur le registre https://hub.docker.com (voir un exemple complet sur cet article beego caddy docker)

docker build -t fredix/hugo .
docker push fredix/hugo

Swarm

On déploie notre image dans notre swarm docker (voir l’article précédent auto-hébergement hybride)

docker service create --name hugo --network traefik-net --label traefik.frontend.rule=Host:fredix.xyz,www.fredix.xyz --label traefik.port=1313 fredix/hugo

Le service hugo est créé et en ligne. A chaque modification de votre blog il suffira de lancer les étapes suivantes

en local (user est le nom de votre compte sur le hub docker, blog le nom de votre dépôt)

git add fichier.md
git commit
git push
docker build -t user/blog .
docker push user/blog

sur votre serveur où se trouve le leader de votre swarm (la VM louée)

ssh root@ip
docker service update --image fredix/hugo hugo

C’est un workflow pas mal mais il nécessite 7 étapes pour mettre à jour le blog. Il est temps de rentrer dans le monde du déploiement continu pour automatiser tout cela.

Drone

drone.io est un outil de déploiement continu (et d’intégration continue) opensource et en Golang. Les plus connus sont travis ci, jenkins ou circleci. Il a la particularité de pouvoir générer des conteneurs docker à la volée en se connectant à la socket docker. On peut ainsi valider dans un conteneur isolé n’importe quel type de code et son environnement associé (sgbd …). On peut même associer une matrice de versions de logiciels que l’on souhaite tester avec son code pour qu’il génère autant de conteneurs (Matrix guide). Le conteneur construit pourra être envoyé vers un registre docker pour être déployé dans l’environnement souhaité (test, prod …).

J’utilise ici docker stack qui permet de générer automatiquement les services nécessaires à une application et de les regrouper sous un nom de stack. En effet drone nécessite un serveur et un agent. Le serveur affiche une interface web et reçoie les callback de github, l’agent execute les tâches définies dans le pipeline du serveur.

sur le serveur leader on créé un fichier yml qui contient la stack du service

cat drone.yml 
version: '3'
services:
  drone-server:
    image: drone/drone:0.7.1
    restart: always
    env_file: .env.production-server
    ports:
      - "8000"
    volumes:
      - /sync/drone:/var/lib/drone/
    networks:
      - default
      - traefik-net
    deploy:
      placement:
    constraints:
      - node.labels.location == home
      labels:
    - "traefik.port=8000"
    - "traefik.docker.network=traefik-net"
    - "traefik.frontend.rule=Host:drone.fredix.xyz"

  drone-agent:
    image: drone/drone:0.7.1
    restart: always
    env_file: .env.production-agent
    command: agent
    depends_on:
      - drone-server
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      placement:
    constraints:
      - node.labels.location == home

networks:
  traefik-net:
    external: true

On ajoute ensuite les fichiers qui contiennent les variables d’environnement

cat .env.production-server.sample 
DRONE_OPEN=true
DRONE_ORGS=nodecast
DRONE_ADMIN=fredix
DRONE_HOST=https://drone.fredix.xyz
DRONE_GITHUB=true
DRONE_GITHUB_CLIENT=tokengithub
DRONE_GITHUB_SECRET=secretgithub
DRONE_SECRET=dronesecret

cat .env.production-agent.sample 
DRONE_SERVER=ws://drone-server:8000/ws/broker
DRONE_SECRET=dronesecret

Dans votre compte github il faut ajouter une application Oauth https://github.com/settings/developers) afin d’obtenir les tokens nécessaires pour drone.Voir la documentation setup Github.
Il faut ensuite renommer les fichiers sans le .sample et indiquer les tokens de github. Le drone secret est une chaine de caractère de votre choix. Drone admin est le nom de votre compte github, éventuellement drone_orgs si vous avez une organisation créée dans github.

On peut ensuite lancer la stack pour mettre directement en production drone grâce à traefik et swarm

docker stack deploy --compose-file=drone.yml drone

on peut voir que les services sont up et fonctionnent

docker stack ps drone
ID            NAME                  IMAGE              NODE                  DESIRED STATE  CURRENT STATE        ERROR  PORTS
wfil5w66jsdw  drone_drone-agent.1   drone/drone:0.7.1  centos-1.localdomain  Running        Running 2 hours ago         
o3rkmchc84gl  drone_drone-server.1  drone/drone:0.7.1  centos-2.localdomain  Running        Running 2 hours ago .        

En cas d’erreur on peut supprimer la stack

docker stack rm drone

Si tout est ok on peut consulter l’interface web de drone drone.fredix.xyz. Il faut ensuite cliquer sur Login pour être renvoyé vers github et autoriser l’application drone à accéder à vos dépôts git. Dans l’interface web de drone on doit pouvoir voir tous les dépôts git dont celui qui contient le blog, on l’active puis on va dans l’onglet secret du dépôt

drone

Les secrets (secret guide) permettent de remplacer un mot de passe par une variable. En effet drone devra faire un push du conteneur hugo vers le hub docker, puis il devra se connecter en ssh sur le node swarm leader pour lancer un docker service update de hugo. Or il est impensable de fournir ces informations en clair. Comment ça ?

workflow github - drone - docker registry

pour lancer la magie du déploiement continu le workflow suivant s’exécute

  1. git push vers github
  2. callback de github vers l’api de drone
  3. drone parse un fichier pipeline déposé dans le dépôt github
  4. ce fichier demande à des plugins drone d’effectuer des actions (plugin docker, plugin ssh)
  5. drone parse le Dockerfile et build une image docker (il est connecté à la socket du démon docker)
  6. il peut lancer ensuite des scripts de tests qui ont été demandé dans le ficher pipeline (il n’y en a pas ici)
  7. l’agent drone push le conteneur qu’il a buildé vers le registre docker hub
  8. l’agent se connecte en ssh pour lancer une mise à jour du conteneur à partir de l’image qu’il vient d’uploader

Or dans ce fichier pipeline on devrait indiquer les mots de passe des services à utiliser, ce fichier étant dans notre dépot github public il n’est pas concevable de les indiquer en clair. Pour cela drone propose une gestion des secrets. Dans l’onglet du projet dans drone, on peut créé des variables qui contiennent les mots de passe. L’agent drone pourra remplacer les variables indiquées dans le fichier pipeline en interrogeant le serveur drone qui lui fournira les informations. Voici mon fichier pipeline déposé à la racine de mon dépôt github

cat .drone.yml 
pipeline:
  docker:
    image: plugins/docker
    repo: fredix/hugo
    tags: latest
    file: Dockerfile
    secrets: [ docker_username, docker_password ]
  ssh:
    image: appleboy/drone-ssh
    host: 192.168.254.1
    user: drone
    port: 22
    secrets: [ssh_username, ssh_password]
    script:
      - "docker service update --image fredix/hugo hugo"
    when:
      status: success

Ce fichier utilise 2 plugins drone (voir la liste http://plugins.drone.io/), docker et ssh. Dans le premier tableau secret on indique les variables docker_username et docker_password qui contiennent le login/pass du registre docker. Le 2ème secret contient le login/pass du compte ssh permettant de lancer le docker service update. Pour cela j’ai simplement créé un utilisateur drone et ajouté dans le groupe docker pour l’autoriser à accéder au service docker. On remarque également que l’ip est celle du vpncloud de la VM louée, car le docker service update ne peut être lancé sur un node manager.

Terminé, si tout va bien, un simple git push local devrait déclencher tout le pipeline et l’interface web affiche en temps réel l’avancé. On peut ensuite sucrer le tout en ajoutant des plugins de notification, type slack ou télégram afin d’être informé de la réussite ou pas d’un pipeline.

capture de l’interface web pendant le déploiement de mon image

drone2
drone3

Mes scripts de déploiement sont publiés ici https://github.com/fredix/swarm

Un tableau de bord en temp réel avec drone-wall et prometheus (source perdue)

drone-wall

workflow chez Cisco avec gogs et drone (source)

drone-ci