Pourquoi faire simple quand on peut faire compliqué ?
De prime-abord, on pourrait se dire : "si je veux un blog, je lance un WordPress comme la moitié du web et je commence à publier en deux minutes". Mais quand on est un vieux routard du web, on a des aspirations et des principes un peu plus élitistes :
- WordPress ne génère pas de sites statiques (nativement)
- Le fait qu'il soit utilisé par la moitié d'Internet me révèle que c'est une cible facile pour des personnes ou des robots mal-intentionnés
- PHP est indispensable pour faire fonctionner WordPress, même si WordPress est configuré pour générer des pages pseudo-statiques
- WordPress est une usine à gaz, qui sort du HTML, du CSS et du Javascript pas optimisés
- Le code de WordPress est abominablement dégueulasse, impossible de faire quoique ce soit de propre, ni même de léger
- WordPress est impersonnel, ce que je trouve embêtant pour un blog (et encore plus pour un site corporate d'ailleurs) : il doit être le reflet de son auteur ; s'il ressemble à tous les autres, quel intérêt ? (et je vous le dis : ce n'est pas simplement parce que vous customisez un thème qu'on ne voit pas que vous utilisez WordPress...)
- WordPress a besoin d'une base de données
- L'écrasante majorité des extensions tierces un tant soit peu intéressantes sont payantes ou sur l'immonde modèle du premium
Un générateur de sites statiques comme Hugo me permet de :
- Séparer l'apparence, le contenu, et l'outil qui construit le site final (je peux utiliser autre chose qu'Hugo si j'en ai envie, moyennant quelques ajustement mineurs). Impossible de faire ça avec WordPress
- Publier un site 100% sécurisé : sans PHP, sans formulaires, sans Javascript, le maillon faible de la sécurité de mon site n'est pas mon site
- Avoir un suivi de mes modifications (grâce à Git)
- Simplifier les processus de sauvegarde et de restauration (en gros, avoir un plan de relance en béton armé en cas de défaillance technique - je peux même faire héberger temporairement mon site ailleurs sans avoir besoin d'aucune autre dépendance qu'un serveur web, impossible de faire ça avec WordPress en un minimum de temps)
- Être réellement libre du point de vue logiciel
L'utilisation de git dans ce processus est totalement facultative : il est tout à fait possible de construire son site avec Hugo sans tout ce que je vais présenter dans cet article. Néanmoins, cette procédure qui semble fastidieuse de prime-abord est en réalité très puissante et très confortable au quotidien :
- Je créé ou modifie mes articles sous la forme de fichiers Markdown - pratiquement du texte brut
- Je les publie sur ma forge Gitea
- Drone-CI fait le reste : construire le site en lançant l'exécutable Hugo et envoyer les fichiers HTML et CSS au serveur de production
Pour peu qu'on choisisse un thème existant plutôt qu'en créer un, on peut réellement ne se préoccuper que du contenu de son site.
Composant logiciels
Tout passe par des containers (docker, évidemment, mais l'utilisation de podman est possible). Je présume donc que docker est installé et fonctionnel.
Gitea
Gitea est une forge logicielle qui s'appuie sur le système de gestion de code Git. J'ai choisi Gitea pour sa sobriété, sa légèreté et sa simplicité, mais il existe d'autres forges logicielles : GitHub qui appartient à Microsoft et qu'il n'est pas possible d'auto-héberger, ou Gitlab pour ne citer que ces deux-là.
docker-compose.yml :
version: "3"
services:
gitea:
image: gitea/gitea:latest
restart: always
ports:
- "${HTTP_PORT}:3000"
- "${SSH_PORT}:22"
volumes:
- ${DATA_DIR}:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
Remplacez les variables en fonction de vos besoins :
-
HTTP_PORT
: le port sur lequel les requêtes HTTP arrivent sur votre serveur (pas le port HTTP du container) -
SSH_PORT
: le port du serveur SSH du serveur (pas le port SSH du container) -
DATA_DIR
: le chemin complet du dossier dans lequel stocker les données de Gitea, ou le volume si vous préférez utiliser un volume
Hugo
Hugo est un générateur de sites statiques. C'est ce que j'utilise pour créer et maintenir mon blog. Il en existe d'autres, tels que Jekyll. J'ai choisi Hugo pour sa légèreté.
L'essentiel du travail avec Hugo se fait sur votre machine locale. Il n'y a rien à installer côté serveur. La construction du site se fera via Drone-CI, dans lequel on va configurer un pipeline pour compiler les ressources CSS (et éventuellement Javascript), et créer l'ensemble du site statique dans un container docker. Tous ces fichiers seront ensuite envoyé au serveur web via rsync, scp, ou ce que vous voulez.
Pour faire tout cela, il "suffit" d'ajouter un fichier .drone.yml à la racine de votre projet Hugo avec le contenu suivant :
kind: pipeline
type: docker
name: default
steps:
- name: submodules
image: alpine/git
commands:
- git submodule update --init --recursive
- name: build
image: klakegg/hugo:ext-ci
commands:
- cd themes/blog_theme && npm i && cd -
- hugo --gc --minify --environment production
- name: deploy
image: appleboy/drone-scp
settings:
host: "10.0.2.1"
target: /mnt/volume1/shares/www/richard-dern.fr/www/
source: public/*
username:
from_secret: ssh_user
password:
from_secret: ssh_password
trigger:
branch:
- master
L'étape submodules (entre les lignes 7 et 10) n'est nécessaire que si le thème que vous utilisez est dans un submodule git dans le répertoire theme de votre projet Hugo. Si le thème est directement dans l'arborescence de votre projet, vous pouvez supprimer les lignes concernées.
Le thème que j'ai créé utilise
le framework Tailwind CSS, j'ai donc besoin de
compiler les ressources via npm
, ce que je fais à la ligne 15, juste avant de
construire toutes les pages du site. Jusqu'à présent, c'est plutôt simple, non ?
Ensuite, l'étape la plus compliquée (deploy). Il faut créer des secrets dans
Drone-CI (ici, ssh_user
et ssh_password
), mais nous verrons cela un peu plus
bas. Pensez simplement à modifier les valeurs de host et target : l'adresse
IP du serveur qui va héberge Hugo et le chemin où les fichiers générés par Hugo
seront envoyés.
Ceci dit, ici j'utilise scp
, mais d'autres services sont disponibles.
Consultez la liste des plugins Drone-CI pour utiliser
celui qui convient à votre hébergement.
Enfin, on a ajouté la directive trigger pour que tout ce processus de publication ne soit déclenché que lorsque la branche git master est modifiée, l'idée étant de protéger la branche master en écriture, modifier le contenu dans une nouvelle branche, fusionner avec master une fois terminé, mais là on part dans des considérations philosophiques devops :smile:
Drone-CI
Drone-CI est une application d'intégration continue : c'est le genre d'applications qui s'intercalent entre la forge logicielle et les serveurs de mise en production, une position privilégiée qui confère à ces outils beaucoup de puissance et un intérêt considérable. Tests unitaires, construction de ressources, publication automatique, les cas d'usage sont nombreux.
La documentation de Drone-CI préconise de l'installer sur une machine physique différente du serveur Gitea afin d'éviter les problèmes et de simplifier la configuration. C'est l'option que j'ai choisi, mais en pratique, si vous savez ce que vous faites, vous ne devriez pas avoir de soucis.
Il faut toutefois prendre en considération les ressources du (des) serveurs. Si vous utilisez des Raspberry Pi par exemple, il vaut peut-être mieux lancer Drone-CI sur un Pi dédié à cet usage.
Pour que Drone-CI puisse communiquer avec Gitea, il faut aller dans Gitea, votre profil, puis dans l'onglet Applications. Là, créez une application OAuth. Mettez ce que vous voulez comme nom ("drone", par exemple), et l'URL de redirection, qui correspond à l'URL complète à laquelle vous accèderez à Drone-CI, suffixée par /login. Dans mon cas, l'URL de redirection est :
https://ci.athaliasoft.com/login
Validez, et Gitea vous communiquera un certain nombre d'informations à renseigner dans le fichier docker-compose.yml pour Drone-CI, notamment un Client ID et un Client Secret, à remplacer ci-dessous.
docker-compose.yml :
version: "3"
services:
drone:
image: drone/drone
restart: always
ports:
- "${PORT}:80"
environment:
- DRONE_GITEA_SERVER=${DRONE_GITEA_SERVER}
- DRONE_GITEA_CLIENT_ID=${DRONE_GITEA_CLIENT_ID}
- DRONE_GITEA_CLIENT_SECRET=${DRONE_GITEA_CLIENT_SECRET}
- DRONE_RPC_SECRET=${DRONE_RPC_SECRET}
- DRONE_SERVER_HOST=${DRONE_SERVER_HOST}
- DRONE_SERVER_PROTO=https
volumes:
- ${VOLUME_ROOT}:/data
-
DRONE_GITEA_SERVER
correspond à l'URL de votre serveur Gitea -
DRONE_RPC_SECRET
est une clé à générer via la commandeopenssl rand -hex 16
(par exemple) -
DRONE_SERVER_HOST
est le nom d'hôte correspondant à l'URL de redirection (donc, dans mon cas spécifique, drone.git.dern.ovh) -
VOLUME_ROOT
, un volume docker ou un répertoire dans lequel seront stockées les données de Drone-CI
Runners
En plus de Drone-CI, il faut configurer des runners, c'est-à-dire un ou plusieurs services qui vont exécuter les pipelines configurés. Comme je dispose de plusieurs serveurs, j'ai installé un runner par serveur, ce qui permet d'améliorer les performances générales de Drone-CI, mais il est tout à fait possible (et viable) d'avoir un seul runner installé sur la même machine que Drone-CI.
docker-compose.yml :
version: "3"
services:
runner:
image: drone/drone-runner-docker
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- "3000:3000"
environment:
- DRONE_RPC_PROTO=http
- DRONE_RPC_HOST=drone
- DRONE_RPC_SECRET=${DRONE_RPC_SECRET}
- DRONE_RUNNER_CAPACITY=${DRONE_RUNNER_CAPACITY}
- DRONE_RUNNER_NAME=${DRONE_RUNNER_NAME}
Pensez à modifier les variables suivantes pour convenir à votre environnement :
-
DRONE_RPC_SECRET
, la même clé que celle définie plus haut pour Drone-CI -
DRONE_RUNNER_CAPACITY
, le nombre de pipelines que le runner peut exécuter en même temps (1 est suffisant pour des environnements modestes, 2 ou plus pour des environnements plus musclés) -
DRONE_RUNNER_NAME
, un nom à attribuer au runner pour savoir quel runner a exécuté quoi et quand
Performances
En voyant tout ça, on pourrait se dire que c'est consommateur de ressources. En
réalité, tout cela est très, très léger, et très performant. Il faut moins de 30
secondes pour déployer en production un nouvel article ; une durée qui serait
beaucoup - beaucoup - plus courte s'il n'y avait pas l'étape de compilation CSS
avec npm
.
La possibilité de tirer partie d'un nombre indéterminé de runners est un gage de scalabilité, même si les performances sont déjà élevées avec un seul serveur, grâce à la sobriété des applications mises en oeuvre.
J'aime ce qui est rapide et performant, même si cela nécessite un peu de temps pour être mis en oeuvre. Le processus expliqué ici respecte mon cahier des charges.
Conclusion
J'ai présenté ici le déploiement automatisé d'un site statique créé avec Hugo, puisant dans un serveur Gitea, en construisant le site via Drone-CI avant de copier les fichiers sur le serveur de production. C'est un cas d'usage parmi d'autres : je me sers également de cette configuration pour créer automatiquement des releases pour certaines de mes librairies, par exemple.
Le simple fait de parcourir la liste des plugins de Drone-CI donne un aperçu de ce qu'il est possible de faire out-of-the-box, mais en réalité, il n'y a pas vraiment de limite dans la mesure où on peut facilement créer ses propres plugins, puisque ce ne sont que des containers docker !