WordPress是用於編輯和發(fā)布Web內容的主流平臺。在本教程中,我將逐步介紹如何使用Kubernetes來建置高可用性(HA)WordPress部署。
WordPress由兩個主要元件組成:WordPress PHP伺服器和用於儲存使用者資訊、貼文和網站資料的資料庫。我們需要讓整個應用程式中這兩個元件在高可用的同時都具備容錯能力。
在硬體和位址發(fā)生變化的時候,運行高可用服務可能會很困難:非常難維護。透過Kubernetes以及其強大的網路元件,我們可以部署高可用的WordPress網站和MySQL資料庫,而無需(幾乎無需)輸入單一IP位址。
在本教學中,我將向你展示如何在Kubernetes中建立儲存類別、服務、設定映射和集合,如何運行高可用MySQL,以及如何將高可用WordPress叢集掛載到資料庫服務上。如果你還沒有Kubernetes集群,你可以在Amazon、Google或Azure上輕鬆找到並且啟動它們,或者在任意的伺服器上使用Rancher Kubernetes Engine (RKE)
#架構概述
現(xiàn)在我來簡要介紹一下我們將要使用的技術及其功能:
WordPress應用程式檔案的儲存:具有GCE持久性磁碟備份的NFS儲存
資料庫叢集:帶有用於奇偶校驗的xtrabackup的MySQL
應用程式層級:掛載到NFS儲存的WordPress DockerHub映像
負載平衡和網路:基於Kubernetes的負載平衡器和服務網路
#該體系架構如下所示:
在K8s中建立儲存類別、服務和設定映射
在Kubernetes中,狀態(tài)集提供了定義pod初始化順序的方法。我們將使用一個有狀態(tài)的MySQL集合,因為它能確保我們的資料節(jié)點有足夠的時間在啟動時複製先前pods中的記錄。我們配置這個狀態(tài)集的方式可以讓MySQL主機在其他附屬機器之前先啟動,因此當我們擴展時,可以直接從主機將克隆發(fā)送到附屬機器上。
首先,我們需要建立一個持久性卷儲存類別和配置映射,以根據(jù)需要應用主從配置。我們使用持久性卷,避免資料庫中的資料受限於叢集中任何特定的pods。這種方式可以避免資料庫在MySQL主機pod遺失的情況下遺失數(shù)據(jù),當主機pod遺失時,它可以重新連接到具有xtrabackup的附屬機器,並將數(shù)據(jù)從附屬機器拷貝到主機中。 MySQL的複製負責主機-附屬的複製,而xtrabackup負責附屬-主機的複製。
要動態(tài)分配持久卷,我們使用GCE持久性磁碟建立儲存類別。不過,Kubernetes提供了各種持久性磁碟區(qū)的儲存方案:
#?storage-class.yamlkind:?StorageClassapiVersion:?storage.k8s.io/v1metadata: ?name:?slowprovisioner:?kubernetes.io/gce-pdparameters: ?type:?pd-standard??zone:?us-central1-a
建立類,並且使用指令:$ kubectl create -f storage-class.yaml
部署它。
接下來,我們將建立configmap,它指定了一些在MySQL設定檔中設定的變數(shù)。這些不同的配置由pod本身選擇有關,但它們也為我們提供了一種便捷的方式來管理潛在的配置變數(shù)。
建立名為mysql-configmap.yaml
的YAML檔案來處理配置,如下:
#?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-resolve
建立configmap
並使用指令:$ kubectl create -f mysql-configmap.yaml
來部署它。
接下來我們要設定服務以便MySQL pods可以互相通信,我們的WordPress pod可以使用mysql-services.yaml
與MySQL通訊。這也為MySQL服務啟動了服務負載平衡器。
#?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:?mysql
透過此服務聲明,我們就為實作一個多寫入、多讀取的MySQL實例叢集奠定了基礎。這種配置是必要的,每個WordPress實例都可能寫入資料庫,所以每個節(jié)點都必須準備好讀寫。
執(zhí)行指令 $ kubectl create -f mysql-services.yaml
來建立上述的服務。
到這為止,我們創(chuàng)建了卷宗聲明存儲類,它將持久性磁碟交給所有請求它們的容器,我們配置了configmap
,在MySQL配置文件中設置了一些變量,而我們配置了一個網路層服務,負責對MySQL伺服器請求的負載平衡。上面說的這些只是準備有狀態(tài)集的框架, MySQL伺服器實際上在哪裡運行,我們接下來將繼續(xù)探討。
配置有狀態(tài)集的MySQL
本節(jié)中,我們將編寫一個YAML配置文件應用于使用了狀態(tài)集的MySQL實例。
我們先定義我們的狀態(tài)集:
1, 創(chuàng)建三個pods并將它們注冊到MySQL服務上。
2, 按照下列模版定義每個pod:
? 為主機MySQL服務器創(chuàng)建初始化容器,命名為init-mysql
.
? ?給這個容器使用mysql:5.7鏡像
? ?運行一個bash腳本來啟動xtrabackup
? ?為配置文件和configmap
掛載兩個新卷
3, 為主機MySQL服務器創(chuàng)建初始化容器,命名為clone-mysql
.
? ?為該容器使用Google Cloud Registry的xtrabackup:1.0
鏡像
? 運行bash腳本來克隆上一個同級的現(xiàn)有xtrabackups
? ?為數(shù)據(jù)和配置文件掛在兩個新卷
? ?該容器有效地托管克隆的數(shù)據(jù),便于新的附屬容器可以獲取它
4, 為附屬MySQL服務器創(chuàng)建基本容器
? ?創(chuàng)建一個MySQL附屬容器,配置它連接到MySQL主機
? ?創(chuàng)建附屬xtrabackup
容器,配置它連接到xtrabackup主機
5, 創(chuàng)建一個卷聲明模板來描述每個卷,每個卷是一個10GB的持久磁盤
下面的配置文件定義了MySQL集群的主節(jié)點和附屬節(jié)點的行為,提供了運行附屬客戶端的bash配置,并確保在克隆之前主節(jié)點能夠正常運行。附屬節(jié)點和主節(jié)點分別獲得他們自己的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部署你的數(shù)據(jù)庫。<br>現(xiàn)在當你調用<code>$="" kubectl="" get="" pods</code>,你應該看到3個pods啟動或者準備好,其中每個pod上都有兩個容器。主節(jié)點pod表示為mysql-0,而附屬的pods為<code>mysql-1</code>和<code>mysql-2</code>.讓pods執(zhí)行幾分鐘來確保<code>xtrabackup</code>服務在pod之間正確同步,然后進行wordpress的部署。<br>您可以檢查單個容器的日志來確認沒有錯誤消息拋出。 查看日志的命令為<code>$="" logs="" <container_name></container_name></code></p><p>主節(jié)點<code>xtrabackup</code>容器應顯示來自附屬的兩個連接,并且日志中不應該出現(xiàn)任何錯誤。</p><h2>部署高可用的WordPress</h2><p>整個過程的最后一步是將我們的WordPress pods部署到集群上。為此我們希望為WordPress的服務和部署進行定義。</p><p>為了讓WordPress實現(xiàn)高可用,我們希望每個容器運行時都是完全可替換的,這意味著我們可以終止一個,啟動另一個而不需要對數(shù)據(jù)或服務可用性進行修改。我們也希望能夠容忍至少一個容器的失誤,有一個冗余的容器負責處理slack。</p><p>WordPress將重要的站點相關數(shù)據(jù)存儲在應用程序目錄<code>/var/www/html</code>中。對于要為同一站點提供服務的兩個WordPress實例,該文件夾必須包含相同的數(shù)據(jù)。</p><p>當運行高可用WordPress時,我們需要在實例之間共享<code>/var/www/html</code>文件夾,因此我們定義一個NGS服務作為這些卷的掛載點。<br>下面是設置NFS服務的配置,我提供了純英文的版本:</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="如何在Kubernetes上運行高可用的WordPress和MySQL"></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="如何在Kubernetes上運行高可用的WordPress和MySQL"></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="如何在Kubernetes上運行高可用的WordPress和MySQL"></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="如何在Kubernetes上運行高可用的WordPress和MySQL"></p><p>使用指令<code>$ kubectl create -f nfs.yaml</code>部署NFS服務。現(xiàn)在,我們需要運行<code>$ kubectl describe services nfs-server</code>獲得IP地址,這在后面會用到。</p><p>注意:將來,我們可以使用服務名稱講這些綁定在一起,但現(xiàn)在你需要對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>
我們現(xiàn)在創(chuàng)建了一個持久卷聲明,和我們之前創(chuàng)建的NFS服務建立映射,然后將卷附加到WordPress pod上,即/var/www/html
根目錄,這也是WordPress安裝的地方。這里保留了集群中WordPress pods的所有安裝和環(huán)境。有了這些配置,我們就可以對任何WordPress節(jié)點進行啟動和拆除,而數(shù)據(jù)能夠留下來。因為NFS服務需要不斷使用物理卷,該卷將保留下來,并且不會被回收或錯誤分配。
使用指令$ kubectl create -f wordpress.yaml
部署WordPress實例。預設部署只會執(zhí)行一個WordPress實例,可以使用指令$ kubectl scale --replicas=<number of="" replicas=""></number>
deployment/wordpress
擴展WordPress實例數(shù)量。
要取得WordPress服務負載平衡器的位址,你需要輸入$ kubectl get services wordpress
並從結果中取得EXTERNAL-IP欄位來導覽到WordPress。
彈性測試
OK,現(xiàn)在我們已經部署好了服務,那我們來拆除它們,看看我們的高可用架構如何處理這些混亂。在這種部署方式中,唯一剩下的單點故障就是NFS服務(原因總結在文末結論中)。你應該能夠測試其他任何的服務來了解應用程式是如何回應的?,F(xiàn)在我已經啟動了WordPress服務的三個副本,以及MySQL服務中的一個主兩個附屬節(jié)點。
首先,我們先kill掉其他而只留下一個WordPress節(jié)點,來看看應用如何回應:$ kubectl scale --replicas=1 deployment/wordpress
現(xiàn)在我們應該看到WordPress部署的pod數(shù)量下降。 $ kubectl get pods
應該可以看到WordPress pods的運行變成了1/1。
點擊WordPress服務IP,我們將看到與之前一樣的網站和資料庫。如果要擴充復原,可以使用$ kubectl scale --replicas=3 deployment/wordpress
再一次,我們可以看到封包留在了三個實例中。
下面測試MySQL的狀態(tài)集,我們使用指令縮小備份的數(shù)量:$ kubectl scale statefulsets mysql --replicas=1
我們會看到兩個附屬從這個實例中遺失,如果主節(jié)點在此時遺失,它所保存的資料將會保存在GCE持久性磁碟上。不過就必須手動從磁碟恢復資料。
如果所有三個MySQL節(jié)點都關閉了,當新節(jié)點出現(xiàn)時就無法複製。但是,如果一個主節(jié)點發(fā)生故障,一個新的主節(jié)點就會自動啟動,並且透過xtrabackup重新配置來自附屬節(jié)點的資料。因此,在運行生產資料庫時,我不建議以小於3的複製係數(shù)來運行。在結論段落中,我們會談談針對有狀態(tài)資料有什麼更好的解決方案,因為Kubernetes並非真正是為狀態(tài)設計的。
結論和建議
到現(xiàn)在為止,你已經完成了在Kubernetes建置並部署高可用WordPress和MySQL的安裝!
不過儘管取得了這樣的效果,你的研究之旅可能還遠遠沒有結束。可能你還沒注意到,我們的安裝仍然存在著單點故障:NFS伺服器在WordPress pods之間共用/var/www/html
目錄。這項服務代表了單點故障,因為如果它沒有運行,在使用它的pods上html目錄就會丟失。教程中我們?yōu)樗欧鬟x擇了非常穩(wěn)定的鏡像,可以在生產環(huán)境中使用,但對於真正的生產部署,你可以考慮使用GlusterFS對WordPress實例共享的目錄開啟多讀多寫。
這個過程涉及在Kubernetes上運行分散式儲存集群,實際上這不是Kubernetes建構的,因此儘管它運作良好,但不是長期部署的理想選擇。
對於資料庫,我個人建議使用託管的關聯(lián)式資料庫服務來託管MySQL實例,因為無論是Google的CloudSQL還是AWS的RDS,它們都以更合理的價格提供高可用和冗餘處理,並且不需擔心資料的完整性。 Kuberntes並不是圍繞著有狀態(tài)的應用程式設計的,任何建立在其中的狀態(tài)更多都是事後考慮。目前有大量的解決方案可以在選擇資料庫服務時提供所需的保證。
也就是說,上面介紹的是一種理想的流程,由Kubernetes教程、web中找到的例子創(chuàng)建一個有關聯(lián)的現(xiàn)實的Kubernetes例子,並且包含了Kubernetes 1.8.x中所有的新特性。
我希望透過這份指南,你能在部署WordPress和MySQL時獲得一些驚喜的體驗,當然,更希望你的運作一切正常。