Building a micro-services application with PHP, Kubernetes and Skaffold

Ben Osborne
6 min readDec 6, 2020

Hello World! Excuse the pun… jump on board with me on this journey and we will learn how to setup and deploy a micro-services architecture using Kubernetes, Laravel Lumen and Skaffold.

Kubernetes

1. Project Setup

First off, let’s create an empty directory with git initialised, this will be our project working folder, I am running on Ubuntu so I would run something similar to the following command:

mkdir ~/projects/php-and-k8s/ && git init ~/projects/php-and-k8s

Next up let’s create some kind of directory structure to hold our services and Kubernetes manifests.

Go ahead and create the folder structure:

--
-- services/
-- manifests/

2. Creating a service

Create a Laravel Lumen application

Now lets create our shiny new micro-service inside the services folder, we will call this service hello-world, navigate to the services folder and run the following command:

composer create-project --prefer-dist laravel/lumen hello-world

This will create a new Laravel Lumen application inside the folder hello-world.

You will need composer installed on your machine, if you don’t have composer you can grab it here.

You will also need PHP installed, please refer to Laravel Lumen Server Requirements to install the necessary software.

3. Docker, Kubernetes and Skaffold

Dockerizing our Lumen Application

Kubernetes is a production-grade orchestration software that automates deployment, scaling and management of containerized applications and so, we will need to dockerize each of our micro-services to run on Kubernetes.

Create the following file at services/hello-world/Dockerfile with the contents below:

FROM php:8.0.0-apache

COPY . /var/www/html

# Update apt
RUN apt-get update
RUN apt-get install libcurl4 libcurl4-openssl-dev libzip-dev libpq-dev libpng-dev libfreetype6-dev libjpeg62-turbo-dev libxml2-dev -y

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Extensions
RUN docker-php-ext-install pcntl opcache soap zip pdo_pgsql pdo_mysql
RUN docker-php-ext-configure gd --with-freetype --with-jpeg && docker-php-ext-install gd
RUN pecl install -o -f redis && rm -rf /tmp/pear \
&& docker-php-ext-enable redis

COPY ./docker/php.ini /usr/local/etc/php/conf.d/

# Apache
RUN a2enmod rewrite
COPY ./docker/000-default.conf /etc/apache2/sites-available/000-default.conf

# Remove dev composer packages
RUN composer install --optimize-autoloader --no-dev
RUN composer dump-autoload

RUN chown -R www-data:www-data storage

EXPOSE 80

The Dockerfile above will use the php:8.0.0-apache image, if you prefer to use a lower version of PHP simply change the version number to one of the supported tags that can be found on Docker.

You may have noticed a few required files in the Dockerfile above, these can be retrieved from a GitHub repository that I have created for this guide.

Download the files from mrbenosborne/php-microservice-docker and place them inside a new folder called services/hello-world/docker/, the files required are listed below:

  • php.ini
  • 000-default.conf

It is good practice to add a .dockerignore file to our service so the build context is much smaller, copy the below to a new file at services/hello-world/.dockerignore

.env
vendor/

Writing our Kubernetes manifest

To deploy our application as a service on Kubernetes we will need to write a Kubernetes manifest file, we will create a simple Deployment and Service as described below.

Create the following file at services/hello-world/service.yaml this file will contain both our deployment and service spec.

apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world
spec:
replicas: 2
strategy:
rollingUpdate:
maxSurge: 2
maxUnavailable: 50%
selector:
matchLabels:
app: hello-world-app
template:
metadata:
labels:
app: hello-world-app
spec:
containers:
- name: hello-world
image: hello-world
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: hello-world
spec:
type: NodePort
ports:
- name: http
targetPort: 80
selector:
app: hello-world-app

I won’t go into much detail regarding every part of the above 2 manifest specs as it is outside the remit of this guide but for my next guide I will show you how to add resource limits, horizontal pod scaling and more to Kubernetes Deployment specs.

The above is a very simple Deployment followed by a Service that forwards traffic onto port 80.

The image is called hello-world, you may be wondering why it is not a full name and version etc, this is because Skaffold will take care of injecting the image name for us when it builds each image, the name hello-world is just a reference.

Skaffold

Skaffold is a piece of software that handles building, pushing and deploying your application. Head over to https://skaffold.dev/ and download the binary.

Once installed you will need Kubectl too, head over to https://kubernetes.io/docs/tasks/tools/install-kubectl/ and following the instructions depending on your OS.

Right! Once that’s all done let’s create our Skaffold configuration file, create a new file in the root of our project at ./skaffold.yaml.

apiVersion: skaffold/v2beta7
kind: Config
metadata:
name: php-and-k8s
build:
artifacts:
- image: hello-world
context: services/hello-world
deploy:
kubectl:
manifests:
- services/hello-world/service.yaml

Great, now that’s done we are almost at the stage where we can run the cluster locally and hit our service.

Notice the image name above, hello-world this is the reference I referred to earlier in the Kubernetes deployment spec, if you change it in the deployment spec then you’ll also need to update it in skaffold.yaml.

We need one more thing to create a local cluster for testing purposes, head over to https://minikube.sigs.k8s.io/docs/start/ and install Minikube dependant on your OS.

4. Bringing up your Cluster

Sweet! You’ve have made it this far now for the big turn on ;)

Open up a terminal and start a local cluster with minikube, the following command can be used:

minikube start

It may take a minute or two to bring up the cluster, you should see an output similar to the following:

minikube v1.13.1 on Ubuntu 20.04
✨ Automatically selected the docker driver
👍 Starting control plane node minikube in cluster minikube
🔥 Creating docker container (CPUs=2, Memory=3900MB) ...
🐳 Preparing Kubernetes v1.19.2 on Docker 19.03.8 ...
🔎 Verifying Kubernetes components...
🌟 Enabled addons: default-storageclass, storage-provisioner
🏄 Done! kubectl is now configured to use "minikube" by default

Once Minikube has started you can now run the following command in a new terminal window, this will rebuild each service when your files change.

skaffold dev

You should see output similar to the below:

Listing files to watch...
- hello-world
Generating tags...
- hello-world -> hello-world:latest
Some taggers failed. Rerun with -vdebug for errors.
Checking cache...
- hello-world: Not found. Building
Found [minikube] context, using local docker daemon.
Building [hello-world]...

Skaffold will now build each of our services, in our case just the one hello-world.

Once the above has finished you should see some output from Apache, run the following command to see our pods statuses.

kubectl get pods

You should see something similar to the below output:

NAME                           READY   STATUS    RESTARTS   AGE
hello-world-85fbbb7ffb-88wj5 1/1 Running 0 29s
hello-world-85fbbb7ffb-wk6gw 1/1 Running 0 29s

As you can see we have 2 pods running our hello world service, this is because we specified replicas: 2 in our Deployment spec earlier on.

Sending a request to the service

Now our service is up and running we can send a request to the service by using the kubectl port forward command, this will forward all traffic from our local host to the service running in our cluster.

Run the following command in a new terminal window, replacing hello-world-85fbbb7ffb-88wj5 with whatever one of your pods name is.

kubectl port-forward hello-world-85fbbb7ffb-88wj5 8000:80

Now you can open http://localhost:8000/ in your browser and you should be presented with:

Lumen (8.2.1) (Laravel Components ^8.0)

That’s it

You have now built and deployed successfully a Laravel Lumen application on Kubernetes using Skaffold and Minikube!

I hope you have enjoyed this guide, feel free to ask me any questions/feedback.

This guide only touches on the basic fundamentals but we can expand on this foundation and explore more into:

  • Adding an Ingress Controller
  • Adding a router such as Envoy or Traefik.
  • Setting up a workflow for continuous integration/continuous delivery.

You can find this complete project over at my GitHub, check out the repository https://github.com/mrbenosborne/php-laravel-k8s-skaffold.

Thanks for reading :)

--

--

Ben Osborne

Experienced Full-stack Developer | PHP | Kubernetes | Go | DevOps | AWS | Google Cloud