Laravel 5-5 example is a tutorial application showing how to run and maintain laravel on kubernetes. It will allow you to quickly get an SSL-enabled wepage with a database, automatic seeding, migrations and sticky sessions working within 10 minutes which can then be tailored to your requirements.
Suggested improvements are welcome as are PRs. There isn't much available online for laravel + kubernetes together, however this presentation is probably the best material https://www.slideshare.net/WilliamStewart20/kubernetes-laravel-and-kubernetes and the following tutorial from Bitnami is useful also https://docs.bitnami.com/kubernetes/how-to/deploy-php-application-kubernetes-helm/.
This tutorial assumes you have access to a cloud-based cluster with kubernetes v1.9 or higher (e.g. GKE (tested on v1.9.3), ACS-engine or AWS - Google Kubernetes Engine's free trial is the easiest to setup and a 3*N1-Standard-1 is sufficient). A domain URL and ability to change DNS A records is also assumed. Nginx-ingress for tls termination is used.
Kubectl (brew install kubectl
>=1.9.3) and helm (brew install kubernetes-helm
>=2.8.2) is assumed to be installed and pointing at your cluster.
Everything in this tutorial is created in the laravel5 namespace. The namespace can be deleted at the end to tidy up. This is a useful approach for a branch-based environment setup.
git clone https://github.com/EamonKeane/larvel5-5-example.git
cd laravel5-5-example
- Create laravel5 namespace
kubectl create namespace laravel5
- Install the RBAC for helm to create a cluster admin role in the laravel5 namespace
kubectl apply -f kubernetes/kubernetes-yaml/rbac-tiller.yaml
- Install helm in the laravel5 namespace
helm init --tiller-namespace laravel5 --service-account tiller
- Set the tiller namespace environment variable (to prevent having to use
--tiller-namespace
in all command as it defaults tokube-system
)
export TILLER_NAMESPACE=laravel5
- check that helm and kubectl are on the right versions on the server and locally
helm version
Client: &version.Version{SemVer:"v2.8.2", GitCommit:"a80231648a1473929271764b920a8e346f6de844", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.8.2", GitCommit:"a80231648a1473929271764b920a8e346f6de844", GitTreeState:"clean"}
kubectl version
Client Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.3", GitCommit:"d2835416544f298c919e2ead3be3d0864b52323b", GitTreeState:"clean", BuildDate:"2018-02-09T21:51:54Z", GoVersion:"go1.9.4", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"9+", GitVersion:"v1.9.3-gke.0", GitCommit:"a7b719f7d3463eb5431cf8a3caf5d485827b4210", GitTreeState:"clean", BuildDate:"2018-02-16T18:26:01Z", GoVersion:"go1.9.2b4", Compiler:"gc", Platform:"linux/amd64"}
- Specify your domain:
MY_URL=laravel2.squareroute.io # change this to your domain
- Install nginx-ingress with the settings to create RBAC and externalTrafficPolicy to preserve source IPs in the logs. Nginx-ingress is also chosen because it allows for 'sticky sessions', something not yet possible with most other Load Balancers to my knowledge https://blog.shanelee.name/2017/10/16/kubernetes-ingress-and-sticky-sessions/.
helm install stable/nginx-ingress --wait --name nginx-ingress --namespace laravel5 --set rbac.create=true,controller.service.externalTrafficPolicy=Local
- Add your nginx-ingress IP address (this takes circa 2 minutes to populate
watch kubectl get svc --namespace laravel5
) as a DNS A record pointing to your laravel URL:
INGRESS_IP=$(kubectl get svc --namespace laravel5 --selector=app=nginx-ingress,component=controller -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}');echo ${INGRESS_IP}
- Verify that it has updated
dig $MY_URL
...
;; ANSWER SECTION:
laravel2.squareroute.io. 5 IN A 35.230.155.177
...
Install Cert-Manager for automatic SSL provisioning. https://github.com/kubernetes/charts/tree/master/stable/cert-manager
helm install stable/cert-manager --version 0.2.10 --name cert-manager --namespace laravel5 --set ingressShim.extraArgs='{--default-issuer-name=letsencrypt-prod,--default-issuer-kind=ClusterIssuer}','extraArgs={--v=4}'
kubectl apply -f kubernetes/kubernetes-yaml/acme-prod-cluster-issuer.yaml
- Install the mysql database with preconfigured password:
helm install stable/mysql --wait --timeout 400 --name mysql --namespace laravel5 --set mysqlRootPassword=imApMsfoDt,mysqlDatabase=homestead
- Make repository for the nginx and phpfpm dockerfiles (skip to the
Replace URL
section to use the images already built):
MY_PHP_REPO=quay.io/eamonkeane/laravel
MY_NGINX_REPO=quay.io/eamonkeane/laravel-nginx
- Build and push the docker images. This tutorial assumes the respositories are publicly accessible.
docker build . -t ${MY_PHP_REPO}:latest -f docker/php-fpm/Dockerfile; docker push ${MY_PHP_REPO}:latest
docker build . -t ${MY_NGINX_REPO}:latest -f docker/nginx/Dockerfile; docker push ${MY_NGINX_REPO}:latest
- Replace the URL in the .env with your url. Note the .env is kept in the helm folder for convenience to make the secret as part of this tutorial. If using this for production, make the secrets separately using
kubectl create secret generic ${SECRET_NAME} --from-file=${SECRET_FILE}
or use a tool to encrypt the secrets such as helm secrets: https://github.com/futuresimple/helm-secrets.
sed -i '' -e "s#laravel2.squareroute.io#${MY_URL}#g" kubernetes/helm/laravel5/laravel5-env.env
- Replace the ingress host with your url:
sed -i '' -e "s#laravel2.squareroute.io#${MY_URL}#g" kubernetes/helm/laravel5/values.yaml
- Install laravel5. This will seed the mysql database before creating the php containers using a pre-install job.
helm upgrade --install --wait --timeout 400 --set phpfpmImage.repository=${MY_PHP_REPO},nginxImage.repository=${MY_NGINX_REPO} --namespace laravel5 laravel5 kubernetes/helm/laravel5
- After approximately 2 minutes the website will be visible at
https://${MY_URL}
For changes to the repository, the same command can be run again. This time it will not perform a database seed, but will only perform the migrations before installing the new pods.
helm upgrade --install --wait --timeout 400 --set phpfpmImage.repository=${MY_PHP_REPO},nginxImage.repository=${MY_NGINX_REPO} --namespace laravel5 laravel5 kubernetes/helm/laravel5
This will delete everything created by the above tutorial but leave everything else in your cluster as it was. Cert-manager and nginx-ingress create resources outside the namespace so helm is used to delete them.
helm del --purge cert-manager
helm del --purge nginx-ingress
kubectl delete namespace laravel5
The helm chart contains the following features which are relevant to laravel:
-
Logs are tailed to standard out from the
/storage/logs/laravel.log
in keeping with Kubernetes best practice -
PHP-FPM is PID 1 in the php container
-
Migrations and seeding are performed as pre-install jobs and upgrades. It is possible to manage these outside of the helm chart (for example as part of a CI/CD workflow, to run a job before running
helm upgrade
). See the discussion here helm/helm#2243 and here https://blog.bigbinary.com/2017/06/16/managing-rails-tasks-such-as-db-migrate-and-db-seed-on-kuberenetes-while-performing-rolling-deployments.html -
Configuration files (nginx.conf and laravel-site.conf) are kept in the helm folder (helm can't access files outside this folder) and the configmap is updated each time the deployment is triggered.
-
Configmap changes trigger an upgrade to the deployment
-
Session affinity is preserved using the nginx 'sticky session' which adds a cookie to the header to store the session.
-
A second version of the chart is also in the helm folder
laravel5-2-services
. This maintains Nginx and PHP as separate deployments to allow for independetly scaling the number of replicas. This is suitable for deployments with a separate front end which purely speaks to an API and is not dependent on session affinity with php. This happens naturally when the nginx and php containers are in the same pod and the nginx container maintains sticky sessions.
As mentioned, improvements are welcome.