Hugo Site mit Gitlab deployen
Wie ich in einem Blog schrieb, wird diese Seite nun mit dem statischen Site-Generierer Hugo erstellt.
Das Schreiben geht leicht von der Hand, aber vor der Veröffentlichung sind noch einige Schritte nötig. Diese habe ich nun mit gitlab-ci auf gitlab.com automatisiert. Jeder Push ins Repository baut die Seite neu und überträgt die Änderungen auf meinen Webspace bei uberspace.
Die Vorlage für meine Umsetzung fand ich in diesem Blog der aber schon ein bisschen älter ist, so dass ich einige Anpassungen vornehmen musste. Genug Vorgeplänkel. Los geht’s.
Gitlab.com ist das Clound-Angebot der gleichnamigen Firma.
Dort kann sich jede(r) einen Account anlegen.
Im Gegensatz zu ähnlichen Anbietern wie Microsoft Github, kann man auch im freien Paket private Repositories anlegen.
Außerdem ist Gitab Community-Edition Freie Software.
Alle diese Schritte lassen sich also auch dort nachvollziehen.
Gitlab-ci ist ein Build-Runner, der Jobs ausführt, die jeweils in einem Docker-Container isoliert ausgeführt werden. Die Jobs werden von einer Pipeline koordiniert, die durch einen Push ausgelöst wird.
Die Hugo-Site wird in ein neues Repository eingecheckt. Das Theme sollte als git-Modul eingebunden werden.
.gitlab-ci.yml
Zusätzlich wird die Datei .gitlab-ci.yml auf der obersten Ebene des Repositories benötigt:
stages:
- build
- deploy
build:
stage: build
image: registry.gitlab.com/softmetz/softmetz.de-ci-build-hugo
script:
- git submodule update --init --recursive
- hugo -b "${BLOG_URL}"
artifacts:
paths:
- public
expire_in: 1 hour
only:
- master
deploy:
stage: deploy
image: registry.gitlab.com/softmetz/softmetz.de-ci-deploy-rsync-ssh
script:
- echo "${SSH_PRIVATE_KEY}" > id_rsa
- chmod 700 id_rsa
- mkdir "${HOME}/.ssh"
- echo "${SSH_HOST_KEY}" > "${HOME}/.ssh/known_hosts"
- rsync -at --quiet --delete --delete-delay --delay-updates --exclude=_ --include=.well-known -e 'ssh -i id_rsa' public/ "${SSH_USER_HOST_LOCATION}"
variables:
GIT_STRATEGY: none
only:
- master
Der Abschnitt
stages:
- build
- deploy
bestimmt, dass die Pipeline aus zwei Stages besteht, build und deploy. Den Stages können dann Jobs zugeordnet werden, die stage-weise nacheinander, aber in der Stage parallel laufen. In diesem Fall gibt es zwei Stages und zwei Jobs, je stage einer.
Der Job build
Das Bauen der Hugo-Site übernimmt der Job namens “build”:
build:
stage: build
image: registry.gitlab.com/softmetz/softmetz.de-ci-build-hugo
script:
- git submodule update --init --recursive
- hugo -b "${BLOG_URL}"
artifacts:
paths:
- public
expire_in: 1 hour
only:
- master
Die beschriebenen Schritte werden in einem Docker-Container namens registry.gitlab.com/softmetz/softmetz.de-ci-build-hugo
ausgeführt.
Gitlab-ci checkt im Standard das Repository an einem festen Pfad aus und wechselt dann in diesem Verzeichnis. Im ersten Schritt werden dann die git-Submodule aktualisiert. Dadurch wird das Theme initial geklont oder aktualisiert.
Der zweite Schritt ist dann der eigentliche Hugo-Lauf.
In diesem Fall wird mit -b ein Base-URL für die Seiten angegeben.
Das Ergebnis wird in das Verzeichnis public
geschrieben.
Der Abschnitt
artifacts:
paths:
- public
expire_in: 1 hour
sorgt nun für den Austausch der Daten zwischen den beiden Jobs build und deploy. Dabei muss bedacht werden, dass die Jobs wirklich von zwei verschiedenen Docker-Instanzen ausgeführt werden, die im Normalfall auch auf verschiedenen Host-Systemen laufen. Die Brücke stellt ein Artifact-Server dar, der die Daten entgegen nimmt.
Abschließend sagt
only:
- master
noch, dass der Job nur für den Branch master
ausgeführt wird.
Andere Tags oder Branches werden ignoriert.
Der Job deploy
Nach dem Ende des Jobs Build liegt die generierte Seite im Cache. Im nächsten Schritt muss sie auf den Webspace gebracht werden. Wieder der Code im Überblick:
deploy:
stage: deploy
image: registry.gitlab.com/softmetz/softmetz.de-ci-deploy-rsync-ssh
script:
- echo "${SSH_PRIVATE_KEY}" > id_rsa
- chmod 700 id_rsa
- mkdir "${HOME}/.ssh"
- echo "${SSH_HOST_KEY}" > "${HOME}/.ssh/known_hosts"
- rsync -at --quiet --delete --delete-delay --delay-updates --exclude=_ --include=.well-known -e 'ssh -i id_rsa' public/ "${SSH_USER_HOST_LOCATION}"
variables:
GIT_STRATEGY: none
only:
- master
Wieder wird ein Docker-Image referenziert, diesmal registry.gitlab.com/softmetz/softmetz.de-ci-deploy-rsync-ssh
.
Dieses stellt das Programm rsync und ssh zur Verfügung.
Da der Container immer wieder neu erstellt wird, müssen einige Vorarbeiten gemacht werden.
Der Abschnitt
- echo "${SSH_PRIVATE_KEY}" > id_rsa
- chmod 700 id_rsa
- mkdir "${HOME}/.ssh"
- echo "${SSH_HOST_KEY}" > "${HOME}/.ssh/known_hosts"
kopiert einen SSH-Private-Key (auf keinen Fall einen dedizierten nehmen, nicht den eigenen der noch außerhalb des Webspace verwendet wird) in die Datei id_rsa
und passt die Berechtigungen an.
Dann wird noch der Host-Key des Webspace-Servers in die Datei `${HOME}/.ssh/known_hosts eingefügt.
Dies ist nötig, damit ssh nicht mit der Meldung abbricht, dass der Benutzer den Host-Key bestätigen muss.
Die eigentliche Arbeit passiert dann in
- rsync -at --quiet --delete --delete-delay --delay-updates --exclude=_ --include=.well-known -e 'ssh -i id_rsa' public/ "${SSH_USER_HOST_LOCATION}"
-atz
verarbeitet den Dateibaum rekursiv im Archiv-Modus.
--delete
löscht auf der Gegenseite Dateien, die an der Quelle existieren.
--delete-delay
und --delay-updates
optimieren die Reihenfolge der Operationen und beschleunigen so den Transfer.
-e 'ssh -i id_rsa'
legt fest, dass der SSH-Private-Key von eben verwendet wird.
Im Kern wird dann das Verzeichnis public/
nach ${SSH_USER_HOST_LOCATION}
übertragen.
Da der Source-Code nicht mehr benötigt wird, sparen wir uns mit dem folgenden Ausdruck den Checkout:
variables:
GIT_STRATEGY: none
Die Repository-Konfiguration
Gerade im zweiten Job wird viel mit Variablen gemacht. Wo kommen diese Variablen her? Sie werden im Gitlab-Frontend hinterlegt.
Wenn man sich auf der Hauptseite des Projekts befindet, wechselt man in Settings
und dann nach CI/CD
.
In der Sektion Variables
können schließlich die Variablen hinterlegt werden:
BLOG_URL
: Die URL, unter der die Seite läuft. Bei mir https://softmetz.deSSH_HOST_KEY
: Der Host-Key vom WebspaceSSH_PRIVATE_KEY
: Der SSH-Private-Key (Nimm einen eigenen für den Job, nicht den üblichen)SSH_USER_HOST_LOCATION
: Der Komplette Zielpfad, inkl. User, Host und Verzeichnis, also user@example.com:/path/to/docroot
Wenn die Variablen gespeichert wurden und ein Push ins Repo gemacht wird, sollte jetzt eigentlich alles funktionieren. Das liegt daran, dass ich die Docker-Images bereits gebaut habe. Aber natürlich kann man sich die selbst erstellen, Sicherheit, Vertrauen und so.
Das Hugo-Docker-Image
Das Docker-Image für Hugo wird mit folgendem Dockerfile erstellt:
FROM alpine:3.7
RUN apk add --update \
git && \
rm -rf /var/cache/apk/*
ENV HUGO_VERSION 0.42.2
ENV HUGO_RESOURCE hugo_${HUGO_VERSION}_Linux-64bit
ADD https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/${HUGO_RESOURCE}.tar.gz /tmp/
RUN mkdir /tmp/hugo && \
tar -xvzf /tmp/${HUGO_RESOURCE}.tar.gz -C /tmp/hugo/ && \
mv /tmp/hugo/hugo /usr/bin/hugo && \
rm -rf /tmp/hugo*
Das ganze basiert auf alpine-Linux, einer sehr kleinen Linux-Distribution.
RUN apk add --update \
git && \
rm -rf /var/cache/apk/*
installiert git aus den Paketquelle von alpine.
ENV HUGO_VERSION 0.54.0
ENV HUGO_RESOURCE hugo_${HUGO_VERSION}_Linux-64bit
ADD https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/${HUGO_RESOURCE}.tar.gz /tmp/
Legt fest, welche Version von Hugo verwendet werden soll und lädt die von Microsoft Github herunter. Das Archiv landet in /tmp.
RUN mkdir /tmp/hugo && \
tar -xvzf /tmp/${HUGO_RESOURCE}.tar.gz -C /tmp/hugo/ && \
mv /tmp/hugo/hugo /usr/bin/hugo && \
rm -rf /tmp/hugo*
schließlich kopiert hugo an die richtige Stelle und löscht Altlasten.
Das Image wird mit
docker build -t repository/image:latest
gebaut und mit
docker push repository/image:latest
veröffentlich.
Wie ich inzwischen herausgefunden habe, kann man auf Gitlab.com sogar Docker-in-Docker verwenden, um Docker-Images zu bauen.
Dafür verwende ich folgendes .gitlab-ci.yml
:
image: docker:latest
services:
- docker:dind
stages:
- build
variables:
DOCKER_IMAGE_TAG: registry.gitlab.com/softmetz/softmetz.de-ci-build-hugo
before_script:
- echo $CI_BUILD_TOKEN | docker login --username gitlab-ci-token --password-stdin registry.gitlab.com
build:
stage: build
script:
- docker build --pull -t $DOCKER_IMAGE_TAG .
- docker push $DOCKER_IMAGE_TAG
Das rsync-Docker-Image
Analog wird mit dem zweiten Images verfahren. Hier das Dockerfile:
FROM alpine:3.7
RUN apk add --update \
openssh \
rsync && \
rm -rf /var/cache/apk/*
Analog gibt es nun folgendes .gitlab-ci.yml
:
image: docker:latest
services:
- docker:dind
stages:
- build
variables:
DOCKER_IMAGE_TAG: registry.gitlab.com/softmetz/softmetz.de-ci-deploy-rsync-ssh
before_script:
- echo $CI_BUILD_TOKEN | docker login --username gitlab-ci-token --password-stdin registry.gitlab.com
build:
stage: build
script:
- docker build --pull -t $DOCKER_IMAGE_TAG .
- docker push $DOCKER_IMAGE_TAG
Das ganze sollte nach dem vorherigen Abschnitt selbsterklärend sein.
Fazit
Gitlab und Gitlab-ci sind richtig cool und nach etwas Lernen geht die Benutzung ganz einfach von der Hand. Die Automatisierung spart pro Änderung an der Seite 5 bis 10 Minuten manuelle Arbeit, Zeit die mir zum Schreiben von solchen schönen Anleitungen bleibt. :-)
Updates:
2019-02-01
- Microsoft Github bietet inzwischen auch kostenlose private Repositories, frei ist es aber immer noch nicht.
- Bei rsync wurde –delete-delay und –delay-updates eingefügt.
- Der Cache in .gitlab-ci.yml wurde durch Artefakte ersetzt.
- Hugo Version auf 0.54.0 geändert.
gitlab-ci.yml
-Dateien für die Docker-Images hinzugefügt.