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:
We are using minikube for this guide.
Installation steps for minikube:
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!
Kubernetes is an orchestration platform for automating the process of deployment.
Now we go into some basic terminology:
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.
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.
Usually four kinds of files needed for setting up .The user can combine all files into one or run commands against all separate 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.
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.
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.
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
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
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 .
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.
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.
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