openEuler22+GreatSQL+dbops玩转MGR
芬达,《芬达的数据库学习笔记》公众号作者,开源爱好者,擅长 MySQL、ansible。
背景
openEuler 是什么
openEuler22.03 LTS 是 openEuler 社区于 2022 年 3 月发布的开源操作系统(从系统版本的命名不难发现吧)。openEuler 社区的运营由华为为主导,社区以全球开源贡献者的合作,构建了这个高效、稳定和安全的操作系统。基于 Linux 内核的 openEuler 操作系统,支持 Kunpeng 以及其他处理器,旨在充分发挥计算芯片的潜力。它适用于数据库、大数据、云计算和人工智能等场景。通过社区合作,openEuler 构建了一个创新平台,创建了一个支持多处理器架构的统一开放操作系统,并推动了软件和硬件应用生态系统的繁荣。
openEuler22.03 LTS 带来了一系列关键功能,包括基于 Linux Kernel 5.10 的深度优化、新型媒体文件系统、分层内存扩展、用户模式协议栈、云原生调度增强、QEMU 热补丁、KubeOS、增强的轻量级安全容器、增强的 iSulad、双平面部署、边缘计算支持、嵌入式镜像,以及 secPaver 等。
openEuler22.03 LTS 可以被视为国产操作系统创新项目的首选系统版本之一。
GreatSQL 是什么
GreatSQL,作为 MySQL 分支 Percona 的延伸,立志成为中国广泛受欢迎的开源数据库。其上一版本基于 Percona Server 8.0.25 构建,而这次的新版本则使用 Percona Server 8.0.32 作为基础,引入了许多重要特性。新发布的 GreatSQL 8.0.32-24 版本增加了并行 load data、逻辑和 CLONE 备份加密、MGR 读写节点可动态绑定 VIP、SQL 兼容扩展、审计日志增强等重大特性。
GreatSQL 8.0.32-24 可以被视为国产开源数据库信创项目,并且解决 MySQL5.7 EOL 问题的重要替代方案之一。
dbops 是什么
dbops 是一款提供生产级别 MySQL 部署的 playbook 工具,由芬达个人开发。
GreatSQL 官方并未提供专门针对 openEuler 的编译安装包,而我发现部署 GreatSQL、GreatSQL MGR、GreatSQL HA 等都有许多细节需要注意。本文主要讲述我如何思考并开发 dbops 的新功能,以在 openEuler22 上成功部署 GreatSQL,并运行其"MGR 读写节点可动态绑定 VIP"功能(以下简称"GreatSQL HA 功能")。
dbops 如何支持 GreatSQL
dbops 本来就支持 MySQL 和 Percona,所以对 GreatSQL 的支持并不需要大规模的改动。以下大部分是一些针对部署 GreatSQL 与 MySQL 或 Percona 的不同之处的调整。
1. 只对 Linux - Generic 包的支持
由于 dbops 的目标是支持大量的国产操作系统,与 MySQL 一样,GreatSQL 提供了针对各种操作系统的预编译二进制包,但有一个是通用的,那就是 Linux - Generic 包。在 dbops 为 MySQL 提供支持以及在实际生产中,都在使用这个包。
同时,为了支持更多的 Linux 系统,我选择了基于 glibc2.17 的包,而非 glibc2.28 的包。前者意味着包是在 glibc 公共库版本为 2.17 的系统下编译出来的,因此,它不会包含大于 2.17 的库函数,兼容性会更强。
我选择支持的是 minimal 包,这个版本剔除了与调试相关的二进制文件和调试符号,体积非常小,仅为常规包的三分之一,我认为非 minimal 包并无优势。
目前,dbops 仅支持 GreatSQL-8.0.32-24-Linux-glibc2.17-x86_64.tar.xz 的部署。
2. dbops 的 mysql_ansible 的配置文件 common_config.yml 的调整
dbops 可以很方便的配置执行 playbook 的变量,变量设置非常集中,一般只需要修改两个文件,一个是全局参数配置文件 common_config.yml,另外一个是当前需要执行的 playbook yaml 文件。
- 新增了 db_type 参数,用于判断待部署的数据库类型,可选值为 MySQL、Percona、GreatSQL。根据 db_type 的不同,部署流程将有所区别。
- 将 mysql_base_dir 参数和 mysql_data_dir_base 参数设定为根据 db_type 自动生成,这样可以得到不同的目录路径。我希望避免将 MySQL 和 GreatSQL 放置在同一目录下,以便于区分。
- 对于 mysql_package 参数,即安装包的名称,如果是 MySQL,会根据 {{ mysql_version }} 自动生成;如果是 GreatSQL,将读取 greatsql_package 的设置。
- 新增了 fcs_use_greatsql_ha 开关参数,其默认值为 1,即如果部署的是 GreatSQL 并且采用 MGR 架构(执行的 playbook==mgr.yml),那么默认会部署"MGR 读写节点可动态绑定 VIP"的功能插件。
## DB TYPE,suport mysql,percona,greatsql
+ db_type: greatsql
#Directory of MySQL installation package
mysql_packages_dir: ../downloads/
+ greatsql_package: GreatSQL-8.0.32-24-Linux-glibc2.17-x86_64-minimal.tar.xz
+ percona_package: Percona-Server-8.0.29-21-Linux.x86_64.glibc2.17.tar.gz
## do not modify
- mysql_package: "{{ 'mysql-' + mysql_version + '-linux-' + ('glibc2.12' if mysql_version.startswith('5.') else 'glibc2.17') + '-x86_64' + ('.tar.gz' if mysql_version.startswith('5.') else '-minimal.tar.xz') }}"
+ mysql_package: "{% if db_type == 'mysql' %}{{ 'mysql-' + mysql_version + '-linux-' + ('glibc2.12' if mysql_version.startswith('5.') else 'glibc2.17') + '-x86_64' + ('.tar.gz' if mysql_version.startswith('5.') else '-minimal.tar.xz') }}{% elif db_type == 'percona' %}{{ percona_package }}{% elif db_type == 'greatsql' %}{{ greatsql_package }}{% endif %}"
## linux mysql run user name
mysql_user: mysql
mysql_group: mysql
mysql_user_password: Dbops@9999
## mysql install directory
- mysql_base_dir: /database/mysql/base/{{ mysql_version }}
+ mysql_base_dir: /database/{{ db_type }}/base/{{ mysql_version }}
## mysql_data_dir_base define mysql datadir base, real datadir= mysql_data_dir_base + /port
- mysql_data_dir_base: /database/mysql
+ mysql_data_dir_base: /database/{{ db_type }}
+ fcs_use_greatsql_ha: 1
3. 新增 GreatSQL 专属 my.cnf 模板,并对 GreatSQL HA 的新参数支持
在 mgr.yml 这个 playbook 里新增三个与 GreatSQL HA 相关的参数设置
+ greatsql_vip: 192.168.199.174
+ greatsql_net_work_interface: "ens33"
+ greatsql_netmast: "255.255.255.255"
新增了 mysql_ansible/roles/mysql_server/templates/8.0/greatsql-my.cnf.j2 模板,此模板是从 percona-my.cnf.j2 模板克隆而来。为了支持 GreatSQL HA 的相关参数设置,我使用 jinja2 语法设置了判断逻辑,只有满足以下三个条件,才会添加这些参数:
- 数据库类型为 'greatsql' (在 common_config.yml 中设置)
- 设置了 fcs_use_greatsql_ha: 1 开关,要求部署 GreatSQL HA 功能(在 common_config.yml 中设置)
- 在安装 MGR 时(运行 ansible-playbook mgr.yml 时)
+ {% if db_type == 'greatsql' and fcs_use_greatsql_ha == 1 and make_mgr_role_included is defined and make_mgr_role_included %}
+ #GreatSQL MGR vip
+ plugin-load-add=greatdb_ha.so
+ loose-greatdb_ha_enable_mgr_vip=1
+ loose-greatdb_ha_mgr_vip_ip={{ greatsql_vip }}
+ loose-greatdb_ha_mgr_vip_mask={{ greatsql_netmast }}
+ loose-greatdb_ha_mgr_vip_nic={{ greatsql_net_work_interface }}
+
+ #single-primary mode
+ loose-group_replication_single_primary_mode=1
+ loose-group_replication_enforce_update_everywhere_checks=0
+ {% endif %}
前面设置的三个参数值,greatsql_vip、greatsql_netmast、greatsql_net_work_interface 会对应传入到 my.cnf 里。
4. 支持自动下载 GreatSQL 安装包
+ - name: Download GreatSQL binary tarball if not found locally and auto download is enabled(local)
+ ansible.builtin.get_url:
+ url: "https://product.greatdb.com/{{ mysql_package[0:18] }}/{{ mysql_package }}"
+ dest: "{{ mysql_packages_dir }}/{{ mysql_package }}"
+ mode: '0644'
+ timeout: 30
+ headers:
+ User-Agent: "Wget/1.21.1"
+ when: not mysql_server__package_file.stat.exists and fcs_auto_download_mysql == 1 and db_type == 'greatsql'
+ delegate_to: 127.0.0.1
在正常情况下,您应该手动上传 GreatSQL 的包到 downloads/ 文件夹中。如果在 downloads/ 文件夹中没有找到对应的包,且在 common_config.yml 文件中设置了 fcs_auto_download_mysql == 1(允许从互联网下载安装包),并且 db_type == 'greatsql',那么将会自动从互联网下载安装包。
dbops 本身就支持下载 MySQL 安装包的功能。但由于 GreatSQL 的安装包下载路径不同,因此我额外添加了一个下载链接,以实现相同的下载功能。
5. 修改 GreatSQL 启动服务
为了让 GreatSQL HA 支持 mysqld 执行通常需要较高权限才能操作的挂载和卸载 VIP 操作,我们需要进行一些特殊设置。官方原先提供了两个方案,但我提出了新的解决方案。
在服务设置中,我们加入以下代码:
[Service]
+ {% if db_type == 'greatsql' and fcs_use_greatsql_ha == 1 and make_mgr_role_included is defined and make_mgr_role_included %}
+ AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
+ {% endif %}
只有当满足条件(需要部署 GreatSQL HA)时,系统会自动在启动服务中配置 "CAP_NET_ADMIN CAP_NET_RAW" 的权限。
我设计的这种方法也得到了官方的采纳,成为首选推荐方法。
6. fix 部署 GreatSQL 有可能报错的问题
GreatSQL 在部署后可能在使用 mysqld 初始化时,或者初始化后使用 mysql 登录时,报告 libcrypto.so 或 libssl.so 找不到。
[root@192-168-199-171 ~]# /usr/local/mysql/bin/mysqld --defaults-file=/database/mysql/etc/3306/my.cnf --initialize-insecure
/usr/local/mysql/bin/mysqld: error while loading shared libraries: libcrypto.so: cannot open shared object file: No such file or directory
[root@192-168-199-171 lib]# mysql -uroot -p -S /database/mysql/data/3307/mysql.sock
mysql: error while loading shared libraries: libssl.so: cannot open shared object file: No such file or directory
针对此问题,我们有两种解决方案。第一种方法是使用 yum install openssl-devel
安装,之后系统库就会包含这两个 so 库:
/usr/lib64/libcrypto.so
/usr/lib64/libssl.so
然而,我们也有第二种解决方法。GreatSQL 的 Generic 包其实已经编译了这两个库,只是它们被命名为 libcrypto.so.10 和 libssl.so.10,而非 libcrypto.so 和 libssl.so。我们只需在 /usr/local/mysql/lib/private/ 文件夹内为这两个库创建软链接即可。
并且,我认为采用第二种方法,使用官方包内的 libcrypto.so 和 libssl.so,是最佳的解决方案。
改动代码如下:
mysql_ansible/roles/mysql_server/tasks/fix_greatsql_install.yml
+ - name: Create symbolic links for libssl.so and libcrypto.so
+ file:
+ src: "/usr/local/mysql/lib/private/{{ link.src }}"
+ dest: "/usr/local/mysql/lib/private/{{ link.dest }}"
+ state: link
+ owner: "{{ mysql_user }}"
+ group: "{{ mysql_group }}"
+ loop:
+ - { src: 'libssl.so.10', dest: 'libssl.so' }
+ - { src: 'libcrypto.so.10', dest: 'libcrypto.so' }
+ loop_control:
+ loop_var: link
mysql_ansible/roles/mysql_server/tasks/main.yml
- name: Install mysql to /usr/local/mysql
ansible.builtin.import_tasks: install_mysql.yml
+ - name: Fix libcrypto.so and libssl.so not find by create link if db_type is greatsql
+ ansible.builtin.import_tasks: fix_greatsql_install.yml
+ when: db_type == 'greatsql'
- name: Init mysql datadir
ansible.builtin.import_tasks: initialize_mysql_datadir.yml
7. 检查网卡是否一致
在设定了 VIP 漂移的三台机器中,与 MHA 架构相同,我们可能会遇到一种状况:在一开始设置网卡绑定时,主机管理员可能会没注意,发生以下情况:
192.168.199.171 网卡名 bond1
192.168.199.172 网卡名 bond1
192.168.199.173 网卡名 bond0
你可能已经发现了,第三台机器的网卡名与前两台不一致。因此,如果发生高可用漂移,MHA 或 MGR 在选择主机时,如果选择了第三台作为主机,那么根据 greatsql_net_work_interface: "bond1"
的设置,漂移操作可能会失败。虽然我还没有进行过测试,但这个问题在 MHA 架构中肯定会发生,而且在 GreatSQL HA 架构中也有很大可能发生!
因此,在运行 ansible-playbook 部署 mgr.yml 时,我已在第一步的 pre_check_and_set(检查和设置系统参数)中,加入了检查网卡名是否一致的步骤。如果网卡名不一致,playbook 将报错并终止运行,给出提示。代码如下:
- name: Add network interface alias to a temporary file
ansible.builtin.lineinfile:
path: "/tmp/net_aliases.txt"
line: "{{ ansible_default_ipv4.alias }}"
create: true
mode: '0644'
- name: Fetch copy
ansible.builtin.fetch:
src: /tmp/net_aliases.txt
dest: /tmp/ssh
- name: Append file /tmp/net_aliases.txt (delegate to 127.0.0.1)
ansible.builtin.shell: set -o pipefail && find /tmp/ssh/ -name "*.txt" -type f -exec sh -c 'cat {}' \; | sort | uniq | wc -l
register: pre_check_and_set__shell_output
changed_when: false
run_once: true
delegate_to: 127.0.0.1
- name: Check if shell output is 1
ansible.builtin.assert:
that: pre_check_and_set__shell_output.stdout | int == 1
fail_msg: "Network card names are different!"
run_once: true
delegate_to: 127.0.0.1
- name: Delete /tmp/net_aliases.txt
ansible.builtin.file:
path: /tmp/net_aliases.txt
state: absent
- name: Delete /tmp/ssh/ (delegate to 127.0.0.1)
ansible.builtin.file:
path: /tmp/ssh/
state: absent
run_once: true
delegate_to: 127.0.0.1
8. 支持使用 jemalloc 内存分配器
GreatSQL 和 Percona 官方都推荐使用 jemalloc 替代默认的 malloc,我至今没有发现 MySQL 官方的类似建议。然而,在实践中,我曾经通过替换 malloc 为 jemalloc 内存管理器来解决内存泄露问题,尽管根本原因仍不清楚。因此,我之前已经在我的 dbops 工具中加入了在部署时选择使用 jemalloc 内存分配器的功能,它是在 systemd 服务中实现的。
以下是涉及的代码:
mysql_ansible/playbooks/common_config.yml
# 配置文件中提供一个开关,决定是否使用 jemalloc 内存分配器,其默认值为 0,即不修改内存分配器
fcs_mysql_use_jemalloc: 1
mysql_ansible/roles/mysql_server/tasks/install_mysql_dependents.yml
- name: Install libaio and numactl
ansible.builtin.yum:
name: "{{ package.name }}"
state: present
loop:
- { name: 'libaio' }
- { name: 'numactl' }
loop_control:
loop_var: package
tags:
- dependents
- name: Install jemalloc
when: fcs_mysql_use_jemalloc == 1
tags:
- dependents
block:
- name: Install jemalloc using yum
ansible.builtin.yum:
name: jemalloc
state: present
register: mysql_server__jemalloc_install_result
ignore_errors: true
- name: Set jemalloc rpm file name based on OS
ansible.builtin.set_fact:
mysql_server__jemalloc_rpm_file: "{{ 'jemalloc-3.6.0-1.el8.x86_64.rpm' if os_type in ['openEuler22', 'openEuler20', 'CentOS8', 'BigCloud8', 'Anolis OS8'] else 'jemalloc-3.6.0-1.el7.x86_64.rpm' }}"
- name: Copy jemalloc rpm to target server
ansible.builtin.copy:
src: "../files/{{ mysql_server__jemalloc_rpm_file }}"
dest: "/tmp/{{ mysql_server__jemalloc_rpm_file }}"
mode: '0755'
when: mysql_server__jemalloc_install_result.failed
- name: Install jemalloc from local file
ansible.builtin.yum:
name: "/tmp/{{ mysql_server__jemalloc_rpm_file }}"
state: present
disable_gpg_check: true
register: mysql_server__jemalloc_local_install_result
when: mysql_server__jemalloc_install_result.failed
ignore_errors: true
- name: Check if jemalloc installation failed
ansible.builtin.fail:
msg: "Failed to install jemalloc"
when: mysql_server__jemalloc_install_result.failed and mysql_server__jemalloc_local_install_result.failed
这段代码主要是用于安装 mysql_server 的依赖包,如果你在配置中设置了 fcs_mysql_use_jemalloc: 1
,那么将会安装 jemalloc。默认情况下,它会尝试使用 yum 来安装。如果 yum 安装失败,那么它会尝试使用 dbops 自带的 jemalloc
包。
原因是,预备的 jemalloc 包只有两个版本:一个是针对 EL7,一个是针对 EL8。并没有专门为国产操作系统准备的专用包。在国产操作系统上,你应该优先使用 yum 来安装适合该系统的包。如果使用 yum 安装失败,你可以考虑使用 EL7 或者 EL8 的 jemalloc 包来进行兼容性安装。
mysql_ansible/roles/mysql_server/templates/mysql.service.j2
[Service]
...
{% if fcs_mysql_use_jemalloc == 1 %}
{% if os_type in ['openEuler22','openEuler20'] %}
Environment="LD_PRELOAD=/usr/lib64/libjemalloc.so.2"
{% else %}
Environment="LD_PRELOAD=/usr/lib64/libjemalloc.so.1"
{% endif %}
{% endif %}
根据 yum 或者 rpm 包方式安装 libjemalloc 后,不同操作系统的 libjemalloc.so 路径可能会不同,我在服务配置里会做判断和正确加载。
结语与反馈征集
如果你对 dbops 感兴趣,欢迎你使用,并提出宝贵的建议或问题。如果在使用过程中遇到任何问题,或有任何改进的建议,欢迎在 dbops 的 gitee 项目页面上提交 issue。你的反馈将帮助我不断改进,使其更好地服务于所有开源数据库用户。
我会认真处理每一个提交的 issue,力求在第一时间给出解答或解决方案。同时,我们也欢迎你为 dbops 的开发做出贡献,无论是提交代码,还是参与讨论,我都非常欢迎。感谢你的支持!
Enjoy GreatSQL,Enjoy dbops :)
文章转载自公众号:GreatSQL社区