WordPress is the mainstream platform for editing and publishing web content. In this tutorial, I'll walk through how to use Kubernetes to build a high-availability (HA) WordPress deployment.
WordPress consists of two main components: the WordPress PHP server and the database used to store user information, posts, and website data. We need to make both components of the entire application highly available and fault-tolerant at the same time.
Running a highly available service can be difficult when hardware and addresses change: very difficult to maintain. With Kubernetes and its powerful networking components, we can deploy highly available WordPress sites and MySQL databases without (almost) having to enter a single IP address.
In this tutorial, I will show you how to create storage classes, services, configuration maps and collections in Kubernetes, how to run highly available MySQL, and how to mount a highly available WordPress cluster to a database service . If you don’t have a Kubernetes cluster yet, you can easily find and start them on Amazon, Google, or Azure, or use Rancher Kubernetes Engine (RKE)
Architecture Overview# on any server
## Now let me briefly introduce the technology we are going to use and its capabilities: Storage of WordPress application files: NFS storage with GCE persistent disk backup
Database cluster: with for parity Tested xtrabackup for MySQL
Application level: WordPress DockerHub image mounted to NFS storage
Load balancing and networking: Kubernetes based load balancer and service network
# storage-class.yamlkind: StorageClassapiVersion: storage.k8s.io/v1metadata: name: slowprovisioner: kubernetes.io/gce-pdparameters: type: pd-standard zone: us-central1-aCreate a class and deploy it using the command:
$ kubectl create -f storage-class.yaml.
mysql-configmap.yaml to handle the configuration, as follows:
# mysql-configmap.yamlapiVersion: v1kind: ConfigMapmetadata: name: mysql labels: app: mysqldata: master.cnf: | # Apply this config only on the master. [mysqld] log-bin skip-host-cache skip-name-resolve slave.cnf: | # Apply this config only on slaves. [mysqld] skip-host-cache skip-name-resolveCreate
configmap and use the directive:
$ kubectl create -f mysql-configmap.yaml to deploy it.
mysql-services.yaml. This also starts the service load balancer for the MySQL service.
# mysql-services.yaml# Headless service for stable DNS entries of StatefulSet members.apiVersion: v1kind: Servicemetadata: name: mysql labels: app: mysqlspec: ports: - name: mysql port: 3306 clusterIP: None selector: app: mysqlWith this service declaration, we have laid the foundation for implementing a multi-write, multi-read MySQL instance cluster. This configuration is necessary as each WordPress instance may write to the database, so each node must be ready to read and write. Execute the command
$ kubectl create -f mysql-services.yaml to create the above service.
configmap, and set some variables in the MySQL configuration file , and we configured a network layer service that is responsible for load balancing requests to the MySQL server. The above is just a framework for preparing stateful sets. We will continue to explore where the MySQL server actually runs.
配置有状态集的MySQL
本节中,我们将编写一个YAML配置文件应用于使用了状态集的MySQL实例。
我们先定义我们的状态集:
1, 创建三个pods并将它们注册到MySQL服务上。
2, 按照下列模版定义每个pod:
♢ 为主机MySQL服务器创建初始化容器,命名为init-mysql
.
♢ 给这个容器使用mysql:5.7镜像
♢ 运行一个bash脚本来启动xtrabackup
♢ 为配置文件和configmap
挂载两个新卷
3, 为主机MySQL服务器创建初始化容器,命名为clone-mysql
.
♢ 为该容器使用Google Cloud Registry的xtrabackup:1.0
镜像
♢ 运行bash脚本来克隆上一个同级的现有xtrabackups
♢ 为数据和配置文件挂在两个新卷
♢ 该容器有效地托管克隆的数据,便于新的附属容器可以获取它
4, 为附属MySQL服务器创建基本容器
♢ 创建一个MySQL附属容器,配置它连接到MySQL主机
♢ 创建附属xtrabackup
容器,配置它连接到xtrabackup主机
5, 创建一个卷声明模板来描述每个卷,每个卷是一个10GB的持久磁盘
下面的配置文件定义了MySQL集群的主节点和附属节点的行为,提供了运行附属客户端的bash配置,并确保在克隆之前主节点能够正常运行。附属节点和主节点分别获得他们自己的10GB卷,这是他们在我们之前定义的持久卷存储类中请求的。
apiVersion: apps/v1beta1kind: StatefulSetmetadata: name: mysqlspec: selector: matchLabels: app: mysql serviceName: mysql replicas: 3 template: metadata: labels: app: mysql spec: initContainers: - name: init-mysql image: mysql:5.7 command: - bash - "-c" - | set -ex # Generate mysql server-id from pod ordinal index. [[ `hostname` =~ -([0-9]+)$ ]] || exit 1 ordinal=${BASH_REMATCH[1]} echo [mysqld] > /mnt/conf.d/server-id.cnf # Add an offset to avoid reserved server-id=0 value. echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf # Copy appropriate conf.d files from config-map to emptyDir. if [[ $ordinal -eq 0 ]]; then cp /mnt/config-map/master.cnf /mnt/conf.d/ else cp /mnt/config-map/slave.cnf /mnt/conf.d/ fi volumeMounts: - name: conf mountPath: /mnt/conf.d - name: config-map mountPath: /mnt/config-map - name: clone-mysql image: gcr.io/google-samples/xtrabackup:1.0 command: - bash - "-c" - | set -ex # Skip the clone if data already exists. [[ -d /var/lib/mysql/mysql ]] && exit 0 # Skip the clone on master (ordinal index 0). [[ `hostname` =~ -([0-9]+)$ ]] || exit 1 ordinal=${BASH_REMATCH[1]} [[ $ordinal -eq 0 ]] && exit 0 # Clone data from previous peer. ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql # Prepare the backup. xtrabackup --prepare --target-dir=/var/lib/mysql volumeMounts: - name: data mountPath: /var/lib/mysql subPath: mysql - name: conf mountPath: /etc/mysql/conf.d containers: - name: mysql image: mysql:5.7 env: - name: MYSQL_ALLOW_EMPTY_PASSWORD value: "1" ports: - name: mysql containerPort: 3306 volumeMounts: - name: data mountPath: /var/lib/mysql subPath: mysql - name: conf mountPath: /etc/mysql/conf.d resources: requests: cpu: 500m memory: 1Gi livenessProbe: exec: command: ["mysqladmin", "ping"] initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 readinessProbe: exec: # Check we can execute queries over TCP (skip-networking is off). command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"] initialDelaySeconds: 5 periodSeconds: 2 timeoutSeconds: 1 - name: xtrabackup image: gcr.io/google-samples/xtrabackup:1.0 ports: - name: xtrabackup containerPort: 3307 command: - bash - "-c" - | set -ex cd /var/lib/mysql # Determine binlog position of cloned data, if any. if [[ -f xtrabackup_slave_info ]]; then # XtraBackup already generated a partial "CHANGE MASTER TO" query # because we're cloning from an existing slave. mv xtrabackup_slave_info change_master_to.sql.in # Ignore xtrabackup_binlog_info in this case (it's useless). rm -f xtrabackup_binlog_info elif [[ -f xtrabackup_binlog_info ]]; then # We're cloning directly from master. Parse binlog position. [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1 rm xtrabackup_binlog_info echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\ MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in fi # Check if we need to complete a clone by starting replication. if [[ -f change_master_to.sql.in ]]; then echo "Waiting for mysqld to be ready (accepting connections)" until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done echo "Initializing replication from clone position" # In case of container restart, attempt this at-most-once. mv change_master_to.sql.in change_master_to.sql.orig mysql -h 127.0.0.1 <p>将该文件存为<code>mysql-statefulset.yaml</code>,输入<code>kubectl="" create="" -f="" mysql-statefulset.yaml</code>并让kubernetes部署你的数据库。<br>现在当你调用<code>$="" kubectl="" get="" pods</code>,你应该看到3个pods启动或者准备好,其中每个pod上都有两个容器。主节点pod表示为mysql-0,而附属的pods为<code>mysql-1</code>和<code>mysql-2</code>.让pods执行几分钟来确保<code>xtrabackup</code>服务在pod之间正确同步,然后进行wordpress的部署。<br>您可以检查单个容器的日志来确认没有错误消息抛出。 查看日志的命令为<code>$="" logs="" <container_name></container_name></code></p><p>主节点<code>xtrabackup</code>容器应显示来自附属的两个连接,并且日志中不应该出现任何错误。</p><h2>部署高可用的WordPress</h2><p>整个过程的最后一步是将我们的WordPress pods部署到集群上。为此我们希望为WordPress的服务和部署进行定义。</p><p>为了让WordPress实现高可用,我们希望每个容器运行时都是完全可替换的,这意味着我们可以终止一个,启动另一个而不需要对数据或服务可用性进行修改。我们也希望能够容忍至少一个容器的失误,有一个冗余的容器负责处理slack。</p><p>WordPress将重要的站点相关数据存储在应用程序目录<code>/var/www/html</code>中。对于要为同一站点提供服务的两个WordPress实例,该文件夹必须包含相同的数据。</p><p>当运行高可用WordPress时,我们需要在实例之间共享<code>/var/www/html</code>文件夹,因此我们定义一个NGS服务作为这些卷的挂载点。<br>下面是设置NFS服务的配置,我提供了纯英文的版本:</p><p><img src="https://img.php.cn/upload/image/456/243/673/1623137239811988.png" title="1623137239811988.png" alt="How to run highly available WordPress and MySQL on Kubernetes"></p><p><img src="https://img.php.cn/upload/image/229/961/277/1623137243449231.png" title="1623137243449231.png" alt="How to run highly available WordPress and MySQL on Kubernetes"></p><p><img src="https://img.php.cn/upload/image/737/299/982/1623137248223155.png" title="1623137248223155.png" alt="How to run highly available WordPress and MySQL on Kubernetes"></p><p><img src="https://img.php.cn/upload/image/282/959/213/1623137253150095.png" title="1623137253150095.png" alt="How to run highly available WordPress and MySQL on Kubernetes"></p><p>使用指令<code>$ kubectl create -f nfs.yaml</code>部署NFS服务。现在,我们需要运行<code>$ kubectl describe services nfs-server</code>获得IP地址,这在后面会用到。</p><p>注意:将来,我们可以使用服务名称讲这些绑定在一起,但现在你需要对IP地址进行硬编码。</p><pre class="brush:php;toolbar:false"># wordpress.yamlapiVersion: v1kind: Servicemetadata: name: wordpress labels: app: wordpressspec: ports: - port: 80 selector: app: wordpress tier: frontend type: LoadBalancer---apiVersion: v1kind: PersistentVolumemetadata: name: nfsspec: capacity: storage: 20G accessModes: - ReadWriteMany nfs: # FIXME: use the right IP server: <ip> path: "/"---apiVersion: v1kind: PersistentVolumeClaimmetadata: name: nfsspec: accessModes: - ReadWriteMany storageClassName: "" resources: requests: storage: 20G---apiVersion: apps/v1beta1 # for versions before 1.8.0 use apps/v1beta1kind: Deploymentmetadata: name: wordpress labels: app: wordpressspec: selector: matchLabels: app: wordpress tier: frontend strategy: type: Recreate template: metadata: labels: app: wordpress tier: frontend spec: containers: - image: wordpress:4.9-apache name: wordpress env: - name: WORDPRESS_DB_HOST value: mysql - name: WORDPRESS_DB_PASSWORD value: "" ports: - containerPort: 80 name: wordpress volumeMounts: - name: wordpress-persistent-storage mountPath: /var/www/html volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: nfs</ip>
我们现在创建了一个持久卷声明,和我们之前创建的NFS服务建立映射,然后将卷附加到WordPress pod上,即/var/www/html
根目录,这也是WordPress安装的地方。这里保留了集群中WordPress pods的所有安装和环境。有了这些配置,我们就可以对任何WordPress节点进行启动和拆除,而数据能够留下来。因为NFS服务需要不断使用物理卷,该卷将保留下来,并且不会被回收或错误分配。
Use the command$ kubectl create -f wordpress.yaml
Deploy a WordPress instance. The default deployment will only run one WordPress instance. You can use the command $ kubectl scale --replicas=<number of="" replicas=""></number>
deployment/wordpress
Scale the number of WordPress instances.
To get the address of the WordPress services load balancer, you need to navigate to WordPress by typing $ kubectl get services wordpress
and getting the EXTERNAL-IP field from the results.
Resilience Test
OK, now that we have deployed the services, let's tear them down and see how our high-availability architecture handles the chaos. In this deployment, the only remaining single point of failure is the NFS service (for reasons summarized in the conclusion at the end of the article). You should be able to test any other service to see how the application responds. Now I have started three replicas of the WordPress service, as well as a master and two slave nodes of the MySQL service.
First, let’s kill the others and leave only one WordPress node to see how the application responds:$ kubectl scale --replicas=1 deployment/wordpress
Now we should see The number of pods deployed by WordPress has decreased. $ kubectl get pods
You should see that the running of WordPress pods has changed to 1/1.
Click on the WordPress service IP and we will see the same site and database as before. If you want to scale the recovery, you can use $ kubectl scale --replicas=3 deployment/wordpress
Once again, we can see that the packets are left in three instances.
To test MySQL's stateful sets, we use the command to reduce the number of backups: $ kubectl scale statefulsets mysql --replicas=1
We will see that two replicas are lost from this instance, If the master node is lost at this time, the data it holds will be saved on the GCE persistent disk. However, the data must be restored from the disk manually.
If all three MySQL nodes are down, replication will not be possible when new nodes come up. However, if a master node fails, a new master node is automatically started and the data from the slave nodes is reconfigured through xtrabackup. Therefore, when running a production database, I would not recommend running with a replication factor less than 3. In the concluding paragraph, we'll talk about what are better solutions for stateful data, since Kubernetes is not really designed for state.
Conclusion and Recommendations
So far, you have completed building and deploying a highly available WordPress and MySQL installation on Kubernetes!
But despite such results, your research journey may be far from over. In case you haven't noticed, our installation still has a single point of failure: the NFS server sharing the /var/www/html
directory between WordPress pods. This service represents a single point of failure because if it is not running, the html directory will be missing on the pods that use it. In the tutorial, we chose a very stable image for the server, which can be used in a production environment, but for real production deployment, you can consider using GlusterFS to enable multi-read and multi-write on the directory shared by the WordPress instance.
This process involves running a distributed storage cluster on Kubernetes, which is not actually built with Kubernetes, so while it works well, it is not ideal for long-term deployments.
For the database, I personally recommend using a managed relational database service to host the MySQL instance, because whether it is Google's CloudSQL or AWS's RDS, they provide high availability and redundant processing at a more reasonable price, and No need to worry about data integrity. Kuberntes is not designed around stateful applications, and any state built into it is more of an afterthought. There are a number of solutions available today that can provide the assurance you need when choosing a database service.
In other words, what is introduced above is an ideal process, which creates a relevant and realistic Kubernetes example from Kubernetes tutorials and examples found on the web, and includes all the new features in Kubernetes 1.8.x characteristic.
I hope that through this guide, you can get some surprising experiences when deploying WordPress and MySQL. Of course, I hope that everything runs normally.