Automatisches Deployment

Hallo zusammen,
ich versuche gerade ein automatisiertes Deployment aufzusetzen:

  • Lokal mit dockware/essentials mit entsprechendem shopware/production Template zusätzlich im GIT
  • Lokales GitLab mit GitLabRunner
  • entsprechende gitlab-ci.yml und Deployer
  • Production-Server ohne NPM

Ich entwickle alles lokal über Dockware und nutze die Standard gitignore ds shopware/production-Template. Ziel ist es Shopware und alle Plugins/Themes über Composer zu verwalten. Im Gitlab-Runner wird das Projekt geklont, alle Abhängigkeiten per composer installiert, das ganze dann per Deployer auf den Server geladen und dort noch verschiedene Kommandos ausgeführt (theme:compile, cache:clear/warmup,…).

Das ganze funktioniert eigentlich auch soweit - bis auf den Storefront/Administration-Build. In der offiziellen Doku Deployment with Deployer - Shopware Developer wird „bin/build-js.sh“ über Deployer auf dem Production-Server ausgeführt. Dort habe ich aber a) kein NPM, b) ist das ja gar nicht sinnvoll.

Ich könnte „bin/build-js.sh“ in der gitlab-ci.yml vor „dep deploy production“ aufruden. Im Gitlab-Runner (Docker-Container) aber habe ich keine Datenbank. Zwar könnte ich die „var/plugins.json“ lokal generieren und in Git einchecken. Dennoch ruft „bin/build-js.sh“ ja immer das Kommando „bundle:dump“ im Voraus auf.

Führe ich „bin/build-js.sh“ lokal in Dockware aus, müsste ich die entsprechenden Ordner „public/bundles“ (und vermutlich „plugins/themes“) in GIT einchecken, um sie auf den Server zu bekommen.

Oder wie ist der beste/sauberste Weg, um nur das nötigste im GIT-Repository zu haben und ein vernünftiges, automatisiertes Deployment ohne NPM auf dem Production-Server zu erhalten?

Anbei meine gitlab-ci.yml und deployer.php - in denen jetzt eigentlich „nur noch“ das Kommando „bin/build-js.sh“ fehlt. (Shopware-Projekt liegt im GIT-Repository im Order „src“)

gitlab-ci.yml

variables:
    GIT_STRATEGY: clone

stages:
    - deploy

.configureSSHAgent: &configureSSHAgent |-
    eval $(ssh-agent -s)
    echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    mkdir -p ~/.ssh
    ssh-keyscan $DEPLOYMENT_SERVER >> ~/.ssh/known_hosts
    chmod 700 ~/.ssh

Build and Deploy:
    stage: deploy
    image: shopware/development:latest
    only:
        - master
    before_script:
        - *configureSSHAgent
        - curl -LO https://deployer.org/deployer.phar
        - mv deployer.phar /usr/local/bin/dep
        - chmod +x /usr/local/bin/dep
    script:
        - cd src
        - composer self-update --2
        - composer install --no-interaction --optimize-autoloader --no-suggest
        - composer install -d vendor/shopware/recovery --no-interaction --optimize-autoloader --no-suggest
        - dep deploy production

deploy.php

<?php

namespace Deployer;

require_once 'recipe/common.php';

set('application', 'Shopware 6');
set('allow_anonymous_stats', false);
set('default_timeout', 3600); 

host('SERVER')
    ->stage('production')
    ->user('USER')
    ->set('deploy_path', 'PATH')
    ->set('writable_mode', 'chmod');

set('shared_files', [
    '.env',
]);

set('shared_dirs', [
    'custom/plugins',
    'config/jwt',
    'files',
    'var/log',
    'public/media',
    'public/thumbnail',
    'public/sitemap',
]);

set('writable_dirs', [
    'custom/plugins',
    'config/jwt',
    'files',
    'public/bundles',
    'public/css',
    'public/fonts',
    'public/js',
    'public/media',
    'public/sitemap',
    'public/theme',
    'public/thumbnail',
    'var',
]);

task('deploy:update_code', static function () {
    upload('src/', '{{release_path}}');
});

task('sw:touch_install_lock', static function () {
    run('cd {{release_path}} && touch install.lock');
});

task('sw:theme:compile', static function () {
    run('cd {{release_path}} && bin/console theme:compile');
});

task('sw:cache:clear', static function () {
    run('cd {{release_path}} && bin/console cache:clear');
});

task('sw:cache:warmup', static function () {
    run('cd {{release_path}} && bin/console cache:warmup');
    run('cd {{release_path}} && bin/console http:cache:warm:up');
});

task('sw:database:migrate', static function () {
    run('cd {{release_path}} && bin/console database:migrate --all');
});

task('sw:deploy', [
    'sw:touch_install_lock',
    'sw:database:migrate',
    'sw:theme:compile',
    'sw:cache:clear',
]);

task('deploy', [
    'deploy:prepare',
    'deploy:lock',
    'deploy:release',
    'deploy:update_code',
    'deploy:shared',
    'sw:deploy',
    'deploy:writable',
    'deploy:clear_paths',
    'sw:cache:warmup',
    'deploy:symlink',
    'deploy:unlock',
    'cleanup',
    'success',
])->desc('Deploy your project');

after('deploy:failed', 'deploy:unlock');

Kämpft wirklich keiner mit dem Problem, dass für einen automatisierten Build NPM und eine Datenbankverbindung vorhanden sein muss?

Oder wie macht ihr eure Deployments in Shopware 6?

  • lokal erstellen, alles in GIT und dann nur noch auf den Server spielen?
  • bei euch läuft NPM auf dem Production-Server (wie in der Shopware 6 Deployment Doku)
  • …?

Vielleicht kann @Moritz_Naczenski ein paar Tipps hierzu geben?

Wie gesagt, wir hängen etwas an dem einen Punkt. Wenn wir den sinnvoll lösen können, haben wir ein sauberes Composer-basiertes Deployment - das wir dann gerne in einem ausführlichen Blog-Artikel vorstellen wollen.

Würde mich über verschiedene Deployment-Architekturen als Beispiel freuen - dann könnten wir das ja auch mal in dem Blog-Artikel komprimiert zusammenfassen.

Hallo,
du kannst doch einen Step in der Gitlab Pipeline für NPM Build nehmen und die Dateien dann mit deployen
Ggf. müssen die Dateien in einem Artefakt zwischengespeichert werden …

Ja, so war/wäre das ja auch angedacht. Dort habe ich aber keinen Datenbankzugriff, den ich für „bin/build-js.sh“ benötige.

Lösungen:
a) SSH-Tunnel auf die Production-Datenbank. Da durch den Befehl „bin/console bundle:dump“ aber nur aktive Plugins in die plugins.json geschrieben werden, sind neu entwickelte aktivierte, lokale Plugins nicht abgedeckt - sprich die benötigten Dateien werden nicht erstellt. Supoptimal bis schlecht.
b) Die plugin.json lokal erstellen und mit ins GIT einchecken. Allerdings ruft „bin/build-js.sh“ zu Beginn immer „bin/console bundle:dump“ auf - was dann vermutlich zu einem Fehler und/oder fehlerhaften/leeren plugin.json führt.

Meine Lösung wäre jetzt b) und die Shell-Skripte anpassen und/oder um einen Parameter zu erweitern, um das erstellen der plugin.json zu unterbinden.

Wundert mich nur, dass es so wenig Rückmeldung gibt. Meines Erachtens ist das automatisierte Deployment von Shopware an dieser Stelle noch nicht so ganz zu Ende gedacht - vor allem mit der Empfehlung von Shopware (bzw. @Moritz_Naczenski hatte es in einem Thread irgendwo so empfohlen/geraten) kein NPM auf dem Production-Server laufen zu lassen.

Vielleicht übersehe ich auch was, oder habe einen falschen Ansatz. Entweder die anderen bekommen automatisierte Deployments locker hin oder es nutzt keiner so wirklich produktiv.

Wie wäre es, wenn du dir eine eigene bin/build-js.sh ohne bin/console bundle:dump baust?
Und was konkret spricht gegen NPM auf einem Produktionsserver?

Und was konkret spricht gegen NPM auf einem Produktionsserver?

Um den Produktionsserver so schlank wie möglich zu halten und auf für den Live-Betrieb nicht benötigte Komponenten verzichten zu können. Das hat u.a. @Moritz_Naczenski von Shopware bereits mehrmals (glaube ich) so gepostet, z.B: Mittwald Hosting kein NPM und Node - #5 von Moritz_Naczenski

Macht für mich ebenfalls so auch Sinn.

Wie wäre es, wenn du dir eine eigene bin/build-js.sh ohne bin/console bundle:dump baust?

Ja, so in die Richtung wird es dann auch hinauslaufen.

Vielleicht hilft euch das hier: Feedback theme:compile / admin build without database · Discussion #2 · shyim/platform · GitHub

Wir arbeiten gerade an einer Möglichkeit, dass man die Assets auch ohne DB Zugriff erzeugen kann. Gerne dort Feedback zu den geplanten Changes geben.

1 Like

Der Build-Prozess ist sehr Ressourcen-hungrig. Das Asset Building von Shopware ist dermaßen intensiv, dass (wie nun in aktuellen Kundenprojekten) auf Shared Hosting Instanzen ein Build „build-js.sh“ auf dem Remote Server zu Abbrüchen führt (wegen überschrittener CPU Zeit und Memory).

Daher empfiehlt es sich (unabhängig von Shopware oder anderen Produkten) solche Prozesse auf unabhängige und möglichst automatisierte Stages innerhalb einer CI/CD Pipeline zu verlagern, um hier im Vorfeld die Assets zu bauen und innerhalb des Deployment Artefakts zu verteilen.

Shopware möchte allerdings hierbei Informationen über installierte Plugins laden. Diese werden standardmäßig aus der DB geladen. In CI ist eine DB nicht unbedingt verfügbar. Um diesem Problem zu begegnen hat Shopware mit 6.4.4 Möglichkeiten im Production Template bereitgestellt, um auch OHNE DATENBANK in automatsierten Umfelden zu bauen.

Im Vorfeld müssen allerdings (lokal, mit DB) ein Bundle und Theme Dump gefahren werden, um Informationen zu installierten Plugins und Themes von der DB in statische JSON Dateien zu überführen.

Dev

$ bin/console bundle:dump
$ bin/console theme:dump

Das Resultat sind die Files var/plugins.json und var/theme-files.json, die nun für die weitere Verwendung im CI Prozess vorgehalten werden müssen (bswp in git einchecken oder wie die Doku meint auf S3 auslagern)

Shopware muss nun noch innerhalb der CI so konfiguriert werden, dass die statischen JSON Dateien auch geladen werden, hierzu muss die Datei config/packages/storefront.yaml

Wichtig ist hierbei, dass die Env Vars CI und SHOPWARE_SKIP_BUNDLE_DUMP vor Ausführung des build-js.sh Commands gesetzt werden, um intern eine Differenzierung vorzunehmen. So wird intern statt bin/console dann bin/ci ausgeführt.

In CI

$ CI=1  SHOPWARE_SKIP_BUNDLE_DUMP=1  bin/build-js.sh

Hey, ich bin auch gerade an dem Thema dran und klappt so weit alles gut. Ich habe auch ein kleines Plugin erstellt und bei Gitlab als Package definiert und kann auch dies per Composer ziehen. Aber eine Sache ist mir noch nicht so richtig klar und habe da einen Kopfhänger…

Wie genau machst du das mit dem Shopware Production Repo und Updates? Ich habe als Erstes das Repo von SW geklont und dann in mein eigenes eingechecked. Aber wenn ein Update von SW kommt, wie überträgst du das in den Repo? Als Lösung wäre da ein Fork des Production Templates und dann würde man das Repo von Shopware als Upstream in Git definieren um dann immer zu mergen. Das kommt mir sehr viel komplizierter und fehleranfälliger vor. Irgendwie… Leider ist die Dokumentation von SW da sehr schlicht und gibt einem da eher nur einen Hinweis bzw. nur die ersten Schritte.

Ich hatte bei einem anderen Projekt einmal ein sehr rudimentäres Deployment (auf Basis von Blue/Green) getestet. Da habe ich direkt auf der Produktionsmaschine Produktivordner per Symlink eingebunden

current -> release/6.4.6.0
und den DocumentRoot auf den Current gesetzt, dann wenn ein neues Release kam habe ich eine Kopie von 6.4.6.0 gemacht und das dann 6.4.7.0 genannt und da dann eine stage Url drauf gesetzt, also
next -> release/6.4.7.0
Dann habe ich dort das Update auf die neue Version gemacht (noch im Backend) und dann wenn alles geklappt hat, hab ich einfach nur den symlink von Current auf die 6.4.7.0 gesetzt
current -> release/6.4.7.0
und bin dann immer so weiter (damals noch niedrigere Versionen). Der Vorteil war, dass ich, wenn doch ein Fehler auf der neuen Version war, ich schnell auf die Alte zurück gehen konnte. Keine Ahnung ob die Methode überhaupt langfristig funktioniert hätte, deswegen wollte ich auch neue und elegantere Methoden testen

Theoretisch hatte das gut geklappt, aber das war quasi eine Methode, die nicht dokumentiert war und die ich mir selbst irgendwie zusammengefummelt habe. Jetzt wollte ich es ein bisschen besser und nach dem Standard wie es von Shopware beschrieben ist machen, aber bedauerlicherweise fehlen mir noch mehr Informationen und im Netz findet man da auch nicht wirklich viel. Deswegen die Frage vom Anfang, ob du da vielleicht für mich etwas erklären kannst, wie du das gelöst hast.