Présentation de ma méthode de publication.
Pour mon blog j’ai choisi le générateur de site statique Hugo1. Contrairement au site dynamique comme Wordpress les générateurs de site statique sont très simples d’utilisation: il n’y a pas d’interface d’administration, pas de base de données à gérer et pas besoin d’un éditeur spécialisé car je rédige mes articles en Markdown.
Le principe est le suivant, je travaille en local sur mon ordinateur et je diffuse les articles sur mon serveur auto-hebergé. Pour suivre les évolutions de mon blog, j’utilise donc le gestionnaire de version Git et pour conserver une copie à distance j’utilise le service Gitlab sur framagit.org.
Après la rédaction de mon article, comment mon blog est mis à jour automatiquement sur mon serveur auto-hebergé ?
Ma première solution fût d’utiliser Ansible pour le déploiement des articles sur mon blog. Les fichiers de configuration me permettaient de réaliser un bon fonctionnent mais cela ne me satisfait pas pleinement. En effet pour faire les mises à jour du blog il faut obligatoirement une installation d’Ansible, ce qui n’est pas toujours possible notamment sur mon smartphone.
Ma solution actuelle repose sur le service CI de Gitlab : on crée un fichier appelé gitlab-ci.yml
, situé à la racine du référentiel Git, qui définit un ensemble d’actions (ou pipeline de déploiement) à réaliser dans un ordre chronologique.
Le pipeline de déploiement gitlab-ci.yml
:
stages:
- build
- deploy
build:
stage: build
image: alpine:latest
tags:
- private
only:
- master
only:
variables:
- $CI_COMMIT_MESSAGE =~ /deploy/
script:
- apk update && apk add hugo git
- git submodule update --init --recursive
- hugo -d public
artifacts:
paths:
- public
expire_in: 10 mins
deploy:
stage: deploy
image: alpine:latest
tags:
- private
only:
- master
only:
variables:
- $CI_COMMIT_MESSAGE =~ /deploy/
before_script:
- apk update && apk add openssh-client bash rsync
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
script:
- rsync -rz --delete public/ $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH
Le pipeline de déploiement
Il est constitué de deux étapes :
build
: permet de construire le dossier public qui contient la structure et les fichiers du blog traduits du Markdown en html/css. Le dossier public est ensuite mis en cache pour être utilisé par l’étape suivante.deploy
: permet de copier avec Rsync ce dossier public sur mon serveur auto-hébergé.
Ce pipeline est conteneurisé dans Gitlab.
Le choix de l’image docker
Si possible je choisis de préférence l’image Alpine, elle est légère ce qui me permet de réduire le temps d’exécution du pipeline. J’ai comparé avec une image Debian, je suis passé de 5min34s à 2min11s avec l’image Alpine.
image: alpine:latest
Les conditions à respecter
Le pipeline est exécuté si et seulement si la branche est master et le message du commit contient le mot clé deploy
only:
- master
only:
variables:
- $CI_COMMIT_MESSAGE =~ /deploy/
La gestion du thème du blog
Le theme que j’utilise pour le blog s’appelle Nederburg2, le code source est disponible sur Gitub.
Avec le générateur de site statique Hugo il y a un répertoire nommé theme. C’est dans ce répertoire que j’importe le thème Nederburg avec la commande git submodule
:
git submodule add https://github.com/appernetic/hugo-nederburg-theme.git
Le theme est un submodule, c’est juste l’adresse URL qui est définie. C’est pourquoi j’utilise la commande suivante dans le script gitlab-ci.yml
pour copier le code du thème.
script:
git submodule update --init --recursive
La génération des clés SSH
Afin que le service Gitlab CI déploie en toute sécurité le dossier public sur mon serveur auto-hébergé, la création d’une paire de clés SSH est nécessaire.
Je crée une paire de clés SSH ed25519 qui est une implémentation de la Courbe d’Edwards tordue. Elle propose le même niveau de sécurité que RSA tout en consommant moins de ressources CPU.
Les clés sont générées sans utiliser de passphrase.
ssh-keygen -f ~/.ssh/gitlabci -t ed25519
cat ~/.ssh/gitlabci.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Par sécurité, la clé privée n’est pas écrite dans le fichier de déploiement gitlab-ci.yml
. J’utilise une variable d’environnement, ce qui signifie que la clé privée est écrite dynamiquement, lorsque le pipeline de déploiement est en cours d’exécution.
Je définis mes variables d’environnement dans les options de mon compte Framagit.
Voir l’image ci-dessous :
SSH_KNOWN_HOSTS
: La clé d’hôte. Permet de vérifier que le client est connecté au bon hôte. Je l’obtiens avec la commande suivante :ssh-keyscan mydomainname
.SSH_PRIVATE_KEY
: La clé privée sans passphrase.
Mise en place d’une instance GitLab Runner
tags:
- private
Framagit met à disposition un GitLab Runnner pour exécuter un pipeline de déploiement. Pour des raisons de sécurité, j’exécute GitLab Runner depuis mon serveur auto-hebergé.
La commande ci-dessous génère le fichier de configuration : /srv/gitlab-runner/config/config.toml
avec l’adresse URL du serveur framagit, le token d’authenfication et d’autres options.
docker run --rm -v /home/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register \
--non-interactive \
--executor "docker" \
--docker-image alpine:latest \
--url "https://*******" \
--registration-token "******" \
--description "private-runner" \
--tag-list "private" \
--run-untagged="false" \
--locked="true" \
--access-level="ref_protected"
Ensuite je démare l’instance GitLab Runner :
docker run -d --name gitlab-runner --restart always -v /home/gitlab-runner/config:/etc/gitlab-runner -v /var/run/docker.sock:/var/run/docker.sock gitlab/gitlab-runner:latest
Mise en place du serveur web
Pour servir les pages statiques du blog j’utilise le serveur web Nginx. Le service écoute sur le port 80, mais j’ai plusieurs autres services web avec le port 80 ouvert, c’est pourquoi j’utilise le reverse-proxy Traefix. De plus Traefix génère automatiquement les certificats TLS.
Voici ci-dessous le fichier docker-compose.yml
---
version: '3.7'
services:
traefik:
container_name: traefik
restart: always
image: traefik:v1.7.16
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /home/traefik/traefik.toml:/traefik.toml
- /home/traefik/acme.json:/acme.json
- /home/traefik/log:/var/log/traefik
labels:
- "traefik.docker.network=traefiknet"
- "traefik.enable=true"
networks:
- traefiknet
blog_web:
container_name: blog_web
image: nginx:stable-alpine
restart: always
volumes:
- /home/blog/output:/usr/share/nginx/html:ro
labels:
- "traefik.docker.network=traefiknet"
- "traefik.frontend.rule=Host:blog.la-forge.ml"
- "traefik.enable=true"
- "traefik.port=80"
networks:
- internal
- traefiknet
networks:
traefiknet:
external: true
internal:
external: false
Cas pratique de publication
La procédure est simple (c’est en effet l’objectif !).
- Je rédige l’article en Markdown.
- La rédaction de l’article terminée, je fais un
git commit
et j’ajoute dans le message de commit le mot clé deploy - Je fais un
git push
. Le pipeline de publication du blog est automatiquement déclenché. - J’attends deux minutes, le temps que le pipeline se termine
Et voilà, le nouvel article est visible sur le blog