Running Laravel on Kubernetes

Lorenzo Aiello
The Startup
Published in
3 min readJan 7, 2020

Running Laravel on Kubernetes should be relatively straightforward for someone comfortable with both components (especially if you’ve been running Laravel in containers for some time).

For better or for worse, I made the leap from Laravel on VMs to Laravel in Containers and on Kubernetes at the same time, so there were some extra hoops to jump through.

Dockerizing Laravel

The first thing to do was to set up the base docker container for Laravel with all of the dependencies it would need to run. Below is the .dockerignore and Dockerfile I built that provides all of the required packages (plus ImageMagick) that Laravel needs to run successfully.

.git .idea .env node_modules storage/framework/cache/** storage/framework/sessions/** storage/framework/views/**FROM php:7.3-fpm WORKDIR /var/www RUN apt-get update && apt-get install -y libmcrypt-dev zip unzip git \ libmagickwand-dev --no-install-recommends \ && pecl install imagick \ && docker-php-ext-enable imagick \ && docker-php-ext-install pdo_mysql pcntl bcmath COPY . /var/www RUN chown -R www-data:www-data \ /var/www/storage \ /var/www/bootstrap/cache RUN mkdir -p /tmp/storage/bootstrap/cache \ && chmod 777 -R /tmp/storage/bootstrap/cache

The base container I am using is the PHP-FPM image, which isn’t a web server, but rather is the process manager generally seen as the defacto for modern deployments — this container will be coupled with an Nginx container which will be actual web serving component of this.

Dockerizing Nginx

The Nginx container is just the alpine container and a custom server configuration block.

The only “interesting” configuration here is the change from the default buffer configuration ( fastcgi_buffers and fastcgi_buffer_size) to account for Laravel's large header payloads.

For instructions on how to run TLS to the NGINX container, check out my End-to-End Encryption with Nginx and Kubernetes blog post.

FROM nginx:alpine ADD vhost.conf /etc/nginx/conf.d/default.confserver { listen 80; index index.php index.html; root /var/www/public; location / { try_files $uri /index.php?$args; } location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } }

Deploying on Kubernetes

Now that it’s containerized, built, and hopefully stored someplace, you can deploy it to Kubernetes. The following is an example Deployment, PodAutoscaler, and LoadBalancer for the service.

You should now have your “Hello World” application up and running.

--- apiVersion: apps/v1beta1 kind: Deployment metadata: name: web spec: replicas: 3 strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 1 minReadySeconds: 5 template: metadata: labels: app: web spec: containers: - name: laravel image: ##CONTAINER_IMAGE## ports: - containerPort: 9000 resources: requests: cpu: 250m limits: cpu: 500m - name: nginx image: nginx:alpine ports: - containerPort: 80 --- apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: web spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web minReplicas: 3 maxReplicas: 20 targetCPUUtilizationPercentage: 50 --- apiVersion: v1 kind: Service metadata: name: loadbalancer spec: type: LoadBalancer ports: - port: 80 selector: app: web

Database Migrations

To get database migrations running, you can use the following Kubernetes Job.

Note: Unfortunately, this implementation isn’t particularly ideal because it requires you to first delete the job as part of your CI/CD process before it can be redeployed. Please let me know if you have a better way to do it.

--- apiVersion: batch/v1 kind: Job metadata: name: artisan-migration spec: ttlSecondsAfterFinished: 300 backoffLimit: 3 template: spec: restartPolicy: Never containers: - name: laravel image: ##CONTAINER_IMAGE## envFrom: - configMapRef: name: laravel-envvars command: ["/usr/local/bin/php", "artisan", "migrate", "--force"]

Scheduled Tasks

Most applications will require some scheduled tasks to run, which can be easily set up with a Kubernetes Cronjob.

--- apiVersion: batch/v1beta1 kind: CronJob metadata: name: cronjob spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: laravel image: ##CONTAINER_IMAGE## command: ["/usr/local/bin/php", "artisan", "schedule:run"]

Laravel Horizon

When your use case needs event-driven processing, and you want to use Laravel Horizon, you can quickly get it up and running with just another Kubernetes Deployment.

--- apiVersion: apps/v1beta1 kind: Deployment metadata: name: horizon-worker spec: replicas: 3 template: metadata: labels: app: horizon-worker spec: containers: - name: laravel image: ##CONTAINER_IMAGE## command: ["/usr/local/bin/php", "artisan", "horizon lifecycle: preStop: exec: command: ["/usr/local/bin/php", "artisan", "horizon:terminate"]

Last, and certainly not least, I want to touch on Secrets management very briefly. It’s a vast topic that warrants it’s own blog post, but there are many ways to plug environment variables into the Kubernetes runtime. Still, I will emphasize that you should NEVER copy the production .env file into your container.

Originally published at https://lorenzo.aiello.family on January 7, 2020.

--

--

Lorenzo Aiello
The Startup

I am cloud engineer and developer who practices DevOps while helping to innovate solutions to new and existing challenges.