前面几节我们集中介绍了怎样将使用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';”。