Ce blog a été reconfiguré sous Hugo (https://gohugo.io/), un générateur statique de pages HTML à partir de pages markdown, extrêmement souple et rapide (quelques millisecondes pour générer un grand nombre de pages).
Voici la présentation d’une solution possible (parmi plusieurs autres alternatives) pour obtenir une chaîne de publication complète (depuis les sources jusqu’à la publication “en prod”), aussi automatisée que possible et nécessitant aussi peu d’interventions manuelles que possible (quasiment aucune, au final) pour toute mise à jour.
Introduction
Hugo est donc utilisé ici pour transformer les pages .md
(markdown) en pages .html
et ressources associées.
Plusieur solutions sont envisageables pour publier le résultat (liste non-exhaustive) :
- générer les pages
.html
en local (avec le binairehugo
) et publier le répertoire final en ligne au sein d’un serveur web sur le serveur de production - idem mais en hébergeant les pages générées sous GitHub
- stocker le répertoire source du projet sous GiT, le push-er sur un repository hébergé sur le serveur de production et automatiser le déclenchement de la génération des pages
.html
sur ce dernier, pages qui seront ensuite publiées via un serveur web - idem mais en utilisant le binaire
hugo
en mode “server” directement sur le serveur de production (Hugo réalisera à la fois la génération des pages finales et leur publication) - utiliser le serveur web Caddy (https://caddyserver.com/) en activant le plugin GiT : c’est alors Caddy qui va checker les modifications sur le GiT
origin
et déclencher le lancement du binairehugo
Ce billet va présenter la solution 3., solution qui offre plusieurs avantages :
- une seule opération à exécuter : pousser le répertoire source (qui peut très bien être cloné sur un laptop afin d’être mis à jour, etc.) vers le serveur de production - sur ce dernier, le stockage se fera bien sûr au sein d’un repository GiT distant et dédié ;
- les mises à jour sont répercutées particulièrement rapidement ;
- l’ensemble est relativement facile à mettre en place (au prérequis près qu’il faut un daemon
docker
) ; - la chaîne est fiable : chaque opération ne réalise qu’une seule opération unitaire, sans frioritures - ainsi, en cas d’erreur lors du commit, ou lors de la génération des .html depuis les .md, le site publié n’est jamais arrêté (et reste donc en ligne sans interruptions) ;
- à l’identique, la chaîne peut être mise à jour en temps masqué, ici aussi afin de minimiser les perturbations sur le site publié.
Etapes
Sur le serveur distant, créer un nouveau repository GIT, dans un répertoire dont le nom se termine, par convention, par .git
.
mkdir -p /datas/git/blog.git/
cd /datas/git/blog.git/
git init --bare
Sur le client, dans le projet git courant, ajouter le repository distant qui vient d’être créé :
git remote add live root@tensin.org:/datas/git/blog.git
Ensuite, toujours sur le client, il suffira de pusher les mises à jour soit sur le repository habituel (origin master
), soit sur le repository de publication live
:
git push -u live
Pour mettre à jour en automatique les pages HTML, il va nous falloir ensuite trois choses :
- un mécanisme qui va faire, lors du push de nouvelles données, un checkout du repository GIT distant dans un répertoire temporaire : ce sera un webhook git (étape 3 du schéma) ;
- un mécanisme qui va créer les pages HTML à partir des sources
.md
du projet : ce sera un premier container docker, constamment démarré, avec le binairehugo
en modewatch
(détection des modifications et republish des .html correspondants) (étape 4 du schéma) ; - un mécanisme qui va publier les pages HTML sur une URL : ce sera un deuxième container docker embarquant un serveur web (
apache
,nginx
, etc. - dans mon cas, j’utilisecaddy
) (étape 5 du schéma)
Ajout du webhook, sur le serveur distant, qui sera déclenché après chaque commit, en créant un fichier sous hooks/post-receive
au sein du dépôt GIT (dans mon cas, dans /datas/git/blog.git/
).
Ce fichier sera exécuté à chaque push réalisé avec succès : il va se contenter de faire un checkout / mise à jour des données à publier, depuis le repository git du serveur, vers un espace temporaire (toujours sur le serveur).
#!/bin/bash
cd /datas/blog/ && env -i git pull
Le container docker embarquant hugo
en mode watch
sera chargé de monitorer ce répertoire et, à la moindre modification, de régénérer les pages .html
correspondantes (dans un répertoire de publication).
# Clonage initial
mkdir /datas/blog/
git clone /datas/git/blog.git/ /datas/blog/
# Démarrage du container (une fois créé)
docker run -d -v /datas/blog/:/data/input/ -v /datas/www/:/data/output/ hugo-build
Dockerfile correspondant :
FROM alpine
ENTRYPOINT ["hugo"]
CMD ["--watch", "-s", "/data/input/", "-d", "/data/output/", "--cleanDestinationDir"]
VOLUME ["/data/input", "/data/output/"]
WORKDIR /data/
ENV PATH $PATH:/opt/hugo/
ENV HUGO_PACKAGE_URL Linux-64bit
ENV HUGO_PACKAGE_NAME linux_amd64
ENV HUGO_VERSION 0.18.1
RUN apk --update add git curl && \
mkdir -p /data/ /opt/ && \
curl -sSL https://github.com/spf13/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_${HUGO_PACKAGE_URL}.tar.gz | tar xzf - -C /opt/ && \
mv /opt/hugo_${HUGO_VERSION}_${HUGO_PACKAGE_NAME} /opt/hugo && \
mv /opt/hugo/hugo_${HUGO_VERSION}_${HUGO_PACKAGE_NAME} /opt/hugo/hugo && \
rm -rf /opt/hugo_*gz
Enfin, un container sera chargé de publier les pages HTML, en se mappant simplement sur le répertoire de publication alimenté par le container précédent. Par exemple :
FROM alpine
VOLUME ["/data"]
WORKDIR /data
EXPOSE 80 443 2015
CMD ["/opt/caddy/caddy", "--conf", "/etc/caddy.conf"]
ADD caddy.conf /etc/caddy.conf
RUN apk add --update curl git tar && \
mkdir -p /opt/caddy/ && \
curl --silent --show-error --fail --location \
--header "Accept: application/tar+gzip, application/x-gzip, application/octet-stream" -o - \
"https://caddyserver.com/download/build?os=linux&arch=amd64&features=filemanager%2Cminify" \
| tar --no-same-owner -C /opt/caddy/ -xz caddy \
&& chmod a+x,a+r /opt/caddy/caddy \
&& /opt/caddy/caddy -version
Qui sera quant à lui démarré via :
docker run -d -v /datas/blog/:/data/input/ -v /datas/www/:/data/output/ --name hugo-publish hugo-publish
Avec une configuration par exemple aussi simple que :
:2015
root /data/
gzip
log /var/log/caddy/access.log
Ces deux containers n’ont jamais besoin d’être arrêtés ni redémarrés ! (en usage courant, i.e. sauf montée de version du binaire hugo)
Les répertoires utilisés sur le serveur de production sont donc pour rappel :
Répertoire | Usage |
---|---|
/datas/git/blog.git |
Le repository GiT distant vers lequel on poussera les mises-à-jour via git push |
/datas/blog/ |
La zone temporaire dans laquelle sera extraite le repository GiT distant via le hoot sur les commits git, zone qui sera monitorée par le premier container docker (chargé de transformer les .md en .html) |
/datas/www/ |
La zone de publication des pages générées par hugo (depuis le premier container), zone qui sera montée au sein du serveur web pour publication en ligne des .html |
La publication / mise à jour est extrêmement rapide. Ci-dessous un push + déclenchemnt du hook (ce sont les lignes commençant par remote:
). La durée du push comprendra donc le temps de push, le temps du checkout “local”, et le temps de transformations des pages markdown via hugo.
0:03 root@jupiter /home/downloads/downloaded/workspaces/blog# git push live
Décompte des objets: 5, fait.
Delta compression using up to 4 threads.
Compression des objets: 100% (5/5), fait.
Écriture des objets: 100% (5/5), 418 bytes | 0 bytes/s, fait.
Total 5 (delta 4), reused 0 (delta 0)
remote: From /datas/git/blog
remote: 6775c32..97d2eed master -> origin/master
remote: Updating 6775c32..97d2eed
remote: Fast-forward
remote: content/drafts/hugo.md | 2 +-
remote: 1 file changed, 1 insertion(+), 1 deletion(-)
To tensin.org:/datas/git/blog.git
6775c32..97d2eed master -> master
Références et liens complémentaires
- Déploiement basé sur
docker
: https://lk4d4.darth.io/posts/hugoblog/ - Déploiement autour de Caddy : https://blog.msfjarvis.me/2016/07/11/how-to-set-up-a-hugo-powered-site-using-caddy/ et https://blog.zenithar.org/post/2016/01/06/automatisation-publication-hugo-avec-caddy/
- Déploiement avec un githook : https://bradleyf.id.au/nix/git-push-deploy-hugo/
- Déploiement via rsync : https://fredrikloch.me/post/automatic_hugo_deploy/