Inhoud van dit artikel
- Waarom het op Kubernetes anders werkt
- Stap 1: alles in het image bouwen
- syntax=docker/dockerfile:1
- Stap 2: taggen, pushen en de deploy-repo bijwerken
- overlays/productie/kustomization.yaml
- Stap 3: detecteren of setup:upgrade nodig is
- Stap 4: maintenance alleen als het moet
- Stap 5: de rolling update
- Stap 6: terugdraaien
- De checklist per deploy
Magento zonder downtime deployen op Kubernetes komt neer op vier dingen: alles bouwen in het image, vóór de uitrol met setup:db:status detecteren of er een databasewijziging is, alleen bij een echte schemawijziging setup:upgrade draaien in een Job, en de pods daarna rollend vervangen. Maintenance mode heb je alleen nodig bij wijzigingen die de oude code breken. Terugdraaien is een Git-revert, maar alleen als je databasewijzigingen achterwaarts compatibel houdt.
Waarom het op Kubernetes anders werkt
Op een klassieke server werkt een Magento-deploy vaak zo: code ophalen, maintenance:enable, setup:upgrade, compileren, static content deployen, maintenance:disable. De shop ligt dan minuten plat, elke deploy.
Op Kubernetes kun je dat niet een-op-een overnemen:
- Pods zijn wegwerpbaar. Compileren in een draaiende pod is zinloos; de volgende pod begint weer bij nul.
- De maintenance-flag is een bestand.
bin/magento maintenance:enableschrijftvar/.maintenance.flag. Met tien pods zonder gedeeldevar/zet je maintenance aan op precies één pod. - Oude en nieuwe pods draaien tegelijk. Tijdens een rolling update bedienen beide versies bezoekers, tegen dezelfde database.
Dat laatste punt bepaalt de hele aanpak. Je deploy moet zo ingericht zijn dat oude code korte tijd tegen het nieuwe databaseschema kan draaien.
Stap 1: alles in het image bouwen
Het image bevat de complete, gecompileerde applicatie. Niets wordt gebouwd bij het opstarten van een pod. Magento kan de build-stap draaien zonder databaseverbinding, zolang app/etc/config.php de modules, scopes en thema's bevat.
Die configuratie zet je eenmalig vast en commit je:
bin/magento app:config:dump scopes themes i18n
Een multi-stage Dockerfile:
# syntax=docker/dockerfile:1
FROM registry.example.nl/base/magento-php:8.4 AS build
WORKDIR /var/www/html
COPY composer.json composer.lock ./
RUN --mount=type=secret,id=composer_auth,target=/var/www/html/auth.json \
composer install --no-dev --prefer-dist --no-interaction
COPY . .
FROM node:22 AS theme
WORKDIR /var/www/html
COPY --from=build /var/www/html ./
RUN cd app/design/frontend/Voorbeeld/shop/web/tailwind \
&& npm ci && npm run build
FROM build AS compile
COPY --from=theme /var/www/html/app/design/frontend/Voorbeeld/shop/web/css/ \
app/design/frontend/Voorbeeld/shop/web/css/
RUN bin/magento setup:di:compile \
&& composer dump-autoload --no-dev --optimize \
&& bin/magento setup:static-content:deploy -f --jobs=4 nl_NL en_US
FROM registry.example.nl/base/magento-php:8.4 AS runtime
WORKDIR /var/www/html
COPY --from=compile --chown=www-data:www-data /var/www/html /var/www/html
USER www-data
Wat hier gebeurt:
- De Composer-credentials voor
repo.magento.comkomen binnen als build secret. Ze staan niet in een image-laag. - Het Hyvä-thema bouwt in een aparte Node-stage, met de vendor-map erbij, omdat Tailwind ook de templates van modules scant. Commit
app/etc/hyva-themes.json(gegenereerd metbin/magento hyva:config:generate) of genereer het in de build-stage. In de runtime zit geen Node. Paden en scriptnamen verschillen per thema en Hyvä-versie; pas ze aan op je eigen structuur. - DI en static content worden één keer gebouwd. Elke pod die start, is direct klaar.
- De PHP-versie van het basisimage volgt je Magento-versie. Volgens de systeemeisen van Adobe is dat PHP 8.3 of 8.4 voor 2.4.8 en PHP 8.5 voor 2.4.9.
env.php zit niet in het image. Die komt bij het draaien uit een Secret, met daarin ook 'MAGE_MODE' => 'production'. Hoe het image in de rest van de architectuur past, lees je in Magento 2 op Kubernetes: de architectuur per component.
Stap 2: taggen, pushen en de deploy-repo bijwerken
Elk image krijgt een onveranderlijke tag, meestal de commit-hash. Nooit latest. Met latest weet je niet wat er draait en kun je niet terug.
In CI ziet dat er ongeveer zo uit:
docker buildx build \
--secret id=composer_auth,src=auth.json \
-t registry.example.nl/shop/magento:${GIT_SHA} \
--push .
Daarna wordt de nieuwe tag in de deploy-repository gezet, bijvoorbeeld in een Kustomize-overlay:
# overlays/productie/kustomization.yaml
resources:
- ../../base
images:
- name: registry.example.nl/shop/magento
newTag: 1f3c9a2
Die commit kan je pipeline doen, of Argo CD Image Updater. Argo CD ziet de wijziging en synchroniseert. Een complete pipeline met GitHub Actions beschrijven we in Magento 2 CI/CD pipeline opzetten met GitHub Actions. Hoe wij builds, registry en promotie tussen omgevingen inrichten, staat op CI/CD naar Kubernetes.
Stap 3: detecteren of setup:upgrade nodig is
Niet elke deploy wijzigt de database. Een template-aanpassing of een bugfix in een controller raakt het schema niet. Dan is setup:upgrade overbodig en alleen maar risico.
Magento heeft daar een commando voor. bin/magento setup:db:status geeft exit code 0 als alles bijgewerkt is, en exit code 2 als setup:upgrade nodig is. Exit code 1 betekent dat de code en de database op een andere manier niet overeenkomen; dan stop je en kijk je wat er mis is.
Wij draaien die controle in een Job die Argo CD vóór de uitrol uitvoert, met het nieuwe image:
apiVersion: batch/v1
kind: Job
metadata:
name: magento-upgrade
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: upgrade
image: registry.example.nl/shop/magento:1f3c9a2
command: ["/bin/sh", "-c"]
args:
- |
set +e
bin/magento setup:db:status
STATUS=$?
set -e
if [ "$STATUS" -eq 0 ]; then
bin/magento app:config:import --no-interaction
elif [ "$STATUS" -eq 2 ]; then
bin/magento setup:upgrade --keep-generated --no-interaction
else
exit "$STATUS"
fi
bin/magento cache:clean config
De keuzes:
PreSync: als deze Job faalt, stopt Argo CD de hele sync. De oude pods blijven draaien. Niemand merkt iets.--keep-generated: de gegenereerde code staat al in het image. Zonder deze vlag gooitsetup:upgradedie weg.cache:clean configin plaats vancache:flush. Een volledige flush laat al je pods tegelijk een koude cache opbouwen.
Een bekende valkuil: bij sommige versies meldt setup:db:status een verschil dat er niet echt is, bijvoorbeeld rond JSON-kolommen of lege comments in db_schema.xml. Dan draait setup:upgrade bij elke deploy. Controleer na een deploy of de status weer 0 is. Zo niet, zoek uit welk verschil blijft hangen.
Hoe je zulke Jobs en de volgorde van resources regelt met sync waves, werken we uit in Argo CD sync waves voor migraties. De basis van onze GitOps-opzet staat op GitOps met Argo CD.
Stap 4: maintenance alleen als het moet
Tijdens setup:upgrade draaien de oude pods door. Dat werkt alleen als de oude code de nieuwe database accepteert. Magento gooit normaal een "Please upgrade your database"-fout zodra de moduleversies in de database nieuwer zijn dan de code verwacht.
Sinds Magento 2.4.4 kun je die controle uitzetten in env.php:
'deployment' => [
'blue_green' => [
'enabled' => true,
],
],
Daarmee blijven de oude pods bedienen terwijl de upgrade draait. Belangrijk: deze vlag maakt destructieve schemawijzigingen niet veilig. Hij haalt alleen de blokkade weg.
Wij onderscheiden daarom drie soorten deploys:
- Geen databasewijziging. Rolling update, geen maintenance. Dit is verreweg de meeste deploys.
- Achterwaarts compatibele wijziging. Een nieuwe kolom, een nieuwe tabel, een nieuwe index.
setup:upgrademet de blue/green-vlag, oude pods draaien door, geen maintenance. - Brekende wijziging. Een kolom die verdwijnt of van type verandert, een grote datamigratie. Hier plannen we maintenance in.
Voor type 3 werkt maintenance:enable niet, om de reden die we bovenaan noemden. Maintenance regel je dan op het netwerkniveau. Bijvoorbeeld door de HTTPRoute tijdelijk naar een simpele maintenance-pagina te sturen:
rules:
- backendRefs:
- name: maintenance-page
port: 80
Dat is een commit in Git, zichtbaar en terug te draaien. Pauzeer ook cron tijdens zo'n upgrade, zodat oude cron-code niet op een half gemigreerde database draait:
kubectl -n shop-productie patch cronjob magento-cron \
-p '{"spec":{"suspend":true}}'
Het beste is: zorg dat type 3 zelden voorkomt. Splits een brekende wijziging op in twee releases. Eerst de nieuwe kolom toevoegen en beide kolommen vullen. Pas in een volgende release de oude kolom weghalen. Dit heet expand en contract, en het is de basis van deploys zonder downtime.
Stap 5: de rolling update
Na een geslaagde PreSync-Job vervangt Kubernetes de pods. Met deze instellingen gaat er geen capaciteit verloren:
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
spec:
terminationGracePeriodSeconds: 30
containers:
- name: nginx
readinessProbe:
httpGet:
path: /health_check.php
port: 8080
lifecycle:
preStop:
exec:
command: ["sleep", "10"]
maxUnavailable: 0: een oude pod gaat pas weg als een nieuwe pod ready is.- Readiness probe: een nieuwe pod krijgt pas verkeer als Magento antwoord geeft.
preStop: de pod blijft nog even draaien terwijl de load balancer hem uit rotatie haalt, zodat lopende requests afmaken.
Queue-consumers en eventuele cron-Deployments rollen gewoon mee met het nieuwe image. Geef consumers een terminationGracePeriodSeconds die past bij hun langste bericht, zodat een import niet halverwege wordt afgebroken.
Static content krijgt in Magento een versienummer in de URL. De standaard Nginx-configuratie van Magento haalt dat versienummer er weer uit bij het serveren. Oude HTML in Varnish laadt na de uitrol dus de nieuwe CSS en JavaScript. Meestal is dat geen probleem. Is de markup flink veranderd, ververs dan de page cache met cache:clean full_page, niet met een volledige flush.
Wil je nog voorzichtiger uitrollen, met eerst een klein deel van het verkeer op de nieuwe versie, dan kun je de Deployment vervangen door een Argo Rollout met een canary-strategie. Dat werken we uit in canary deploys met Argo Rollouts.
Stap 6: terugdraaien
Een rollback van de code is simpel. Zet de vorige image-tag terug in Git:
git revert HEAD
git push
Argo CD synchroniseert en de pods rollen terug naar het vorige image. Omdat het image alles bevat, is de oude versie binnen de tijd van een rolling update terug.
De database draait niet mee terug. Magento heeft geen "setup:downgrade". Daarom is expand en contract zo belangrijk: na een type-2-wijziging werkt de oude code nog steeds tegen het nieuwe schema. Een rollback is dan veilig.
Na een type-3-wijziging ligt dat anders. Daar hoort een database-snapshot vlak voor de upgrade bij, en een bewuste keuze: terug naar de snapshot met verlies van tussentijdse bestellingen, of vooruit met een fix. Die keuze maak je vóór de deploy, niet tijdens het incident.
De checklist per deploy
- Image gebouwd met onveranderlijke tag, inclusief DI en static content.
config.phpbijgewerkt en gecommit als er modules zijn bijgekomen.- Databasewijziging beoordeeld: geen, compatibel of brekend.
- Bij brekend: snapshot gemaakt, maintenance-route klaar, cron gepauzeerd.
- PreSync-Job geslaagd,
setup:db:statusdaarna op 0. - Rolling update afgerond, alle pods ready.
- Steekproef in de shop: homepage, categorie, product, winkelwagen, checkout.
Zo inrichten doen we voor elke shop die we op Kubernetes zetten. Meer over onze aanpak: Magento 2 op Kubernetes.
Veelgestelde vragen
Kan ik bin/magento maintenance:enable gebruiken op Kubernetes?+
Alleen als var/ gedeeld is tussen alle pods, en dat raden we af. Regel maintenance liever op de Gateway of in Varnish, zodat het voor alle pods tegelijk geldt.
Moet setup:upgrade bij elke deploy draaien?+
Nee. Laat setup:db:status bepalen of het nodig is. Bij exit code 0 volstaat app:config:import en blijft de database ongemoeid.
Wat doet de blue_green-instelling in env.php precies?+
Hij voorkomt dat Magento een "upgrade je database"-fout geeft als de database nieuwer is dan de code. Oude pods kunnen dan doordraaien tijdens een upgrade. Hij maakt brekende schemawijzigingen niet veilig.
Hoe snel kan ik terugdraaien?+
Zo snel als een rolling update duurt, omdat het vorige image compleet is. Voorwaarde is dat de databasewijziging van de nieuwe release achterwaarts compatibel was.
Werkt dit ook voor security patches?+
Ja. Een patch is een nieuwe image-build en een normale uitrol. Veel patches raken het schema niet, waardoor het een deploy zonder databasestap wordt.





