Automatisches Deployment mit Git-Hooks

Automatisches Deployment muss weder teuer, noch kompliziert sein. Du brauchst keinen teuren Build-Server, um Deinen Code automatisch auf Deinem Webspace zu veröffentlichen. Alles was Du brauchst ist Git und ein paar Zeilen in einem Bash-Skript. Heute zeige ich Dir, wie man mit diesen einfachen Mitteln ein automatisches Deployment realisiert.

Oft kann man sich eine Menge Zeit als Entwickler sparen, wenn man einmal einen Moment zurück tritt und darüber nachdenkt, welche Arbeiten sich ständig wiederholen. Deployment ist auf jeden Fall etwas, was man meistens auf die gleiche Art und Weise tut. Und immer wenn etwas repetitiv und gleich ist, eignet es sich für eine Automatisierung.

Nehmen wir zum Beispiel meine Routine zum Deployment von neuem Code. Zunächst einmal schiebe ich meine letzten Änderungen per Git auf den Server. Dann melde ich mich per SSH auf dem Server an, checke die neueste Version aus, führe eventuell noch ein Update der Pakete aus. Ich nutze in der Regel NPM für meinen Workflow, also führe ich ein npm install oder npm update aus. Anschließend kompiliere ich meine SCSS- und ES6-Dateien mit npm run compile.

Eine Sache fällt hierbei sofort auf. Erstens nutze ich nur Bash-Befehle und zweitens immer die gleichen. Ich könnte also ein Skript schreiben, welches genau diese Befehle nacheinander ausführt. Über SSH kann man übrigens auch direkt Befehle ausführen. Es geht allerdings noch einfacher.

Git verwendet ein Konzept von sogenannten Hooks. Das sind Shell-Skripte, welche bei bestimmten Aktionen automatisch ausgeführt werden. Das kann zum Beispiel bei pushen oder pullen von Daten sein. Uns interessiert hierbei vor allem die post-receive-Hook. Dieses Skript wird immer dann ausgeführt, wenn Daten aus einem anderen Repository empfangen wurden. Git-Hooks werden immer im Ordner .git/hooks abgelegt. Dort findet Ihr auch bereits einige Beispiele.

Automatisch den aktuellsten Commit auschecken

Wenn wir zum Beispiel immer automatischen den aktuellsten Commit auschecken wollen, dann erstellen wir zunächst eine Datei post-receive im Ordner .git/hooks im Ziel-Repository auf Eurem Server. Dieser muss ausführbar sein, wir geben Ihr also mit chmod +x .git/hooks/post-receive die entsprechenden Rechte. Der Inhalt der Datei sieht wie folgt aus:

#!/usr/bin/env bash

commit=$(git rev-parse master)

echo "Checking out latest commit $commit..."
git --work-tree=./.. checkout -f $commit

cd ..
npm install
npm run compile

echo '...done.'

Zunächst einmal holen wir uns mit git rev-parse master den Hash des letzten Commits aus dem Branch master. Dann checken wir diesen Commit mit dem Flag -f (also force) aus. Wir geben hier explizit den work-tree an, denn in der Regel befinden wir uns nun im Ordner .git und nicht im Root des Repositories. Sollte das bei Euch anders sein, dann könnt Ihr Euch mit pwd den aktuellen Ordner angeben lassen und das Skript entsprechend anpassen.

Anschließend wechseln wir in den Root-Ordner (des Repositories) und installieren die Dependencies nochmal neu. Zu guter Letzt wird npm run compile ausgeführt und der Source-Code kompiliert. Diese Schritte werden natürlich bei Euch verschieden sein. Hier könnt Ihr das Skript also entsprechend Eures eigenen Workflows abändern.

Automatisch den aktuellsten Tag auschecken

Immer den neuesten Commit auszuchecken, kann natürlich auch gefährlich sein. Wenn man etwas mehr Kontrolle über die Veröffentlichung haben möchte, sollte man statt dessen mit Tags arbeiten. Wir erstellen also wieder eine Datei post-receive im Ordner .git/hooks auf dem Ziel-Server. Diese Datei hat diesmal folgenden Inhalt:

#!/usr/bin/env bash

version=$(git describe --tags $(git rev-list --tags --max-count=1))

echo "Checking out $version..."
git --work-tree=./.. checkout -f $version

echo "Updating & compiling website..."

cd ..
npm install
npm run compile

echo "done."

Diesmal ermitteln wir den aktuellsten Tag und checken diesen anschließend aus. Ansonsten brauchen wir keine Änderungen am Skript vorzunehmen.

Automatisch den aktuellsten Tag auschecken (mit Caching)

Das Problem mit der vorherigen Lösung ist, dass wir wirklich bei jedem Push wieder den neuesten Tag auschecken und die Source-Dateien kompilieren. Wirklich Sinn macht das natürlich nur, wenn sich der Tag vom vorherigen unterscheidet. Hierfür müssen wir die aktuelle Version irgendwo zwischen speichern. Wir verwenden nun also statt dessen folgendes Skript:

#!/usr/bin/env bash

new_version=$(git describe --tags $(git rev-list --tags --max-count=1))
old_version=""

if [ -f ".version" ]; then
  old_version=$(cat .version)
fi

if [ "$old_version" != "$new_version" ]; then
  echo $new_version > ../.version

  echo "Update detected... checking out $new_version."
  git --work-tree=./.. checkout -f $new_version

  echo "Updating & compiling website..."

  cd ..
  npm install
  npm run compile

  echo "done."
else
  echo "No update detected. Version is $old_version."
fi

Wir speichern nun jeweils die aktuelle Version in einer Datei .version zwischen. Es bietet sich an diese Datei in die .gitignore aufzunehmen. Bei jedem neuen Durchlauf, checken wir zunächst, ob sich der gefundene Tag (new_version) von dem aktuell ausgecheckten Tag (old_version) unterscheidet. Wenn nicht, überspringen wir den kompletten Prozess einfach.

Fazit

Ihr seht also, automatisches Deployment ist weder kompliziert, noch besonders aufwendig. Mit ein paar einfachen Tricks kommt man sehr schnell zu einem passablen Ergebnis. Was hier natürlich noch nicht berücksichtigt ist, sind etwaige Fehler beim Build-Prozess und auch das Arbeiten mit mehreren Entwicklern am gleichen Projekt. Hier kann man sich allerdings mit ein paar einfachen Regeln behelfen. Wie zum Beispiel, dass nur ein Entwickler Code auf den Live-Server bzw. in den master-Branch schiebt. Fehler könnte man abfangen, indem man den Exit-Code überprüft und im Fehlerfall eine Mail an den entsprechenden Entwickler schickt.

Noch keine Kommentare vorhanden.