This tutorial shows you how to set up a single-replica WordPress deployment and a single-replica MySQL database on your cluster. Both applications use PersistentVolumes (PV) and PersistentVolumeClaims (PVC) to store data.
A PersistentVolume is a representation of storage in the cluster that has been manually provisioned by an administrator, or dynamically provisioned by Kubernetes using a StorageClass. A PersistentVolumeClaim is a request for storage by a user that can be fulfilled by a PersistentVolume. PersistentVolumes and PersistentVolumeClaims are independent from Pod lifecycles and preserve data through restarting, rescheduling, and even deleting Pods. Both applications will use persistent disks as storage to back the Persistent Volumes.
WordPress is a blogging tool which uses MySQL as its database to store the blog articles and the local filesystem to store assets, such as pictures in a blog post, or extensions. This tutorial uses the official MySQL and WordPress container images from Docker Hub.
In general, a container’s root filesystem is not suitable to store persistent data. The containers you run on GKE are typically disposable entities, and the cluster manager may delete, evict, or reschedule any containers that become unavailable due to node failure or other causes. In such an occurrence, all data saved to a container’s root filesystem is lost.
Using persistent volumes backed by persistent disks lets you store data for WordPress and MySQL applications outside the containers themselves. This way, even if the containers are deleted, their data persists.
Both MySQL and Wordpress require a PersistentVolume to store data. For this tutorial, you will use the default storage class which dynamically creates persistent disks and create two PersistentVolumeClaims - one for each Deployment.
Next, you create two Deployments: one for MySQL and one for WordPress. Both Deployments run only a single replica of each Pod.
Next, you create two Services: one for the WordPress container to communicate with the MySQL container, another one to expose WordPress Deployment to the internet on an external IP address with a load balancer.
Before you beginTake the following steps to enable the Kubernetes Engine API:
- Visit the Kubernetes Engine page in the Google Cloud Platform Console.
- Create or select a project.
- Wait for the API and related services to be enabled. This can take several minutes.
Make sure that billing is enabled for your project.
Install the following command-line tools used in this tutorial:
gcloudis used to create and delete Kubernetes Engine clusters.
gcloudis included in the Google Cloud SDK.
kubectlis used to manage Kubernetes, the cluster orchestration system used by Kubernetes Engine. You can install
gcloud components install kubectl
Set defaults for the
To save time typing your project ID and Compute Engine zone
options in the
gcloud command-line tool
gcloudcommand-line tool, you can set the defaults:
gcloud config set project [PROJECT_ID] gcloud config set compute/zone us-central1-b
Download application manifests
For this tutorial, you create the necessary Kubernetes objects using manifest files in YAML format.
git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples cd kubernetes-engine-samples/wordpress-persistent-disks
(If you do not have
git installed in your system, you can download them from
the links below.)
mysql.yaml: The MySQL deployment configuration file.
mysql-service.yaml: The MySQL service configuration file.
mysql-volumeclaim.yaml: The MySQL PersistentVolumeClaim configuration file.
wordpress.yaml: The WordPress deployment configuration file.
wordpress-service.yaml: The WordPress service configuration file.
wordpress-volumeclaim.yaml: The WordPress PersistentVolumeClaim configuration file.
Step 1: Create a GKE cluster
The first step is to create a GKE cluster to host your
WordPress and MySQL application containers. The following command creates a
persistent-disk-tutorial with 3 nodes:
gcloud container clusters create persistent-disk-tutorial --num-nodes=3
Step 2: Create your PersistentVolumes and PersistentVolumeClaims
In order to create the storage required for MySQL and Wordpress the first step is to create PersistentVolumeClaims. When a PersistentVolumeClaim is created, if there is no existing PersistentVolume for it to bind to, a new PersistentVolume is dynamically provisioned based on the StorageClass configuration.
GKE has a default StorageClass installed that will allow you to dynamically provision PersistentVolumes backed by persistent disks. When a StorageClass is not specified in the PersistentVolumeClaim, the cluster's default StorageClass is used instead.
You will use the
wordpress-volumeclaim.yaml files to create
the PersistentVolumeClaims required for the deployments. The
file looks like:
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: mysql-volumeclaim spec: accessModes: - ReadWriteOnce resources: requests: storage: 200Gi
wordpress-volumeclaim.yaml file looks like:
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: wordpress-volumeclaim spec: accessModes: - ReadWriteOnce resources: requests: storage: 200Gi
These manifests describe PersistentVolumeClaims that each request 200Gi of storage formatted with a filesystem. Note that a StorageClass has not been specified so these PersistentVolumeClaims will use the default StorageClass that will provision PersistentVolumes backed by persistent disks.
To deploy these manifests, run:
kubectl apply -f mysql-volumeclaim.yaml kubectl apply -f wordpress-volumeclaim.yaml
Run the following command to check to see if the claims get bound. It might take a couple seconds to provision the PersistentVolumes backed by persistent disk and bind them to your PersistentVolumeClaims.
kubectl get pvcOutput:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE mysql-volumeclaim Bound pvc-864bf3d6-3c44-11e8-80a6-42010a800002 200G RWO standard 11s wordpress-volumeclaim Bound pvc-89d49350-3c44-11e8-80a6-42010a800002 200G RWO standard 5s
Step 3: Set up MySQL
First step to deploy MySQL is to create a Kubernetes Secret to store the
password for the database. To create a Secret named
mysql, run the following command (and replace
YOUR_PASSWORD with a passphrase of your choice):
kubectl create secret generic mysql --from-literal=password=YOUR_PASSWORD
Then you will use the
mysql.yaml manifest file to deploy the single instance
MySQL application running on port
mysql.yaml file looks like:
apiVersion: apps/v1 kind: Deployment metadata: name: mysql labels: app: mysql spec: replicas: 1 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - image: mysql:5.6 name: mysql env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql key: password ports: - containerPort: 3306 name: mysql volumeMounts: - name: mysql-persistent-storage mountPath: /var/lib/mysql volumes: - name: mysql-persistent-storage persistentVolumeClaim: claimName: mysql-volumeclaim
This manifest describes a Deployment with a single instance MySQL Pod which will
MYSQL_ROOT_PASSWORD environment variable whose value is set from the
secret created. The
mysql container will use the PersistentVolumeClaim and mount
the persistent disk at
/var/lib/mysql inside the container.
To deploy this manifest file, run:
kubectl create -f mysql.yaml
Check to see if the Pod is running. It might take a few minutes for the Pod to
Running status as attaching the persistent disk to the compute
node takes a while:
kubectl get pod -l app=mysqlOutput:
NAME READY STATUS RESTARTS AGE mysql-259040-flmqh 1/1 Running 0 3m
Create MySQL service
The next step is to create a Service to expose the
MySQL container and make it accessible from the
wordpress container you are
going to create.
You will use the Service manifest defined in
mysql-service.yaml, which looks like:
apiVersion: v1 kind: Service metadata: name: mysql labels: app: mysql spec: type: ClusterIP ports: - port: 3306 selector: app: mysql
This manifest describes a Service that creates a Cluster
IP on port 3306 for the Pods matching the label
mysql container deployed in the previous step has this label. In
this case, you use
type:ClusterIP for the Service, as this value makes the
Service reachable only from within the cluster.
The Cluster IP created routes traffic to the MySQL container from all compute
nodes in the cluster and is not accessible to clients outside the cluster. Once
the Service is created, the
wordpress container can use the DNS name of the
mysql container to communicate, even though they are not in the same compute
To deploy this manifest file, run:
kubectl create -f mysql-service.yaml
Check to see if the Service is created:
kubectl get service mysqlOutput:
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE mysql 10.51.240.175 <none> 3306/TCP 4m
You have completed setting up the database for your new WordPress blog!
Step 4: Set up WordPress
The next step is to deploy your WordPress container to the container cluster.
You will use the
wordpress.yaml manifest file to deploy a single instance
wordpress.yaml looks like:
apiVersion: apps/v1 kind: Deployment metadata: name: wordpress labels: app: wordpress spec: replicas: 1 selector: matchLabels: app: wordpress template: metadata: labels: app: wordpress spec: containers: - image: wordpress name: wordpress env: - name: WORDPRESS_DB_HOST value: mysql:3306 - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: name: mysql key: password ports: - containerPort: 80 name: wordpress volumeMounts: - name: wordpress-persistent-storage mountPath: /var/www/html volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: wordpress-volumeclaim
This manifest describes a Deployment with a single instance WordPress Pod. This
container reads the
WORDPRESS_DB_PASSWORD environment variable from the database
password Secret you created earlier.
This manifest also configures the WordPress container to communicate MySQL with
the host address
mysql:3306. This value is set on the
environment variable. We can refer to the database as
mysql, because of
Kubernetes DNS allows Pods to communicate a Service by
To deploy this manifest file, run:
kubectl create -f wordpress.yaml
Check to see if the Pod is running. It might take a few minutes for the Pod to transition to Running status as attaching the persistent disk to the compute node takes a while:
kubectl get pod -l app=wordpressOutput:
NAME READY STATUS RESTARTS AGE wordpress-387015-02xxb 1/1 Running 0 9h
Expose WordPress Service
In the previous step, you have deployed a WordPress container which is not currently accessible from outside your cluster as it does not have an external IP address.
wordpress-service.yaml file contains the manifest for this Service looks
apiVersion: v1 kind: Service metadata: labels: app: wordpress name: wordpress spec: type: LoadBalancer ports: - port: 80 targetPort: 80 protocol: TCP selector: app: wordpress
To deploy this manifest file, run:
kubectl create -f wordpress-service.yaml
Deploying this manifest will create a load balancer, which may take a few minutes. Run the following command to find out the external IP address of your blog:
kubectl get svc -l app=wordpressOutput:
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE wordpress 10.51.243.233 203.0.113.3 80:32418/TCP 1m
In the output above, the
EXTERNAL-IP column will show the public IP address
created for your blog. Save this IP address for the next step.
You now have completed deploying and exposing your WordPress blog!
Step 5: Visit your new WordPress blog
After finding out the IP address of your blog, point your browser to this IP address and you will see the WordPress installation screen as follows:
Once you complete the WordPress setup, point your browser to the IP address of the WordPress app again to visit your blog. Everything is working as expected.
Step 6: (Optional) Test data persistence on failure
With PersistentVolumes, your data lives outside the application container. When your container becomes unavailable and gets rescheduled onto another compute instance by Kubernetes, GKE will make the PersistentVolume available on the instance that started running the Pod.
kubectl get pods -o=wide
NAME ... IP NODE mysql-259040-flmqh ... 10.48.2.8 gke-persistent-disk-tutorial-default-pool-fe4fe9af-wcb4 wordpress-387483-02xxb ... 10.48.2.7 gke-persistent-disk-tutorial-default-pool-fe4fe9af-wcb4
Now, delete the
mysql pod by running:
kubectl delete pod -l app=mysql
mysql Pod is deleted, the Deployment controller will notice that the
Pod is missing and will recreate the Pod. It is likely that the new
Pod will start on a different node than it was running before.
Run the following command again to observe that the
mysql Pod is scheduled
onto a different compute instance than before (if not, you can delete the Pod
again until it is running somewhere different).
kubectl get pods -o=wideOutput:
NAME ... IP NODE mysql-259040-flmqh ... 10.48.2.8 gke-persistent-disk-tutorial-default-pool-fe4fe9af-vg56 wordpress-387483-02xxb ... 10.48.2.7 gke-persistent-disk-tutorial-default-pool-fe4fe9af-wcb4
Visit your blog again to see that the website is functioning properly and the data is persisted even though you deleted your Pod and the Pod is scheduled to another instance in your cluster.
Step 7: Updating application images
It’s important to keep deployed software up to date. For example, vulnerabilities may be reported in WordPress that require an update. To update the WordPress container image, find the newest image version on Docker Hub and update the image: value in the wordpress.yaml file. To apply the update, run:
kubectl apply -f wordpress.yaml
To avoid incurring charges to your Google Cloud Platform account for the resources used in this tutorial:
After completing this tutorial, follow these steps to remove the following resources to prevent unwanted charges incurring on your account:
- Delete the Service: This step will deallocate the Cloud Load Balancer
created for your
kubectl delete service wordpress
Wait for the Load Balancer provisioned for the
wordpressService to be deleted: The load balancer is deleted asynchronously in the background when you run kubectl delete. Wait until the load balancer is deleted by watching the output of the following command:
gcloud compute forwarding-rules list
Delete the PersistentVolumeClaims for MySQL and WordPress: This will automatically trigger deletion of the dynamically provisioned PersistentVolumes and Persistent Disks associated with these PersistentVolumeClaims.
kubectl delete pvc wordpress-volumeclaim kubectl delete pvc mysql-volumeclaim
Delete the container cluster: This step will delete the resources that make up the container cluster, such as the compute instances, disks and network resources.
gcloud container clusters delete persistent-disk-tutorial