Knowledge Base | Xelon AG

How to: Deploy a Laravel app using Kubernetes

Written by Michael Dudli | Aug 24, 2022 7:43:36 AM
Table of Contents

This article shows you how to deploy a Laravel app using Kubernetes. To demonstrate it we us a Kubernetes Cluster provided by the platform Xelon HQ. Kubernetes gives you the possibility to scale your app fast and you can also use it to separate your test and product environment to ensure a smooth migration path.

Before we get started, please install any of the tools below:

  • Docker
  • kubectl
  • minikube

We are using minikube for this guide.

Installation steps for minikube:

  1. https://minikube.sigs.k8s.io/docs/start/

After installing minikube, run it

minikube start

You don’t have a plattform to proceed your Laravel app deployment? Use Kubernetes within the Xelon HQ and start for free!

 

Pre-requisites

  • Familiarity with Dockerfile , laravel ,nginx and mysql tools required.

What is Kubernetes?

Kubernetes is an orchestration platform for automating the process of deployment.

Now we go into some basic terminology:

Difference in pod/container/nodes?

A node is the smallest unit and can be either physical or vm machine.

A node usually has multiple pods which in turn has also multiple containers. Pods are a group of one or more application containers and it consists of volumes with shared ip address and basic configuration information on how to execute it.

Last but not least, Containers are docker images running inside pods.

Any container image is a software package and also containing all configuration necessary to run the application. So basically, the code and any runtime it requires, application and system libraries, and default values for any essential settings.

Why do we need secrets in pods?

Secrets are used for storing user/pass of database / applications , which later can be referenced by other container apps which is important on our way to deploy a laravel app using Kubernetes.

Minimal yaml files needed

Usually four kinds of files needed for setting up .The user can combine all files into one or run commands against all separate files.

  1. Deployment yaml file
    This includes container/image details that are pulled while deployment. It also includes CPU resource, secrets that are configured , volume mounts location (where to mount the path in system)
  1. Service yaml file
    This file is needed to expose internal pods via some external port. Usually the port and target port are the same. The only port that needs to be configured is the “node port “ and it can be any ephemeral port.
  1. Persistent Volume
    This is used to allocate the persistent volume resource for MySQL from Kubernetes cluster
  1. ConfigMap
    This is used for storing configuration .In our case we will use it for nginx

Creating and combining yaml files

Note:All the terminal commands are tested on debian/ubuntu machines and should be run as a non root user.

Pull any laravel simple hello app from hub docker or create a custom Dockerfile for the same.

For our use case, we are using https://hub.docker.com/r/reasonebeat/laravel-realworld-example-app step by step for creating all above 4 yaml files and then combining all of them in one single deployment yaml file.

Create configmap yaml file as below

apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
labels:
app: laravel
data:
nginx.conf: |
events {
}
http {
server {
listen 80 default_server;
listen [::]:80 default_server;
# Set nginx to serve files from the shared volume!
root /var/www/html/vdc/public;
server_name _;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass localhost:9000;
}
}
}

The apiVersion refers to the Kubernetes API resource version that should be used.

“Kind” specifies the type which is config here. In short, metadata can be described as the data that helps uniquely identify the object, including a name string, UID, and an optional namespace. Labels can be considered as key value pairs for easy and fast querying and lookup later.

In this step, data is the key part. It contains the basic Nginx configuration when it is installed as standalone applications

The above config serves laravel index.php pages via  /var/www/html/app/public path.

It also proxies the non static page content to port 9000

The Nginx 80 port basically forwards traffic to 9000 laravel app ports.

Create PersistentVolume yaml file

This is used to allocate the persistent volume resource for MySql from the Kubernetes cluster.

Hence, the data is persistent and not dynamically allocated when the pod restarts. This means that the data is preserved at restarts.

apiVersion: v1
metadata:
kind: PersistentVolume
name: mysql-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

You can modify “storage” and “access modes” according to your needs.

Create a service yaml file

apiVersion: v1
kind: Service
metadata:
name: laravel
labels:
app: laravel
spec:
type: NodePort
ports:
- nodePort: 32223
port: 80
name: php-8000
- port: 9000
name: php-9000
- port: 3306
name: mysql
selector:
app: laravel

The above yaml file tells pods to expose ports of laravel as 9000 and Nginx as 80 and MySQL as 3306.

This is the main yaml file that actually exposes the services and via which apps can be reached outside the cluster.

Now,create deployment.yaml file similar to below

Next, combine all the above 4 yaml files into one single file called laravel-deployment.yaml

Complete yaml file

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: laravel-deployment
labels:
app: laravel
spec:
selector:
matchLabels:
app: laravel
replicas: 1
template:
metadata:
labels:
app: laravel
spec:
containers:
- name: laravel # php container with installed laravel app
image: reasonebeat/laravel-realworld-example-app
resources:
requests:
cpu: 100m
memory: 100Mi
env: # get environment variables from secrets
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: mysql-secret
key: dbName
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-secret
key: dbUserNameKey
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: dbPasswordKey
ports:
- containerPort: 9000
name: laravel-9000
- containerPort: 8000
name: laravel-8000
lifecycle:
postStart: # commands that will be executed after container was created
exec:
command: #generate key,
- "sh"
- "-c"
- >
mv /var/www/vdc/.env.example /var/www/vdc/.env;
php artisan key:generate;
sed -i
"s/DB_DATABASE=homestead/DB_DATABASE=
$MYSQL_DATABASE/g" /var/www/vdc/.env;
sed -i
"s/DB_USERNAME=homestead/DB_USERNAME=$MYSQL_USER/g"
/var/www/vdc/.env;
sed -i
"s/DB_PASSWORD=secret/DB_PASSWORD=$MYSQL_PASSWORD/g"
/var/www/vdc/.env;
cp -R /var/www/vdc/ /var/www/html/;
chown -R www-data:www-data /var/www/html
volumeMounts:
# mount volume for communication with nginx
- name: shared-files
mountPath: /var/www/html/vdc/
- image: mysql:latest # mysql container for keeping php data
name: mysql
env:
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: mysql-secret
key: dbName
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-secret
key: dbUserNameKey
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: dbPasswordKey
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: rootPasswordKey
ports:
- containerPort: 3306
name: mysql
volumeMounts: # volume for mysql data
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
- name: nginx # nginx web server for php app
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: shared-files
# mount volume for communication between nginx and php
mountPath: /var/www/html/vdc/
- name: nginx-config-volume # mount volume for nginx config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: mysql-persistent-storage # mysql storage
persistentVolumeClaim:
claimName: mysql-pv-volume
- name: shared-files
# volume for communication between nginx and php
emptyDir: {}
- name: nginx-config-volume
# volume for nginx config from configmap
configMap:
name: nginx-config

Explanation containerPort

containerPort are the ports via which the pods can be reached outside. Here 9000 is for laravel , 80 for nginx and 3306 for mysql.

As shown above, we are running 3 containers in a single pod.

The containers are the laravel , mysql and nginx image.

We are currently using  /var/www/app/  as path for laravel. This can be changed based on the laravel image being used. In our case, our docker file for laravel uses WORKDIR as “app”.

Moreover, we are configuring some postStart commands in the above yaml file which actually copies the files from shared storage to Kubernetes resources.

Replicas define how many pods should be running. This is generally more than 1 for load balancing the web apps. In our case, we will be using single.

Also we can set auto scaling option  using either horizontal or vertical auto scaling.

This can be configured based on resource metrics such as CPU percentage, ram consumption, traffic, etc.

Once the limit is crossed, for example CPU percentage, a new pod is automatically created .

Example format for hpa yaml file

kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta1
metadata:
name: testingHPA
spec:
maxReplicas: 3
minReplicas: 5
scaleTargetRef:
apiVersion: app/v1
kind: Deployment
name: laravel
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 85

The scaling metrics above is CPU percentage which is 85%. Once that limit is crossed, Kubernetes will be creating a new pod replica. Please note that the max replica or copies that can exist are 5.

So we can have 5 copies of say laravel app.

We have also used MySQL-secret for storing user/pass values.

The command to add secrets in Kubernetes cluster is as follows:

kubectl create secret generic mysql-secret

--from-literal=dbUserNameKey=homestead --from-literal=dbPasswordKey="secret"

--from-literal=dbName=homestead --from-literal=password=root

--from-literal=rootPasswordKey="ASdf456+" 

It creates key value pairs of database user/pass.

dbUserNameKey implies database username whose value is “homestead

This works similarly for other key values.

 

Commands:

Apply/create deployment to cluster

kubectl apply -f laravel-deployment.yaml

Checking deployment and pods status

kubectl  get  deployment

NAME                 READY   UP-TO-DATE   AVAILABLE   AGE

laravel-deployment   1/1     1            1           66m

kubectl get pods

NAME                                 READY   STATUS    RESTARTS   AGE

laravel-deployment-7c6bd96bf-6wlfn   3/3     Running   0          66m

The random number “7c6bd96bf-6wlfn” is dynamically assigned by the cluster.

It might take a while to get status 3/3. The “3” number refers to the docker containers which are MySQL  , Nginx, and laravel images.

You can check the run time kubernetes logs and error by running:

kubectl  describe pod laravel-deployment-7c6bd96bf-6wlfn

Now run 

kubectl  get services

Output

NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE     

kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP                   25h              

laravel      NodePort    10.101.219.132   <none>       80:32223/TCP,9000:32208/TCP,3306:30047/TCP   3h50m

And finally (get the name of the service and use below)

minikube service laravel

This will open the browser  and will show all the external ports that are mapped to individual pods:

In the example above, target port “php-8000/80” is the one where the end user will be routed to nginx which will further proxy traffic to laravel app running on port 9000.

You can now open the index.php page (the IP address and port might be different in your case)

http://192.168.39.65:32223/index.php on browser.

FAQ/Troubleshooting

1. If you get an error like the one below

Error: couldn’t find key password in Secret default/mysql-secret

Solution

This means the secret key is missing the key called “password”.

Please run the Kubernetes secret command and configure the missing key.

If you have any questions about how to deploy a Laravel app using Kubernetes please feel free to contact us