Laravel to MicroK8s
Kubernetes can be difficult to learn. In this post I'll help you to get your hands dirty by deploying a Laravel application and to a MicroK8s cluster. From there you've got a nice entrance into exploring the vast world of Kubernetes.
Introduction
I recently moved from Enschede to Groningen. This introduced a nice opportunity for me to speak at the Groningen PHP meetup about deploying a PHP application to a MicroK8s cluster. I thought it would be nice to also turn it into a blog post.
We will explore the process of deploying a Laravel application to MicroK8s, a lightweight Kubernetes distribution ideal for development environments. MicroK8s offers several advantages, including ease of setup, low resource consumption, and seamless integration with various Kubernetes tools. By the end of this guide, you’ll understand how to containerize your Laravel app using Docker, configure it for Kubernetes, and deploy it with Helm
Find the source code on GitLab.
Prerequisites
Before we start, ensure you have the following installed and configured:
MicroK8s:
- MicroK8s Installation Guide
- Ensure it’s up and running: microk8s status –wait-ready
Docker:
- Docker Installation Guide
- Verify installation:
docker --version
- For Linux users: Set up Docker Buildx to enable multi-platform builds:
- Install Buildx:
sudo apt install docker-buildx-plugin
- Verify Buildx:
docker buildx version
- Install Buildx:
- For Linux users: Set up Docker Buildx to enable multi-platform builds:
Creating our docker images
App with PHP-FastCGI Process Manager
Now we need to create the docker images. To run our PHP code we’ll use the PHP FastCGI (Common Gateway Interface) Process Manager better known as PHP-FPM. FPM is used to dramatically improve the performance of your web server. It does so by working as a process manager which spawns multiple processes that can handle your PHP requests concurrently. This leads to a significant reduction in latency for the user. Let’s have a look at its docker file
1
2
3
4
5
6
7
8
9
10
11
12
13
FROM php:8.3.8-fpm-alpine3.20
WORKDIR /var/www/app
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
COPY --chown=www-data:www-data . /var/www/app
RUN composer install
RUN rm /usr/local/bin/composer
EXPOSE 9000
NGINX Proxy
In order to expose our PHP-FPM instance to the internet we need something to funnel the network traffic into our application. In our case we’ll use NGINX which proxies the traffic using the fastcgi_pass
proxy method. Below is the configuration for our NGINX server:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 80;
server_name app.localhost;
root /var/www/app/public;
index index.html index.htm index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
Now that we have a basic configuration in place we can create a docker image that uses it:
1
2
3
4
5
6
7
FROM nginx:1.26.1-alpine3.19
COPY ./deployments/docker/proxy/default.conf /etc/nginx/conf.d/default.conf
COPY . /var/www/app
EXPOSE 80
Pushing the images to MicroK8s
Now we need to make our docker images available inside the MicroK8s cluster. We can do this by enabling the registry plugin. Doing so allows us to push our images to the registry localhost:32000
.
1
microk8s enable registry
Now that we have two docker files aand somewhere to push them to it’s time to bring the docker part together. To easily build multiple targets we can create a bake file which contains all the information of our targets.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
group "default" {
targets = [
"proxy",
"app"
]
}
variable "TAG" {
default = formatdate("YYYYMMDDhhmm", timestamp())
}
variable "REGISTRY" {
default = "localhost:32000"
}
function "tags" {
params = [name]
result = [
"${REGISTRY}/${name}:${TAG}",
"${REGISTRY}/${name}:latest"
]
}
target "app" {
context = "."
dockerfile = "deployments/docker/app/Dockerfile"
tags = tags("app")
}
target "proxy" {
context = "."
dockerfile = "deployments/docker/proxy/Dockerfile"
tags = tags("proxy")
}
Helm is a Last but not least execute the following command to build and push your image to the MicroK8s image registry:
1
docker buildx bake -f deployment/docker/bake.hcl --push
Helm templates
Now that the images are available to our MicroK8s cluster we can start tinkering. For the most common commands the Kubernetes docs contains a nice quick reference section with commonly used commands.
To make our lives easier we’ll make use of Helm. Helm is a templating language to manage bundling of kubernetes resources. Let’s create a separate helm template for both the app and our proxy layer:
1
helm create app && helm create proxy
Now we need to make some minor changes to the values file of app:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
image:
--- repository: nginx
+++ repository: localhost:32000/app
--- pullPolicy: Always
+++ pullPolicy: IfNotPresent
--- tag: ""
+++ tag: "latest"
...
service:
type: ClusterIP
--- port: 80
+++ port: 9000
...
In order for our cluster to accept traffic we’ll need to enable MicroK8s ingress:
1
microk8s enable ingress
And let’s also update our NGINX helm chart to point to the right images. We’ll also enable the ingress to allow traffic to enter our proxy layer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
image:
--- repository: nginx
+++ repository: localhost:32000/proxy
--- pullPolicy: IfNotPresent
+++ pullPolicy: Always
# Overrides the image tag whose default is the chart appVersion.
--- tag: ""
+++ tag: "latest"
...
ingress:
--- enabled: false
+++ enabled: true
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
--- - host: chart-example.local
+++ - host: app.localhost
paths:
- path: /
pathType: ImplementationSpecific
...
Deploying! 🎉
Now let’s deploy our application using the following commands in the app
and proxy
directories respectively:
1
2
3
# Execute from the project root
microk8s helm upgrade -i -f deployments/helm/app/values.yaml app deployments/helm/app
microk8s helm upgrade -i -f deployments/helm/proxy/values.yaml proxy deployments/helm/proxy
We can now check on our application by running:
1
2
mirok8s kubectl get pods app
mirok8s kubectl get pods proxy
And we can check the logs by running:
1
2
mirok8s kubectl logs app
mirok8s kubectl logs proxy
Finally navigating to app.localhost should show us our laravel default page.
Wrapping Up
We’ve taken an empty Laravel project and written Dockerfiles for it. We added a docker bake file to it to easily manage multiple images. This docker bake files pushes our images to the MicroK8s single node local kubernetes cluster. From there we’ve written helm files that allow us to easily deploy our application with all the necessary requirements. Finally, we deployed the application and checked if it was healthy. Now it’s time to tinker! If you run into any problems comment below, and I’ll do my best to help out!
In the next part I’ll add vault to our project and retrieve secrets from it.