Déploiement automatique d'Hugo avec Gitlab & Docker

Bonjour à tous ! Aujourd'hui un article un peu différent, nous allons voir ensemble comment déployer un site Hugo (https//rm-it.fr) automatiquement avec Gitlab, Gitlab Runner et Docker.

Tout d'abord je tiens à remercier Adrien Pavone qui m'a beaucoup (trop?) aidé dans la mise en place de ce système.

Hugo ?

Tout d'abord, voyons un peu ce qu'est Hugo. C'est un générateur de site statique HTML/CSS écrit en Go. En gros, ça permet de créer facilement et très rapidement des sites Web sans avoir à faire de developpement. De mon côté j'ai utilisé un thème trouvé sur Github, il m'a suffit de l'installer, de modifier les quelques variables du fichier fourni avec le thème et mon site était prêt.

Hugo est un logiciel Open Source sous licence Apache 2.0 et sa dernière version lorsque j'écris ces lignes est la 0.60.1.

En plus d'être très facile d'installation et d'utilisation, Hugo est très rapide, pour la simple et bonne raison que le site est entièrement statique. C'est pour moi une très bonne alternative à WordPress pour créer des sites vitrines ou même un simple blog.

Installation et déploiement d'un site avec Hugo

Même si ce n'est pas le but de l'article, nous allons aborder très rapidement l'installation du logiciel Hugo et son fonctionnement.

Un exemple d'installation sur ma machine PopOS :

 1sudo apt update
 2sudo apt upgrade
 3
 4apt install -y wget git
 5
 6wget https://github.com/gohugoio/hugo/releases/download/v0.60.1/hugo_0.60.1_Linux-64bit.deb
 7
 8sudo dpkg -i hugo_0.60.1_Linux-64bit.deb
 9
10hugo version

>

Vous pouvez maintenant utiliser la commande suivante pour créer votre nouveau site et installer votre thème :

 1# Création du site Hugo
 2hugo new site example.com
 3
 4cd example.com/
 5cd themes 
 6
 7# Installation du thème dans le bon dossier
 8git clone https://github.com/LordMathis/hugo-theme-nix
 9
10# Retour à la racine du site et installation du site d'exemple 
11cd ..
12cp -R themes/hugo-theme-nix/exampleSite/* ./

Nous avons maintenant un thème et un exemple de site installé. Vous noterez que l'architecture est assez classique et que vous pouvez définir chaque page au format Markdown sauf pour le fichier de configuration qui lui est au format TOML.

Vous pouvez également lancer un test pour vérifier que votre site fonctionne bien avec la commande suivante à la racine du projet :

hugo server --bind 127.0.0.1 --port 8080 --disableFastRender

>

Et nous retrouvons bien notre site Web :

>

Vous pouvez maintenant modifier les différents fichiers de votre site pour l'adapter à vos besoins.

Pour terminer sur les commandes Hugo car ce n'est pas le coeur de l'article, vous pouvez générer les différentes pages de votre site afin de les installer sur un serveur Web (Apache dans mon cas).

La commande est tout simplement « hugo » à la racine du projet :

hugo

Cette commande va créer un dossier « public » qui contiendra les différents fichiers :

La problématique

Comme vous pouvez le voir, Hugo nécessite l'installation d'un serveur Web pour fonctionner. Dans mon cas, mon ancienne procédure pour le déploiement était très archaïque et non évolutive.

J'utilisais un serveur Ubuntu Server 18.04 sur lequel j'avais installé Hugo ainsi qu'Apache pour le serveur Web.

J'avais créé un site Hugo et j'avais importé mon thème. Je réalisais donc les modifications directement sur les fichiers de configuration du site, je « buildais » mon site avec la commande « hugo » et je remplaçais en suite le contenu /var/www/html à grand coup de :

1rm -rf /var/www/html/*
2cp -R public/* /var/www/html/

En vrai, c'est franchement dégueulasse. En plus de ça, je ne pouvais faire des modifications que depuis ce serveur, je n'avais pas l'historique des versions, pas d'ajout de contenu à distance, pas de sauvegarde de la configuration, etc.

Le seul truc que je faisais, c'était de pousser le résultat de de mon build Hugo sur mon git.

La solution

Pour corriger tout ça, j'ai utilisé les outils suivants :

  • Gitlab comme repo pour les fichiers de configuration et comme registry Docker
  • Gitlab Runner pour réaliser les différentes actions sur les serveurs
  • Docker pour builder et lancer les différents conteneurs

Avec ces trois outils j'ai créé un cycle de déploiement continu. C'est à dire que dès que je pousse une modification sur le repo de mon site, un nouveau site est automatiquement créé et déployé.

Cela me permet également de migrer mon site très rapidement, vu que j'ai simplement besoin d'une machine avec Docker. Alors qu'avant j'aurais dû réinstaller, Apache, Hugo, importer la configuration...

Vous avez accès à toutes les informations sur mon repo Gitlab.

Installation

Passons maintenant à l'installation de cette solution. J'utilise toujours un serveur Ubuntu Server 18.04 avec de mon côté :

  • 4 Go RAM
  • 2 vCPU
  • 40 Go de DD

Sur ce serveur j'ai installé Docker ainsi que mes deux runner Gitlab. Commençons par l'installation de Docker sur cette machine :

 1sudo apt-get update
 2sudo apt-get install \
 3    apt-transport-https \
 4    ca-certificates \
 5    curl \
 6    gnupg-agent \
 7    software-properties-common
 8
 9curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
10
11sudo add-apt-repository \
12   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
13   $(lsb_release -cs) \
14   stable"
15
16sudo apt-get update
17
18sudo apt-get install docker-ce docker-ce-cli containerd.io
19
20docker -v

Normalement, si tout ce passe bien la dernière commande vous renvoi ce résultat :

Maintenant l'installation de Gitlab Runner sur la même machine :

1curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | bash
2apt-get install gitlab-runner -y

En suite il faut installer les deux runner Gitlab, le 1er en mode shell. C'est à dire que ce dernier exécutera simplement des commandes shell sur la machine :

1sudo gitlab-runner register \
2   --non-interactive \
3   --url "https://gitlab.com/" \
4   --registration-token "PROJECT_REGISTRATION_TOKEN" \
5   --executor "shell" \
6   --name "[RM-IT-HOME] Shell Runner" \
7   --tag-list "shell,hugo" \
8   --run-untagged="false" \
9   --locked="true"

Et le 2nd en mode « docker » car il pourra interagir directement avec la socket docker :

 1  --non-interactive \
 2  --url https://gitlab.com/ \
 3  --registration-token "PROJECT_REGISTRATION_TOKEN" \
 4  --executor "docker" \
 5  --name "[RM-IT-HOME] Docker Runner" \
 6  --tag-list="hugo,docker" \
 7  --run-untagged="false" \
 8  --docker-image "docker:19.03.1" \
 9  --docker-volumes /var/run/docker.sock:/var/run/docker.sock \
10  --locked="true"

Update : après un petit moment je me suis rendu compte que j'avais un problème avec mon runner docker interne et que le build ne se faisait pas correctement. Si vous rencontrez le même problème je vous invites à utiliser un runner Gitlab public (comme fait dans le fichier gitlab-ci sur mon repo). N'hésitez pas à revenir vers moi si ce n'est pas clair.

Au niveau des différentes options, vous avez « executor » qui permet de définir le type de runner que vous lancez. Les tags eux permettent d'identifier les runner que vous voulez utiliser dans votre pipeline? Le locked signifie que les runner sont rattachés directement à ce projet.

Il reste maintenant à ajouter l'utilisateur gitlab-runner au groupe Docker pour qu'il puisse exécuter des commandes docker :

1usermod -aG docker gitlab-runner

Et également de supprimer le fichier .bash_logout de l'utilisateur gitlab-runner. Si le fichier est présent aucun job ne se lancera et ça vous remontera une erreur :

1rm -f /home/gitlab-runner/.bash_logout

Pour finir vous pouvez retrouver votre « PROJECT REGISTRATION TOKEN » directement dans votre projet Gitlab. Il se situe dans Settings -> CICD -> Runner :

>

Vous retrouvez également dans cette interface les runner que vous avez déjà ajouté ainsi que leurs états.

Au niveau des flux, vous n'avez rien à ouvrir entre vos runner et Gitlab, car c'est le runner directement qui va initier la connexion vers Gitlab en HTTPS.

Nous avons maintenant une machine avec Docker ainsi que deux runner gitlab installés. Voyons maintenant la configuration de ce pipeline.

Configuration et déploiement du Pipeline

Avant de vous présenter le fichier Gitlab-CI nous allons voir les différents éléments que comprend ce pipeline et comment il se déroule.

Il est divisé en 4 « stages » ou étapes qui utilisent 2 images docker différentes.

Une première image qui va nous permettre de créer notre site statique avec Hugo, et une seconde image basée sur Apache 2 pour lancer notre site internet et le rendre accessible.

Voici le Dockerfile de l'image Hugo :

 1FROM debian:buster
 2
 3# Download and install hugo
 4ENV HUGO_VERSION 0.60.1
 5ENV HUGO_BINARY hugo_${HUGO_VERSION}_Linux-64bit.deb
 6ENV HUGO_THEME_URL https://gitlab.com/mrigonnaux/hugo-theme
 7
 8WORKDIR /usr/share/blog
 9COPY . /usr/share/blog
10
11ADD https://github.com/spf13/hugo/releases/download/v${HUGO_VERSION}/${HUGO_BINARY} /tmp/hugo.deb
12
13COPY entrypoint.sh /opt/entrypoint.sh
14
15RUN    apt-get -qq update \
16    && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y --no-install-recommends git ca-certificates \
17    && rm -rf /var/lib/apt/lists/* \
18    && chmod +x /opt/entrypoint.sh \
19    && dpkg -i /tmp/hugo.deb \
20    && rm /tmp/hugo.deb
21
22ENTRYPOINT ["/opt/entrypoint.sh"]

Ainsi que son « entrypoint.sh » :

1#!/bin/bash
2
3git clone $HUGO_THEME_URL /opt/themes/theme
4
5hugo --themesDir /opt/themes -d /output

Dans cette partie nous retrouvons l'installation d'Hugo sur une base de Debian Buster. On peut également voir la copie des différents fichiers de configuration présents dans le repo dans le dossier « /usr/share/blog ».

Et pour finir le script « entrypoint.sh » qui permet de récupérer le thème voulu et de lancer le build de l'image.

Egalement le Dockerfile de l'image Apache, beaucoup plus simple cette fois-ci :

1FROM httpd:2.4.41
2
3COPY output /usr/local/apache2/htdocs/

On remarquera que le dossier « output » est mis en cache, ce qui permet de récupérer son contenu.

Passons à la partie la plus importante, le fichier gitlab-ci.yml :

 1# ==============================================================================
 2# Cache definition
 3# ==============================================================================
 4cache:
 5  paths:
 6    - output/
 7
 8
 9# ==============================================================================
10# Templates definition
11# ==============================================================================
12.job_template_docker: &job_definition_docker
13    before_script:
14      - '[ -z "$CI_JOB_TOKEN" ] && echo "Registry Password is not set or empty (or protected) in Secret Variables" && exit 1'
15      - '[ -z "gitlab-ci-token" ] && echo "Registry User is not set or empty (or protected) in Secret Variables" && exit 1'
16      - echo $CI_JOB_TOKEN | docker login $CI_REGISTRY --username gitlab-ci-token --password-stdin
17      
18.except: &except
19    except: 
20      changes: 
21        - "*.md"
22
23
24# ==============================================================================
25# Stages definition
26# ==============================================================================
27stages: 
28  - build-hugo
29  - deploy-hugo
30  - build-site
31  - deploy-site
32
33
34# ==============================================================================
35# Stage: build-hugo
36# ==============================================================================
37build-hugo-container: 
38    <<: *job_definition_docker
39    stage: build-hugo
40    tags:
41      - hugo,shell
42    script:
43      - docker build -t $CI_REGISTRY/$CI_PROJECT_PATH/hugo-build:latest --file ./Dockerfile-hugo .
44      - docker push $CI_REGISTRY/$CI_PROJECT_PATH/hugo-build:latest
45    <<: *except
46
47
48# ==============================================================================
49# Stage: deploy-hugo
50# ==============================================================================
51deploy-hugo-container: 
52    <<: *job_definition_docker
53    stage: deploy-hugo
54    tags:
55      - docker,shared #runner public, remplacer par docker,hugo pour de l'interne
56    image: docker:19.03.1
57    services:
58      - docker:19.03.0-dind
59    script: 
60      - docker pull $CI_REGISTRY/$CI_PROJECT_PATH/hugo-build:latest
61      - docker stop hugo-site-build || true
62      - docker rm hugo-site-buid || true
63      - docker run -v ${PWD}/output:/output $CI_REGISTRY/$CI_PROJECT_PATH/hugo-build:latest
64    artifacts:
65      paths:
66        - output/*
67    <<: *except
68
69
70# ==============================================================================
71# Stage: build-site
72# ==============================================================================
73build-site-container: 
74    <<: *job_definition_docker
75    stage: build-site
76    tags: 
77      - hugo,shell 
78    script: 
79      - docker build -t $CI_REGISTRY/$CI_PROJECT_PATH/hugo-site:latest -f ./Dockerfile-apache . 
80      - docker push $CI_REGISTRY/$CI_PROJECT_PATH/hugo-site:latest 
81    <<: *except
82 
83# ==============================================================================
84# Stage: deploy-hugo
85# ==============================================================================
86deploy-site: 
87    <<: *job_definition_docker
88    stage: deploy-site
89    tags: 
90      - hugo,shell
91    script: 
92      - docker pull $CI_REGISTRY/$CI_PROJECT_PATH/hugo-site:latest
93      - docker stop site-perso || true
94      - docker rm site-perso || true
95      - docker run --name site-perso --restart always -p 8081:80 --detach $CI_REGISTRY/$CI_PROJECT_PATH/hugo-site:latest
96    <<: *except

Pour les explications nous allons découper en parties :

Partie 1 : Cache, Templates & Stages

Dans cette partie il y a d'abord, la configuration du cache qui permet de récupérer le résultat du conteneur Hugo pour l'injecter dans le conteneur Apache.

Nous avons en suite la partie templates, cette dernière va permettre de créer des sortes d'alias pour ne pas avoir à retaper plusieurs fois les mêmes commandes/lignes. Le 1er « .job_template_docker » est présent pour la gestion de l'authentification au niveau de la regitry de Gitlab et le 2nd permet de ne pas lancer le pipeline si des fichiers avec l'extension « .md » sont modifiés.

La dernière partie concerne la déclaration des différentes « stages » au sein du fichier.

Partie 2 : build-hugo-container

Cette partie permet de créer notre conteneur Hugo. Pour cela nous choisissons l'étape « build-hugo » ainsi que notre runner en lui indiquant les tags. Nous exécutons en suite les commandes pour « build » notre image et la pousser dans le registry de Gitlab.

Partie 3 : deploy-hugo-container

Cette section est concernée par le lancement de notre conteneur Hugo préalablement créé. Ce dernier va récupérer les fichiers de configuration, le thème et générer notre site statique.

Pour ce cas nous utilisons le runner Docker car la fonction « dind » est utilisée. Dind permet de faire du docker dans un conteneur. Dans notre cas c'est utilisé pour ne pas déposer des fichiers sur le serveur hôte et pour s'affranchir de la gestion de droits.

Nous voyons bien que le conteneur est tiré de la registry, que les anciens conteneurs sont supprimés et pour finir que le nouveau est lancé.

Partie 4 & 5

Dans les deux dernières sections, nous réutilisons le runner en mode shell seulement. Les actions effectuées sont cette fois-ci au niveau du conteneur Apache. De dernier est créé avec les sources déposées dans « output » et est lancé.

Après test avec cette configuration, le pipeline s'est exécuté correctement :

Si vous voulez plus d'information sur ce pipeline, vous pouvez vous rendre directement sur le projet Gitlab.

Dans ce même projet il reste sur la partie Hugo seulement le fichier principal TOML ainsi que les fichiers markdown, tout le reste est récupéré lors du pipeline.

Conclusion

En fin de compte, même si ça parait compliqué et que ça m'a demandé pas mal de temps pour comprendre, je suis content du résultat. De plus c'est ma 1ère expérience avec Gitlab-CI. C'est sur, il y a encore des choses à améliorer, mais comparé à mon système de base, c'est le jour et la nuit.

Je peux maintenant déployer mon site ou je le veux, le modifier à distance, gérer les versions, etc. Et ce système fait exactement ce que je veux.

J’espère que cet article vous aura plu, si vous avez des questions ou des remarques sur ce que j’ai pu écrire n’hésitez pas à réagir avec moi par mail ou en commentaire !

Merci pour votre lecture et à bientôt !

Mickael Rigonnaux @tzkuat