民生银行数据库容器化探索


背景

近些年来,微服务被捧上了天,不会Docker都不好意思说自己是做运维的了。民生银行也已经建设了自己的应用容器云平台并正在全面推广使用中,应用容器化的好处非常多,轻量化、标准化、弹性、可移植、高效运维等等,那数据库要不要也搞容器化呢?经过我们一番调研之后,发现其实业内对数据库容器化是有存疑的,总有人说数据库容器化是个伪命题,挑战非常大,但我们为什么还想把数据库放到容器里呢?数据库容器化的目的应该和应用容器化不一样,不是单纯为了实现弹性调度,更多的是对容器技术做渐进式的规划,第一步可以先当作安装包使用,再考虑安全隔离,最后再实现调度编排系统。应用程序放到容器里很多时候是为了部署,但是我们把数据库放容器里就是为了做调度,因为数据库本身没有特别多的版本,不需要像应用一样做频繁发布。做了容器化之后,数据库在一个物理机上可以和其他的容器做混合部署,在能进行标准化调度的同时也极大减少了主机资源的浪费。民生银行本着实践出真知的探索精神,做了数据库容器化的尝试,并初步建设了数据库容器云平台(iPaaS)。

挑战及探索实践

什么样的数据库适合放容器里?

MySQL虽然很“轻量”,但由于是数据库是有状态化的,因此也在容器化的过程中会遇到很多问题,而数据库又是生产环境中的重中之重,任何小问题都可能会导致整体服务的不可用,我们在探索研究的过程中也是本着最高要求,以数据库服务的高可用性、高性能、高可靠性为主要目标。

计算存储资源分离

我们先去想数据库要像应用一样实现非常简单的弹性调度,那么数据库要做到什么?我们首先想到的是计算和存储必须分离!大家知道计算资源是很容易被移动,但是存储资源基本很难在短时间内移动它,所以弹性是非常非常困难的。

数据库对持久化存储是强依赖的,数据库需要记录一切微小的变动,而容器最初的设计是针对无状态应用的,所以当我们想把数据库也放到容器中时,事情就会变得很复杂。这里我们思考过两条可行的方案,一是依赖分布式存储,让不同物理机上被拉起的容器可以看到同一份数据,不过目前分布式存储的性能还不足以支撑数据库业务(下面会细说),因此方案一止步于此。第二种方案,我们可以凭借MySQL主从数据库的特性,通过多份存储资源并行,使每份存储资源对应的计算资源在一定意义上成为“可移动资源”,无论哪个物理硬件主机出现问题时,我们只需要进行MySQL数据库的主从切换,就可以让计算资源进行“移动”,目前我们在现有的平台设计中采用了方案二。

在容器中MySQL数据库的主从架构是通过容器启动脚本来实现的,目前我们在测试环境的MySQL容器启动脚本部分内容如下:
……
# Get pod ordinal index
host_name=`hostname`
dns_name=`dnsdomainname`
if [[ $host_name =~ -([0-9]+)$ ]]; then
echo "k8s pod ordinal index do not end with numbers!"
exit 1
fi
index=${BASH_REMATCH[1]}
master_host_name=`echo $host_name |sed "s/.$/0/g"`
master_host_dns=`echo $master_host_name"."${dns_name%%.*}`
……
/mysql/myinst1/service/bin/mysqld --defaults-file=/mysqldata/myinst1/etc/my.cnf --user=mysql --initialize
/mysql/myinst1/service/bin/mysqld  --defaults-file=/mysqldata/myinst1/etc/my.cnf  --user=mysql &
    for i in {30..0}; do
       if /mysql/myinst1/service/bin/mysql -e  " select  1 " &> /dev/null; then
               break
               fi
             echo "MySQL init process in progress..."
               sleep 1
     done
……
     if [[ $index -eq 0 ]]; then
         for((i=1;i<=$SLAVE_COUNT;i++))
         do
             slave_host_name=`echo $host_name |sed "s/.$/$i/g"`
             slave_fqdn=`echo $slave_host_name"."$dns_name`
         done
……
     else
……
      fi
……
/mysql/myinst1/service/bin/mysqld_safe --defaults-file=/mysqldata/myinst1/etc/my.cnf

这里我们使用了Kubernetes的Pod序号,基于Pod名称(即容器Hostname)解析出主从标识index,0为主,其余为从,再根据OS命令Hostname和dnsdomainname解析出MySQL容器的DNS全限定名称,用于建立主从复制关系和赋予从节点的半同步复制权限。针对MySQL主容器,解析StatefulSet模板的环境变量方式注入的MySQL从节点数量SLAVE_COUNT,生成MySQL从DNS全限定名称,并授予GRANT REPLICATION SLAVE权限,根据MySQL主从设置对应的半同步参数。
这么一来,我们就通过同一个启动脚本能够实现主从MySQL容器的启动及自身的一系列不同操作需求。

数据库性能

性能也是人们经常关注的一个维度。从性能的角度来看,数据库的基本部署原则当然是离硬件越近越好,额外的隔离与抽象不利于数据库的性能:越多的隔离意味着越多的开销,即使只是内核栈中的额外拷贝。对于追求性能的场景,一些数据库选择绕开操作系统的页面管理机制直接操作磁盘,而一些数据库甚至会使用FPGA甚至GPU加速查询处理。

实事求是地讲,Docker作为一种轻量化的容器,性能上的折损并不大,这也是Docker相比虚拟机的优势所在。而我们在设计的时候也是本着数据库性能优先考虑的目的,在对比了各种网络存储(Ceph、GPFS等)后还是决定采用本地SSD做为持久化数据卷,充分保证数据库性能优势。不过这样一来,我们面临着一个问题,本地存储是不能在不同物理机之间漂移的,而根据Kubernetes的一般情况,同一个容器每次启动会出现在不同的物理机上,那我们就需要把这个特性摒弃掉,让每个固定的容器只启动在固定的物理机上,也就是做到容器和物理机一一对应才行。
这里我们对测试环境的PV和PVC创建模板进行了修改,利用Kubernetes中nodeAffinity特性,先让PV绑定到指定的物理机Node上,然后PVC绑定PV,PV和PVC指定相同的sc,最后在容器StatefulSet模板中指定sc,通过这样一个关联关系,我们的MySQL容器就只能在指定的物理Node上才会被拉起,如果Node节点不具备拉起条件,那就会处于pending状态。

目前我们在测试环境的MySQL容器PV模板内容如下:
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/bound-by-controller: "yes"
labels:
pv-label: mysql
name:  mysql-<appname>-<count>-pv-<flag>
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 50Gi
local:
path: /data/mysql-<appname>-<count>
nodeAffinity:
required:
  nodeSelectorTerms:
  - matchExpressions:
    - key: kubernetes.io/hostname
      operator: In
      values:
      - <nodename>
persistentVolumeReclaimPolicy: Retain
storageClassName: mysql-local-volume

目前我们在测试环境的MySQL容器PVC模板内容如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-mysql-<appname>-<count>-<flag>
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
  storage: 20Gi
storageClassName: mysql-local-volume
volumeName: mysql-<appname>-<count>-pv-<flag>

目前我们在测试环境的MySQL容器StatefulSet模板部分内容如下:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql-<appname>-<count>
spec:
replicas: 2
……
volumeClaimTemplates:
- metadata:
  name: data
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: mysql-local-volume

由于同一个MySQL主从集群内的MySQL需要分配到不同的物理机Node上,我们再次利用Kubernetes的Pod序号做为判断,确保不同Pod序号的MySQL容器在StatefulSet模板中指定不同的PV和PVC,从而分配到不同的物理机Node上去。

数据库可靠性

应用容器化后可以很好的跑起来,而且有一组相同的应用同时在提供服务器,任何一个应用异常都不会导致服务的完全不可用,而数据库就不一样了,出了问题会要命的,如果数据库挂了又恢复不了,基本上可以宣告关门大吉了。Docker本身是不提供可靠性保障的,只会根据我们设计的逻辑反复在物理机上重启问题容器,所以我们需要把逻辑设计得尽可能完美,这时候光靠容器本身是不太够的,那就需要我们自己的外围辅助系统进行这方面的“加固”。

通过我们自研开发的数据库网关调度层,在MySQL容器中加入Agent进行实时监控,探测主库状态,当主库状态异常时,更新配置模块,为切换模块提供切换依据,同时调度MySQL容器中的脚本进行主从切换。
1.jpg

  • Pod:一个Pod里启动两个Docker容器,MySQL和Agent。
  • Agent:由于自动化执行平台无法操作到Docker内部,所以规划Agent主要有两个作用,1是调用一些无法通过JDBC远程执行的命令,2是执行具体的切换命令。
  • Service:对外提供接入服务。


数据库工具

数据库需要工具来维护,包括各式各样的运维脚本,如部署、备份、归档、故障切换、大小版本升级、插件安装、连接池、性能分析、监控、调优、巡检、修复。这些工具,也大多针对裸机部署而设计。这些工具与数据库一样,都需要精心而充分的测试。让一个东西跑起来,与确信这个东西能持久稳定正确的运行,是完全不同的可靠性水准。

那么一大堆脚本我们维护起来是相当困难的,而且容器持久化里面去更新各式各样的脚本也是比虚机或者物理机上更加的繁琐复杂。这里我们使用了Kubernetes中的ConfigMap模块,将MySQL配置文件、密码文件及其他相关脚本都放在ConfigMap中统一管理,Kubernetes Pod启动时自动挂载ConfigMap至容器中,维护单独的一份数据大大减轻了DBA的工作量,也不会出现文件版本不一致带来的执行风险。

测试环境中ConfigMap的创建脚本如下,我们把需要的配置文件、密码文件及其他相关脚本全部放到/tmp/hjm/mysql/common/下即可,内容如下:
kubectl create configmap mysql-<appname>-config -from-file=/tmp/hjm/mysql/common/

资源调度及快速部署(iPaas平台)

对于容器资源的分配主要采用以下几种方式:一种分配方式是根据接到指令的先后顺序来分配,这种方式没有考虑不同容器自身所需资源的大小,容易造成资源浪费;另一种分配方式是根据容器执行功能的不同来分配,这种方式下,虽然一定程度上实现了资源的配置,但是容器并不是固定不变的,执行的功能也是不同的,因此在资源配置上也会存在浪费的问题。

我们目前在测试环境MySQL容器资源是和应用共用集群资源的,而数据库又是比较“吃资源”的,因此需要我们进行合理的评估分析,尽可能使得每个物理主机都能均衡承担一定的负载。这里我们是和自研开发的数据库容器云平台(iPaaS)相结合,由平台对物理资源先进行一层把关筛选,选择出最优的物理部署资源,再进行MySQL容器的创建分配。目前分配物理机,规则判断顺序如下:
  1. 物理机存储空间空闲率,优先选择空闲率高的机器
  2. 物理机主节点数量,优先选择部署主节点较少的机器
  3. CPU、内存使用率,优先选择使用率较低的机器


2.png

同时,由于不同应用使用MySQL集群的需求不同,需要PaaS平台根据实际应用需求动态设置部分参数和生成部署配置文件,集群初始化及配置初始化包括如下:
  1. 根据Kubernetes集群信息初始化Kubernetes集群信息表,集群维护(优先级低)(PaaS平台数据库表)
  2. 根据端口分配情况初始化端口信息表,端口维护(优先级低)(PaaS平台数据库表)
  3. 将创建ConfigMap脚本保存到服务器,并在配置文件中配置路径
  4. 选择网络分区,网络分区与Kubernetes集群一一对应(测试环境为了应用和数据库不在同一节点,sit,uat,版本环境采用错配方式)
  5. 填写应用名称、MySQL集群规则、MySQL存储容量、应用用户名、应用用户数据库、应用用户权限


通过iPaaS平台页面填写基本信息后,就能快速生成相应的模板,同时调用Kubernetes接口传参进行,实现快速部署。
3.png

结束语

MySQL容器化后,部署一套高可用集群加备份监控,只需要用时1~2分钟,标准化的系统管理,部署环境统一,配置文件统一,系统化的操作降低人为失误和重复劳动,资源使用集中管理,有效利用服务器资源。我行目前测试环境新部署MySQL均已使用该方式,截止发文已部署MySQL主从集群30余套,全部使用正常。

企业数据库容器化平台建设过程往往会遇到的一些问题,需要我们进行深入调查和剖析,并切合企业具体问题案例从特定问题角度出发给出分析思路,随着目前的容器化技术、分布式存储技术等不断发展,容器化平台建设的内容会呈现越来越多的新模式和新思路,相信大家在建设各自企业的容器化平台系统的时候也会有很多高招。

作者介绍

  • 胡吉铭,民生银行信息科技部DBA,拥有十余年数据备份恢复领域经验,近期主要致力于数据库容器化工作。
  • 李宁宁,民生银行信息科技部DBA,Oracle OCM及MySQL OCP,有多年数据库运维经验,主要负责SQL审核推广及MySQL运维相关工作。
  • 曹啸,民生银行信息科技部DBA,目前主要致力于各类数据库运维,MySQL源码及新型数据复制工具研究等工作。在银行科技行业工作多年,兼具开发及运维工作经验,同时对银行多个业务领域亦有涉猎。


原文链接:https://mp.weixin.qq.com/s/UJKqEBSVQP_9Xrk5UOBVeQ

0 个评论

要回复文章请先登录注册