使用 Ansible 管理 MySQL 復制
簡介:
Ansible 是一個配置管理和應用部署工具,功能類似於目前業界的配置管理工具 Chef,Puppet,Saltstack。Ansible 是通過 Python 語言開發。Ansible 平臺由 Michael DeHaan 創建,他同時也是知名軟件 Cobbler 與 Func 的作者。Ansible 的第一個版本發布於 2012 年 2 月,相比較其它同類產品來說,Ansible 還是非常年輕的,但這並不影響他的蓬勃發展與大家對他的熱愛。
Ansible 默認通過 SSH 協議管理機器,所以 Ansible 不需要安裝客戶端程序在服務器上。您只需要將 Ansible 安裝在一臺服務器,在 Ansible 安裝完後,您就可以去管理控制其它服務器。不需要為它配置數據庫,Ansible 不會以 daemons 方式來啟動或保持運行狀態。Ansible 的目標有如下:
- 自動化部署應用
- 自動化管理配置
- 自動化的持續交付
- 自動化的(AWS)雲服務管理。
根據 Ansible 官方提供的信息,當前使用 Ansible 的用戶有:evernote、rackspace、NASA、Atlassian、twitter 等。
回頁首
安裝前的準備
Ansible 服務器需求
當前 Ansible 可以運行在安裝了 Python2.6 的任何機器上(暫不支持 windows 機器做中心控制服務器),這包括 RedHat,Debian,CentOS,OS X,BSDS 等等。
被管理服務器需求
在被管理節點服務器上,需要安裝 Python2.4 或更高的 Python 版本,如果在遠程機器上運行的 Python 小於 Python2.5 的話,您將需要安裝 python-simplejson。如果在遠程機器的啟用了 SELinux,你還需要安裝 libselinux-python。
回頁首
安裝 Ansible
如果你是 CentOS/RedHat、Debian、Ubuntu 的用戶,您可以使用系統包管理器 yum 或 apt-get 安裝(CentOS/RedHat 需要安裝 epel 包才能通過 yum 安裝)。不過在這裏,筆者強烈建議您使用 pip 安裝 Ansible。那麽什麽是 pip 呢? pip 是一個 Python 包安裝和管理工具,功能類似 Node.js 的 npm、Ruby 的 gem。通過 pip 可以非常方便對 Python 包進行安裝、升級、刪除等管理操作。前面介紹過 Ansible 是使 Python 開發,那麽自然 Ansible 也可以通過 pip 安裝。如想了解 pip 的更多詳情,請訪問 https://pypi.python.org/pypi/pip/。
通過 pip 安裝 Ansible
在使用 pip 之前,請確保您的系統已經安裝 Pthon 的 setuptools 包
yum – y install python-setuptools
安裝 pip
easy_install pip
通過 pip 安裝 Ansible
pip install ansbile
怎麽樣?通過 pip 安裝 Ansible 是不是很簡單,只要簡單的三個步驟,就完成了操作。只要支持 Python 的操作系統,都可以使用 pip 安裝。
好的,現在你如果照著做的話,你已經在你的服務器上安裝好了 Ansible。
前面介紹過 Ansible 是通過 SSH 協議管理機器,Ansible 默認使用 SSH Keys 方式通信,這個也是 Ansible 官方極力推薦的方法,但是如果您想使用密碼的話,當然也是可以的。為了啟用密碼驗證,使用 --ask-pass 選項,在本文中,筆者采用密鑰驗證方式進行演示,讀者可以根據的需要選擇使用密碼驗證還是密鑰驗證。
回頁首
Ansible 實戰
使用 Ansible 在遠程服務器上執行命令
在安裝好 Ansible 後,先通過使用 Ansible 在其它服務器上執行一條命令來確認 Ansible 服務器與其它服務器的連通性。在執行 Ansible 管理服務器前,需要要配置服務器信息。Ansible 使用文件來存儲您要管理的服務器信息,這個文件在 Ansible 中叫清單文件,默認的清單文件存放在 /etc/ansible/hosts , 如果這個文件不存在,您可以新建該文件。當然您也可以在執行 Ansible 命令時執行清單文件的路徑。清單文件的格式類似於 INI 文件,如下:
[webserver] 192.168.1.1 192.168.1.2 [databaseServer] 192.168.1.10 192.168.1.11
中括號符號中的是組名,組名可以自定義,但建議使用一個有說明意義的名稱,如 webserver、databaseServer,組名後是各組內的成員 IP。下面筆者通過 Ansible 在遠程服務器上執行一條命令:
ansible webserver -a "whoami"
圖 1.ansible 執行命令結果
從 Ansible 的執行結果,我們知道 Ansible 服務器與遠程服務器的連通是沒有問題,並且執行命令成功。如果您想指定清單文件的路徑,那麽使用 – i 參數,加上文件路徑即可,如下:
ansible webserver – i /etc/ansible/other-hosts – a“whoami”
以上是一條 ad-hoc 命令,也即臨時執行命令。ad-hoc 是 Ansible 裏的一個概念,通過使用 ad-hoc 您可以快速的完成命令的操作,如果你的操作只包含命令操作,而沒有配置管理的內容的話,那麽強烈建議使用 ad-hoc 命令,如文件傳輸、包管理、用戶和組的管理、服務管理等等。
使用 Ansible 管理 MySQL 復制
Ansible 附帶了非常多的模塊,Ansible 將之稱為“模塊庫”。模塊可以在遠程服務器上直接執行,也可以通過 Playbooks 執行,關於 Playbooks 將會在後面做介紹。Ansible 的模塊非常豐富,包括雲服務管理、文件、數據庫、命令、網絡等各個方面。筆者在這裏演示如果通過 Ansible 其中的一個模塊“mysql_replication”來管理 MySQL 的復制。首先在配置好 MySQL master 服務器與 slave 服務器的 MySQL 環境,使其滿足 MySQL 復制的配置需求,並且 MySQL 主從服務器都要安裝 Python 的 MySQLdb 模塊,通過 Ansible 在主、從服務器上安裝 MySQLdb。
ansible databaseServer -m yum -a "name=MySQL-python state=present"
在 MySQL master 服務器上創建 MySQL 復制帳號
mysql -uroot -p mysql > GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO ‘repl‘@‘192.168.1.%‘ IDENTIFIED BY ‘123456‘; mysql > flush privileges; mysql > quit;
在 MySQL master 服務器打開 mysql 的二進制日誌功能 , 在 /etc/my.cnf 文件的 [mysqld] 部分添加以下內容:
server-id = 1
log-bin=mysql-bin
重啟 MySQL , 使設置生效。
/etc/init.d/mysqld restart
修改 MySQL slave 數據庫的 mysql 配置文件 , 在 /etc/my.cnf 文件的 [mysqld] 部分添加以下內容:
server-id = 2
重啟 MySQL , 使設置生效。
/etc/init.d/mysqld restart
通過 Ansible 的 mysql_replication 模塊獲取到 MySQL master 服務器的狀態
ansible mysql-master -m mysql_replication -a "login_user=root login_password=123456 mode=getmaster"
圖 2.獲取 MySQL Master 服務器狀態
Ansible 的結果都是采用的 json 格式顯示,結果輸出了 MySQL 的正在使用的二進制日誌文件名稱以及 Position 的值,記錄這兩個值。
通過 Ansible 的 mysql_replication 模塊在 MySQL Slave 服務器上指定 MySQL master 服務器及設置復制信息。
ansible mysql-slave -m mysql_replication – a "login_user=root login_password=123456 mode=changemaster master_host=‘172.16.64.147‘ master_user=repl master_password=123456 master_log_file=mysql.000003 master_log_pos=711"
圖 3.MySQL Change Master 執行結果:
在 MySQL 從服務器上啟動復制
ansible mysql-slave -m mysql_replication -a "login_user=root login_password=123456 mode=startslave"
圖 4.在 MySQL Slave 開啟復制執行結果:
查看 MySQL 復制狀態
ansible mysql-slave -m mysql_replication -a "login_user=root login_password=123456 mode=getslave"
圖 5.查看 MySQL 復制狀態
請註意圖中紅框選處,顯示 Slave_IO_Running 和 Slave_SQL_Running 的值均為“Yes”,Slave_IO_State 的狀態為“waiting for master to send event”,這表示剛剛通過 ansible 建立 MySQL 主從服務器之間的復制是正常的。現在您就可以嘗試在 MySQL 主服務器上添加一些數據,看看會不會同步同步從服務器上。
通過 Ansible playbooks 管理 MySQL 復制
通過使用 Ansible 的 ad-hoc 命令行指令來使用模塊命令是不是感覺很便捷,但我們仍然要手工去做很多事情,如安裝 MySQL、配置 MySQL 復制環境等。雖然這些也可以通過 ad-hoc 命令行完成,但是如果我下次還要在其它服務器上配置 MySQL 復制,那麽我又得重復敲一遍指令,如果是這樣的話,那麽這將是一樣非常糟糕的事情。那麽 Ansible 有沒有辦法解決這個問題呢?當然是有,Ansible 的 Playbooks 就可以很好的解決這個問題,Playbooks 翻譯成中文就是劇本的意思,顧名思義,就是將所有的操作按照約定好的規則在文件中定義好做什麽,然後再按照劇本演出一部大劇。Playbooks 非常適合部署復雜應用及配置的重復使用。Playbooks 聲明配置,並你設置好的配置推送到指定的遠程主機應用。Playbooks 與 saltstack 的 SLS(Salt State Tree)、puppet 的配置管理相同。
Playbooks 使用 YAML 格式,語法應用簡明易懂。以下筆者就使用 Ansilbe 來實現 MySQL 的安裝、MySQL 復制環境的配置、MySQL 復制設置。
圖 6.目錄結構圖
為了有效的組織和管理 Ansible 中的文件,筆者使用了 Ansible Playbook 的 roles 特性,這個特性在 Ansible 版本 1.2 或之後的版本中有效。筆者在 /etc/ansible 目錄下建立了一個 roles 目錄用於存放 Playbooks 定義的各個角色,這個是 Ansible 官方定義的默認 roles 目錄,當然你可以添加或修改 roles 的目錄路徑,通過修改 /etc/ansible/ansible.cfg 文件中的 roles_path 值。roles 目錄下是各個應用的目錄名稱,各目錄以應用名稱命令,這個名稱可以自定義,但最好是設置一個有意義的名稱,如本例中的 mysql,mysql 命令下包括了 defaults、handlers、meta、tasks、templates、vars 目錄,分別對應不同的功能,這幾個目錄的命名都是 Ansible 官方定義好的,不可以修改,各目錄功能說明見表 1。默認 Ansible 只處理各目錄下文件名為 main.yml 中定義的操作,如果您有多個文件,可以在 main.yml 文件中 include 其它文件。
表 1.各目錄功能說明
目錄名 | 說明 |
---|---|
defaults | 默認變量存放目錄 |
handlers | 處理程序(當發生改變時需要執行的操作) |
meta | 角色依賴關系處理 |
tasks | 具體執行的任務操作定義 |
templates | 模板文件存放目錄 |
vars | 變量文件目錄 |
代碼 1.defaults 目錄的 main.yml 信息
cat /etc/ansible/roles/mysql/defaults/main.yml --- mysql_port: 3306 mysql_bind_address: "0.0.0.0" mysql_root_db_pass: 123456 mysql_db: - name: foo replicate: yes - name: bar replicate: no mysql_users: - name: jack pass: 123456 priv: "*.*:ALL" mysql_repl_user: - name: repl pass: 123456 mysql_repl_role: master mysql_db_id: 7
該文件包含了一些與 MySQL 相關的一些默認的變量信息,在這裏設置了 MySQL 的端口、綁定地址,root 用戶的密碼,及新增的數據庫、用戶、復制用戶信息。
代碼 2.handlers 目錄的 main.yml 信息
cat /etc/ansible/roles/mysql/handlers/main.yml --- - name: restart mysql service: name={{ mysql_service }} state=restarted
handlers 目錄中的 main.yml 文件包含的操作是執行完 tasks 之後服務器發生變化之後可供調用 的操作,本例中是重啟 MySQL 操作
代碼 3.tasks 目錄裏的 main.yml 信息
--- - name: Add the OS specific variables include_vars: "{{ ansible_os_family }}.yml" - name: Install the mysql packages in Redhat derivatives yum: name={{ item }} state=installed with_items: mysql_pkgs when: ansible_os_family == ‘RedHat‘ - name: Install the mysql packages in Debian derivatives apt: name={{ item }} state=installed update_cache=yes with_items: mysql_pkgs environment: env when: ansible_os_family == ‘Debian‘ - name: Copy the my.cnf file template: src=my.cnf.{{ ansible_os_family }}.j2 dest={{ mysql_conf_dir }}/my.cnf notify: - restart mysql - name: Start the mysql services Redhat service: name={{ mysql_service }} state=started enabled=yes - name: update mysql root password for all root accounts mysql_user: name=root host={{ item }} password={{ mysql_root_db_pass }} with_items: - "{{ ansible_hostname }}" - 127.0.0.1 - ::1 - localhost when: ansible_hostname != ‘localhost‘ - name: update mysql root password for all root accounts mysql_user: name=root host={{ item }} password={{ mysql_root_db_pass }} with_items: - 127.0.0.1 - ::1 - localhost when: ansible_hostname == ‘localhost‘ - name: ensure anonymous users are not in the database mysql_user: name=‘‘ host={{ item }} login_user=root login_password={{ mysql_root_db_pass }} state=absent with_items: - localhost - "{{ ansible_hostname }}" - name: remove the test database mysql_db: name=test login_user=root login_password={{ mysql_root_db_pass }} state=absent - name: Create the database‘s mysql_db: name={{ item.name }} login_user=root login_password={{ mysql_root_db_pass }} state=present with_items: mysql_db when: mysql_db|lower() != ‘none‘ - name: Create the database users mysql_user: login_user=root login_password={{ mysql_root_db_pass }} name={{ item.name }} password={{ item.pass|default("foobar") }} priv={{ item.priv|default("*.*:ALL") }} state=present host={{ item.host | default("localhost") }} with_items: mysql_users when: mysql_users|lower() != ‘none‘ - name: Create the replication users mysql_user: login_user=root login_password={{ mysql_root_db_pass }} name={{ item.name }} host="%" password={{ item.pass|default("foobar") }} priv=*.*:"REPLICATION SLAVE" state=present with_items: mysql_repl_user when: mysql_repl_role == ‘master‘ - name: Check if slave is already configured for replication mysql_replication: login_user=root login_password={{ mysql_root_db_pass }} mode=getslave ignore_errors: true register: slave when: mysql_repl_role == ‘slave‘ - name: Ensure the hostname entry for master is available for the client. lineinfile: dest=/etc/hosts regexp="{{ mysql_repl_master }}" line="{{ mysql_repl_master + " " + hostvars[mysql_repl_master].ansible_default_ipv4.address }}" state=present when: slave|failed and mysql_repl_role == ‘slave‘ and mysql_repl_master is defined - name: Get the current master servers replication status mysql_replication: login_user=root login_password= {{ mysql_root_db_pass }} mode=getmaster delegate_to: "{{ mysql_repl_master }}" register: repl_stat when: slave|failed and mysql_repl_role == ‘slave‘ and mysql_repl_master is defined - name: Change the master in slave to start the replication mysql_replication: login_user=root login_password={{ mysql_root_db_pass }} mode=changemaster master_host={{ mysql_repl_master }} master_log_file={{ repl_stat.File }} master_log_pos={{ repl_stat.Position }} master_user={{ mysql_repl_user[0].name }} master_password={{ mysql_repl_user[0].pass }} when: slave|failed and mysql_repl_role == ‘slave‘ and mysql_repl_master is defined - name: start slave in slave to start the replication mysql_replication: login_user=root login_password={{ mysql_root_db_pass }} mode=startslave when: slave|failed and mysql_repl_role == ‘slave‘ and mysql_repl_master is defined
tasks 目錄中的 main.yml 包括了 playbooks 中所有要執行操作,每一個操作都會有一個名稱,用於簡單的描述本次的操作內容,本例中的 tasks 包括了:
- MySQL 安裝;
- MySQL 管理員用戶 root 的密碼修改;
- MySQL 配置文件的生成;
- 測試庫的刪除;
- 空用戶的刪除;
- 數據庫的創建(若需要);
- 數據庫用戶的創建及賦權(苦需要);
- 復制用戶創建及密碼設置;
- 獲取 master 狀態;
- Slave 連接 master 並開啟復制。
Ansible playbooks 中的每個任務是按序依次執行的,這個大家可以看 ansible playbooks 的執行過程。
代碼 4.templates 目錄中的 my.cnf.RedHat.j2
cat /etc/ansible/roles/mysql/templates/my.cnf.RedHat.j2 [mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock user=mysql # Disabling symbolic-links is recommended to prevent assorted security risks symbolic-links=0 port={{ mysql_port }} bind-address={{ mysql_bind_address }} log_bin = mysql-bin server-id = {{ mysql_db_id }} {% if mysql_repl_role == ‘master‘ %} #log_bin = mysql-bin expire_logs_days = 10 max_binlog_size = 100M {% for i in mysql_db %} {% if i.replicate|default(1) %} binlog_do_db = {{ i.name }} {% endif %} {% endfor %} {% for i in mysql_db %} {% if not i.replicate|default(1) %} binlog_ignore_db = {{ i.name }} {% endif %} {% endfor %} {% endif %} [mysqld_safe] log-error=/var/log/mysqld.log pid-file=/var/run/mysqld/mysqld.pid
templates 目錄中包括了一個 MySQL 的配置文件樣本。
代碼 5.vars 目錄中的 main.yml
cat /etc/ansible/roles/mysql/vars/main.yml --- env: RUNLEVEL: 1
代碼 6.vars 目錄中的 RedHat.yml
cat /etc/ansible/roles/mysql/vars/RedHat.yml --- mysql_pkgs: - libselinux-python - mysql-server - MySQL-python mysql_service: mysqld mysql_conf_dir: "/etc/"
vars 目錄中是此次操作所涉及到的變量信息。
代碼 7.ansible 執行 MySQL 復制配置的 playbook 配置文件 mysql_repl.yml
cat /etc/ansible/mysql_repl.yml - hosts: mysql-master roles: - {role: mysql, mysql_db: none,mysql_users: [{name: jack, pass: 123456, priv: "*.*:ALL"}],mysql_db_id: 1008 } - hosts: mysql-slave roles: - {role: mysql, mysql_db: none, mysql_users: none,mysql_repl_role: slave, mysql_repl_master: 192.168.1.10,mysql_db_id:1009, mysql_repl_user: [{name: repl, pass: 123456}] }
安裝並配置 MySQL 服務器,並完成 MySQL 主從復制設置。
在 /etc/ansible/hosts 文件中添加服務器信息
cat /etc/ansible/hosts [mysql-master] 192.168.1.10 [mysql-slave] 192.168.1.11
使用 Ansible playbook 配置 MySQL 復制
ansible-playbook mysql_repl.yml PLAY [mysql-master] *********************************************************** GATHERING FACTS *************************************************************** ok: [192.168.1.10] TASK: [mysql | Add the OS specific variables] ********************************* ok: [192.168.1.10] TASK: [mysql | Install the mysql packages in Redhat derivatives] ************** changed: [192.168.1.10] => (item=libselinux-python,mysql-server,MySQL-python) TASK: [mysql | Install the mysql packages in Debian derivatives] ************** skipping: [192.168.1.10] TASK: [mysql | Copy the my.cnf file] ****************************************** ok: [192.168.1.10] TASK: [mysql | Start the mysql services Redhat] ******************************* changed: [192.168.1.10] TASK: [mysql | update mysql root password for all root accounts] ************** changed: [192.168.1.10] => (item=client001) changed: [192.168.1.10] => (item=127.0.0.1) changed: [192.168.1.10] => (item=::1) changed: [192.168.1.10] => (item=localhost) TASK: [mysql | update mysql root password for all root accounts] ************** skipping: [192.168.1.10] => (item=127.0.0.1) skipping: [192.168.1.10] => (item=::1) skipping: [192.168.1.10] => (item=localhost) TASK: [mysql | ensure anonymous users are not in the database] **************** changed: [192.168.1.10] => (item=localhost) ok: [192.168.1.10] => (item=client001) TASK: [mysql | remove the test database] ************************************** changed: [192.168.1.10] TASK: [mysql | Create the database‘s] ***************************************** changed: [192.168.1.10] => (item={‘name‘: ‘benz‘}) changed: [192.168.1.10] => (item={‘name‘: ‘benz2‘}) TASK: [mysql | Create the database users] ************************************* changed: [192.168.1.10] => (item={‘pass‘: ‘foobar‘, ‘name‘: ‘ben3‘, ‘priv‘: ‘*.*:ALL‘}) changed: [192.168.1.10] => (item={‘name‘: ‘ben2‘, ‘pass‘: ‘foo‘}) TASK: [mysql | Create the replication users] ********************************** changed: [192.168.1.10] => (item={‘name‘: ‘repl‘, ‘pass‘: ‘foobar‘}) TASK: [mysql | Check if slave is already configured for replication] ********** skipping: [192.168.1.10] TASK: [mysql | Ensure the hostname entry for master is available for the client.] *** skipping: [192.168.1.10] TASK: [mysql | Get the current master servers replication status] ************* skipping: [192.168.1.10] TASK: [mysql | Change the master in slave to start the replication] *********** skipping: [192.168.1.10] TASK: [mysql | start slave in slave to start the replication] ***************** skipping: [192.168.1.10] #### 限於篇幅,部分輸出省略 ####### PLAY RECAP ******************************************************************** 192.168.1.10 : ok=12 changed=8 unreachable=0 failed=0 192.168.1.11 : ok=17 changed=8 unreachable=0 failed=0
大家仔細看輸出,會發現 ansible 的 task 是按順序一個個依次執行,在執行每個任務時,都會顯示該任務的名稱及狀態,這樣非常利於查看任務執行的狀態。然後在任務的最終結尾處(即 PLAY RECAP)是各個 task 的狀態統計總結,包含了各個 task 的執行情況,如執行成功數,變更數、錯誤數。通過這裏可以判斷 task 是否成功執行完成。
回頁首
Ansible 與其它配置管理的對比
筆者選擇了目前幾款主流的與 Ansible 功能類似的配置管理軟件 Puppet、Saltstack,這裏所做的對比不針對各個軟件的性能作比較,只是對各個軟件的特性做個對比。具體內容見表 1:
表 2.Ansible Vs. puppet Vs. Saltstack 對比
Puppet | Saltstack | Ansible | |
---|---|---|---|
開發語言 | Ruby | Python | Python |
是否有客戶端 | 有 | 有 | 無 |
是否支持二次開發 | 不支持 | 支持 | 支持 |
服務器與遠程機器是否相互驗證 | 是 | 是 | 是 |
服務器與遠程機器通信是否加密 | 是,標準 SSL 協議 | 是,使用 AES 加密 | 是,使用 OpenSSH |
平臺支持 | 支持 AIX、BSD、HP-UX、Linux、 MacOSX、Solaris、 Windows | 支持 BSD、Linux、Mac OS X、Solaris、 Windows | 支持 AIX、BSD、 HP-UX、 Linux、Mac OSX、Solaris |
是否提供 web ui | 提供 | 提供 | 提供,不過是商業版本 |
配置文件格式 | Ruby 語法格式 | YAML | YAML |
命令行執行 | 不支持,但可通過配置模塊實現 | 支持 | 支持 |
回頁首
結束語:
Ansible 是一個新興的 IT 自動化管理工具。目前它的下載量已經超過了 100 萬。在 GitHub,它是排名前 10 位的 Python 項目。可以預見 Ansible 的發展是不可限量。筆者在本文中使用 Anaible 來管理 MySQL 復制來向大家介紹了 Ansible 一些使用方法和應用場景,希望通過本文,能夠讓大家都愛上這個超級有能量的系統自動化管理工具。
使用 Ansible 管理 MySQL 復制