Shopware auf Kubernetes: Build, Test und Debugging

Mit zunehmender Skalierung steigt die Komplexität der Konfiguration einer Shopware-Infrastruktur deutlich an. Deshalb empfiehlt es sich, den gesamten Software-Stack – also Shopware mit allen erforderlichen Diensten – sowohl lokal als auch in der Continuous-Integration-Pipeline zu starten und zu testen. Besonders Integrations- und End-to-End-Tests sollten in einer sauberen, isolierten Umgebung laufen, um Zuverlässigkeit und Wiederholbarkeit sicherzustellen.

In diesem Artikel erläutere ich, wie du eine Shopware-6-Plattform für ein lokales Kubernetes-Cluster bauen und bereitstellen kannst und wie du PHP-Code direkt darin debuggst.

Alle Codebeispiele und Snippets in diesem Artikel stammen aus einem Proof-of-Concept-Projekt namens Shopware-Kube, dessen Hauptfunktionen sind:

  • Erstellen von Container-Images mit Shopware-Anwendungsserver
  • Nutzung von Minikube zur Einrichtung eines lokalen Kubernetes-Clusters
  • Installation aller Komponenten, die Shopware im Cluster benötigt
  • Deployment eines Shopware-Containers im Cluster
  • Ausführen von Integrations- und End-to-End-Tests

Die meisten Codebeispiele habe ich zur Übersichtlichkeit gekürzt. Ich empfehle dir jedoch, den vollständigen Quellcode auf GitHub selbst zu analysieren.

Voraussetzungen für den Start mit Shopware auf Kubernetes lokal

Ich setze Grundkenntnisse über Kubernetes, seine Komponenten und die Architektur voraus.

Um mit dem Projekt zu starten, benötigst du folgende Tools auf deinem Rechner:

  1. Docker: Docker Engine auf Linux mit BuiltKit-Add-on oder Docker Desktop. Docker Desktop enthält BuiltKit bereits und unterstützt auch andere Betriebssysteme wie macOS und Windows.
  2. Kubernetes-Distribution, optimiert für die lokale Entwicklung. Meine Empfehlung ist Minikube. Es ist leichtgewichtig und für dieses Projekt vielseitig genug. Minikube läuft auf allen gängigen Betriebssystemen – Linux, macOS und Windows, sowohl auf x86- als auch ARM-Architektur (Windows ARM64 wird noch nicht unterstützt).
  3. Skaffold: Für effizientes Bauen, Testen und Deployen der Shopware-Anwendung. Skaffold setzt voraus, dass kubectl, kustomize und helm bereits installiert sind.
  4. Ktunnel: Zum Aufbau eines Reverse-Tunnels, der Xdebug-Verbindungen mit deinem Code-Editor ermöglicht.

Shopware-Implementierung zu Cloud-native E-Commerce transformieren

Cloud-native steht für eine Reihe von Praktiken zur Architektur, Entwicklung und Bereitstellung von Anwendungen auf Cloud-Infrastrukturen. Ein zentrales Ziel dieses Projekts ist es, eine horizontale Skalierung der Shopware-Anwendung zu ermöglichen – also die Verteilung auf mehrere Maschinen. Horizontale Skalierung ist ein entscheidender Schritt für Hochverfügbarkeit, um das Risiko von Ausfällen bei Deployments oder Anwendungsfehlern zu minimieren.

Hier kommen die Twelve-Factor-App-Prinzipien ins Spiel – ein Leitfaden für das Design cloud-nativer Anwendungen. Die Anwendung sollte stateless sein (keine temporären Dateien, außer ggf. auf tmpfs), containerisiert und für schnellen Start sowie sauberes Herunterfahren optimiert werden.

Cloud-native Shopware-Konfiguration vorbereiten

Um dem 11. Faktor der Twelve-Factor-App (Logs als Streams) und dem 4. Faktor (angebundene Dienste) zu entsprechen und optimale Funktionalität im Cluster zu gewährleisten, benötigt Shopware eine spezielle Konfiguration für:

  • Cache-Speicher
  • Benutzersitzungen
  • Medienspeicher
  • Logs

Optimale Shopware-Cache-Konfiguration

Standardmäßig werden alle Cache-Daten in das lokale Dateisystem geschrieben, insbesondere in das Verzeichnis var/cache. Für einfache Einzelserver-Setups ist das ausreichend. Bei Skalierung führt dies jedoch zu massiver Datenredundanz und Performance-Problemen, da jeder Container seinen eigenen lokalen Cache schreibt. Die Lösung ist eine Key-Value-Datenbank wie Redis, die von allen Shopware-Containern gemeinsam genutzt wird, um Anwendungs- und HTTP-Cache zu speichern. Eine typische Konfiguration sieht folgendermaßen aus:

# config/packages/framework.yaml
framework:
  cache:
    default_redis_provider: "%env(string:REDIS_CACHE_OBJECT_URL)%"
    app: cache.adapter.redis_tag_aware
    system: cache.adapter.system
    pools:
      cache.tags:
        adapter: cache.app
      cache.object:
        default_lifetime: '3600'
        adapter: cache.app
        tags: cache.tags
      cache.http:
        default_lifetime: '7200'
        adapter: cache.adapter.redis_tag_aware
        provider: "%env(string:REDIS_CACHE_HTTP_URL)%"
        tags: cache.tags

Beachte, dass für verschiedene Cache-Arten eigene Variablen verwendet werden. Das ermöglicht die Nutzung mehrerer Redis-Instanzen oder mehrerer Datenbanken innerhalb einer Instanz.

Redis als Session-Speicher einbinden

Wie beim Cache sollte auch der Standard-Session-Speicher (Dateisystem) bei Skalierung nicht eingesetzt werden. Bei mehreren HTTP-Anfragen eines Nutzers kann jede Anfrage von einem anderen Shopware-Container verarbeitet werden – der Nutzer würde so bei fast jedem Seitenaufruf eine neue Session starten und könnte sich nicht einloggen oder Produkte in den Warenkorb legen. Die Lösung ist eine zentrale Datenbank wie Redis. Redis bietet als In-Memory-Datenbank eine bessere Performance als relationale Datenbanken wie MySQL oder Netzwerkdateisysteme wie NFS oder Ceph. Um Redis für Sessions zu nutzen, ergänze Folgendes:

# config/packages/framework.yaml
framework:
  ...
  session:
    handler_id:
      "%env(string:REDIS_SESSION_URL)%"
...

Shopware für S3-kompatiblen Objektspeicher für Medien konfigurieren

Objektspeicher mit S3-kompatibler API ist der Standard für cloud-native Speicherung von Mediendateien. Viele Anbieter haben dies inzwischen im Portfolio, und du bist nicht auf große Cloud-Provider beschränkt. Es gibt auch Open-Source-Alternativen zum Self-Hosting wie MinIO oder Rook. Für das Shopware-Kube-Projekt habe ich MinIO gewählt, da es sich in einem Single-Node-Cluster besonders einfach konfigurieren lässt.

Hier ein Beispiel für die S3-Speicherkonfiguration öffentlicher und privater Dateien in Shopware:

# config/packages/shopware.yaml
shopware:
  ...
  filesystem:
    public: &public-filesystem
      type: "amazon-s3"
      url: "%env(string:BUCKET_URL_PUBLIC)%"
      config:
        endpoint: "%env(string:BUCKET_ENDPOINT)%"
        region: "%env(string:BUCKET_REGION_PUBLIC)%"
        bucket: "%env(string:BUCKET_NAME_PUBLIC)%"
        credentials:
          key: "%env(string:AWS_ACCESS_KEY_ID)%"
          secret: "%env(string:AWS_SECRET_ACCESS_KEY)%"
        use_path_style_endpoint: true
        options:
          visibility: "public"
    private: &private-filesystem
      type: "amazon-s3"
      config:
        endpoint: "%env(string:BUCKET_ENDPOINT)%"
        region: "%env(string:BUCKET_REGION_PRIVATE)%"
        bucket: "%env(string:BUCKET_NAME_PRIVATE)%"
        credentials:
          key: "%env(string:AWS_ACCESS_KEY_ID)%"
          secret: "%env(string:AWS_SECRET_ACCESS_KEY)%"
        use_path_style_endpoint: true
        options:
          visibility: "private"
    theme: *public-filesystem
    sitemap: *public-filesystem
    asset: *public-filesystem
...

Das obige Beispiel geht davon aus, dass Theme- und Asset-Dateien im Public-Bucket gespeichert werden (Shopware-Standard). Daher musst du das Storefront-Theme kompilieren und neue Assets bei jedem Deployment ins Cluster kopieren. Dies lässt sich mit einer Kubernetes-Job-Ressource erledigen, die bei jedem Deployment erstellt, ausgeführt und danach (bei Erfolg) gelöscht wird. Der Job muss den shopware install-Befehl wie im folgenden Beispiel ausführen:

# deploy/bases/app/shopware-init.yaml
...           
if bin/console system:is-installed; then
   echo "Running Shopware updates."
   bin/console system:install   
else
   echo "Running Shopware first time install."
   bin/console system:install --basic-setup 
fi
...

Shopware-Logs konfigurieren

Stelle dir dutzende oder hunderte Container vor, die jeweils Logs generieren. Es ist nahezu unmöglich, alle zu durchsuchen, wie es bei einer Einzel-Server-Anwendung möglich wäre. Theoretisch könntest du ein gemeinsames Volume für Logs nutzen, aber das führt zu Performance-Problemen durch Netzwerk-Latenzen und Dateisperren bei gleichzeitigen Zugriffen. Cloud-native Logging bedeutet, Logs zentral zu indexieren und über ein Dashboard auffindbar zu machen. So lassen sich Logeinträge aggregieren, Anomalien erkennen und Alarme auslösen.

Damit Logs indexiert werden können, müssen sie an stdout und stderr statt in Dateien geschrieben werden. So kann ein Log-Indexer sie abgreifen. Diese Konfiguration erfüllt den 11. Faktor der 12-Factor-App. Empfehlenswert ist zudem das JSON-Format, das die Auswertung und Analyse vereinfacht. Eine Beispielkonfiguration für Monolog in Shopware sieht so aus:

# config/packages/monolog.yaml
monolog:
  handlers:
    main:
      type: fingers_crossed
      excluded_http_codes: [404, 405]
      action_level: error
      handler: nested
    nested:
      type: stream
      path: "php://stderr"
      level: error
      formatter: 'monolog.formatter.json'
    console:
      type: console
      process_psr_3_messages: false
      channels: ["!event", "!doctrine"]
      formatter: 'monolog.formatter.json'

Lokales Kubernetes-Cluster mit Minikube erstellen

Vor dem Deployment von Shopware musst du einige Minikube-Addons installieren:

  • storage-provisioner: Aktiviert die automatische Bereitstellung von Persistent Volumes
  • default-storageclass: Aktiviert die Standard-Storage-Class (hostpath im lokalen Dateisystem)
  • ingress: Minikube-optimierter NGINX Ingress Controller
  • ingress-dns: Ein dedizierter DNS-Server für Ingresses

Führe das Skript create_cluster.sh aus, um ein Minikube-Kubernetes-Cluster mit allen genannten Addons zu erstellen.

./create_cluster.sh

Lokale Test-Domains registrieren

Folgende lokale Domains für die Shopware-Installation müssen eingerichtet werden:

  • shopware.test (für das Storefront)
  • media.test (für die öffentlichen Medien)

Der einfachste Weg ist, die gewünschten Domains in die Hosts-Datei deines Systems einzutragen. Je nach verwendetem Betriebssystem gehst du wie folgt vor:

echo $(minikube ip)' media.test shopware.test' | sudo tee -a /etc/hos
ts
  • macOS
echo '127.0.0.1 media.test shopware.test' | sudo tee -a /etc/hosts
  • Windows (cmd muss als Administrator ausgeführt werden)
echo 127.0.0.1 media.test shopware.test >> C:\Windows\system32\drivers\etc\hosts

Lokale Testdomains im Cluster verfügbar machen

Damit die Hosts media und storefront (shopware.test und media.test) auch innerhalb des Clusters aufgelöst werden können, müssen sie erreichbar sein. Dies ist beispielsweise für den Befehl theme:compile erforderlich.

Die von Minikube empfohlene Methode besteht darin, die CoreDNS-Konfiguration zu bearbeiten und die .test-Domains weiterzuleiten. Führe zunächst minikube ip aus, um die IP-Adresse des Minikube-Knotens anzuzeigen.

Öffne anschließend die CoreDNS ConfigMap zur Bearbeitung:

kubectl edit configmap coredns -n kube-system

Füge die DNS-Weiterleitungsregel für .test-Domains ein:

    test:53 {
        errors
        cache 30
        // forward . <minikube ip>
        forward . 192.168.49.2
    }

Die Forward-IP-Adresse entspricht dem Ergebnis von minikube ip.
Die Konfiguration sieht dann beispielsweise folgendermaßen aus:

apiVersion: v1
data:
  Corefile: |
    .:53 {
        log
        errors
        health {
           lameduck 5s
        }
        ready
        ...
    }
    test:53 {
        errors
        cache 30
        forward . 192.168.49.2
    }
    ...
kind: ConfigMap

Shopware-Container-(Docker-)Image vorbereiten

Je kleiner das Container-Image und je weniger Bibliotheken enthalten sind, desto besser: Ein schlanker Container startet schneller und ist sicherer. Nach diesem Prinzip habe ich beschlossen, FrankenPHP als Anwendungsserver für Shopware auszuprobieren. Darin ist ein Caddy-HTTP-Server integriert.

Mit dem FrankenPHP-Builder lässt sich eine einzige ausführbare Binärdatei erstellen, die deine Shopware-Anwendung, alle Abhängigkeiten, PHP und den Caddy-Server enthält. Dieser Single-Binary-Ansatz führt zu einem kleineren und weniger komplexen Anwendungscontainer als PHP-FPM mit NGINX oder PHP-FPM mit Apache – besonders aus Sicherheitssicht ein Vorteil.

Dies wird durch das Projekt static-php-cli ermöglicht, mit dem sich Anwendungsexecutables bauen lassen. Da die gesamte Anwendung in einer einzigen Binärdatei enthalten ist, kann sie auf einem minimalen Container-Image wie debian-slim, alpine oder wolfi-base installiert werden. Die von den FrankenPHP-Mitwirkenden empfohlene Wahl ist allerdings Debian – daher habe ich mich dafür entschieden.

Shopware Docker-Image-Build

Das Dockerfile ist in folgende Schritte unterteilt:

  1. Einen Shopware-Builder vorbereiten.
  2. Mit dem Application Builder eine produktive Shopware-Anwendung bauen.
  3. Mit dem Application Builder eine Dev-Shopware-Anwendung bauen.
  4. PHP kompilieren und die Shopware-Anwendung als ausführbare Binärdatei erstellen.
  5. Das finale Produktions-Image erstellen, indem die Binärdatei der Anwendung darauf kopiert wird.
  6. Die Dev-Shopware-Anwendung in das Basis-Dev-image kopieren.

Shopware Docker-Container-Image-Build-StufenShopware Docker-Container-Image-Build-Stufen

Deklariere die Build-Konfiguration in der Datei skaffold.yaml.

...
build:
  artifacts:
      - image: kiweeteam/franken-shopware
        docker:
          dockerfile: Dockerfile
          target: app-prod
          secrets:
            - id: composer_auth
              src: auth.json
      - image: kiweeteam/franken-shopware-dev       
        docker:
          dockerfile: Dockerfile
          target: app-dev
          secrets:
            - id: composer_auth
              src: auth.json
...

Die oben gezeigte Konfiguration stellt sicher, dass zwei Images erstellt werden – eines für die Produktion und eines für die Entwicklung. Sie erkennt dabei intelligent, ob im
Deployment der Kubernetes-Objekte auf beide Images verwiesen wird. Möchtest du beispielsweise die Dev-Anwendung bauen und bereitstellen, bietet Skaffold mit Profilen eine großartige Funktion. Wir definieren zwei Profile: dev und production, wobei production das Standardprofil ist.

# Standardprofil - Produktion
manifests:
  kustomize:
    paths:
      - deploy/overlays/production
# Definition des Profils
profiles:
  - name: dev
    manifests:
      kustomize:
        paths:
          - deploy/overlays/dev
  - name: production
    manifests:
      kustomize:
        paths:
          - deploy/overlays/production

Diese Änderung stellt sicher, dass Kustomize das Dev-Shopware-Manifest nicht lädt und Skaffold das entsprechende Container-Image nicht erstellt.

skaffold build --file-output=.build-artifacts.json

Skaffold speichert Referenzen auf die Images in der
.build-artifacts.json. Diese Datei wird für Deploy- und Verifizierungsbefehle benötigt.

Shopware im lokalen Kubernetes-Cluster bereitstellen

Nachdem du nun weißt, wie du ein Docker-Image deiner Shopware-Anwendung erstellst, gehen wir zum Deployment im lokalen Cluster mit allen Abhängigkeiten über:

  • Operatoren und Controller, die Workloads wie folgt erstellen und steuern: HTTP-Reverse-Proxy (Ingress Controller), TLS-Zertifikatsmanager, Generator für zufällige Passwörter und Object-Storage-Manager (MinIO).
  • Shopware-Backend-Dienste (MariaDB-Server, Redis, OpenSearch)
  • Shopware-Anwendungsserver
  • Shopware-Message-Scheduler und Consumer zur Ausführung asynchroner Backend-Aufgaben Shopware Kubernetes-Cluster – Übersicht

Shopware Kubernetes Cluster ÜbersichtShopware Kubernetes Cluster Übersicht

Projektstruktur für die Shopware-Bereitstellung

Die Struktur folgt dem Konzept von Bases und Overlays, das für Projekte empfohlen wird, die mit Kustomize mehrere Umgebungen wie Entwicklung, Test und Produktion verwalten. Als Argument wird der Pfad zu einem Overlay, beispielsweise „dev“, angegeben. Dieses Overlay umfasst die Basis-Manifeste und nimmt Anpassungen vor – etwa das Hinzufügen neuer, umgebungsspezifischer Ressourcen oder das Patchen beziehungsweise Löschen von Ressourcen aus den Bases.

In der Dev-Umgebung möchten wir beispielsweise einen Shopware-Container für die Entwicklung bereitstellen, diesen jedoch nicht in der Produktion einsetzen. Das Dev-Overlay enthält dafür ein zusätzliches Manifest, das den Shopware-Dev-Container bereitstellt.

Die Projektstruktur ist wie folgt aufgebaut:

deploy
├── bases
│   ├── app
│   ├── database
│   ├── opensearch
│   ├── redis
│   └── storage
└── overlays
    ├── dev
    ├── production
    └── test
  • deploy/bases/app/app-server.yaml
    Deployment des Shopware-Anwendungsservers im Produktivmodus.
  • deploy/bases/app/ingress.yaml
    Erstellt ein Ingress-Objekt, das den Applikationsservice und den Mediaservice mit lokalen Test-Domains bereitstellt: http://shopware.test und http://media.test.
  • deploy/bases/app/task-scheduler.yaml
    Deployment des Task-Schedulers, der für das Senden von Aufgaben an die Message Queue zuständig ist.
  • deploy/bases/app/message-consumer.yaml
    Deployment des Message-Consumers, der Aufgaben aus der Message Queue abarbeitet.
  • deploy/bases/app/shopware-init.yaml
    Initialisierungsjob, der bei jedem neuen Rollout ausgeführt werden muss – idealerweise vor dem Deployment von App-Server, Task-Scheduler und Message-Consumer. Er übernimmt folgende Aufgaben:

    • Installation oder Updates von Plugins und Apps
    • Datenbankmigrationen
    • Theme-Kompilierung
    • Installation von Assets
    • Grundinstallation von Shopware (frisch), falls die Datenbank in einem neu erstellten Kubernetes-Cluster leer ist
  • deploy/bases/database
    Manifeste zur Bereitstellung eines MariaDB-Datenbankservers.
  • deploy/bases/opensearch
    Manifeste für den Basisbetrieb einer Single-Node-OpenSearch-Instanz.
  • deploy/bases/redis
    Manifeste für den Basisbetrieb einer Single-Node-Redis-Instanz. Es werden separate Instanzen für Cache und Kundensitzungen erstellt.
  • deploy/bases/storage/minio-tenant.yaml
    Stellt eine MinIO-Storage-Instanz (Tenant) bereit. Der MinIO-Operator muss bereits im Cluster installiert sein.
  • deploy/overlays/dev/app/app-server-dev.yaml
    Deployment des Shopware-Containers im Dev-Modus (mit aktiviertem Xdebug).
  • deploy/overlays/dev
    Quellverzeichnis für das Deployment der Dev-Anwendung.
  • deploy/overlays/production
    Quellverzeichnis für das Deployment im Produktionscluster.
  • test/e2e/jobs/e2e.yaml
    Job-Manifest für End-to-End-Tests auf Basis des Shopware Playwright Acceptance Tests Frameworks.
  • test/e2e/jobs/step-summary.yaml
    Job-Manifest, das die Zusammenfassung der End-to-End-Tests für GitHub Actions ausgibt.
  • test/integration/jobs/integarion-tests.yaml
    Job-Manifest zur Ausführung von Integrationstests.

Um zu prüfen, ob die Manifestdateien korrekt verarbeitet werden, führe die folgenden Befehle aus:

kustomize build deploy/overlays/dev
kustomize build deploy/overlays/production

Diese Befehle geben alle Kubernetes-Objekte aus, die auf den Cluster angewendet werden, oder zeigen eine Fehlermeldung, falls Probleme auftreten.

Konfiguration der Shopware-Deployments mit Skaffold

Die Deployment-Konfiguration in der skaffold.yaml besteht aus drei Teilen:

  • manifests: Speicherort der Manifestdateien – deploy/overlays/dev für die Entwicklung, deploy/overlays/production für die Produktion
  • deploy: Einstellungen für das Deployment
  • deploy.helm: Liste der zu installierenden Helm-Charts

Beim Deployment verarbeitet Skaffold zuerst alle Helm-Charts und wendet anschließend die Manifeste an.

...
manifests:
  kustomize:
    paths:
      - deploy/overlays/dev
deploy:
  statusCheck: true
  # Scheitern Sie den Einsatz, wenn er sich nicht innerhalb von 20 Minuten stabilisiert.
  # Ein Kaltstart kann lange dauern, wenn viele Plugins aktiviert werden müssen.
  statusCheckDeadlineSeconds: 1200
  # Pods may initially start and crash but eventually self-heal.   # This parameter allows for failures until   # the deadline, set by the statusCheckDeadlineSeconds above.
  tolerateFailuresUntilDeadline: true
  kubectl:
    defaultNamespace: shopware
  helm:
    # Installiere alle erforderlichen Operatoren
    releases:
      - name: kubernetes-secret-generator
        repo: https://helm.mittwald.de
        remoteChart: kubernetes-secret-generator
        namespace: secret-generator
        createNamespace: true
        wait: true
        version: 3.4.0
        setValues:
          image: # überschreibt die Standardwerte
            registry: ghcr.io
            # Dieses Image ist nicht offiziell, unterstützt aber sowohl amd64 als auch arm64.
            repository: belodetek/kubernetes-secret-generator
            tag: 0.0.4
      ...

Starten des Shopware-Deployments

Die Eingabedatei ist das Ergebnis zuvor gebauter Container-Images. Du kannst jedoch auch Build und Deployment mit einem einzigen Befehl ausführen:

skaffold deploy --build-artifacts=.build-artifacts.json

Zugriff auf Shopware im lokalen Kubernetes-Cluster per Webbrowser

Auf einem Linux-Host funktioniert alles direkt ohne weitere Anpassungen. Die Storefront ist erreichbar unter

http://shopware.test.

Unter macOS und Windows musst du zusätzlich einen Tunnel öffnen, um die LoadBalancer-Services bereitzustellen.

minikube tunnel

Shopware auf Kubernetes - StartseiteDemostore-Startseite nach Projektstart und Öffnen des Tunnels

Integrationstests im Cluster ausführen

Integrationstests in der CI auf einem schlanken Kubernetes-Cluster sind besonders empfehlenswert, wenn auch deine Produktionsumgebung auf Kubernetes basiert. Dieses Vorgehen erhöht die Zuverlässigkeit der Tests und bezieht Kubernetes-spezifische Abhängigkeiten wie ConfigMaps, Secrets oder ServiceAccounts mit ein.

Um Integrationstests mit dem einfachen Befehl skaffold verify auszuführen, benötigst du einen benutzerdefinierten Job, der auf die in ConfigMaps und Secrets gespeicherten Shopware-Konfigurationen verweist.

Eine direkte Einrichtung in der skaffold.yaml ist nicht möglich. Nachfolgend findest du ein Beispiel für ein entsprechendes Job-Manifest:

# ./test/integration/jobs/integration-tests.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: integration-tests
spec:
  backoffLimit: 4
  template:
    spec:
      containers:
        - name: integration-tests

          # Verwenden Sie ein Dev-Image, auf dem phpunit vorinstalliert ist.
          image: kiweeteam/franken-shopware-dev
          envFrom:
            - configMapRef:
                name: shopware-app-config
            - secretRef:
                name: shopware-app-config
            - secretRef:
                name: database-credentials
          env:
            - name: APP_ENV
              value: "test"
            - name: SHOPWARE_ADMINISTRATION_PATH_NAME
              value: "admin_$(SHOPWARE_ADMINISTRATION_PATH_SUFFIX)"
            - name: DATABASE_URL
              value: "mysql://$(MYSQL_USER):$(MYSQL_PASSWORD)@$(MYSQL_HOST):$(MYSQL_PORT)/$(MYSQL_DATABASE)"
            - name: AWS_ACCESS_KEY_ID
              valueFrom:
                secretKeyRef:
                  key: CONSOLE_ACCESS_KEY
                  name: shopware-s3
            - name: AWS_SECRET_ACCESS_KEY
              valueFrom:
                secretKeyRef:
                  key: CONSOLE_SECRET_KEY
                  name: shopware-s3
      restartPolicy: Never

Als Nächstes fügst du einen neuen Verify-Job in der skaffold.yaml hinzu. Das folgende Beispiel kann verwendet werden, um die Integrationstests des Demo-Plugins auszuführen.

# ./skaffold.yaml
...
verify:
...
  - name: integration-tests
    container:
      name: integration-tests
      image: kiweeteam/franken-shopware-dev
      command:
        - /bin/sh
      args:
        - -c
        - |
          set -e
          echo "Running integration tests of shopware-demo-plugin..."          
          /shopware-bin php-cli vendor/bin/phpunit -c vendor/kiwee/shopware-demo-plugin/phpunit.xml
          echo "Integration tests finished."
    executionMode:
      kubernetesCluster:
        jobManifestPath: test/integration/jobs/integration-tests.yaml
...

Nutze die Datei .build-artifacts.json, die Referenzen auf die zuletzt mit dem Befehl skaffold build gebauten Container-Images enthält.

skaffold verify -a .build-artifacts.json

Alternativ kannst du Integrationstests auch mit kubectl ausführen. Der Dev-Shopware-Anwendungs-Pod (app-server-dev) muss jedoch bereits laufen, bevor du die Tests startest.

kubectl exec service/app-server-dev -n shopware -- /shopware-bin php-cli 
vendor/bin/phpunit -c vendor/kiwee/shopware-demo-plugin/phpunit.xml

Wenn mehrere app-server-dev-Pods vorhanden sind, spielt es keine Rolle, auf welchem die Tests ausgeführt werden – kubectl exec service wählt einen beliebigen Pod aus, der dem Service zugeordnet ist.

Playwright End-to-End-Tests im Cluster ausführen

Für End-to-End-Tests gilt das gleiche Prinzip wie bei Integrationstests. Da hierbei nicht nur alle Services, sondern auch die Benutzeroberfläche einbezogen werden, lässt sich eine produktionsähnliche Umgebung simulieren.

Du kannst die Shopware Acceptance Test Suite nutzen, um Tests im Cluster einfach per skaffold verify-Befehl zu starten.

Zunächst erstellst du ein einfaches Testszenario, das Hinzufügen eines Produkts zum Warenkorb prüft.

# ./test/e2e/tests/CartTest.spec.ts
import {test, expect} from '../BaseTest';

test('Product detail test scenario',
    async KiweeEu/({
               ShopCustomer,
               StorefrontProductDetail,
               ProductData,
               AddProductToCart
           }) => {

        await ShopCustomer.goesTo(StorefrontProductDetail.url(ProductData));
        await ShopCustomer.attemptsTo(AddProductToCart(ProductData));
        await ShopCustomer.expects(StorefrontProductDetail.offCanvasSummaryTotalPrice).toHaveText('€10.00*');
    });

Die Playwright-Konfiguration muss die Storefront-URL, die Administrations-URL sowie die Admin-Zugangsdaten enthalten und sieht wie folgt aus:

# ./test/e2e/playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

process.env['SHOPWARE_ADMIN_USERNAME'] = process.env['SHOPWARE_ADMIN_USERNAME'] || 'admin';
process.env['SHOPWARE_ADMIN_PASSWORD'] = process.env['SHOPWARE_ADMIN_PASSWORD'] || 'shopware';

const defaultAppUrl = 'http://shopware.test/';
process.env['APP_URL'] = process.env['APP_URL']  ?? defaultAppUrl;

// make sure APP_URL ends with a slash
process.env['APP_URL'] = (process.env['APP_URL'] ?? '').replace(/\/+$/, '') + '/';
if (process.env['ADMIN_URL']) {
    process.env['ADMIN_URL'] = process.env['ADMIN_URL'].replace(/\/+$/, '') + '/';
} else {
    process.env['ADMIN_URL'] = process.env['APP_URL'] + 'admin_' + process.env['SHOPWARE_ADMINISTRATION_PATH_SUFFIX'] + '/';
}

export default defineConfig({
    testDir: './tests',
    fullyParallel: true,
    forbidOnly: !!process.env.CI,
    timeout: 60000,
    expect: {
        timeout: 10_000,
    },
    retries: 0,
    workers: process.env.CI ? 2 : 1,
    reporter: process.env.CI ? [
        ['list'],
        ['@estruyf/github-actions-reporter', <GitHubActionOptions>{
            title: 'E2E Test Results',
            useDetails: true,
            showError: true,
            debug: true
        }]
    ] : 'html',
    use: {
        baseURL: process.env['APP_URL'],
        trace: 'retain-on-failure',
        video: 'off',
    },
    projects: [
        {
            name: 'chromium',
            use: { ...devices['Desktop Chrome'] },
        }
    ],
});

Abschließend benötigst du die Datei BaseTest.ts, um alles aus dem Acceptance-Test-Suite-Framework zu exportieren, sowie die package.json zum Installieren aller Abhängigkeiten.

# ./test/e2e/BaseTest.ts
export * from '@shopware-ag/acceptance-test-suite';
// ./test/e2e/package.json
{
  "dependencies": {
    "@shopware-ag/acceptance-test-suite": "^11.6.0",
    "playwright": "^1.10.0",
    "@estruyf/github-actions-reporter": "^1.10.0"
  }
}

Das Dockerfile zum Bau des Test-Docker-Images muss folgende Elemente enthalten:

  • Installation der benötigten Bibliotheken und Abhängigkeiten
  • Installation von Playwright
  • Kopieren des Testcodes in das Container-Image

Führe anschließend den Build aus.

skaffold build --file-output=.build-artifacts.json

Die Option --file-output erzeugt eine Datei, die die zuletzt gebauten Container-Images mit Namen und Tags aufgelistet – diese werden später benötigt.

Ein von Skaffold standardmäßig erstellter Test-Job enthält keine Referenzen auf die Shopware-Secrets im Cluster. Es ist jedoch möglich, ein benutzerdefiniertes Job-Manifest zu definieren, in dem du die für die Tests benötigten Secrets referenzierst.

# ./test/e2e/jobs/e2e.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: e2e
spec:
  template:
    spec:
 ...
      containers:
        - name: e2e
          image: kiweeteam/shopware-e2e
          # Shopware-Konfiguration und Geheimnisse für die e2e-Tests einbeziehen
          envFrom:
            - configMapRef:
                name: shopware-app-config
            - secretRef:
                name: shopware-app-config
...
# ./skaffold.yaml
verify:
    ...
    name: e2e
    image: kiweeteam/shopware-e2e
    ...
    executionMode:
      kubernetesCluster:
        jobManifestPath: test/e2e/jobs/e2e.yaml

Sobald alles eingerichtet und Shopware bereits im Cluster deployt ist, führe den Verify-Befehl aus und übergib die Datei mit dem Referenz-Image als -a-Parameter.

skaffold verify -a .build-artifacts.json

Skaffold führt nun den End-to-End-Test-Job aus und zeigt die Testergebnisse an.

Wie debuggt man Shopware im Kubernetes-Cluster?

Eine umfassende Anleitung zum Debuggen einer Anwendung auf Kubernetes findest du in der Kubernetes-Dokumentation. Im Folgenden konzentrieren wir uns jedoch auf Shopware-spezifische Lösungen.

Wie kann man Shopware-Workloads inspizieren?

 kubectl describe deployment/app-server -n shopware

Dieser Befehl zeigt vollständige Informationen zum aktuellen Status des Shopware-Deployments an, darunter:

  • Die verwendete Deployment-Strategie
  • Anzahl der Shopware-Pod-Replikate (gewünscht, verfügbar, nicht verfügbar, insgesamt)
  • Liste der Init-Container und deren ausgeführte Befehle
  • Details zum Shopware-Anwendungscontainer wie Image-Version, im Cluster freigegebene Ports, Befehle und Argumente, Ressourcenanforderungen und -limits (CPU, Speicher), verwendete ConfigMaps und Secrets sowie gemountete persistente Volumes
  • Eine Historie der Statusmeldungen und Fehlermeldungen

Mit diesem Befehl erhältst du wertvolle Hinweise auf die Ursache eines fehlgeschlagenen Deployments.

Der describe-Befehl kann auf jede Ressource angewendet werden, um detaillierte Informationen zum aktuellen Status zu erhalten – etwa auf Pods, Services, Ingresses und andere.

# Shopware-Service inspizieren
kubectl describe service/app-server -n shopware

# Alle Shopware-Pods mit Label app=shopware inspizieren
kubectl describe Pods -n shopware -l app=shopware

# Alle Ingresses im Shopware Namespace inspizieren
kubectl describe ingress -n shopware

Code-Debugging mit Xdebug im Kubernetes-Cluster

Um einen vollständigen Stacktrace nachvollziehen zu können, musst du die Vendor-Abhängigkeiten und das index.php-Skript von einem Dev-Pod auf dein lokales Dateisystem kopieren.

# Namen des Dev Pods von Shopware ermitteln
APP_DEV=$(kubectl get Pod -n shopware --no-headers -l app=shopware-dev -o=custom-columns=NAME:.metadata.name) && \# vendor/ und public/ aus dem app-server-dev-Container ins lokale Projektverzeichnis kopieren
kubectl cp shopware/${APP_DEV}:/app/vendor ./vendor -c app-server-dev && \
kubectl cp shopware/${APP_DEV}:/app/public ./public -c app-server-dev

Im nächsten Schritt aktivierst du Xdebug, um eine DBGP-Verbindung mit deiner IDE herzustellen. Verwende dazu das Tool ktunnel, um einen Reverse-Tunnel zwischen dem Shopware app-server-dev-Pod im Kubernetes-Cluster und deiner IDE zu öffnen.

tunnel inject deployment -n shopware app-server-dev 9003

Dieser Befehl injiziert einen Sidecar-Container, der einen Tunnel auf Port 9003 öffnet – dieser wird von Xdebug verwendet.

PHP Xdebug Diagramm zur Funktionsweise Xdebug-Verbindung mit Code-Editor

Zum Abschluss exponierst du den app-server-dev-Pod mit dem Port-Forward-Befehl, der den lokalen Port 8000 weiterleitet. Dadurch ist der Dev-server über http://localhost:8000 erreichbar. Achte darauf, diese URL auch dem Vertriebskanal hinzuzufügen, den du debuggen möchtest.

kubectl port-forward deploy/app-server-dev -n shopware 8000:8000

localhost dem Kanal zum Debuggen hinzugefügtFüge die URL http://localhost:8000 dem Kanal zum Debuggen hinzu.

Demo Storefront im Entwicklermodus Die Storefront im Entwicklermodus, sobald Port 8000 an den app-server-dev weitergeleitet wurde.

PhpStorm / IntelliJ IDE für Debugging auf Kubernetes konfigurieren

Öffne: Settings > Languages & Frameworks > PHP > Servers. Füge einen neuen localhost -Server hinzu, falls dieser noch nicht vorhanden ist. Trage als Host localhost und als Port 8000 ein. Aktiviere danach das Kontrollkästchen Use path mappings(...). Ordne anschließend das Verzeichnis custom dem Pfad /app/custom zu, public zu /app/public und vendor zu /app/vendor.

Nach dem Speichern bist du bereit zum Debuggen! Klicke auf Start Listening for PHP Connections. Öffne im Browser die Seite, die du debuggen möchtest.

PHP-Server-Konfigurationsbildschirm in PhpStorm und IntelliJ PHP-Server-Konfigurationsbildschirm in PhpStorm und IntelliJ

PHP-Debugging-VerbindungenSchaltfläche zum Starten des Lauschens auf PHP-Debugging-Verbindungen

Debug-Bildschirm in IntelliJ/PhpStormBeispiel für den Debug-Bildschirm in IntelliJ / PhpStorm

Visual Studio Code für das Debugging auf Kubernetes konfigurieren

Stelle zunächst sicher, dass die PHP Debug-Erweiterung von Xdebug bereits installiert ist.

Öffne oder erstelle anschließend die Datei launch.json im .vscode-Ordner deines Projektverzeichnisses. Passe darin die Einstellungen für Listen for Xdebug an, indem du Pfadzuordnungen zwischen deinem lokalen Arbeitsbereich und dem Container hinzufügst oder aktualisierst. Die Konfiguration sollte wie folgt aussehen:

        {
            "name": "Listen for Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9003,
            "pathMappings": {
                "/app/custom": "${workspaceRoot}/custom",
                "/app/public": "${workspaceRoot}/public",
                "/app/vendor": "${workspaceRoot}/vendor"
            }
        },

Voilà – jetzt ist alles bereit zum Debuggen! Klicke einfach auf die Debug- und Listen for Xdebug-Schaltflächen und öffne im Browser die Seite, die du debuggen möchtest.

Debugging mit Xdebug in Visual Studio CodeDebugging mit Xdebug in Visual Studio Code

Debug-Ansicht in Visual Studio CodeBeispielhafte Debug-Ansicht in Visual Studio Code

Zusammenfassung

Eine cloud-native Shopware-Anwendung muss zustandslos sein, um horizontal skalierbar zu werden. Dies erreichst du durch:

  • Die Konfiguration eines gemeinsamen Cache-Speichers wie Redis anstelle des
    standardmäßig lokalen, dateibasierten Caches.
  • Die Konfiguration eines gemeinsamen Speichers für Medien- und private Dateien, idealerweise eines S3-kompatiblen Objektspeichers.
  • Die Konfiguration der Protokollierung, sodass Logs in stdout und stderr gestreamt werden, um sie zentral – etwa in einem Index wie Loki oder Elasticsearch oder in SaaS-Lösungen wie New Relic oder Datadog – weiter zu aggregieren und zu speichern.

Um deine Shopware-Anwendung im lokalen Kubernetes-Cluster bereitzustellen, zu testen und zu debuggen:

  • Containerisiere deine Shopware-Anwendung: Ziehe FrankenPHP als
    Alternative zu PHP-FPM mit Apache oder NGINX in Betracht. Mit FrankenPHP kannst du die gesamte Anwendung zu einer eigenständigen Binärdatei bündeln und dadurch minimale sowie sicherere Basis-Container-Images verwenden.
  • Bauen und testen: Nutze Skaffold – ein praktisches Tool, um Shopware mit einfachen Befehlen auf Kubernetes zu bauen, bereitzustellen und zu testen: skaffold build erstellt Container-Images, skaffold run startet den gesamten Shopware-Stack lokal, und mit skaffold verify führst du die Tests aus.
  • Debuggen: Um Shopware im Cluster zu debuggen, deploye Shopware mit
    aktivierter Xdebug-PHP-Erweiterung. Nutze anschließend das Tool ktunnel, das Xdebug-Verbindungen mit deinem Code-Editor ermöglicht.

Ich lade dich herzlich ein, dich mit deinen Ideen, Anregungen oder gefundenen Problemen am Shopware-Kube-Projekt zu beteiligen. Wir bei Kiwee werden FrankenPHP weiterhin als Application Server für Shopware anpassen und testen.

Wenn du Beratung für deine cloudbasierte Shopware-Infrastruktur benötigst oder deinen eigenen Stack aufbauen möchtest, kontaktiere uns gerne! Wir helfen gern weiter.

FacebookTwitterPinterest