前面幾節我們集中介紹了怎樣將使用react編寫的前端項目部署上雲,從這節開始,我們會集中幾節來介紹怎樣在k8s集群中部署一些常用的中間件,比較常見就如mysql集群、redis集群、kafka集群。
需要注意的是,在生產環境下,這些中間件一般會由我們親愛的運維團隊根據業務需求與業務團隊深入交流后先行部署,而在小型項目或者人員緊張的項目中大多還是開發或者測試人員自行搭建,如果您的項目本身對這些中間件的要求沒有那么高,不需要基於這些中間件進行拔高的開發,那這幾節關於中間件在k8s集群中的搭建或許正適合您。
這節介紹的是mariadb集群的搭建,首先我們的需求比較簡單,項目的並發量雖然不高,但對於數據的一致性以及數據庫的備份、容災這些基本要求還是有的,基於此,我們選擇了集成了Galera插件的mariadb集群,關於Galera cluster,簡單理解起來就是實現了multi-master即多主架構,因此當客戶端要讀取或者寫入數據時,不需要選擇特定的節點,連接任何一個節點都一樣,對於寫操作,集群會負責同步到其他節點。
首先,我們從github中下載了一個同行基於official的mariaDB鏡像做的集成了Galera插件的集群項目,項目鏈接如下,非常感謝該項目的作者:
https://github.com/ausov/k8s-mariadb-cluster
其次,將下載的項目打開,能看到下面的工作目錄:

其中提供了Dockerfile告知我們鏡像是怎樣編譯出來的,Dockerfile中會調用docker-entrypoint.sh以及galera文件夾下的文件,關鍵的腳本docker-entrypoint.sh中定義了mysql集群化所需的功能比如怎樣發現其他mysql實例等。
而在example文件夾下就是定義了在k8s下部署mariaDB集群所需的yaml文件,最主要的是兩個galera.yaml和secrets-development.yaml,前者定義了mariaDB集群的服務和pod信息,其中會調用后者yaml文件中定義的mysql數據庫的密碼。
galera.yaml
apiVersion: v1
kind: Service
metadata:
annotations:
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
name: galera
labels:
app: mysql
spec:
ports:
- port: 3306
name: mysql
clusterIP: None
selector:
app: mysql
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config-vol
data:
galera.cnf: |
[galera]
user = mysql
bind-address = 0.0.0.0
default_storage_engine = InnoDB
binlog_format = ROW
innodb_autoinc_lock_mode = 2
innodb_flush_log_at_trx_commit = 0
query_cache_size = 0
query_cache_type = 0
# MariaDB Galera settings
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_sst_method=rsync
# Cluster settings (automatically updated)
wsrep_cluster_address=gcomm://
wsrep_cluster_name=galera
wsrep_node_address=127.0.0.1
mariadb.cnf: |
[client]
default-character-set = utf8
[mysqld]
character-set-server = utf8
collation-server = utf8_general_ci
# InnoDB tuning
innodb_log_file_size = 50M
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: "galera"
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: copy-mariadb-config
image: busybox
command: ['sh', '-c', 'cp /configmap/* /etc/mysql/conf.d']
volumeMounts:
- name: configmap
mountPath: /configmap
- name: config
mountPath: /etc/mysql/conf.d
containers:
- name: mysql
image: ausov/k8s-mariadb-cluster
securityContext:
privileged: true
ports:
- containerPort: 3306
name: mysql
- containerPort: 4444
name: sst
- containerPort: 4567
name: replication
- containerPort: 4568
name: ist
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql
key: password
readinessProbe:
exec:
command: ["bash", "-c", "mysql -uroot -p\"${MYSQL_ROOT_PASSWORD}\" -e 'show databases;'"]
initialDelaySeconds: 20
timeoutSeconds: 5
volumeMounts:
- name: config
mountPath: /etc/mysql/conf.d
- name: datadir
mountPath: /var/lib/mysql
volumes:
- name: config
emptyDir: {}
- name: configmap
configMap:
name: mysql-config-vol
items:
- path: "galera.cnf"
key: galera.cnf
- path: "mariadb.cnf"
key: mariadb.cnf
volumeClaimTemplates:
- metadata:
name: datadir
annotations:
volume.beta.kubernetes.io/storage-class: "nfs"
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 2Gi
有幾個關鍵的點需要注意:
1. 該集群是通過StatefulSet有狀態集的方式進行部署的,因此最終的pod名稱也是順序定義的mysql-0, mysql-1, mysql-2(mysql是我定義的StatefulSet的name屬性),另外需要注意StatefulSet中的serviceName一定要和上面創建的headless service的name一致。

2. 該yaml文件中使用ConfigMap導入了兩個配置文件galera.conf和mariadb.conf,前者定義了Galera集群的一些配置信息,后者則是mysql的一些配置信息,關於galera.conf需要着重關注下面一段:
# MariaDB Galera settings
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_sst_method=rsync
# Cluster settings (automatically updated)
wsrep_cluster_address=gcomm://
wsrep_cluster_name=galera
wsrep_node_address=127.0.0.1
這段是實現Galera集群node之間replication的關鍵信息,其中標紅的wsrep_node_address會在pod啟動時修改成當前Galera集群中node的address.
3. 按照我們從github中下載下來的k8s-mariadb-cluster項目的要求,應該提前創建mysql數據持久化需要的PVC和PV,但是因為我們的k8s環境中提前配置了持久化解決方案----nfs,因此我們直接創建了一個storageClass關聯nfs,而在galera.yaml中使用了volumeClaimTemplates來自動生成PVC和PV。如果你對創建nfs、storageClass關聯nfs以及動態創建PVC和PV感興趣,我會在本系列博文后面有一節專門介紹它們。
列出StorageClass的yaml:
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: nfs
provisioner: mynfs
reclaimPolicy: Delete
volumeBindingMode: Immediate
4. 請注意上面yaml文件中mysql這個容器有多個端口,有的是mysql的訪問端口,有的是負責replication的端口等等,使用的時候一定不要出錯。尤其是端口3306一定要對應service的targetPort(本例中service資源定義中只指定了service的port是3306,沒有指定targetPort,這種情況下默認port和targetPort相同)。
下面看看secrets-development.yaml的內容,很簡單,只是定義了數據庫的密碼:
apiVersion: v1
kind: Secret
metadata:
name: mysql
type: Opaque
data:
# Root password (base64): changeit
password: Y2hhbmdlaXQ=
有了這兩個yaml文件,我們還需要自己創建一個service的yaml文件,它用來負責mariaDB集群和其他微服務之間的訪問通信(注意,您可能會說,上面的yaml文件中不是已經定義了一個service嗎?為什么還需要再定義這個呢?原因就是上面定義的那個service是一個headless service,即我們常說的無頭服務,它僅僅是用來與StatefulSet協作生成具有固定命名規則和固定順序的pod及其訪問路徑;而一般我們都需要重新定義一個負責與其他服務通信的service資源):
apiVersion: v1
kind: Service
metadata:
name: galera-service
labels:
app: mysql
spec:
type: ClusterIP
ports:
- port: 8000
targetPort: 3306
protocol: TCP
selector:
app: mysql
至此,只要你執行這兩個yaml文件,就能看到pod都已正常啟動。
后續你如果使用Springboot做后端,只需要在Springboot的application.properties中定義好mariaDB集群對應的service及其端口即可:

使用的時候無論您是使用mybatis或者jdbcTemplate都可以。
最后,我列舉一下在搭建過程中您可能會遇到的問題以及解決方案:
問題1、如果你也使用了nfs作為mariaDB集群的后端持久化存儲,並且出現錯誤提示“chown: changing ownership of '/var/lib/mysql/ ': Operation not permitted”,這表示mariaDB集群在訪問你的nfs路徑時候沒有權限,你需要保證你的nfs路徑允許所有root以及非root用戶訪問,修改方式如下:
第一步:在你安裝nfs的機器上,cat /etc/exports,找到nfs的路徑,顯示的結果類似於“/appl/install *(rw,insecure)”,確實沒有授權非root用戶。
第二步:更新/etc/exports加上“no_root_squash”,修改后的內容類似於“/appl/install *(rw,no_root_squash,insecure)”。
第三步:重啟使之生效,重啟命令為“exportfs -rv”
問題2、訪問mysql數據庫是提示“ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)”,這表示root用戶訪問localhost數據庫沒有權限,解決方案就是通過命令“mysql -uroot”進入數據庫,查看mysql這個數據庫中的user表“select * from user where user='root';”,查看用戶‘root’對應的記錄是否沒有password,如果沒有直接更新上password即可,命令類似“alter user 'root'@'localhost' identified by 'root';”。
