Deployment von PHP-Anwendungen mit Deployer und GitLab CI
Eine einfache Anleitung für das unkomplizierte Deployment von PHP/Symfony-Anwendungen aus GitLab und vom lokalen Rechner.
In diesem Artikel
Das Ausrollen einer neuen Anwendungsversion ist meist eine routinemäßige Abfolge vieler Schritte, von denen man besser keinen vergessen sollte. Etwas komplizierter wird es, wenn sich beispielsweise Eigenschaften von Daten ändern, die nicht im Repository versioniert sind – etwa Schreibrechte oder die Struktur von Anwendungsdaten.
Ideal ist es daher, den gesamten Prozess so weit wie möglich zu automatisieren, ihn in den Entwicklungsprozess einzubinden und dort schnell und einfach verfügbar zu machen. Wenn Sie Ihre Anwendung in einer Container-Engine betreiben, nutzen Sie für das Deployment wahrscheinlich eines der Tools zur Container-Orchestrierung. Betreiben Sie die Anwendung jedoch auf klassische Weise, lässt sich ein automatisiertes Deployment leicht erreichen, zum Beispiel mit einem einfachen Bash-Skript und einer Möglichkeit, es remote auszuführen. Für diese Art des Deployments neuer Versionen von Symfony-Anwendungen hat sich bei uns jedoch das Tool Deployer am besten bewährt.
Die folgende Anleitung ergänzt die Möglichkeit, das Deployment manuell aus GitLab über CI sowie vom lokalen Rechner mit geklontem Repository zu starten.
Deployer
Als ersten Schritt fügen wir dem Repository die Phar-Dateien von Deployer und Composer hinzu. Auch wenn deployer.org den Dateinamen auf ein schlichtes dep verkürzt, empfehle ich, die Endung .phar beizubehalten. In PhpStorm lässt sich dann ihr Inhalt aufklappen, sodass Sie alle verschiedenen 'Rezepte' für unterschiedliche Frameworks und Situationen bequem durchsehen und untersuchen können.
$ curl -LO https://deployer.org/deployer.phar
Danach genügt es, ein einfaches Skript deploy.php anzulegen, das die einzelnen Schritte zum Starten der Anwendung und deren Reihenfolge beschreibt. Der Vollständigkeit halber: Unter den mitgelieferten Rezepten gibt es kein eigenes Rezept für das aktuelle Symfony5. Das Deployment unterscheidet sich jedoch in nichts von Symfony4, daher kann man einfach das Rezept für diese Version verwenden. Das unten aufgeführte Rezept muss in den konkreten Details (Repo-URL, gemeinsam genutzte Dateien und Verzeichnisse, beschreibbare Verzeichnisse usw.) an die jeweiligen Anforderungen angepasst werden.
<?php
namespace Deployer;
require 'recipe/symfony4.php';
set('application', 'iD-SIGN Website');
set('repository', 'ssh://git@gitlab.url/your/repo');
set('shared_dirs', ['var/log', 'var/sessions', 'public/media', 'data']);
set('shared_files', ['.env.local', 'public/robots.txt']);
set('writable_dirs', ['public/media', 'data', 'var/cache']);
set('bin/composer', '{{release_path}}/composer.phar');
set('keep_releases', 3);
// Hosts
host('production')
->hostname('yourserver.com')
->user('user')
->set('deploy_path', '/var/www/id-sign.com/www')
->set('http_user', 'www-data')
->become('id-sign');
// Tasks
task('deploy:build_assets', static function () {
run('cd {{release_path}} && yarn install && yarn encore production');
});
// Migrate database before symlink new release.
before('deploy:symlink', 'database:migrate');
before('database:migrate', 'deploy:build_assets');
// [Optional] if deploy fails automatically unlock.
after('deploy:failed', 'deploy:unlock');
Das Skript selbst ist weitgehend selbsterklärend. Zum schnelleren Verständnis, ohne den Code studieren zu müssen, füge ich noch die Liste der Tasks an, die das Rezept für Symfony4 im Rahmen des Tasks 'deploy' definiert und ausführt.
desc('Deploy project');
task('deploy', [
'deploy:info',
'deploy:prepare',
'deploy:lock',
'deploy:release',
'deploy:update_code',
'deploy:shared',
'deploy:vendors',
'deploy:writable',
'deploy:cache:clear',
'deploy:cache:warmup',
'deploy:symlink',
'deploy:unlock',
'cleanup',
]);
Wie funktioniert das Ganze? Deployer verbindet sich per SSH mit dem Server hostname() als user(). Muss anschließend zu einem anderen Benutzer gewechselt werden, legt man diesen mit der Methode become() fest; für den Wechsel wird standardmäßig sudo verwendet. Dieser Wechsel user() -> become() muss also erlaubt sein.
Die Einrichtung von sudo ist einfach: Es genügt, eine Konfigurationsdatei in /etc/sudoers.d anzulegen – z. B. /etc/sudoers.d/projectname –, darin eine einfache Regel einzutragen und sudo neu zu laden. Hier ein Beispiel, das dem Benutzer newman erlaubt, ohne Passwort zum Benutzer id-sign zu wechseln, und diesem wiederum den Wechsel zum Benutzer www-data.
newman ALL=(id-sign) NOPASSWD:ALL
id-sign ALL=(www-data) NOPASSWD:ALL
Dass alles funktioniert, können Sie ganz einfach prüfen, zum Beispiel mit:
newman@server$ sudo -u id-sign bash
Vor dem ersten Testlauf sei angemerkt, dass Deployer auf dem Server eine eigene Verzeichnisstruktur für die einzelnen Releases anlegt. Wenn Sie die Anwendung also bereits auf dem Server installiert haben, müssen Sie sehr wahrscheinlich ihren Speicherort und ebenso die Konfiguration des Webservers anpassen.
Die ersten Versuche empfehle ich außerhalb der Produktionsumgebung :-)
Den Testlauf können wir vom lokalen Rechner aus dem Verzeichnis mit dem geklonten Projekt starten. Für vollen Komfort sollte die Anmeldung am Server per SSH-Schlüssel eingerichtet sein. Falls nicht, lässt sich das sehr einfach nachholen.
SSH-Schlüssel erzeugen:
$ ssh-keygen
Schlüssel auf dem Server installieren (Serverbenutzer und Hostname passen Sie an Ihre Situation an):
$ ssh-copy-id user@your-server.com
Jetzt können wir Ihr erstes Deployment ausprobieren, das etwa so aussehen kann:
$ php deployer.phar deploy production
✈︎ Deploying master on production
✔ Executing task deploy:prepare
✔ Executing task deploy:lock
✔ Executing task deploy:release
✔ Executing task deploy:update_code
✔ Executing task deploy:shared
✔ Executing task deploy:vendors
✔ Executing task deploy:writable
✔ Executing task deploy:cache:clear
✔ Executing task deploy:cache:warmup
✔ Executing task deploy:build_assets
✔ Executing task database:migrate
✔ Executing task deploy:symlink
✔ Executing task deploy:unlock
✔ Executing task cleanup
Successfully deployed!
Tritt während des Deployments ein Fehler auf, informiert Deployer Sie darüber recht verständlich. Ist alles gut gelaufen, sollte auf dem Server im Anwendungsverzeichnis eine neue Verzeichnisstruktur angelegt worden sein:
$ ls -l
celkem 8
lrwxrwxrwx 1 id-sign id-sign 10 čec 9 13:15 current -> releases/1
drwxr-xr-x 8 id-sign id-sign 4096 čec 9 13:17 releases
drwxr-xr-x 5 id-sign id-sign 4096 lis 18 2020 shared
Passen Sie die Webserver-Konfiguration so an, dass der Document Root auf das Verzeichnis public im Verzeichnis current zeigt. Sollte etwas vom oben Beschriebenen unklar sein oder interessieren Sie alle Konfigurationsmöglichkeiten, empfehle ich, die Dokumentation von Deployer durchzugehen
GitLab CI
Für das Deployment können wir auch GitLab nutzen, um neue Versionen direkt über dessen Weboberfläche auszurollen. Das werden Sie zu schätzen wissen, wenn zu Ihrer Arbeit etwa die Kontrolle des Mergens verschiedener Entwicklungs-Branches in den Main-Branch und das anschließende Release gehört. Selbstverständlich lässt sich alles so einrichten, dass es automatisch nach erfolgreich durchgelaufenen Tests, Linting usw. ausgeführt wird. In diesem Beispiel wird der Einfachheit halber jedoch ein manuelles Release konfiguriert, das sich dann leicht an die eigenen Bedürfnisse anpassen lässt.
Als ersten Schritt fügen wir dem Repository die Datei .gitlab-ci.yml mit folgendem Inhalt hinzu (Server-Hostname und PHP-Version passen wir nach Bedarf an):
stages:
- deploy
deploy:
image: chialab/php:8.0
stage: deploy
script:
- eval "$(ssh-agent -s)"
- ssh-add <(echo "$DEPLOY_KEY" | openssl base64 -A -d)
- mkdir ~/.ssh && ssh-keyscan -H your.server.com >> ~/.ssh/known_hosts
- php ./deployer.phar deploy production --branch $CI_COMMIT_REF_NAME -vv
when: manual
Anschließend legen wir in GitLab in den Repository-Einstellungen unter Settings -> CI/CD eine maskierte Variable DEPLOY_KEY an, in der wir den Base64-String des privaten SSH-Schlüssels speichern, mit dem wir uns am Server anmelden. Aus Sicherheitsgründen soll der private Schlüssel für die Anmeldung am Server nicht Teil des Repositorys sein. Den Base64-String erhalten Sie ganz einfach entweder über einen Onlinedienst oder unter Linux mit dem Befehl base64. Damit die Variable in GitLab maskiert werden kann, muss ihr Wert einzeilig sein – mit dem Parameter -w 0 schalten wir den Zeilenumbruch des erzeugten Textes ab:
$ base64 -w 0 private.key > private64
Den Inhalt der entstandenen Datei kopieren wir und speichern ihn in der oben genannten Variablen DEPLOY_KEY.
Und das ist im Grunde alles. In GitLab sollte Ihnen jetzt die Möglichkeit angezeigt werden, den CI-Deploy-Job zu starten, und wenn für das Repository auch CI-Pipelines verfügbar sind, sollte alles problemlos durchlaufen. So können Sie ein Deployment beispielsweise direkt nach dem Mergen von Änderungen aus verschiedenen Branches in den Haupt-Branch starten, ohne die Kommandozeile öffnen und sich am Server anmelden zu müssen.