WordPress-Deployment via Git
Veröffentlicht: – Kommentar hinterlassen Letzte Aktualisierung:
WordPress kann schnell installiert und verwendet werden. Aber man kann es auch übertreiben und via Git bereitstellen, um einige Vorteile zu haben. In diesem Artikel findest du Informationen darüber, warum das eine gute Idee sein kann und welche Unwägbarkeiten auf dich warten.
Da unterschiedliche Anforderungen zu unterschiedlichen Lösungen führen, werde ich an dieser Stelle keinen wirklichen Deployment-Code teilen. Dennoch werde ich dessen verwendete Logik hier erklären, um WordPress auf einen Server zu laden.
Unser Ziel
Als wir in unserem Szenario darüber nachdachten, was wir erreichen wollten, klang es recht einfach: denselben WordPress-Code auf drei Maschinen zu haben, die von einem Load Balancer verwendet werden, um eingehende Anfragen beantworten zu können.
Der Status quo war, dass wir ein NFS (Network File System) verwendeten, um den Code von WordPress an einem einzigen Punkt zu speichern und von jedem der drei Maschinen auf ihn zuzugreifen. Das macht dieses NFS zu einem „Single Point of Failure“, und das ist der Hauptgrund, warum wir das ändern wollten. (Abgesehen davon ist NFS in unserem Szenario nicht so zuverlässig, wie wir gehofft haben.)
Ein weiterer Vorteil der Bereitstellung über Git ist, dass wir einfach die Schreibrechte für einen großen Teil der WordPress-Instanz deaktivieren können, was die Sache ziemlich sicher macht. Denn selbst wenn ein Plugin es einem Angreifer ermöglichen würde, bösartigen Code auf den Server zu schreiben, geht das nicht, wenn die Verzeichnisse von WordPress schreibgeschützt sind.
In unserem Fall möchten wir auch ein Deployment über den Ansible-Deploy-Helper verwenden, der es uns ermöglicht, automatisch zu einem früheren Zustand zurückzukehren, wenn ein Deployment fehlschlägt oder die Website nach einem Deployment nicht mehr zugänglich ist. Ein noch größerer Vorteil bei der Verwendung eines solchen Systems ist, dass die Dateien zunächst in ein anderes Verzeichnis übertragen werden und erst nachdem alle Dateien übertragen wurden, das alte Verzeichnis durch die neue Version ersetzt wird, so dass immer ein konsistenter Zustand auf Ihrem Server vorliegt. Andernfalls kann es gern mal für eine kurze Zeit vorkommen, dass nicht alle Dateien zur Verfügung stehen und vereinzelt Benutzer dann die Website aufgrund eines solchen Fehlers nicht erreichen können.
Initialisierung des Repositorys
Zuerst benötigen wir den WordPress-Code in unserem Repository. Glücklicherweise ist das WordPress-Projekt nicht mehr nur an SVN mit ihrem Code gebunden, sodass wir lediglich das offizielle Git-Repository von GitHub klonen müssen:
https://github.com/WordPress/WordPress
Stelle dabei sicher, dass du dieses Repository verwendest, da es den Code in seiner finalen Form speichert, wie es auch als ZIP-Datei zum Download verwendet wird. Das „wordpress-develop“-Repository dagegen wird für die aktive Entwicklung verwendet.
WordPress-Konfiguration via .env
Die Bereitstellung des Codes selbst ist eine Sache, aber um ihn zum Laufen zu bringen, musst du auch die richtige Konfiguration einrichten. Da das WordPress-Repository selbst keine wp-config.php
enthält, musst du deine eigene erstellen.
Du könntest deine Anmeldedaten direkt in diese Datei eingeben, aber da du sie zu deinem Git-Repository hinzufügen musst, solltest du das nicht tun. Anmeldedaten gehören nicht in das Git-Repository, da es keinerlei Schutz bietet, was ein großes Sicherheitsrisiko für deine WordPress-Installation darstellt.
Deshalb solltest du eine .env
-Datei verwenden, die dann über deine Continuous Delivery (CD) innerhalb deiner Continuous Integration (CI), z. B. über GitHub oder GitLab, auf den Server übertragen werden kann. In meinem Fall sieht diese Datei wie folgt aus:
WORDPRESS_DB_NAME=wordpress_database
WORDPRESS_DB_USER=wordpress_user
WORDPRESS_DB_PASSWORD=*snip*
WORDPRESS_DB_HOST=localhost
WORDPRESS_AUTH_KEY='*snip*'
WORDPRESS_SECURE_AUTH_KEY='*snip*'
WORDPRESS_LOGGED_IN_KEY='*snip*'
WORDPRESS_NONCE_KEY='*snip*'
WORDPRESS_AUTH_SALT='*snip*'
WORDPRESS_SECURE_AUTH_SALT='*snip*'
WORDPRESS_LOGGED_IN_SALT='*snip*'
WORDPRESS_NONCE_SALT='*snip*'
WORDPRESS_DISALLOW_FILE_MODS=true
WORDPRESS_DOMAIN_CURRENT_SITE=example.com
WORDPRESS_SENTRY_DSN=*snip*
WORDPRESS_MEMCACHED_HOST=localhost
WORDPRESS_MEMCACHED_PORT=11211
WORDPRESS_ACF_PRO_LICENSE='*snip*'
Code-Sprache: JavaScript (javascript)
Du kannst Variablen definieren, wie du möchtest, die du dann in deiner wp-config.php
verwenden kannst. Übertrage die .env
-Datei in dasselbe Verzeichnis wie die wp-config.php
oder vielleicht sogar eine Ebene darüber. Stelle lediglich sicher, dass PHP Zugriff auf sie hat. Setze ebenso sinnvolle Zugriffsrechte für die Datei, um sie für andere Benutzer nicht lesbar zu machen.
Dann kannst du sie am Anfang deiner wp-config.php
über diese einzelne Code-Zeile laden:
$env = \parse_ini_file( '.env' );
Code-Sprache: PHP (php)
Damit sind alle deine Variablen, die du in deiner .env
-Datei gespeichert hast, in der Variable $env
gespeichert und folgendermaßen zugänglich:
define( 'DB_NAME', $env['WORDPRESS_DB_NAME'] );
define( 'DB_USER', $env['WORDPRESS_DB_USER'] );
define( 'DB_PASSWORD', $env['WORDPRESS_DB_PASSWORD'] );
define( 'DB_HOST', $env['WORDPRESS_DB_HOST'] );
Code-Sprache: PHP (php)
Da das normales PHP ist, kannst du auch die Existenz einer Variable prüfen und andernfalls einen Standardwert verwenden:
define( 'WP_SENTRY_DSN', $env['WORDPRESS_SENTRY_DSN'] ?? '' );
Code-Sprache: PHP (php)
Wenn du nun den WordPress-Code sowie deine -Konfiguration so verteilst, solltest du eine funktionierende WordPress-Instanz erhalten, sofern die Datenbank entsprechend vorbereitet ist. Da diese nicht via Git verteilt wird, musst du sie vorher in MySQL importieren.
Woher kommen Plugins/Themes?
Sicherlich willst du dein WordPress mit Plugins und/oder Themes erweitern. Hier kommt der wirklich interessante Teil, denn du musst prüfen, woher du sie bekommst.
Composer
Für Plugins/Themes aus dem offiziellen WordPress-Repository auf wp.org kannst du Composer mit dem Projekt von WordPress Packagist verwenden. Jedes Plugin und Theme aus dem WordPress-Repository kann auf diesem Weg hinzugefügt werden. Über Composers installer-paths
–extra
-Konfiguration kannst du sie direkt in das richtige Verzeichnis laden.
Einige Entwickler erlauben dir sogar, ihre Pro-Plugins via Composer zu laden, allen voran Advanced Custom Fields Pro.
Git-Repositorys
Für individuelle Plugins (oder auch für Pro-Plugins, für die es keine andere Möglichkeit gibt), kannst du Git-Repositorys verwenden, um sie hochzuladen. Dafür benötigst du jedoch ein CI-Skript, um diese Repositorys während der Bereitstellung ins richtige Verzeichnis zu klonen, bevor der Code auf den Server geladen wird.
Git-Submodule
Eine weitere Möglichkeit, ähnlich zu Git-Repositorys, ist die Verwendung von Git-Submodulen. Diese erlauben dir, direkt eine Referenz zu einem anderen Git-Repository in deinem WordPress-Git-Repository zu setzen. Du müsstest nach wie vor sicherstellen, dass der Code korrekt aus dem Submodule innerhalb deiner CI geklont wird, aber normalerweise passiert das automatisch.
In beiden Git-Fällen musst du sicherstellen, dass du die neueste Version des Codes bekommst. In unserem Fall: das neueste Tag, da jedes Release ein solches Tag besitzt (wenn deine das nicht hat: fange an, sie für jedes Release zu erstellen). Du kannst den folgenden Befehl verwenden, um das letzte Tag jedes Submodules zu bekommen:
git submodule foreach --recursive 'git fetch --tags && git checkout $(git describe --tags `git rev-list --tags --max-count=1`)'
Code-Sprache: Bash (bash)
Manuell
Wenn du Code außerhalb von Git hast (bitte nicht), den du für deine WordPress-Instanz benötigst, kannst du ihn ebenfalls via CI hinzufügen. Das bedeutet aber manuellen Aufwand, um den Code automatisch zu laden und innerhalb deines CI-Skripts an der richtigen Stelle im Deployment zu speichern.
Assets generieren und Abhängigkeiten laden
Ein großes Problem bei der Verwendung von Git für Plugins, Themes etc. ist, dass normalerweise Assets wie CSS oder JavaScript erst verarbeitet/minifiziert und Abhängigkeiten zuerst geladen werden müssen, bevor sie fehlerfrei in einer Produktivumgebung funktionieren. Deshalb muss dein Deployment-Skript einen Schritt beinhalten, um diese Assets zu generieren, abhängig von deinen Anforderungen. In unserem Fall haben wir damit begonnen, in einer Schleife über alle unserer Git-Submodule zu laufen und die Assets via Composer (PHP-Abhängigkeiten) und npm (node package manager, Verarbeiten von SCSS/Minifizierung von JavaScript) zu generieren, bis wir einen funktionierenden Code erhielten.
Extra-Deployment für Plugins/Themes
Am Ende haben wir jedoch einen anderen Ansatz verwendet, da wir die WordPress-Instanz selbst bereitstellen wollten, ohne Abhängigkeiten zu Plugins/Themes zu haben. Deshalb haben wir lediglich den WordPress-Core-Code bereitgestellt und symbolische Links zum jeweiligen Pfad des Plugins/Themes erstellt, unter dem sie selbst dann bereitgestellt werden. Das sieht dann in der Verzeichnisstruktur so aus:
.
|-- plugin-releases
| |-- rh-plugin-name
| | |-- current
| | `-- releases
| | `-- […]
| `-- rh-different-plugin
| |-- current
| `-- releases
| `-- […]
|-- wp-content
`-- plugins
|-- rh-plugin-name -> ../../../plugin-releases/rh-plugin-name/current
`-- rh-different-plugin -> ../../../plugin-releases/rh-different-plugin/current
Code-Sprache: JavaScript (javascript)
Der eigentliche Plugins-Code ist unter /plugin-releases
zu finden und die Plugin-Verzeichnisse unter wp-content/plugins
verlinken nur zu diesen Releases. Wieso? Weil wir den Ansible-Deploy-Helper verwenden, der immer das gesamte Verzeichnis von WordPress bei seinem Deployment ersetzt, was heißt, dass alle Änderungen in Unterverzeichnissen von WordPress praktisch zurückgesetzt werden. Durch das Laden von Plugins/Themes aus anderen Verzeichnissen, die davon nicht betroffen sind, stellt sicher, dass sie auch nach einem WordPress-Deployment noch verfügbar sind.
Natürlich solltest du dafür sicherstellen, dass die Plugins/Themes bereits verfügbar sind, bevor du die WordPress-Instanz selbst bereitstellst. Diese Struktur benötigst du dabei jedoch nur für Plugins/Themes, die du via Git bereitstellst. Für Plugins, die du via Composer lädst, benötigst du sie nicht. Wenn du deine Plugins/Themes als Git-Submodule bereitstellst (beispielsweise in einem separaten Verzeichnis, das dann vom eigentlichen Deployment ausgeschlossen ist), kannst du in einer Schleife über sieh gehen und die symbolischen Links automatisch während des Deployments anlegen.
Sprachen installieren
Da wir unser WordPress nach Möglichkeit auf „nur lesen“ stellen wollen, und insbesondere Datei-Veränderungen via DISALLOW_FILE_MODS
deaktiviert haben, sind Updates über WordPress direkt nicht möglich, ebenso wie die Aktualisierung und Installation von neuen Sprachen. Deshalb haben wir innerhalb unseres CIs eine Variable mit den verwendeten Sprachen definieren, und verwenden diese in einem separaten Deployment-Schritt, um WordPress in einem Docker-Container zu laden, alle Plugins/Themes aus Composer dort hinein zu kopieren und dann die Sprachen für Core, Plugins und Themes via WP-CLI zu installieren. Und da wir es können, erstellen wir auch direkt PHP-Dateien aus den Übersetzungsdateien via WP-CLI. Dann können wir das wp-content/languages
-Verzeichnis in einem späteren Schritt direkt auf dem Server bereitstellen. Da wir vorher auch alle Plugins und Themes beachtet haben, sind deren Übersetzungen auch direkt vorhanden und können verwendet werden.
Geteilte Ressourcen
Da der PHP-Code von WordPress nicht alles ist, was du brauchst, musst du auch geteilte Ressourcen verwalten, insbesondere das Verzeichnis wp-content/uploads
(aber auch alle individuellen Verzeichnisse, in denen du oder Plugins Inhalte speichern). In unserem Fall können wir damit nicht das gesamte NFS loswerden, da alle drei Server Zugriff auf die Uploads haben müssen, aber immerhin müssen wir kein PHP mehr daraus ausführen. Ich bin mir ziemlich sicher, dass du auch die geteilten Ressourcen anderweitig synchronisieren kannst, aber das bedeutet dann auch, dass durch das Duplizieren der Daten der benötigte Speicherplatz entsprechend steigt. Das wären in unserem Fall dann Speicherkapazitäten in den hunderten Gigabytes, weshalb wir mit dem NFS hier besser dran sind.
Normalerweise willst du daher für dein Deployment auch einen symbolischen Link vom Uploads-Verzeichnis von WordPress zu deinem eigentlichen Speicherplatz für Uploads erstellen.
Deployment-Struktur
Als eine Übersicht sieht unsere Deployment-Struktur für die WordPress-Instanz so aus:
- Installiere Composer-Abhängigkeiten (das heißt: Plugins/Themes aus dem WordPress-Repository)
- Installiere Sprachpakete via WP-CLI (in einem WordPress-Docker-Container)
- Lade den gesamten Code via Ansible hoch
GitLab-spezifische Probleme
Da wir GitLab in unserem Deployment verwenden, hatten wir ein paar GitLab-spezifische Probleme, die manchmal länger als benötigt gebraucht haben, um korrigiert zu werden. GitLabs Dokumentation ist dabei nicht immer hilfreich, manchmal auch irreführend oder sogar völlig falsch.
Job-Tokens
Um Zugriff auf andere Repositorys innerhalb deines Deployments zu bekommen, musst du dein WordPress-Instanz-Repository in die „Zulassungsliste für CD-Job-Token“ für jedes deiner Git-Repositorys hinzufügen. Das geht unter Einstellungen > CI/CD Job-Token-Berechtigungen im jeweiligen Repository. Andernfalls kannst du machen, was du willst, du wirst keine Möglichkeit haben, den Code des Repositorys zu pullen.
Absolute Submodule
Damit GitLab problemlos auf Submodule während des Deployments zugreifen kann, darfst du keine absoluten Pfade/URLs in der Quelle der Submodule verwenden. Wenn dein WordPress-Git-Repository beispielsweise unter gitlab.com/username/wordpress.git
und ein Plugin unter gitlab.com/username/my-plugin.git
zu finden ist, muss die URL des Subomdules url = ./plugin.git
lauten, damit GitLab Zugriff darauf hat. Zumindest für eine selbst-gehostete GitLab-Instanz.
Fazit
WordPress-Deployment via Git gibt dir die Möglichkeit, eine sicherere Instanz zu haben, die auf mehreren Servern identisch ist und dir auch dabei helfen kann, eine hochverfügbare Plattform zu erstellen, ohne auch den Wartungsmodus für Updates zu verwenden.
Neuveröffentlichungen