Magento 2 op Kubernetes: de architectuur per component

Coding.nl
8 min leestijd
Inhoud van dit artikel
  1. Het overzicht
  2. Eén image voor alles
  3. Nginx en PHP-FPM
  4. Varnish
  5. Gateway API en TLS
  6. Sessies en cache in Valkey of Redis
  7. OpenSearch
  8. De database: buiten het cluster
  9. Media en bestanden
  10. Cron als CronJob
  11. Queue-consumers als Deployments
  12. Secrets en configuratie
  13. Waar we op letten bij de eerste opzet

Magento 2 draait goed op Kubernetes als je de applicatie stateless maakt: PHP-FPM en webserver in pods zonder lokale data, media op object storage, sessies en cache in Valkey of Redis, cron en queue-consumers als aparte workloads, en de database bij voorkeur managed buiten het cluster. Static content en DI-compilatie bouw je in het image, nooit bij het opstarten. Hieronder per component hoe wij dat inrichten, met de YAML erbij.

Het overzicht

Een productie-opzet bestaat uit deze onderdelen:

ComponentKubernetes-objectWaar staat de data?
Gateway / TLSGateway + HTTPRouteCertificaten via cert-manager
VarnishDeployment of StatefulSetIn geheugen, wegwerpbaar
Nginx + PHP-FPMDeploymentNergens, stateless
CronCronJobNergens
Queue-consumersDeployment per consumerRabbitMQ of database-queue
Valkey / RedisStatefulSet of managedCache en sessies
OpenSearchOperator of managedZoekindex
DatabaseManaged, buiten het clusterMySQL of MariaDB
MediaObject storage of RWX-volumeBuiten de pods

Het verkeer loopt van de Gateway naar Varnish, en van Varnish naar Nginx met PHP-FPM. Alles wat state heeft, staat buiten de web-pods.

Controleer de versies altijd tegen de officiële systeemeisen van Adobe. Volgens de huidige tabel ondersteunt Magento 2.4.8-p5 PHP 8.3 en 8.4, OpenSearch 3 en Valkey 8.1. Magento 2.4.9 gaat naar PHP 8.5, Valkey 9, MySQL 8.4 of MariaDB 12.3, RabbitMQ 4.3 en Varnish 8. Bouw je images op een combinatie die Adobe ondersteunt.

Eén image voor alles

We bouwen één image per release. Web, cron en consumers draaien hetzelfde image, alleen met een ander commando. Zo weet je zeker dat cron dezelfde code draait als de frontend.

In het image zitten:

  • de code via composer install --no-dev;
  • de gecompileerde DI (setup:di:compile);
  • de static content (setup:static-content:deploy);
  • een app/etc/config.php met de modules, scopes en thema's.

Niet in het image: app/etc/env.php met wachtwoorden, pub/media en var/. Hoe je dat image bouwt en uitrolt zonder downtime, staat in Magento deployen op Kubernetes zonder downtime.

Nginx en PHP-FPM

We draaien Nginx en PHP-FPM als twee containers in dezelfde pod. Ze delen de code via het image en praten over 127.0.0.1:9000. Dat houdt de configuratie simpel en de latency laag.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: magento-web
spec:
  replicas: 3
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: magento-web
  template:
    metadata:
      labels:
        app: magento-web
    spec:
      terminationGracePeriodSeconds: 30
      containers:
        - name: php-fpm
          image: registry.example.nl/shop/magento:1f3c9a2
          resources:
            requests:
              cpu: "500m"
              memory: 1Gi
            limits:
              memory: 2Gi
          volumeMounts:
            - name: env-php
              mountPath: /var/www/html/app/etc/env.php
              subPath: env.php
        - name: nginx
          image: registry.example.nl/shop/magento-nginx:1f3c9a2
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health_check.php
              port: 8080
            periodSeconds: 5
          lifecycle:
            preStop:
              exec:
                command: ["sleep", "10"]
      volumes:
        - name: env-php
          secret:
            secretName: magento-env-php

De punten die ertoe doen:

  • /health_check.php zit standaard in Magento en controleert onder meer de databaseverbinding en de cache. Gebruik hem voor de readiness probe, niet voor de liveness probe. Anders herstart Kubernetes al je pods tegelijk als de database even hapert.
  • preStop met een korte sleep geeft de load balancer tijd om de pod uit rotatie te halen voordat PHP-FPM stopt.
  • Een geheugen-limit, geen CPU-limit. CPU-limits veroorzaken throttling bij pieken. Geheugen-limits beschermen de node tegen een weggelopen proces.

Voeg een PodDisruptionBudget toe, zodat een node-upgrade nooit alle web-pods tegelijk weghaalt.

Varnish

Varnish draait als eigen workload voor de web-pods. Magento genereert de VCL met bin/magento varnish:vcl:generate; die zetten we in een ConfigMap.

Het lastige punt is purgen. Magento stuurt een PURGE-request naar elke host in http_cache_hosts. Draai je drie Varnish-replica's achter één Service, dan komt een purge maar bij één replica aan. De andere twee serveren oude content.

Twee oplossingen:

  1. Varnish als StatefulSet met een headless Service, en elke pod los in de configuratie:
bash
bin/magento setup:config:set \
  --http-cache-hosts=varnish-0.varnish:6081,varnish-1.varnish:6081
  1. Eén Varnish-instantie met genoeg geheugen en een snelle herstart. Bij veel shops is dat voldoende, en het is eenvoudiger.

Hoe je Varnish voor Magento afstelt, los van Kubernetes, lees je in Varnish configureren voor Magento 2.

Gateway API en TLS

Het community-project ingress-nginx is in maart 2026 met pensioen gegaan: geen releases en geen securityfixes meer. Voor nieuwe clusters gebruiken we de Gateway API, met een implementatie als Envoy Gateway, NGINX Gateway Fabric, Traefik of Cilium. Waarom en hoe je migreert: ingress-nginx stopt, wat nu?.

Een HTTPRoute die verkeer naar Varnish stuurt:

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: shop
spec:
  parentRefs:
    - name: public
      namespace: gateway
  hostnames:
    - www.voorbeeldshop.nl
  rules:
    - backendRefs:
        - name: varnish
          port: 6081

TLS termineert op de Gateway. Certificaten vraagt cert-manager aan en vernieuwt hij automatisch. Zorg dat Magento weet dat het verkeer via HTTPS binnenkwam, via de X-Forwarded-Proto-header en de offloader-instelling. Meer op onze pagina over ingress, Gateway API en TLS.

Sessies en cache in Valkey of Redis

Sessies in bestanden werken niet met meerdere pods. Een bezoeker die op een andere pod landt, is zijn winkelwagen kwijt. Sessies gaan dus naar Valkey of Redis, net als de default cache en de page cache als je geen Varnish gebruikt.

Een uitsnede uit env.php:

php
'session' => [
    'save' => 'redis',
    'redis' => [
        'host' => getenv('SESSION_REDIS_HOST'),
        'port' => '6379',
        'database' => '0',
        'disable_locking' => '1',
    ],
],
'cache' => [
    'frontend' => [
        'default' => [
            'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
            'backend_options' => [
                'server' => getenv('CACHE_REDIS_HOST'),
                'port' => '6379',
                'database' => '0',
            ],
        ],
    ],
],

We gebruiken aparte instanties voor sessies en cache. Een cache:flush mag nooit sessies raken, en een volle cache mag geen sessies wegdrukken.

Over de keuze tussen Valkey en Redis: Redis wijzigde in 2024 de licentie, waarna de Linux Foundation de fork Valkey startte. Redis 8 kwam in 2025 met AGPLv3 als extra licentie-optie. Magento ondersteunt Valkey officieel. Voor nieuwe opzetten kiezen wij Valkey.

Let bij Helm-charts op de bron van het image. Bitnami heeft in 2025 zijn gratis images grotendeels naar een legacy-repository verplaatst die geen updates meer krijgt. Een chart die nog naar die images wijst, draait in productie op ongepatchte software.

OpenSearch

Magento 2.4.8 en hoger gebruiken OpenSearch. Je kunt OpenSearch in het cluster draaien met de officiële OpenSearch Kubernetes Operator, of een managed variant van je cloudaanbieder nemen.

Onze afweging: in het cluster als je de nodes en het geheugen hebt en iemand die de index-upgrades begrijpt. Managed als je zoekfunctie bedrijfskritisch is en je geen zin hebt in shard-beheer. OpenSearch heeft persistent storage nodig en is gevoelig voor geheugen. Geef het eigen nodes of in elk geval harde requests.

De database: buiten het cluster

We zetten de database bijna altijd buiten het cluster, bij een managed dienst. De redenen:

  • back-ups, point-in-time restore en failover zijn daar standaard geregeld;
  • upgrades van MySQL of MariaDB zijn geen cluster-operatie;
  • de database is de laatste component die je wilt laten meebewegen met node-upgrades.

Wil je toch in het cluster, gebruik dan een operator die back-ups en failover regelt, en test het herstel. Een back-up die je nooit hebt teruggezet, is een hoop, geen back-up.

Media en bestanden

pub/media moet gedeeld zijn tussen alle pods. Er zijn twee routes:

  • Remote storage naar S3-compatibele object storage. Magento heeft dit ingebouwd sinds 2.4.2:
bash
bin/magento setup:config:set \
  --remote-storage-driver=aws-s3 \
  --remote-storage-bucket=shop-media \
  --remote-storage-region=eu-central-1
  • Een ReadWriteMany-volume via NFS, CephFS of de bestandsdienst van je cloud. Werkt met elke extensie, maar is trager en een extra component om te beheren.

Remote storage is netter, maar test je extensies. Niet elke extensie schrijft via het Magento-filesystem; sommige gebruiken directe PHP-bestandsfuncties en breken dan.

var/ en generated/ horen niet op een gedeeld volume. Logs gaan naar stdout, zodat je log-stack ze oppikt.

Cron als CronJob

Magento verwacht dat bin/magento cron:run elke minuut draait. Op Kubernetes is dat een CronJob:

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: magento-cron
spec:
  schedule: "* * * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 0
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: cron
              image: registry.example.nl/shop/magento:1f3c9a2
              command: ["bin/magento", "cron:run"]

concurrencyPolicy: Forbid voorkomt dat runs elkaar inhalen als een indexer lang duurt. Elke minuut een nieuwe pod starten kost wel opstarttijd. Bij zware cron-belasting draaien we daarom soms een Deployment met een eenvoudige lus die elke minuut cron:run aanroept. Vastlopende cron herken je aan dezelfde signalen als op een server: een groeiende cron_schedule-tabel en jobs die blijven hangen op status running.

Queue-consumers als Deployments

Standaard start Magento queue-consumers vanuit cron. Op Kubernetes zet je dat uit en draai je elke belangrijke consumer als eigen Deployment:

php
'cron_consumers_runner' => [
    'cron_run' => false,
],
yaml
containers:
  - name: consumer
    image: registry.example.nl/shop/magento:1f3c9a2
    command:
      - bin/magento
      - queue:consumers:start
      - product_action_attribute.update
      - --max-messages=1000

Met --max-messages stopt de consumer na een aantal berichten en start Kubernetes hem opnieuw. Dat houdt geheugenlekken in toom. Draai je RabbitMQ, dan kun je consumers met KEDA schalen op de queue-lengte. Meer over RabbitMQ in Magento: RabbitMQ in Magento 2: async processing. Over schalen: autoscaling met HPA, KEDA en Karpenter.

Secrets en configuratie

env.php is gewoon PHP. Wij laten het waarden lezen met getenv() en vullen die environment-variabelen vanuit Kubernetes Secrets. De Secrets zelf komen uit een externe vault via External Secrets Operator, zodat er geen wachtwoorden in Git staan.

Instellingen uit de admin die per omgeving verschillen, zoals base-URL's, zet je in env.php of via CONFIG__DEFAULT__...-variabelen. Dan zijn ze reproduceerbaar en niet afhankelijk van iemand die in de admin klikt. Hoe we secrets, RBAC en image-scans inrichten, staat op secrets en security op Kubernetes.

Waar we op letten bij de eerste opzet

  • Requests op basis van metingen, niet op gevoel. Meet eerst een week, stel dan bij.
  • Eén namespace per omgeving, met eigen Secrets en NetworkPolicies.
  • Varnish-purge testen voordat je live gaat: wijzig een product en controleer elke Varnish-pod.
  • Een loadtest op staging met cron en consumers actief, niet alleen de frontend.
  • Een draaiboek voor database-herstel en voor het terugzetten van een release.

Het totaalplaatje met onze werkwijze staat op Magento 2 op Kubernetes.

Veelgestelde vragen

Moet static content echt in het image?+

Ja. Static content deployen bij het opstarten van een pod maakt opstarten traag en onvoorspelbaar, en elke pod doet hetzelfde werk opnieuw. In het image is het één keer gebouwd en identiek op elke pod.

Kan ik Magento sessies in de database opslaan?+

Dat kan, maar het belast je database met elke paginaweergave. Valkey of Redis is sneller en houdt de database vrij voor bestellingen en catalogusdata.

Hoeveel Varnish-pods heb ik nodig?+

Vaak één, met voldoende geheugen. Meerdere replica's kunnen, maar dan moet elke pod purges ontvangen. Dat vraagt een StatefulSet met adresseerbare pods in http_cache_hosts.

Werkt Hyvä anders op Kubernetes dan Luma?+

Niet wezenlijk. Hyvä bouwt de Tailwind-CSS bij de build; dat doe je in de image-build, net als de static content.

Is een RWX-volume voor pub/media een probleem?+

Het werkt, maar is trager dan lokale schijf en een extra component om te beheren. Remote storage naar object storage is schaalbaarder, mits je extensies ermee overweg kunnen.

Ruthger Idema

Geschreven door Ruthger Idema

Eigenaar van Coding.nl. 15+ jaar e-commerce met Magento, Shopify en Laravel, samen met het vaste team in Emmen.

Wil je dit voor jouw shop laten uitzoeken? Plan een half uur

Een half uur via video met de mensen die het werk ook echt doen. Kies hieronder een moment dat jou past.

  • Direct met het vaste team, geen tussenlagen
  • Concrete feedback op je huidige situatie of plannen
  • Eerlijk advies, ook als het antwoord "niet doen" is
  • Indicatie van kosten en tijdlijn
Liever bellen? 0591-239 299
Beschikbaar deze week

Plan een gratis Tech Check

Vrijblijvend jouw uitdagingen bespreken

Of bel direct: 0591-239 299