WordPress是用於編輯和發布Web內容的主流平台。在本教程中,我將逐步介紹如何使用Kubernetes來建置高可用性(HA)WordPress部署。
WordPress由兩個主要元件組成:WordPress PHP伺服器和用於儲存使用者資訊、貼文和網站資料的資料庫。我們需要讓整個應用程式中這兩個元件在高可用的同時都具備容錯能力。
在硬體和位址發生變化的時候,運行高可用服務可能會很困難:非常難維護。透過Kubernetes以及其強大的網路元件,我們可以部署高可用的WordPress網站和MySQL資料庫,而無需(幾乎無需)輸入單一IP位址。
在本教學中,我將向你展示如何在Kubernetes中建立儲存類別、服務、設定映射和集合,如何運行高可用MySQL,以及如何將高可用WordPress叢集掛載到資料庫服務上。如果你還沒有Kubernetes集群,你可以在Amazon、Google或Azure上輕鬆找到並且啟動它們,或者在任意的伺服器上使用Rancher Kubernetes Engine (RKE)
#架構概述
現在我來簡要介紹一下我們將要使用的技術及其功能:
WordPress應用程式檔案的儲存:具有GCE持久性磁碟備份的NFS儲存
資料庫叢集:帶有用於奇偶校驗的xtrabackup的MySQL
應用程式層級:掛載到NFS儲存的WordPress DockerHub映像
負載平衡和網路:基於Kubernetes的負載平衡器和服務網路
#該體系架構如下所示:
在K8s中建立儲存類別、服務和設定映射
在Kubernetes中,狀態集提供了定義pod初始化順序的方法。我們將使用一個有狀態的MySQL集合,因為它能確保我們的資料節點有足夠的時間在啟動時複製先前pods中的記錄。我們配置這個狀態集的方式可以讓MySQL主機在其他附屬機器之前先啟動,因此當我們擴展時,可以直接從主機將克隆發送到附屬機器上。
首先,我們需要建立一個持久性卷儲存類別和配置映射,以根據需要應用主從配置。我們使用持久性卷,避免資料庫中的資料受限於叢集中任何特定的pods。這種方式可以避免資料庫在MySQL主機pod遺失的情況下遺失數據,當主機pod遺失時,它可以重新連接到具有xtrabackup的附屬機器,並將數據從附屬機器拷貝到主機中。 MySQL的複製負責主機-附屬的複製,而xtrabackup負責附屬-主機的複製。
要動態分配持久卷,我們使用GCE持久性磁碟建立儲存類別。不過,Kubernetes提供了各種持久性磁碟區的儲存方案:
# 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設定檔中設定的變數。這些不同的配置由pod本身選擇有關,但它們也為我們提供了一種便捷的方式來管理潛在的配置變數。
建立名為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實例都可能寫入資料庫,所以每個節點都必須準備好讀寫。
執行指令 $ kubectl create -f mysql-services.yaml
來建立上述的服務。
到這為止,我們創建了卷宗聲明存儲類,它將持久性磁碟交給所有請求它們的容器,我們配置了configmap
,在MySQL配置文件中設置了一些變量,而我們配置了一個網路層服務,負責對MySQL伺服器請求的負載平衡。上面說的這些只是準備有狀態集的框架, MySQL伺服器實際上在哪裡運行,我們接下來將繼續探討。
配置有状态集的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="如何在Kubernetes上運行高可用的WordPress和MySQL"></p><p><img src="https://img.php.cn/upload/image/229/961/277/1623137243449231.png" title="1623137243449231.png" alt="如何在Kubernetes上運行高可用的WordPress和MySQL"></p><p><img src="https://img.php.cn/upload/image/737/299/982/1623137248223155.png" title="1623137248223155.png" alt="如何在Kubernetes上運行高可用的WordPress和MySQL"></p><p><img src="https://img.php.cn/upload/image/282/959/213/1623137253150095.png" title="1623137253150095.png" alt="如何在Kubernetes上運行高可用的WordPress和MySQL"></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服务需要不断使用物理卷,该卷将保留下来,并且不会被回收或错误分配。
使用指令$ kubectl create -f wordpress.yaml
部署WordPress實例。預設部署只會執行一個WordPress實例,可以使用指令$ kubectl scale --replicas=<number of="" replicas=""></number>
deployment/wordpress
擴展WordPress實例數量。
要取得WordPress服務負載平衡器的位址,你需要輸入$ kubectl get services wordpress
並從結果中取得EXTERNAL-IP欄位來導覽到WordPress。
彈性測試
OK,現在我們已經部署好了服務,那我們來拆除它們,看看我們的高可用架構如何處理這些混亂。在這種部署方式中,唯一剩下的單點故障就是NFS服務(原因總結在文末結論中)。你應該能夠測試其他任何的服務來了解應用程式是如何回應的。現在我已經啟動了WordPress服務的三個副本,以及MySQL服務中的一個主兩個附屬節點。
首先,我們先kill掉其他而只留下一個WordPress節點,來看看應用如何回應:$ kubectl scale --replicas=1 deployment/wordpress
現在我們應該看到WordPress部署的pod數量下降。 $ kubectl get pods
應該可以看到WordPress pods的運行變成了1/1。
點擊WordPress服務IP,我們將看到與之前一樣的網站和資料庫。如果要擴充復原,可以使用$ kubectl scale --replicas=3 deployment/wordpress
再一次,我們可以看到封包留在了三個實例中。
下面測試MySQL的狀態集,我們使用指令縮小備份的數量:$ kubectl scale statefulsets mysql --replicas=1
我們會看到兩個附屬從這個實例中遺失,如果主節點在此時遺失,它所保存的資料將會保存在GCE持久性磁碟上。不過就必須手動從磁碟恢復資料。
如果所有三個MySQL節點都關閉了,當新節點出現時就無法複製。但是,如果一個主節點發生故障,一個新的主節點就會自動啟動,並且透過xtrabackup重新配置來自附屬節點的資料。因此,在運行生產資料庫時,我不建議以小於3的複製係數來運行。在結論段落中,我們會談談針對有狀態資料有什麼更好的解決方案,因為Kubernetes並非真正是為狀態設計的。
結論和建議
到現在為止,你已經完成了在Kubernetes建置並部署高可用WordPress和MySQL的安裝!
不過儘管取得了這樣的效果,你的研究之旅可能還遠遠沒有結束。可能你還沒注意到,我們的安裝仍然存在著單點故障:NFS伺服器在WordPress pods之間共用/var/www/html
目錄。這項服務代表了單點故障,因為如果它沒有運行,在使用它的pods上html目錄就會丟失。教程中我們為伺服器選擇了非常穩定的鏡像,可以在生產環境中使用,但對於真正的生產部署,你可以考慮使用GlusterFS對WordPress實例共享的目錄開啟多讀多寫。
這個過程涉及在Kubernetes上運行分散式儲存集群,實際上這不是Kubernetes建構的,因此儘管它運作良好,但不是長期部署的理想選擇。
對於資料庫,我個人建議使用託管的關聯式資料庫服務來託管MySQL實例,因為無論是Google的CloudSQL還是AWS的RDS,它們都以更合理的價格提供高可用和冗餘處理,並且不需擔心資料的完整性。 Kuberntes並不是圍繞著有狀態的應用程式設計的,任何建立在其中的狀態更多都是事後考慮。目前有大量的解決方案可以在選擇資料庫服務時提供所需的保證。
也就是說,上面介紹的是一種理想的流程,由Kubernetes教程、web中找到的例子創建一個有關聯的現實的Kubernetes例子,並且包含了Kubernetes 1.8.x中所有的新特性。
我希望透過這份指南,你能在部署WordPress和MySQL時獲得一些驚喜的體驗,當然,更希望你的運作一切正常。