Laravel Kubernetes

Laravel op Kubernetes: web, queues en scheduler elk op hun eigen plek

Een Laravel-applicatie is zelden alleen een website. Er draaien queue workers, een scheduler, soms websockets en vaak koppelingen met ERP, PIM of betaalproviders. Kubernetes geeft elk van die rollen een eigen workload, met eigen schaalgedrag. Het vaste team bouwt de images, de charts en de deploy-flow, en draait het daarna mee. Is Laravel Cloud of Forge voor jouw app eenvoudiger, dan hoor je dat van ons.

  • Queue workers die schalen op de lengte van de wachtrij met KEDA
  • Migraties als Job vóór de nieuwe pods, niet in het opstartscript
  • Eerlijke keuze tussen Kubernetes, Laravel Cloud en Forge

Waarom Laravel op Kubernetes, en wanneer niet

Laravel schaalt goed horizontaal, mits de applicatie stateless is. Sessies, cache, uploads en logs horen dan niet op de server, maar in Redis of Valkey, object storage en een logcollector. Wie dat al op orde heeft, zit dichter bij Kubernetes dan hij denkt.

Kubernetes loont vooral bij applicaties met veel achtergrondwerk. Denk aan orderverwerking, voorraadsynchronisatie, PDF-generatie of imports die 's nachts duizenden jobs aanmaken. Op vaste servers koop je capaciteit voor de piek. Op Kubernetes schalen workers mee met de wachtrij en schaalt de webkant los daarvan.

Het loont ook als je meerdere Laravel-applicaties beheert, of Laravel naast Magento draait. Eén cluster, één deploy-flow met Argo CD en één monitoringstack is dan overzichtelijker dan vijf losse servers met elk hun eigen gewoontes.

De eerlijke versie: voor één Laravel-app met een paar workers is Kubernetes vaak overkill. Laravel Forge op een paar servers, of Laravel Cloud, is dan sneller opgezet en goedkoper in beheer. Laravel Cloud draait volgens Laravel zelf op Kubernetes bij AWS, maar je beheert dat cluster niet zelf. Op deze pagina staat hoe wij Laravel op Kubernetes inrichten als het wel de juiste keuze is.

Laravel Kubernetes-architectuur: de workloads op een rij

Eén image, meerdere rollen. Dezelfde code draait als webpod, worker, scheduler of websocketserver, met een ander startcommando.

RolKubernetes-vormStartcommandoSchalen op
Web met php-fpmDeployment met Nginx en PHP-FPMphp-fpm, Nginx als tweede containerCPU of requests via HPA
Web met OctaneDeployment met één containerphp artisan octane:start --server=frankenphpCPU of requests via HPA
HorizonDeploymentphp artisan horizonProcessen binnen Horizon, of pods via KEDA
Queue workers zonder HorizonDeployment per queuephp artisan queue:work --queue=ordersLengte van de Redis-lijst via KEDA
SchedulerCronJob of Deployment met één replicaphp artisan schedule:run of schedule:workNiet schalen: precies één
ReverbDeployment met eigen Service en routephp artisan reverb:startAantal verbindingen, met Redis voor scaling
MigratiesJob of Argo CD PreSync-hookphp artisan migrate --force --isolatedEén keer per release

Plan een vrijblijvend gesprek over je project

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

Het image: één build voor alle omgevingen

Elke release begint met een image. Wij bouwen dat in een multi-stage Dockerfile. De eerste stage draait composer install met --no-dev en --optimize-autoloader. Een tweede stage bouwt de frontend met npm ci en vite build. De laatste stage start vanaf een slank PHP-image, kopieert alleen vendor, de gebouwde assets en de applicatiecode, en draait als niet-root gebruiker.

Wat niet in het image hoort: een .env-bestand, API-sleutels of databasewachtwoorden. Die komen in de cluster binnen als environment-variabelen. Daardoor draait exact hetzelfde image op test, acceptatie en productie. Wat je op acceptatie hebt goedgekeurd, is bit voor bit wat er live gaat.

Elk image krijgt een tag die naar de commit verwijst, nooit latest. In de pipeline scannen we het image op kwetsbaarheden, bijvoorbeeld met Trivy, voordat het naar de registry gaat. Een kritieke kwetsbaarheid in een basis-image houdt de release tegen in plaats van dat je hem achteraf in productie ontdekt.

Het startcommando bepaalt de rol. In de Helm-chart krijgt elke Deployment hetzelfde image met een eigen command: php-fpm voor web, horizon voor queues, schedule:work voor de scheduler. Eén build, één versie, geen verschillen tussen rollen die later voor verrassingen zorgen.

php-fpm of Octane met FrankenPHP?

Met php-fpm start elke request met een schone applicatie. Dat is voorspelbaar en elke Laravel-package werkt ermee. In een pod draaien we dan Nginx en PHP-FPM als twee containers die via een socket of localhost praten. Het is de veilige standaard voor bestaande applicaties met veel packages.

Laravel Octane houdt de applicatie in het geheugen tussen requests. Octane ondersteunt FrankenPHP, Swoole en RoadRunner. Met FrankenPHP heb je één container die zelf HTTP afhandelt, zonder losse Nginx. Dat scheelt een container per pod en geeft lagere responstijden, omdat het framework niet bij elke request opnieuw boot.

De prijs is dat state blijft hangen. Een singleton die per request andere data bevat, een statische property of een package dat aanneemt dat elke request vers begint: dat geeft in Octane bugs die je op php-fpm nooit zag. Daarom testen we een overstap op Octane eerst onder belasting op acceptatie. We zetten ook --max-requests, zodat workers na een vast aantal requests herstarten en geheugenlekken geen kans krijgen.

Onze vuistregel: nieuwe applicaties en API's met hoge requestvolumes zetten we op Octane met FrankenPHP. Bestaande applicaties met veel oudere packages laten we op php-fpm, tot er een concrete reden is om te wisselen. Meer over de afweging staat op onze pagina over Laravel Octane.

Horizon en queue workers: schalen op de wachtrij

Horizon is het dashboard en de procesmanager voor Redis-queues. Op Kubernetes draait Horizon als Deployment. Binnen een pod start Horizon zelf meerdere workerprocessen per supervisor, en met de balance-strategie auto verdeelt het die over de queues. Dat is één laag van autoscaling.

KEDA is de andere laag. KEDA is een CNCF graduated project dat pods schaalt op externe signalen, zoals de lengte van een Redis-lijst. Laravel zet wachtende jobs in een lijst met de naam queues: plus de queuenaam, voorafgegaan door de Redis-prefix uit config/database.php. Een KEDA ScaledObject met de redis-trigger leest die lijst en voegt pods toe zodra er meer jobs wachten dan je drempel.

Combineer die twee lagen niet blind. Als Horizon binnen een pod opschaalt en KEDA tegelijk pods toevoegt, krijg je meer workers dan je database aankan. Wij kiezen per applicatie één leidende laag. Vaak is dat Horizon met vaste pods voor het dashboard en de kleine queues, en losse queue:work-Deployments met KEDA voor de queues met grote pieken, zoals imports.

Belangrijk bij elke worker is een nette shutdown. Kubernetes stuurt bij een deploy of schaalactie een SIGTERM. queue:work en Horizon maken dan de lopende job af en stoppen. Zet terminationGracePeriodSeconds daarom ruimer dan de timeout van je langste job. Anders breekt Kubernetes jobs halverwege af en worden ze opnieuw uitgevoerd.

Scheduler, migraties en configuratie

Drie plekken waar Laravel op Kubernetes vaak misgaat. Dit is de checklist die we per applicatie aflopen.

Scheduler

  • Precies één scheduler: een CronJob die elke minuut schedule:run start, of één Deployment met schedule:work
  • Bij een CronJob concurrencyPolicy op Forbid, zodat runs niet overlappen
  • withoutOverlapping en onOneServer op taken die nooit dubbel mogen draaien, met een cache-driver die atomic locks ondersteunt
  • Langlopende taken uit de scheduler halen en als job op de queue zetten

Migraties

  • php artisan migrate --force als Kubernetes Job vóór de nieuwe webpods, met Argo CD als PreSync-hook of in een vroege sync wave
  • --isolated toevoegen, zodat twee gelijktijdige runs elkaar niet in de weg zitten
  • Migraties backwards-compatibel schrijven: eerst kolom toevoegen, later pas oude kolom verwijderen
  • Nooit migreren vanuit het opstartscript van een webpod; bij tien replicas start je dan tien migraties

Configuratie en secrets

  • Instellingen via environment-variabelen uit een ConfigMap, gevoelige waarden uit Secrets
  • Secrets synchroniseren uit een vault of cloud secret manager met External Secrets Operator
  • config:cache bij het opstarten van de container draaien, niet tijdens de image-build, omdat de omgeving per cluster verschilt
  • route:cache, view:cache en event:cache kunnen wel in de build

Storage, sessies en logs

Een pod heeft geen vaste schijf. Uploads, gegenereerde PDF's en exports gaan daarom naar S3 of een S3-compatibele object storage via de s3-disk van Laravel. Bij een EU-provider kan dat de object storage van dezelfde aanbieder zijn, zodat data in de EU blijft. storage:link heb je dan niet meer nodig voor publieke bestanden; je serveert ze via de URL van de bucket of een CDN.

Sessies en cache gaan naar Redis of Valkey. De database-driver werkt ook, maar legt extra last op je database. Logs stuur je met LOG_CHANNEL op stderr naar de output van de container. Loki, of de logdienst van je cloudprovider, haalt ze daar op. Zo verdwijnen logs niet als een pod wordt vervangen.

Laravel heeft vanaf versie 11 een standaard health-route op /up. Die gebruiken we voor de readiness- en liveness-probes. Voor workers en de scheduler, die geen HTTP spreken, controleren we via metrics of ze werk oppakken. Een worker die draait maar niets verwerkt, is erger dan een worker die crasht.

Reverb voor websockets op Kubernetes

Laravel Reverb is de eigen websocketserver van Laravel. Op Kubernetes draait Reverb als aparte Deployment met een eigen Service en een eigen route in de Gateway API, bijvoorbeeld op ws.jouwdomein.nl. Zo schaal je websockets los van de webpods, en blokkeert een piek in verbindingen niet je gewone requests.

Met meerdere Reverb-pods moet een event dat op de ene pod binnenkomt ook de clients op de andere pods bereiken. Reverb lost dat op met horizontal scaling via Redis pub/sub, die je met REVERB_SCALING_ENABLED aanzet. Let ook op de time-outs van je Gateway-implementatie en load balancer. Websocketverbindingen blijven lang open, en een te korte idle-timeout verbreekt ze elke paar minuten.

Kubernetes, Laravel Cloud, Forge of Vapor?

Vier routes om Laravel in productie te draaien. Wij werken met alle vier en kiezen op basis van de applicatie.

RouteWat het isPast bijMinder geschikt als
Laravel ForgeServerbeheer op je eigen VPS of cloudserversEén of enkele apps met voorspelbaar verkeerJe veel workers dynamisch wilt schalen
Laravel CloudVolledig beheerd platform van Laravel zelfTeams die geen infrastructuur willen beherenJe eigen netwerk-, data- of complianceseisen hebt
Laravel VaporServerless op AWS LambdaSterk pieken, korte requestsJe langlopende processen of grote uploads hebt
KubernetesEigen cluster, managed of zelf beheerdMeerdere apps, veel achtergrondwerk, portabiliteitHet om één kleine app gaat zonder beheercapaciteit

Zo zetten we Laravel op Kubernetes

Van bestaande applicatie naar een cluster dat je kunt uitleggen, in zes stappen.

  1. 1

    Applicatiescan

    We controleren of de app stateless is: sessies, uploads, cache, logs en lokale bestanden. Uitkomst: wat er moet veranderen, en of Kubernetes de juiste route is.

  2. 2

    Image bouwen

    Multi-stage Dockerfile met composer install --no-dev, frontend-build en caches die in de build kunnen. Eén image voor alle rollen.

  3. 3

    Chart en omgevingen

    Helm-chart met web, workers, scheduler en Reverb als aparte onderdelen, en values per omgeving voor replicas, resources en hosts.

  4. 4

    Deploy-flow

    Argo CD met migraties als PreSync-Job, rolling updates en optioneel canary-releases met Argo Rollouts.

  5. 5

    Autoscaling en monitoring

    HPA voor web, KEDA voor workers, alerts op wachtrijlengte, mislukte jobs en responstijden.

  6. 6

    Livegang en beheer

    Overstap met terugvaloptie, daarna Laravel- en Kubernetes-upgrades, security-updates en kostenbewaking als doorlopend beheer.

Wat je opgeleverd krijgt

  • Applicatiescan met de aanpassingen die nodig zijn voor stateless draaien
  • Productie-image voor php-fpm of Octane met FrankenPHP
  • Helm-chart met web, Horizon of workers, scheduler en Reverb
  • KEDA ScaledObjects per queue met onderbouwde drempels
  • Migratie-Job en rollback-procedure in Argo CD
  • Secrets via External Secrets Operator in plaats van in Git
  • Dashboards en alerts voor queues, scheduler en responstijden
  • Draaiboek voor releases, upgrades en incidenten

Waarom Coding.nl voor Laravel op Kubernetes

We bouwen de Laravel-apps zelf

Koppelingen, middleware en klantportalen in Laravel zijn dagelijks werk. We weten welke jobs zwaar zijn en waar state blijft hangen.

Infrastructuur en code in één team

Dezelfde mensen schrijven de applicatie en het cluster. Geen overdracht tussen een bouwer en een hostingpartij die elkaar niet kennen.

Geen tussenlagen

Je spreekt direct met wie het werk doet. Vragen over een mislukte job of een trage deploy worden niet doorgegeven, maar opgelost.

Eerlijk over de route

Past Forge of Laravel Cloud beter bij je app, dan zeggen we dat. Kubernetes is een middel, geen doel.

Hyvä Silver PartnerHypernode Silver Partner

Veelgestelde vragen

Met één image dat in meerdere rollen draait: webpods met php-fpm of Octane, queue workers of Horizon, één scheduler en eventueel Reverb. Sessies en cache gaan naar Redis of Valkey, bestanden naar S3, logs naar stderr. Migraties draaien als Job voor de nieuwe pods starten.

Laravel-app naar Kubernetes, of toch niet?

Vraag een applicatiescan aan. We bekijken je app, queues, scheduler en huidige hosting, en vertellen je eerlijk of Kubernetes, Laravel Cloud of Forge het meeste oplevert.