Kubernetes技巧与窍门:加速大型数据库的引导

在本文中,我们打开了一系列出版物,其中包含有关如何使自己(操作)和开发人员在各种情况下每天都会发生的生活变得更加轻松的实用说明。 所有这些都是从解决客户问题的实际经验中收集的,并且随着时间的流逝有所改善,但仍然不能说是理想的-请将它们更多地视为想法和空白。

我将以“技巧”开始,准备像MySQL和PostgreSQL这样的大型数据库转储,以针对各种需求快速部署它们-首先,在开发人员的平台上。 下述操作的上下文是我们的典型环境,其中包括一个可正常工作的Kubernetes集群,以及用于CI / CD的GitLab(和dapp )。 走吧



当使用功能分支时,Kubernetes的主要痛苦是大型数据库,当开发人员想要在生产的完整(或几乎完整)数据库上测试/演示其更改时。 例如:

  • MySQL中有一个具有1 TB数据库的应用程序,有10个开发人员开发了自己的功能。
  • 开发人员需要单独的测试循环,以及一些用于测试和/或演示的更具体的循环。
  • 另外,需要在生产电路的测试电路中恢复生产基地的夜间维护,以保持正常的时间-以重现客户或bug的问题。
  • 最后,可以减少至少150 GB的数据库大小-不算多,但仍可以节省空间。 即 我们仍然需要以某种方式准备转储。

注意 :通常,我们使用Percona的innobackupex备份MySQL数据库,这使我们可以保存所有数据库和用户...-简而言之,可能需要做的一切。 尽管在一般情况下,如何进行备份并不重要,但本文将进一步考虑这种示例。

因此,假设我们有一个数据库备份。 接下来要做什么?

步骤1:从转储中准备新数据库


首先,我们将在Kubernetes Deployment中创建它,它由两个init容器(即在应用程序炉前运行并允许您执行预配置的特殊容器和一个炉床组成。

但是在哪里放置呢? 我们有一个大型数据库(1 TB),我们想增加其十个实例-我们需要一台具有大磁盘(10+ TB)的服务器。 我们为此任务单独订购它,并用dedicated: non-prod-db的专用标签 dedicated: non-prod-db在该服务器上标记该节点。 同时,我们将使用同义的taint,Kubernetes会说这个异味是只有对它有抵抗力(具有容忍度 )的应用程序才能滚动到该节点,即将Kubernetes转换为dedicated Equal non-prod-db

使用nodeSelectornodeSelector tolerations选择所需的节点(位于具有大磁盘的服务器上):

  nodeSelector: dedicated: non-prod-db tolerations: - key: "dedicated" operator: "Equal" value: "non-prod-db" effect: "NoExecute" 

...,并对该节点的内容进行描述。

初始化容器:get-bindump


我们将第一个初始化容器称为get-bindump 。 它挂载emptyDir (在/var/lib/mysql ),将从备份服务器接收到的数据库转储添加到该目录中。 为此,容器提供了您所需的一切:SSH密钥,备份服务器地址。 在本例中,此阶段大约需要2个小时。

部署中对此容器的描述如下:

  - name: get-bindump image: db-dumps imagePullPolicy: Always command: [ "/bin/sh", "-c", "/get_bindump.sh" ] resources: limits: memory: "5000Mi" cpu: "1" requests: memory: "5000Mi" cpu: "1" volumeMounts: - name: dump mountPath: /dump - name: mysqlbindir mountPath: /var/lib/mysql - name: id-rsa mountPath: /root/.ssh 

容器中使用的get_bindump.sh脚本:

 #!/bin/bash date if [ -f /dump/version.txt ]; then echo "Dump file already exists." exit 0 fi rm -rf /var/lib/mysql/* borg extract --stdout user@your.server.net:somedb-mysql::${lastdump} stdin | xbstream -x -C /var/lib/mysql/ echo $lastdump > /dump/version.txt 

初始化容器:prepare-bindump


下载备份后,启动第二个初始化容器prepare-bindump 。 它执行innobackupex --apply-log (因为文件已经在/var/lib/mysql可用-感谢get-bindump ),然后MySQL服务器启动。

正是在此init容器中,我们对数据库进行了所有必要的转换,为选定的应用程序做好准备:我们清除允许使用的表,更改数据库内部的访问权限,等等。 然后,我们关闭MySQL服务器,只需将整个/var/lib/mysql存档到tar.gz文件中。 结果,转储可容纳100 GB的文件,该文件已经比原始1 TB小一个数量级。 此阶段大约需要5个小时。

Deployment中第二个初始化容器的描述:

  - name: prepare-bindump image: db-dumps imagePullPolicy: Always command: [ "/bin/sh", "-c", "/prepare_bindump.sh" ] resources: limits: memory: "5000Mi" cpu: "1" requests: memory: "5000Mi" cpu: "1" volumeMounts: - name: dump mountPath: /dump - name: mysqlbindir mountPath: /var/lib/mysql - name: debian-cnf mountPath: /etc/mysql/debian.cnf subPath: debian.cnf 

其中使用的prepare_bindump.sh脚本如下所示:

 #!/bin/bash date if [ -f /dump/healthz ]; then echo "Dump file already exists." exit 0 fi innobackupex --apply-log /var/lib/mysql/ chown -R mysql:mysql /var/lib/mysql chown -R mysql:mysql /var/log/mysql echo "`date`: Starting mysql" /usr/sbin/mysqld --character-set-server=utf8 --collation-server=utf8_general_ci --innodb-data-file-path=ibdata1:200M:autoextend --user=root --skip-grant-tables & sleep 200 echo "`date`: Creating mysql root user" echo "update mysql.user set Password=PASSWORD('password') WHERE user='root';" | mysql -uroot -h 127.0.0.1 echo "delete from mysql.user where USER like '';" | mysql -uroot -h 127.0.0.1 echo "delete from mysql.user where user = 'root' and host NOT IN ('127.0.0.1', 'localhost');" | mysql -uroot -h 127.0.0.1 echo "FLUSH PRIVILEGES;" | mysql -uroot -h 127.0.0.1 echo "truncate somedb.somedb_table_one;" | mysql -uroot -h 127.0.0.1 -ppassword somedb /usr/bin/mysqladmin shutdown -uroot -ppassword cd /var/lib/mysql/ tar -czf /dump/mysql_bindump.tar.gz ./* touch /dump/healthz rm -rf /var/lib/mysql/* 


最后一个和弦是主炉膛的启动,这是在执行初始化容器之后发生的。 在pod中,我们有一个简单的nginx,并通过emtpyDir压缩和裁剪的100 GB转储。 这个nginx的功能是给这个转储。

炉膛配置:

  - name: nginx image: nginx:alpine resources: requests: memory: "1500Mi" cpu: "400m" lifecycle: preStop: exec: command: ["/usr/sbin/nginx", "-s", "quit"] livenessProbe: httpGet: path: /healthz port: 80 scheme: HTTP timeoutSeconds: 7 failureThreshold: 5 volumeMounts: - name: dump mountPath: /usr/share/nginx/html - name: nginx-config mountPath: /etc/nginx/nginx.conf subPath: nginx.conf readOnly: false volumes: - name: dump emptyDir: {} - name: mysqlbindir emptyDir: {} 

这就是整个部署及其initContainers的外观...
 --- apiVersion: apps/v1beta1 kind: Deployment metadata: name: db-dumps spec: strategy: rollingUpdate: maxUnavailable: 0 revisionHistoryLimit: 2 template: metadata: labels: app: db-dumps spec: imagePullSecrets: - name: regsecret nodeSelector: dedicated: non-prod-db tolerations: - key: "dedicated" operator: "Equal" value: "non-prod-db" effect: "NoExecute" initContainers: - name: get-bindump image: db-dumps imagePullPolicy: Always command: [ "/bin/sh", "-c", "/get_bindump.sh" ] resources: limits: memory: "5000Mi" cpu: "1" requests: memory: "5000Mi" cpu: "1" volumeMounts: - name: dump mountPath: /dump - name: mysqlbindir mountPath: /var/lib/mysql - name: id-rsa mountPath: /root/.ssh - name: prepare-bindump image: db-dumps imagePullPolicy: Always command: [ "/bin/sh", "-c", "/prepare_bindump.sh" ] resources: limits: memory: "5000Mi" cpu: "1" requests: memory: "5000Mi" cpu: "1" volumeMounts: - name: dump mountPath: /dump - name: mysqlbindir mountPath: /var/lib/mysql - name: log mountPath: /var/log/mysql - name: debian-cnf mountPath: /etc/mysql/debian.cnf subPath: debian.cnf containers: - name: nginx image: nginx:alpine resources: requests: memory: "1500Mi" cpu: "400m" lifecycle: preStop: exec: command: ["/usr/sbin/nginx", "-s", "quit"] livenessProbe: httpGet: path: /healthz port: 80 scheme: HTTP timeoutSeconds: 7 failureThreshold: 5 volumeMounts: - name: dump mountPath: /usr/share/nginx/html - name: nginx-config mountPath: /etc/nginx/nginx.conf subPath: nginx.conf readOnly: false volumes: - name: dump emptyDir: {} - name: mysqlbindir emptyDir: {} - name: log emptyDir: {} - name: id-rsa secret: defaultMode: 0600 secretName: somedb-id-rsa - name: nginx-config configMap: name: somedb-nginx-config - name: debian-cnf configMap: name: somedb-debian-cnf --- apiVersion: v1 kind: Service metadata: name: somedb-db-dump spec: clusterIP: None selector: app: db-dumps ports: - name: http port: 80 

附加说明:

  1. 就我们而言,我们每晚使用GitLab中的预定作业准备一个新的转储。 即 每天晚上,此部署都会自动推出,从而产生一个新的转储,并准备将其分发给所有测试开发人员环境。
  2. 为什么还要将volume /dump扔到init容器中(并且在脚本中检查/dump/version.txt的存在)? 如果在其下运行的服务器重新启动,则可以这样做。 容器将重新开始,并且没有进行此检查,转储将再次开始下载。 如果我们已经准备好一次转储,那么在下一次启动时(如果服务器重新启动),/ /dump/version.txt标志/dump/version.txt将提供有关此信息。
  3. 什么是db-dumps ? 我们使用dapp收集它,其Dappfile如下所示:

     dimg: "db-dumps" from: "ubuntu:16.04" docker: ENV: TERM: xterm ansible: beforeInstall: - name: "Install percona repositories" apt: deb: https://repo.percona.com/apt/percona-release_0.1-4.xenial_all.deb - name: "Add repository for borgbackup" apt_repository: repo="ppa:costamagnagianfranco/borgbackup" codename="xenial" update_cache=yes - name: "Add repository for mysql 5.6" apt_repository: repo: deb http://archive.ubuntu.com/ubuntu trusty universe state: present update_cache: yes - name: "Install packages" apt: name: "{{`{{ item }}`}}" state: present with_items: - openssh-client - mysql-server-5.6 - mysql-client-5.6 - borgbackup - percona-xtrabackup-24 setup: - name: "Add get_bindump.sh" copy: content: | {{ .Files.Get ".dappfiles/get_bindump.sh" | indent 8 }} dest: /get_bindump.sh mode: 0755 - name: "Add prepare_bindump.sh" copy: content: | {{ .Files.Get ".dappfiles/prepare_bindump.sh" | indent 8 }} dest: /prepare_bindump.sh mode: 0755 

步骤2:在开发人员环境中启动数据库


在开发人员的测试环境中推出MySQL数据库时,它在GitLab中具有一个按钮,该按钮通过RollingUpdate.maxUnavailable: 0策略使用MySQL启动部署的重新部署:



如何实施?
在GitLab中,当您单击reload db时 ,将部署具有以下规范的部署:

 spec: strategy: rollingUpdate: maxUnavailable: 0 

即 我们告诉Kubernetes更新Deployment (在下面创建一个新的),并确保至少有一个处于活动状态。 由于创建新壁炉时,它在工作时具有初始化容器,因此新容器不会进入运行状态,这意味着旧容器会继续工作。 而且只有在MySQL本身启动(并且就绪探测器工作)的那一刻,流量才切换到它,并且旧的(带有旧的数据库)才被删除。

有关此方案的详细信息,请参见以下材料:


选择的方法使我们能够等待,直到下载,解压缩并启动新的转储为止,只有在此之后,旧的转储才会从MySQL中删除。 因此,当我们准备一个新的转储时,我们正在与旧的基地安静地工作。

部署的init容器使用以下命令:

 curl "$DUMP_URL" | tar -C /var/lib/mysql/ -xvz 

即 我们下载在步骤1中准备的压缩数据库转储,将其解压缩到/var/lib/mysql ,然后在Deployment下启动,在该部署中,MySQL将使用已准备好的数据启动。 所有这些大约需要2个小时。

部署如下...
 apiVersion: apps/v1beta1 kind: Deployment metadata: name: mysql spec: strategy: rollingUpdate: maxUnavailable: 0 template: metadata: labels: service: mysql spec: imagePullSecrets: - name: regsecret nodeSelector: dedicated: non-prod-db tolerations: - key: "dedicated" operator: "Equal" value: "non-prod-db" effect: "NoExecute" initContainers: - name: getdump image: mysql-with-getdump command: ["/usr/local/bin/getdump.sh"] resources: limits: memory: "6000Mi" cpu: "1.5" requests: memory: "6000Mi" cpu: "1.5" volumeMounts: - mountPath: /var/lib/mysql name: datadir - mountPath: /etc/mysql/debian.cnf name: debian-cnf subPath: debian.cnf env: - name: DUMP_URL value: "http://somedb-db-dump.infra-db.svc.cluster.local/mysql_bindump.tar.gz" containers: - name: mysql image: mysql:5.6 resources: limits: memory: "1024Mi" cpu: "1" requests: memory: "1024Mi" cpu: "1" lifecycle: preStop: exec: command: ["/etc/init.d/mysql", "stop"] ports: - containerPort: 3306 name: mysql protocol: TCP volumeMounts: - mountPath: /var/lib/mysql name: datadir - mountPath: /etc/mysql/debian.cnf name: debian-cnf subPath: debian.cnf env: - name: MYSQL_ROOT_PASSWORD value: "password" volumes: - name: datadir emptyDir: {} - name: debian-cnf configMap: name: somedb-debian-cnf --- apiVersion: v1 kind: Service metadata: name: mysql spec: clusterIP: None selector: service: mysql ports: - name: mysql port: 3306 protocol: TCP --- apiVersion: v1 kind: ConfigMap metadata: name: somedb-debian-cnf data: debian.cnf: | [client] host = localhost user = debian-sys-maint password = password socket = /var/run/mysqld/mysqld.sock [mysql_upgrade] host = localhost user = debian-sys-maint password = password socket = /var/run/mysqld/mysqld.sock 

总结


事实证明,我们始终有Deployment ,它每天晚上推出并执行以下操作:

  • 获取新的数据库转储
  • 以某种方式为测试环境中的正确操作做好准备(例如,对某些表进行trankeytit,替换实际用户数据,创建必要的用户等);
  • 通过按CI中的按钮,每个开发人员都有机会将这样一个准备好的数据库推出到Deployment中的名称空间-由于其中提供了Service ,因此数据库将在mysql上可用(例如,它可能是名称空间中服务的名称)。

对于我们研究的示例,从真实副本创建转储大约需要6个小时,准备“基本映像”需要7个小时,而在开发人员环境中更新数据库则需要2个小时。 由于前两个动作是“在后台”执行的,并且对于开发人员是不可见的,因此实际上,他们可以在相同的2个小时内部署数据库的生产版本(大小为1 TB)。

评论中欢迎对拟议的计划及其组成部分提出疑问,批评和更正!

PS当然,我们知道在使用VMware和其他工具的情况下,可以创建虚拟机快照并从快照启动新的virusalka(这甚至更快),但是此选项不包括准备基础,但要考虑到结果会相同时间...更不用说并非每个人都有机会或渴望使用商业产品的事实。

PPS


K8s提示和技巧周期中的其他内容:


另请参阅我们的博客:

Source: https://habr.com/ru/post/zh-CN417509/


All Articles