运维自动化工具之Ansible

简介:

一、简介

    ansible是新出现的自动化运维工具,基于Python开发,集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。

ansible是基于模块工作的,本身没有批量部署的能力。真正具有批量部署的是ansible所运行的模块,ansible只是提供一种框架。主要包括:

(1)、连接插件connection plugins:负责和被监控端实现通信;

(2)、host inventory:指定操作的主机,是一个配置文件里面定义监控的主机;

(3)、各种模块核心模块、command模块、自定义模块;

(4)、借助于插件完成记录日志邮件等功能;

(5)、playbook:剧本执行多个任务时,非必需可以让节点一次性运行多个任务。

二、安装Ansible

ansible依赖于Python 2.6或更高的版本、paramiko、PyYAML及Jinja2。所以,安装ansible之前要先解决以上依赖关系。(http://www.rpmfind.net/)找到相应的版本下载之。

1
2
# yum install python-paramiko-1.7.5-2.1.el6.noarch.rpm PyYAML-3.10-3.1.el6.x86_64.rpm python-jinja2-26-2.6-3.el6.noarch.rpm
  python-crypto2.6-2.6.1-2.el6.x86_64.rpm python-babel-0.9.4-5.1.el6.noarch.rpm

2.1 编译安装

1
2
3
4
5
6
7
# yum -y install python-jinja2 PyYAML python-paramiko python-babel python-crypto
# tar xf ansible-1.5.4.tar.gz
# cd ansible-1.5.4
# python setup.py build
# python setup.py install
# mkdir /etc/ansible
# cp -r examples/* /etc/ansible

2.2 rpm包安装

1
# yum install ansible-2.2.1.0-1.el6.noarch.rpm

  1. 1
    2
    yum  install  http: //mirrors .sohu.com /fedora-epel/6/x86_64/epel-release-6-8 .noarch.rpm
    yum  install  anisble

注意:不同版本的ansible的功能差异可能较大。

三、简单应用

ansible通过ssh实现配置管理、应用部署、任务执行等功能,因此,需要事先配置ansible端能基于密钥认证的方式联系各被管理节点。

参考:配置Linux主机SSH无密码访问

ansible <host-pattern> [-f forks] [-m module_name] [-a args]

-m module:默认为command

ansible-doc: Show Ansible module documentation

 -l, --list            List available modules

  -s, --snippet         Show playbook snippet for specified module(s)

四、YAML

4.1 YAML介绍

YAML是一个可读性高的用来表达资料序列的格式。YAML参考了其他多种语言,包括:XML、C语言、Python、Perl以及电子邮件格式RFC2822等。Clark Evans在2001年在首次发表了这种语言,另外Ingy dt Net与Oren Ben-Kiki也是这语言的共同设计者。

YAML Ain't Markup Language,即YAML不是XML。不过,在开发的这种语言时,YAML的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。其特性:

YAML的可读性好

YAML和脚本语言的交互性好

YAML使用实现语言的数据类型

YAML有一个一致的信息模型

YAML易于实现

YAML可以基于流来处理

YAML表达能力强,扩展性好

更多的内容及规范参见http://www.yaml.org。

4.2 YAML语法

YAML的语法和其他高阶语言类似,并且可以简单表达清单、散列表、标量等数据结构。其结构(Structure)通过空格来展示,序列(Sequence)里的项用"-"来代表,Map里的键值对用":"分隔。下面是一个示例。

name: John Smith

age: 41

gender: Male

spouse:

    name: Jane Smith

    age: 37

    gender: Female

children:

    -   name: Jimmy Smith

        age: 17

        gender: Male

    -   name: Jenny Smith

        age 13

        gender: Female

YAML文件扩展名通常为.yaml,如example.yaml。

4.2.1 list

列表的所有元素均使用“-”打头,例如:

# A list of tasty fruits

- Apple

- Orange

- Strawberry

- Mango

4.2.2 dictionary

字典通过key与valuef进行标识,例如:

---

# An employee record

name: Example Developer

job: Developer

skill: Elite

也可以将key:value放置于{}中进行表示,例如:

---

# An employee record

{name: Example Developer, job: Developer, skill: Elite}

五、Ansible基础元素

5.1 变量

5.1.1 变量命名

变量名仅能由字母、数字和下划线组成,且只能以字母开头。

5.1.2 facts

facts是由正在通信的远程目标主机发回的信息,这些信息被保存在ansible变量中。要获取指定的远程主机所支持的所有facts,可使用如下命令进行:

1
# ansible hostname -m setup

5.1.3 register

把任务的输出定义为变量,然后用于其他任务,示例如下:

1
2
3
4
tasks:
      - shell:  /usr/bin/foo
        register: foo_result
        ignore_errors: True

5.1.4 通过命令行传递变量

在运行playbook的时候也可以传递一些变量供playbook使用,示例如下:

1
ansible-playbook  test .yml --extra-vars  "hosts=www user=bovin"

5.1.5 通过roles传递变量

当给一个主机应用角色的时候可以传递变量,然后在角色内使用这些变量,示例如下:

1
2
3
4
- hosts: webservers
   roles:
     - common
     - { role: foo_app_instance,  dir '/web/htdocs/a.com' ,  port: 8080 }

5.2 Inventory

ansible的主要功用在于批量主机操作,为了便捷地使用其中的部分主机,可以在inventory file中将其分组命名。默认的inventory file为/etc/ansible/hosts。

inventory file可以有多个,且也可以通过Dynamic Inventory来动态生成。

5.2.1 inventory文件格式

inventory文件遵循INI文件风格,中括号中的字符为组名。可以将同一个主机同时归并到多个不同的组中;此外,当如若目标主机使用了非默认的SSH端口,还可以在主机名称之后使用冒号加端口号来标明。

1
2
3
4
5
6
7
8
mail.example.com
[webservers]
foo.example.com:8888
bar.example.com
[dbservers]
one.example.com
two.example.com
three.example.com

方括号[]中是组名,用于对系统进行分类,便于对不同系统进行个别的管理.

一个系统可以属于不同的组,比如一台服务器可以同时属于 webserver组 和 dbserver组.这时属于两个组的变量都可以为这台主机所用.

如果有主机的SSH端口不是标准的22端口,可在主机名之后加上端口号,用冒号分隔.SSH 配置文件中列出的端口号不会在 paramiko 连接中使用,会在 openssh 连接中使用.

端口号不是默认设置时,可明确的表示为:

1
badwolf.example.com:5309

假设你有一些静态IP地址,希望设置一些别名,但不是在系统的 host 文件中设置,又或者你是通过隧道在连接,那么可以设置如下:

1
jumper ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50

在这个例子中,通过 “jumper” 别名,会连接 192.168.1.50:5555.记住,这是通过 inventory 文件的特性功能设置的变量. 一般而言,这不是设置变量(描述你的系统策略的变量)的最好方式.后面会说到这个问题.


如果主机名称遵循相似的命名模式,还可以使用列表的方式标识各主机,例如:

1
2
3
4
[webservers]
www[01:50].example.com
[databases]
db-[a:f].example.com

对于每一个 host,你还可以选择连接类型和连接用户名:

1
2
3
4
[targets]
localhost           ansible_connection= local
other1.example.com     ansible_connection= ssh         ansible_ssh_user=mpdehaan
other2.example.com     ansible_connection= ssh         ansible_ssh_user=mdehaan

5.2.2 主机变量

可以在inventory中定义主机时为其添加主机变量以便于在playbook中使用。例如:

1
2
3
[webservers]
host1 http_port=80 maxRequestsPerChild=808
host2 http_port=303 maxRequestsPerChild=909

5.2.3 组变量

组变量是指赋予给指定组内所有主机上的在playbook中可用的变量。例如:

1
2
3
4
5
6
[webservers]
host1
host2
[webserver:vars]
ntp_server=ntp.webservers.example.com
nfs_server=nfs.webservers.example.com

5.2.4 组嵌套

inventory中,组还可以包含其它的组,并且也可以向组中的主机指定变量。不过,这些变量只能在ansible-playbook中使用,而ansible不支持。例如:

1
2
3
4
5
6
7
8
9
10
11
[apache]
httpd1.centos.com
httpd2.centos.com
[nginx]
ngx1.centos.com
ngx2.centos.com
[webservers:children]
apache
nginx
[webservers:vars]
ntp_server=ntp.centos.com

5.2.5 inventory参数

ansible基于ssh连接inventory中指定的远程主机时,还可以通过参数指定其交互方式;这些参数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ansible_ssh_host
       将要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设置.
ansible_ssh_port
       ssh 端口号.如果不是默认的端口号,通过此变量设置.
ansible_ssh_user
       默认的  ssh  用户名
ansible_ssh_pass
       ssh  密码(这种方式并不安全,我们强烈建议使用 --ask-pass 或 SSH 密钥)
ansible_sudo_pass
       sudo  密码(这种方式并不安全,我们强烈建议使用 --ask- sudo -pass)
ansible_sudo_exe (new  in  version 1.8)
       sudo  命令路径(适用于1.8及以上版本)
ansible_connection
       与主机的连接类型.比如: local ssh  或者 paramiko. Ansible 1.2 以前默认使用 paramiko.1.2 以后默认使用  'smart' , 'smart'  方式会根据是否支持 ControlPersist, 来判断 'ssh' 
方式是否可行.
ansible_ssh_private_key_file
       ssh  使用的私钥文件.适用于有多个密钥,而你不想使用 SSH 代理的情况.
ansible_shell_type
       目标系统的shell类型.默认情况下,命令的执行使用  'sh'  语法,可设置为  'csh'  或  'fish' .
ansible_python_interpreter
       目标主机的 python 路径.适用于的情况: 系统中有多个 Python, 或者命令路径不是 "/usr/bin/python" ,比如  \*BSD, 或者  /usr/bin/python
       不是 2.X 版本的 Python.我们不使用  "/usr/bin/env"  机制,因为这要求远程用户的路径设置正确,且要求  "python"  可执行程序名不可为 python以外的名字(实际有可能名为
python26).
       与 ansible_python_interpreter 的工作方式相同,可设定如 ruby 或 perl 的路径....

5.3 条件测试

如果需要根据变量、facts或此前任务的执行结果来做为某task执行与否的前提时要用到条件测试。

5.3.1 when语句

在task后添加when子句即可使用条件测试;when语句支持Jinja2表达式语法。例如:

1
2
3
4
tasks
   - name:  "shutdown Debian flavored systems"
     command /sbin/shutdown  -h now
     when: ansible_os_family ==  "Debian"

when语句中还可以使用Jinja2的大多“filter”,例如要忽略此前某语句的错误并基于其结果(failed或者sucess)运行后面指定的语句,可使用类似如下形式:

1
2
3
4
5
6
7
8
9
10
tasks:
   command /bin/false
     register: result
     ignore_errors: True
   command /bin/something
     when: result|failed
   command /bin/something_else
     when: result|success
   command /bin/still/something_else
     when: result|skipped

此外,when语句中还可以使用facts或playbook中定义的变量。

5.4 迭代

当有需要重复性执行的任务时,可以使用迭代机制。其使用格式为将需要迭代的内容定义为item变量引用,并通过with_items语句来指明迭代的元素列表即可。例如:

1
2
3
4
5
- name: add several  users
   user: name={{ item }} state=present  groups =wheel
   with_items:
      - testuser1
      - testuser2

上面语句的功能等同于下面的语句:

1
2
3
4
- name: add user testuser1
   user: name=testuser1 state=present  groups =wheel
- name: add user testuser2
   user: name=testuser2 state=present  groups =wheel

事实上,with_items中可以使用元素还可为hashes,例如:

1
2
3
4
5
- name: add several  users
   user: name={{ item.name }} state=present  groups ={{ item. groups  }}
   with_items:
     - { name:  'testuser1' groups 'wheel'  }
     - { name:  'testuser2' groups 'root'  }

ansible的循环机制还有更多的高级功能,具体请参见官方文档(http://docs.ansible.com/playbooks_loops.html)。

六、Ansible的基本使用

1.

1
2
3
4
5
6
7
8
9
10
11
[root@Centos ansible] # rpm -ql ansible | head
/etc/ansible   
/etc/ansible/ansible .cfg  --ansible的主配置文件
/etc/ansible/hosts         --ansible的host inventory文件
/etc/ansible/roles
/usr/bin/ansible           --ansible的命令,实现批量部署的命令
/usr/bin/ansible-console
/usr/bin/ansible-doc       --ansible模块相关命令,可以获取相关帮助
/usr/bin/ansible-galaxy
/usr/bin/ansible-playbook  --playbook相关命令
/usr/bin/ansible-pull

2、定义Host Inventory  

1
2
3
4
5
6
# vim /etc/ansible/hosts 
[webserver]
192.168.1.101 ansible_ssh_user=root ansible_ssh_pass=fanshine
192.168.1.102 ansible_ssh_user=root ansible_ssh_pass=fanshine
[dbserver]
192.168.1.110

解释 :

#ansible_ssh_user=root 是ssh登陆用户  

#ansible_ssh_pass=fanshine是ssh登陆密码

# 注意每个模块的用法可以使用 ansible-doc MOD 来查看例如ansible-doc copy  

ansible命令最常用的用法:

1
2
3
ansible <host-pattern> [-f forks] [-m module_name] [-a args]
     args:
          key=value

注意:command模块要执行命令无须为key=value格式,而是直接给出要执行的命令即可;

ansible常用模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
command
         -a  'COMMAND'
user
         -a  'name= state={present|absent} system= uid='
group
         -a  'name= gid= state= system='
cron
         -a  'name= minute= hour= day= month= weekday= job= user= state='
copy
         -a  'dest= src= mode= owner= group='
file
         -a  'path= mode= owner= group= state={directory|link|present|absent} src='
ping
         没有参数
yum
         -a  'name= state={present|latest|absent}'
service
         -a  'name= state={started|stopped|restarted} enabled='
shell
         -a  'COMMAND'
script
         -a  '/path/to/script'
setup

所支持的模块可以使用ansible-doc -l来查看.

Ansible简单实例:

1.使用command模块,查看客户机上的网卡信息:

wKiom1kZZryyQ5SXAABVR6eDP3M482.png-wh_50

2使用user模块进行添加用户。

wKiom1kZaW-RbBJdAAA9FWaje2w962.png-wh_50

3.使用copy模块进行复制。

wKiom1kZbqOSxVNFAABmCCzp8K0874.png-wh_50

在使用copy模块,出现以下错误:

1
2
3
4
5
6
7
8
9
10
11
12
192.168.1.102 | FAILED! => {
     "changed" false
     "checksum" "8a7d578b4e911043538f101eae8291c428c67605"
     "failed" true
     "msg" "Aborting, target uses selinux but python bindings (libselinux-python) aren't installed!"
}
192.168.1.101 | FAILED! => {
     "changed" false
     "checksum" "8a7d578b4e911043538f101eae8291c428c67605"
     "failed" true
     "msg" "Aborting, target uses selinux but python bindings (libselinux-python) aren't installed!"
}

错误: "msg": "Aborting, target uses selinux but python bindings (libselinux-python) aren't installed!"
解决: yum install -y libselinux-python

4.使用commad模块,安装软件或者服务。

wKioL1kZa1zAXDKPAABaxH6EN0k073.png-wh_50

5、支持管道的命令。raw模块,类似于shell模块

wKiom1kZb8bSPXnGAAAZHPUrjKE464.png-wh_50

6.使用file模块,创建目录。

wKioL1kZcw7h-9pLAAA5v0kipI4378.png-wh_50

7.使用yum模块进行安装服务。

wKioL1kZfqyDgrbjAABraGkMPhA303.png-wh_50

8.使用service模块,启动,停止,重启服务。

wKioL1kZfwCg4uHUAAA3o-e6Ivs435.png-wh_50

9.简单的Ping命令进行各主机检查。

wKioL1kZeOeR0r0gAAAX3NZ7b1M884.png-wh_50

10.使用shell模块,完成更多操作。

wKiom1kZeiuySqU7AAAp0p-fBn8315.png-wh_50

七、ansible playbooks

playbook是由一个或多个“play”组成的列表。play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲,所谓task无非是调用ansible的一个module。将多个play组织在一个playbook中,即可以让它们联同起来按事先编排的机制同唱一台大戏。下面是一个简单示例。

- hosts: webnodes

 vars:

   http_port: 80

   max_clients: 256

 remote_user: root

 tasks:

 - name: ensure apache is at the latest version

   yum: name=httpd state=latest

 - name: ensure apache is running

   service: name=httpd state=started

 handlers:

   - name: restart apache

     service: name=httpd state=restarted

7.1 playbook基础组件

7.1.1 Hosts和Users

playbook中的每一个play的目的都是为了让某个或某些主机以某个指定的用户身份执行任务。hosts用于指定要执行指定任务的主机,其可以是一个或多个由冒号分隔主机组;remote_user则用于指定远程主机上的执行任务的用户。如上面示例中的

-hosts: webnodes

remote_user: root

不过,remote_user也可用于各task中。也可以通过指定其通过sudo的方式在远程主机上执行任务,其可用于play全局或某任务;此外,甚至可以在sudo时使用sudo_user指定sudo时切换的用户。

- hosts: webnodes

 remote_user: centos

 tasks:

   - name: test connection

     ping:

     remote_user: centos

     sudo: yes

7.1.2 任务列表和action

play的主体部分是task list。task list中的各任务按次序逐个在hosts中指定的所有主机上执行,即在所有主机上完成第一个任务后再开始第二个。在运行自下而下某playbook时,如果中途发生错误,所有已执行任务都将回滚,因此,在更正playbook后重新执行一次即可。

task的目的是使用指定的参数执行模块,而在模块参数中可以使用变量。模块执行是幂等的,这意味着多次执行是安全的,因为其结果均一致。

每个task都应该有其name,用于playbook的执行结果输出,建议其内容尽可能清晰地描述任务执行步骤。如果未提供name,则action的结果将用于输出。

定义task的可以使用“action: module options”或“module: options”的格式,推荐使用后者以实现向后兼容。如果action一行的内容过多,也中使用在行首使用几个空白字符进行换行。

tasks:

 - name: make sure apache is running

   service: name=httpd state=running

在众多模块中,只有command和shell模块仅需要给定一个列表而无需使用“key=value”格式,例如:

tasks:

 - name: disable selinux

   command: /sbin/setenforce 0

如果命令或脚本的退出码不为零,可以使用如下方式替代:

tasks:

 - name: run this command and ignore the result

   shell: /usr/bin/somecommand || /bin/true

或者使用ignore_errors来忽略错误信息:

tasks:

 - name: run this command and ignore the result

   shell: /usr/bin/somecommand

   ignore_errors: True

7.1.3 handlers

用于当关注的资源发生变化时采取一定的操作。

    “notify”这个action可用于在每个play的最后被触发,这样可以避免多次有改变发生时每次都执行指定的操作,取而代之,仅在所有的变化发生完成后一次性地执行指定操作。在notify中列出的操作称为handler,也即notify中调用handler中定义的操作。

- name: template configuration file

 template: src=template.j2 dest=/etc/foo.conf

 notify:

    - restart memcached

    - restart apache

handler是task列表,这些task与前述的task并没有本质上的不同。

handlers:

   - name: restart memcached

     service:  name=memcached state=restarted

   - name: restart apache

     service: name=apache state=restarted

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
heartbeat.yaml
- hosts: hbhosts
   remote_user: root
   tasks:
     - name: ensure heartbeat latest version
       yum: name=heartbeat state=present
     - name: authkeys configure  file
       copy: src= /root/hb_conf/authkeys  dest= /etc/ha .d /authkeys
     - name: authkeys mode 600
       file : path= /etc/ha .d /authkeys  mode=600
       notify:
         - restart heartbeat
     - name: ha.cf configure  file
       copy: src= /root/hb_conf/ha .cf dest= /etc/ha .d /ha .cf
       notify: 
        - restart heartbeat
   handlers:
   - name: restart heartbeat
     service: name=heartbeat state=restarted

八、roles

ansilbe自1.2版本引入的新特性,用于层次性、结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单来讲,roles就是通过分别将变量、文件、任务、模块及处理器放置于单独的目录中,并可以便捷地include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中。

一个roles的案例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
site.yml
webservers.yml
fooservers.yml
roles/
    common/
      files/
      templates/