Déploiement automatique d’Hugo avec Gitlab & Docker

Publié par Mickael Rigonnaux le

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 :

sudo apt update
sudo apt upgrade

apt install -y wget git

wget https://github.com/gohugoio/hugo/releases/download/v0.60.1/hugo_0.60.1_Linux-64bit.deb

sudo dpkg -i hugo_0.60.1_Linux-64bit.deb

hugo version

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

# Création du site Hugo
hugo new site example.com

cd example.com/
cd themes 

# Installation du thème dans le bon dossier
git clone https://github.com/LordMathis/hugo-theme-nix

# Retour à la racine du site et installation du site d'exemple 
cd ..
cp -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 :

rm -rf /var/www/html/*
cp -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 :

sudo apt-get update
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io

docker -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 :

curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | bash
apt-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 :

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

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

sudo gitlab-runner register \
  --non-interactive \
  --url https://gitlab.com/ \
  --registration-token "PROJECT_REGISTRATION_TOKEN" \
  --executor "docker" \
  --name "[RM-IT-HOME] Docker Runner" \
  --tag-list="hugo,docker" \
  --run-untagged="false" \
  --docker-image "docker:19.03.1" \
  --docker-volumes /var/run/docker.sock:/var/run/docker.sock \
  --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 :

usermod -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 :

rm -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 :

FROM debian:buster

# Download and install hugo
ENV HUGO_VERSION 0.60.1
ENV HUGO_BINARY hugo_${HUGO_VERSION}_Linux-64bit.deb
ENV HUGO_THEME_URL https://gitlab.com/mrigonnaux/hugo-theme

WORKDIR /usr/share/blog
COPY . /usr/share/blog

ADD https://github.com/spf13/hugo/releases/download/v${HUGO_VERSION}/${HUGO_BINARY} /tmp/hugo.deb

COPY entrypoint.sh /opt/entrypoint.sh

RUN    apt-get -qq update \
    && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y --no-install-recommends git ca-certificates \
    && rm -rf /var/lib/apt/lists/* \
    && chmod +x /opt/entrypoint.sh \
    && dpkg -i /tmp/hugo.deb \
    && rm /tmp/hugo.deb

ENTRYPOINT ["/opt/entrypoint.sh"]

Ainsi que son « entrypoint.sh » :

#!/bin/bash

git clone $HUGO_THEME_URL /opt/themes/theme

hugo --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 :

FROM httpd:2.4.41

COPY 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 :

# ==============================================================================
# Cache definition
# ==============================================================================
cache:
  paths:
    - output/


# ==============================================================================
# Templates definition
# ==============================================================================
.job_template_docker: &job_definition_docker
    before_script:
      - '[ -z "$CI_JOB_TOKEN" ] && echo "Registry Password is not set or empty (or protected) in Secret Variables" && exit 1'
      - '[ -z "gitlab-ci-token" ] && echo "Registry User is not set or empty (or protected) in Secret Variables" && exit 1'
      - echo $CI_JOB_TOKEN | docker login $CI_REGISTRY --username gitlab-ci-token --password-stdin
      
.except: &except
    except: 
      changes: 
        - "*.md"


# ==============================================================================
# Stages definition
# ==============================================================================
stages: 
  - build-hugo
  - deploy-hugo
  - build-site
  - deploy-site


# ==============================================================================
# Stage: build-hugo
# ==============================================================================
build-hugo-container: 
    <<: *job_definition_docker
    stage: build-hugo
    tags:
      - hugo,shell
    script:
      - docker build -t $CI_REGISTRY/$CI_PROJECT_PATH/hugo-build:latest --file ./Dockerfile-hugo .
      - docker push $CI_REGISTRY/$CI_PROJECT_PATH/hugo-build:latest
    <<: *except


# ==============================================================================
# Stage: deploy-hugo
# ==============================================================================
deploy-hugo-container: 
    <<: *job_definition_docker
    stage: deploy-hugo
    tags:
      - docker,shared #runner public, remplacer par docker,hugo pour de l'interne
    image: docker:19.03.1
    services:
      - docker:19.03.0-dind
    script: 
      - docker pull $CI_REGISTRY/$CI_PROJECT_PATH/hugo-build:latest
      - docker stop hugo-site-build || true
      - docker rm hugo-site-buid || true
      - docker run -v ${PWD}/output:/output $CI_REGISTRY/$CI_PROJECT_PATH/hugo-build:latest
    artifacts:
      paths:
        - output/*
    <<: *except


# ==============================================================================
# Stage: build-site
# ==============================================================================
build-site-container: 
    <<: *job_definition_docker
    stage: build-site
    tags: 
      - hugo,shell 
    script: 
      - docker build -t $CI_REGISTRY/$CI_PROJECT_PATH/hugo-site:latest -f ./Dockerfile-apache . 
      - docker push $CI_REGISTRY/$CI_PROJECT_PATH/hugo-site:latest 
    <<: *except
 
# ==============================================================================
# Stage: deploy-hugo
# ==============================================================================
deploy-site: 
    <<: *job_definition_docker
    stage: deploy-site
    tags: 
      - hugo,shell
    script: 
      - docker pull $CI_REGISTRY/$CI_PROJECT_PATH/hugo-site:latest
      - docker stop site-perso || true
      - docker rm site-perso || true
      - docker run --name site-perso --restart always -p 8081:80 --detach $CI_REGISTRY/$CI_PROJECT_PATH/hugo-site:latest
    <<: *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 !

MRigonnaux


2 commentaires

Valentin · 13 décembre 2019 à 5 h 56 min

Salut, Sympa ton article,

Tu devrais utiliser un Dockerfile DualStep build, comme ceci :

FROM hugo
# copying project files
COPY app/ /app/
WORKDIR /app/assets/plugins
RUN yarn –network-timeout 600000
WORKDIR /app
RUN hugo -b / –debug \
–gc –log –minify \
–verbose –verboseLog \
–templateMetrics –templateMetricsHints \
–config config.yaml

# Second Image : only generated files + nginx
FROM nginx:latest
COPY –from=0 /app/public/ /var/www/html/
COPY config/nginx.conf /etc/nginx/nginx.conf
CMD [« nginx », « -g », « daemon off; »]

    Mickael Rigonnaux · 13 décembre 2019 à 8 h 35 min

    Bonjour Valentin,

    Merci pour ton retour,

    Je ne connaissais pas mais effectivement ça pourrait réduire la complexité et la taille de mon fichier gitlab-ci,

    J’essaye de tester dans le week-end,

    Bonne journée à toi !

    Mickael Rigonnaux

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.