Ansible 是一个配置管理和应用部署工具,功能类似于目前业界的配置管理工具 Chef,Puppet,Saltstack。Ansible 是通过 Python 语言开发。Ansible 平台由 Michael DeHaan 创建,他同时也是知名软件 Cobbler 与 Func 的作者。Ansible 的第一个版本发布于 2012 年 2 月,相比较其它同类产品来说,Ansible 还是非常年轻的,但这并不影响他的蓬勃发展与大家对他的热爱。
Ansible 默认通过 SSH 协议管理机器,所以 Ansible 不需要安装客户端程序在服务器上。您只需要将 Ansible 安装在一台服务器,在 Ansible 安装完后,您就可以去管理控制其它服务器。不需要为它配置数据库,Ansible 不会以 daemons 方式来启动或保持运行状态。Ansible 的目标有如下:
根据 Ansible 官方提供的信息,当前使用 Ansible 的用户有:evernote、rackspace、NASA、Atlassian、twitter 等。
回页首
当前 Ansible 可以运行在安装了 Python2.6 的任何机器上(暂不支持 windows 机器做中心控制服务器),这包括 RedHat,Debian,CentOS,OS X,BSDS 等等。
在被管理节点服务器上,需要安装 Python2.4 或更高的 Python 版本,如果在远程机器上运行的 Python 小于 Python2.5 的话,您将需要安装 python-simplejson。如果在远程机器的启用了 SELinux,你还需要安装 libselinux-python。
回页首
如果你是 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 之前,请确保您的系统已经安装 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 中叫清单文件,默认的清单文件存放在 /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 附带了非常多的模块,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 的 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 包括了:
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 功能类似的配置管理软件 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 一些使用方法和应用场景,希望通过本文,能够让大家都爱上这个超级有能量的系统自动化管理工具。