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.
配置有狀態(tài)集的MySQL
本節(jié)中,我們將編寫一個(gè)YAML配置文件應(yīng)用于使用了狀態(tài)集的MySQL實(shí)例。
我們先定義我們的狀態(tài)集:
1, 創(chuàng)建三個(gè)pods并將它們注冊到MySQL服務(wù)上。
2, 按照下列模版定義每個(gè)pod:
? 為主機(jī)MySQL服務(wù)器創(chuàng)建初始化容器,命名為init-mysql
.
? ?給這個(gè)容器使用mysql:5.7鏡像
? ?運(yùn)行一個(gè)bash腳本來啟動(dòng)xtrabackup
? ?為配置文件和configmap
掛載兩個(gè)新卷
3, 為主機(jī)MySQL服務(wù)器創(chuàng)建初始化容器,命名為clone-mysql
.
? ?為該容器使用Google Cloud Registry的xtrabackup:1.0
鏡像
? 運(yùn)行bash腳本來克隆上一個(gè)同級(jí)的現(xiàn)有xtrabackups
? ?為數(shù)據(jù)和配置文件掛在兩個(gè)新卷
? ?該容器有效地托管克隆的數(shù)據(jù),便于新的附屬容器可以獲取它
4, 為附屬M(fèi)ySQL服務(wù)器創(chuàng)建基本容器
? ?創(chuàng)建一個(gè)MySQL附屬容器,配置它連接到MySQL主機(jī)
? ?創(chuàng)建附屬xtrabackup
容器,配置它連接到xtrabackup主機(jī)
5, 創(chuàng)建一個(gè)卷聲明模板來描述每個(gè)卷,每個(gè)卷是一個(gè)10GB的持久磁盤
下面的配置文件定義了MySQL集群的主節(jié)點(diǎn)和附屬節(jié)點(diǎn)的行為,提供了運(yùn)行附屬客戶端的bash配置,并確保在克隆之前主節(jié)點(diǎn)能夠正常運(yùn)行。附屬節(jié)點(diǎn)和主節(jié)點(diǎn)分別獲得他們自己的10GB卷,這是他們在我們之前定義的持久卷存儲(chǔ)類中請求的。
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部署你的數(shù)據(jù)庫。<br>現(xiàn)在當(dāng)你調(diào)用<code>$="" kubectl="" get="" pods</code>,你應(yīng)該看到3個(gè)pods啟動(dòng)或者準(zhǔn)備好,其中每個(gè)pod上都有兩個(gè)容器。主節(jié)點(diǎn)pod表示為mysql-0,而附屬的pods為<code>mysql-1</code>和<code>mysql-2</code>.讓pods執(zhí)行幾分鐘來確保<code>xtrabackup</code>服務(wù)在pod之間正確同步,然后進(jìn)行wordpress的部署。<br>您可以檢查單個(gè)容器的日志來確認(rèn)沒有錯(cuò)誤消息拋出。 查看日志的命令為<code>$="" logs="" <container_name></container_name></code></p><p>主節(jié)點(diǎn)<code>xtrabackup</code>容器應(yīng)顯示來自附屬的兩個(gè)連接,并且日志中不應(yīng)該出現(xiàn)任何錯(cuò)誤。</p><h2>部署高可用的WordPress</h2><p>整個(gè)過程的最后一步是將我們的WordPress pods部署到集群上。為此我們希望為WordPress的服務(wù)和部署進(jìn)行定義。</p><p>為了讓W(xué)ordPress實(shí)現(xiàn)高可用,我們希望每個(gè)容器運(yùn)行時(shí)都是完全可替換的,這意味著我們可以終止一個(gè),啟動(dòng)另一個(gè)而不需要對數(shù)據(jù)或服務(wù)可用性進(jìn)行修改。我們也希望能夠容忍至少一個(gè)容器的失誤,有一個(gè)冗余的容器負(fù)責(zé)處理slack。</p><p>WordPress將重要的站點(diǎn)相關(guān)數(shù)據(jù)存儲(chǔ)在應(yīng)用程序目錄<code>/var/www/html</code>中。對于要為同一站點(diǎn)提供服務(wù)的兩個(gè)WordPress實(shí)例,該文件夾必須包含相同的數(shù)據(jù)。</p><p>當(dāng)運(yùn)行高可用WordPress時(shí),我們需要在實(shí)例之間共享<code>/var/www/html</code>文件夾,因此我們定義一個(gè)NGS服務(wù)作為這些卷的掛載點(diǎn)。<br>下面是設(shè)置NFS服務(wù)的配置,我提供了純英文的版本:</p><p><img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/image/456/243/673/1623137239811988.png" class="lazy" title="1623137239811988.png" alt="How to run highly available WordPress and MySQL on Kubernetes"></p><p><img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/image/229/961/277/1623137243449231.png" class="lazy" title="1623137243449231.png" alt="How to run highly available WordPress and MySQL on Kubernetes"></p><p><img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/image/737/299/982/1623137248223155.png" class="lazy" title="1623137248223155.png" alt="How to run highly available WordPress and MySQL on Kubernetes"></p><p><img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/image/282/959/213/1623137253150095.png" class="lazy" title="1623137253150095.png" alt="How to run highly available WordPress and MySQL on Kubernetes"></p><p>使用指令<code>$ kubectl create -f nfs.yaml</code>部署NFS服務(wù)?,F(xiàn)在,我們需要運(yùn)行<code>$ kubectl describe services nfs-server</code>獲得IP地址,這在后面會(huì)用到。</p><p>注意:將來,我們可以使用服務(wù)名稱講這些綁定在一起,但現(xiàn)在你需要對IP地址進(jìn)行硬編碼。</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>
我們現(xiàn)在創(chuàng)建了一個(gè)持久卷聲明,和我們之前創(chuàng)建的NFS服務(wù)建立映射,然后將卷附加到WordPress pod上,即/var/www/html
根目錄,這也是WordPress安裝的地方。這里保留了集群中WordPress pods的所有安裝和環(huán)境。有了這些配置,我們就可以對任何WordPress節(jié)點(diǎn)進(jìn)行啟動(dòng)和拆除,而數(shù)據(jù)能夠留下來。因?yàn)镹FS服務(wù)需要不斷使用物理卷,該卷將保留下來,并且不會(huì)被回收或錯(cuò)誤分配。
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.