> 芬達,《芬達的資料庫學習筆記》公眾號作者,開源愛好者,擅長 MySQL、ansible。 ## 背景 ### openEuler 是什麼 openEuler22.03 LTS 是 openEuler 社區於 2022 年 3 月發佈的開源操作系統(從系統版本的命名不難發現吧)。openEuler ...
芬達,《芬達的資料庫學習筆記》公眾號作者,開源愛好者,擅長 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 問題的重要替代方案之一。
關於 MySQL5.7 EOL 問題,可以翻閱我之前寫的文章《陰謀論: MySQL 將死,國產資料庫的春天?》
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" 的許可權。
![圖片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
我設計的這種方法也得到了官方的採納,成為首選推薦方法。
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