This tutorial walks you through how to deploy Drupal on Kubernetes a minimal set of tools.
This tutorial includes a local Kubernetes cluster.
In this step we will provision it.
Using Docker Compose, "up" the cluster.
docker-compose up -d
Next we will configure our command line so the Kubernetes command line (kubectl
) can connect to this cluster.
kubectl
uses the $KUBECONFIG environment variable to discover the file which contains cluster connection configuration.
This configuration is automatically created by the K3s service running inside the Docker Compose stack.
We can now set the environment variable.
export KUBECONFIG=$(pwd)/.kube/config
Let's verify you can connect to the cluster.
kubectl get pods --all-namespaces
The above command should yield a result similiar to the following:
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system local-path-provisioner-58fb86bdfd-wxqhg 1/1 Running 0 35s
kube-system metrics-server-6d684c7b5-m7hss 1/1 Running 0 35s
kube-system coredns-d798c9dd-plr6c 0/1 Running 0 35s
The Docker Compose stack also comes with a Docker Registry.
Now we need to ensure we have a consistent DNS entry inside the cluster and on your local command line.
Add the following record to you local /etc/hosts
file.
127.0.0.1 registry.drupal-tutorial.svc.cluster.local
In the next step we will package the Drupal application and push it to this registry.
export REGISTRY=registry.drupal-tutorial.svc.cluster.local:5000/project1
# This variable is generally derived from the command: git describe --tags --always
export VERSION=0.0.1
# Builds the Drupal application using Composer.
docker build -t ${REGISTRY}/php:${VERSION} -f dockerfiles/php.dockerfile .
# Builds the Nginx container by copying the application from the PHP image.
docker build -t ${REGISTRY}/nginx:${VERSION} -f dockerfiles/nginx.dockerfile --build-arg PHP_IMAGE=${REGISTRY}/php:${VERSION} .
# Push the images to the registry.
docker push ${REGISTRY}/php:${VERSION}
docker push ${REGISTRY}/nginx:${VERSION}
In the next step we will be deploying the Drupal application.
export NAMESPACE=project1-dev
# OPTIONAL - Create the namespace if it does not exist.
kubectl create ns $NAMESPACE
# Update the images which will be deployed.
# https://kubectl.docs.kubernetes.io/pages/app_management/container_images.html
kustomize edit set image nginx=$REGISTRY/nginx:$VERSION
kustomize edit set image php=$REGISTRY/php:$VERSION
# Rollout the new version of the application.
# https://kubectl.docs.kubernetes.io/pages/app_management/apply.html
kubectl -n $NAMESPACE apply -k .
We can now verify the application by running the following command:
$ kubectl -n $NAMESPACE get -k .
NAME DATA AGE
configmap/drupal 0 8m26s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/drupal ClusterIP 10.43.106.13 <none> 8080/TCP 8m26s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/drupal 3/3 3 3 8m26s
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
cronjob.batch/drupal-cron @hourly False 0 <none> 8m26s
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
horizontalpodautoscaler.autoscaling/drupal Deployment/drupal 1%/90%, 0%/300% 2 4 3 8m26s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/drupal-private Bound pvc-9e2725d9-7936-47dd-9ee8-8b773440f1a6 20Gi RWO local-path 8m26s
persistentvolumeclaim/drupal-public Bound pvc-11828727-0ca8-4941-beb4-3fd9fadeea5a 20Gi RWO local-path 8m26s
persistentvolumeclaim/drupal-temporary Bound pvc-d8663c34-81f4-4c52-bc7c-c2eb4e1030eb 20Gi RWO local-path 8m26s
The deployment will be complete once:
- The Deployment has 1/1 Ready replicas.
- The PersistentVolumeClaims have becomes Bound.
Now we configure the application to talk to backend resources.
In this section we will be configuring our application to connect to the MySQL instance already setup on the cluster.
We have the MySQL instace separate to emulate a production setup where the MySQL database is hosted on a managed service.
The Twelve-Factor App manifesto recommends applications store config in the environment, not the application.
We also don't want to use environment variables for security reasons.
To achieve this for Drupal site we:
- Mount a file with our database credentials:
/etc/drupal/config.json
- Load it using a helper function (see settings.k8s.php).
This is all achieved by a Kubernetes ConfigMap, which we are going to PATCH with our config.json
file.
kubectl -n $NAMESPACE patch configmap -p '{"data": {"config.json": "{\"mysql.database\": \"drupal\", \"mysql.username\": \"root\", \"mysql.password\": \"password\", \"mysql.hostname\": \"nonprod.mysql\"}"}}' drupal
To verify this deployment we can install the site and inspect it via our local bowser.
# Install the site using Drush.
kubectl -n $NAMESPACE exec -it -c php deployment/drupal -- vendor/bin/drush site-install standard
# Setup a tunnel to the Drupal application running on the cluster.
kubectl -n $NAMESPACE port-forward deployment/drupal 8080:8080
You can now inspect the site on:
All the above can be automated via a CI/CD pipeline.
The following is a snippet which can be used as part of a deployment pipeline.
export NAMESPACE=project1-dev
export REGISTRY=registry.drupal-tutorial.svc.cluster.local:5000/project1
export VERSION=$(git describe --tags --always)
kustomize edit set image nginx=$REGISTRY:$VERSION
kustomize edit set image php=$REGISTRY:$VERSION
kubectl -n $NAMESPACE apply -k .
kubectl -n $NAMESPACE rollout status -w deployment/drupal
kubectl -n $NAMESPACE exec deployment/drupal -- drush cr