并发运行

ansible默认只会创建5个进程,所以一次任务只能同时控制5台机器执行.那如果你有大量的机器需要控制,或者你希望减少进程数,那你可以采取异步执行.ansible的模块可以把task放进后台,然后轮询它.这使得在一定进程数下能让大量需要的机器同时运作起来.

使用async和poll这两个关键字便可以并行运行一个任务. async这个关键字触发ansible并行运作任务,而async的值是ansible等待运行这个任务的最大超时值,而poll就是ansible检查这个任务是否完成的频率时间.

如果你希望在整个集群里面平行的执行一下updatedb这个命令.使用下面的配置

1- hosts: all
2    tasks:
3      - name: Install mlocate
4        yum: name=mlocate state=installed
5      - name: Run updatedb
6        command: /usr/bin/updatedb
7        async: 300
8        poll: 10

你会发现当你使用上面的例子控制超过5台机器的时候,command.在上面yum模块会先在5台机器上跑,完成后再继续下面的机器.而上面command模块的任务会一次性在所有机器上都执行了,然后监听它的回调结果

如果你的command是控制机器开启一个进程放到后台,那就不需要检查这个任务是否完成了.你只需要继续其他的动作,最后再使用wait_for这个模块去检查之前的进程是否按预期中开启了便可.只需要把poll这个值设置为0,便可以按上面的要求配置ansible不等待job的完成.

最后,或者你还有一种需求是有一个task它是需要运行很长的时间,那你需要设置一直等待这个job完成.这个时候你把async的值设成0便可.

总结来说,大概有以下的一些场景你是需要使用到ansible的polling特性的

  1. 你有一个task需要运行很长的时间,这个task很可能会达到timeout.
  2. 你有一个任务需要在大量的机器上面运行
  3. 你有一个任务是不需要等待它完成的

当然也有一些场景是不适合使用polling特性的

  1. 你的这个任务是需要运行完后才能继续另外的任务的
  2. 你的这个任务能很快的完成

Looping

在ansible你能够通过不同的输入去重复的执行同一个模块,举个例子,你需要管理几个具有相同权限的文件.你能够用一个for循环迭代一个facts或者variables去减少你的重复劳动.

使用with_items这个关键字就可以完成迭代一个列表.列表里面的每个变量都叫做item.有一些模块譬如yum,它就支持使用with_items去安装一列表的包,而不需要写好多个yum的task

下面来一个with_items的例子

1tasks:
2  - name: Secure config files
3    file: path=/etc/{{ item }} mode=0600 owner=root group=root
4    with_items:
5     - my.cnf
6     - shadow
7     - fstab

除了使用items轮训,ansible还有一种方式是lookup插件.这些插件可以让ansible从外部取得数据,例如,你或许希望可以通过一种特定模式去上传你的文件.

在这个例子里面,我们会上传所有的public keys到一个目录,然后聚合它们到一个authorized_keys文件

 1tasks:
 2    - name: Make key directory
 3      file: path=/root/.sshkeys ensure=directory mode=0700
 4      owner=root group=root
 5    - name: Upload public keys
 6      copy: src={{ item }} dest=/root/.sshkeys mode=0600
 7      owner=root group=root
 8      with_fileglob:
 9       - keys/*.pub
10    - name: Assemble keys into authorized_keys file
11      assemble: src=/root/.sshkeys dest=/root/.ssh/authorized_keys
12      mode=0600 owner=root group=root

loop模块一般在下面的场景中使用

  1. 类似的配置模块重复了多遍
  2. fact是一个列表
  3. 创建多个文件,然后使用assemble聚合成一个大文件
  4. 使用with_fileglob匹配特定的文件管理

条件语句

有一些模块,例如copy这个模块有一些机制能跳过本次模块的运行.其实我们也可以使用自己的条件语句去配置跳过模块,这样方便你服务能够选择使用不同的包管理(apt,yum)和不同的文件系统.并且你还可以使用set_fact这个模块做成更多的差异配置

你能够使用when这个关键字去达到跳过本次模块运行的效果,when关键字后面跟着的是python的表达式,在表达式中你能够使用任何的变量或者fact,当表达式的结果返回的是false,便会跳过本次的模块

下面一段配置就说明了如何在debian和redhat系统中选择apt还是yum包管理,并且如果不是以上两个系统,会用debug模块把系统打印出来

 1---
 2- name: Install VIM
 3  hosts: all
 4  tasks:
 5    - name: Install VIM via yum
 6      yum: name=vim-enhanced state=installed
 7      when: ansible_os_family == "RedHat"
 8    - name: Install VIM via apt
 9      apt: name=vim state=installed
10      when: ansible_os_family == "Debian"
11    - name: Unexpected OS family
12      debug: msg="OS Family {{ ansible_os_family }} is not supported" fail=yes
13      when: not ansible_os_family == "RedHat" or ansible_os_family == "Debian"

条件语句还有一种用法,它还可以让你当达到一定的条件的时候暂停下来,等待你的输入确认.一般情况下,当ansible遭遇到error时,它会直接结束运行.那其实你可以当遭遇到不是预期的情况的时候给使用pause模块,这样可以让用户自己决定是否继续运行任务

1name: pause for unexpected conditions
2pause: prompt="Unexpected OS"
3when: ansible_os_family != "RedHat"

下面一些情景建议你使用条件语句做跳过动作

  1. job里面有不同操作系统的机器
  2. 提示用户,然后再执行操作请求
  3. 提高性能,避免运行一个需要执行一段时间模块,而且你知道这个模块不会返回changed

task委托

默认ansible的所有task是在我们的配置的管理机器上面运行的,当在一个独立的群集里面配置,那是适用的.而有一些情况是,某些任务运行的状态是需要传递给其他机器的,在同一个任务你需要在其他机器上执行,这时候你就许多要用task委托

使用delegate_to关键字便可以配置任务在其他机器上执行.其他模块还是在所有配置的管理机器上运行的,当到了这个关键字的任务就是使用委托的机器上运行.而facts还是适用于当前的host,下面我们演示一个例子,使用get_url模块去下载一个web集群的配置

1---
2  - name: Fetch configuration from all webservers
3    hosts: webservers
4    tasks:
5      - name: Get config
6        get_url: dest=configs/{{ ansible_hostname }} force=yes url=http://{{ ansible_hostname }}/diagnostic/config
7        delegate_to: localhost

如果需要委托loaclhost执行任务,这里提供一个快捷的方式,只要使用local_action作为task的key便行.我们尝试使用这种方式来配置上面的例子,会更加简洁.

1---
2- name: Fetch configuration from all webservers
3  hosts: webservers
4  tasks:
5    - name: Get config
6      local_action: get_url dest=configs/{{ ansible_hostname }}.cfg url=http://{{ ansible_hostname }}/diagnostic/config

委托不限于localhost,可以在你的inventory里面的任何host.下列一些场景适用使用委托

  1. 部署之前你希望从负载均衡里面把host移除
  2. 更改你的server时候更改dns的指向
  3. 创建一个iSCSI卷存储设备
  4. 使用一个外部服务器去检测一下服务

额外的变量

大家应该在之前的章节的例子里面有看到group_names这个变量.这个是ansible提供的一个很神奇变量.直至写本书的时候,有7个这样的变量,我会在下面的章节介绍

a.hostvars变量

hostvars允许你在当前的任务中应用所有host的变量.当setup模块没有运行的时候,只有这些变量将是可用.例如你配置 ${hostvars.hostname.fact}可以访问其他复杂的变量.例如你可以配置${hostvars.ns1.ansible_ distribution}得到ns1这个server的linux发型版本.

下面的例子设置了一个dns_master变量,这是ns1 server的ip.然后这个变量能够在所有机器上调用

 1---
 2- name: Setup DNS Servers
 3   hosts: allnameservers
 4   tasks:
 5     - name: Install BIND
 6       yum: name=named state=installed
 7- name: Setup Slaves
 8  hosts: slavenamesservers
 9  tasks:
10    - name: Get the masters IP
11      set_fact: dns_master="{{ hostvars.ns1.ansible_default_ipv4.address }}"
12    - name: Configure BIND
13      template: dest=/etc/named.conf src/templates/named.conf.j2

b.groups变量

groups变量是inventory里面的group分组列表.这个是一个非常强大的工具,能够让你迭代你配置的所有的hosts.看下面的例子.

 1---
 2- name: Configure the database
 3  hosts: dbservers
 4  user: root
 5  tasks:
 6    - name: Install mysql
 7      yum: name={{ item }} state=installed
 8      with_items:
 9        - mysql-server
10        - MySQL-python
11    - name: Start mysql
12      service: name=mysqld state=started enabled=true
13    - name: Create a user for all app servers
14      with_items: groups.appservers
15      mysql_user: name=kate password=test host={{ hostvars[item].ansible_eth0.ipv4.address }} state=present

groups变量实际不是你的hosts变量的列表.它只是你hosts的name的列表.如果你需要调用host里面的变量还需要配合hostvars使用。下面的例子配置创建known_hosts文件,playbook配置:

1---
2   hosts: all
3   tasks:
4   - name: Setup known hosts
5     hosts: all
6     tasks:
7       - name: Create known_hosts
8         template: src=templates/known_hosts.j2 dest=/etc/ssh/ssh_known_hosts owner=root group=root mode=0644

template模板

1{% for host in groups['all'] %}
2{{ hostvars[host]['ansible_hostname'] }}
3{{ hostvars[host]['ansible_ssh_host_key_rsa_public'] }}
4{% endfor %}

c.group_names变量

group_names是当前host所属的组的列表.这可以用于在条件语句中调用成员的group关系,或者用于debugging.通常来说这变量大部分用于跳过一些task或者在模板中用于条件语句的变量.在下面的例子中,如果你有两套sshd的配置文件,一套用于安全性更加严谨的,一个安全性普通的.然后我们根据group名来配分host到哪个sshd配置下.

 1---
 2- name: Setup SSH
 3  hosts: sshservers
 4  tasks:
 5    - name: For secure machines
 6      set_fact: sshconfig=files/ssh/sshd_config_secure
 7      when: "'secure' in group_names"
 8    - name: For non-secure machines
 9      set_fact: sshconfig=files/ssh/sshd_config_default
10      when: "'secure' not in group_names"
11    - name: Copy over the config
12      copy: src={{ sshconfig }} dest=/tmp/sshd_config

d.inventory_hostname变量

inventory_hostname是机器的hostname,当你没有使用setup模块,或者由于各种原因导致setup的变量是错误的,你可以选择使用这个变量.此变量可以帮助你初始化你的机器和改变hostname

e.inventory_hostname_short

inventory_hostname_short类似与上面的inventory_hostname变量,只是它是截取第一个句点的前面的字符,例如hostname是host.example.com,就会只截取到host

f.inventory_dir

此变量是inventory文件的路径,包括目录与文件名

g.inventory_file

类似上面的变量,只是它只有文件名

使用变量来查找文件

所有的模块都可以把变量作为参数的一部分,通过使用”{{}}”符号扩起来.譬如变量test就是”{{ test }}”.这样你就可以通过变量加载特定的文件.例如,你希望根据不同的机器architecture选择不同的NRPE(nagios的客户端)配置文件,那可以像这样的配置

1---
2- name: Configure NRPE for the right architecture
3 hosts: ansibletest
4 user: root
5 tasks:
6   - name: Copy in the correct NRPE config file
7     copy: src=files/nrpe.{{ ansible_architecture }}.conf dest=/etc/nagios/nrpe.cfg

在copy和tempalate模块里面,你能够使用ansible去查找一组的文件.然后默认使用第一个文件.这能够让你达到效果是,当第一个文件不存在时,会查找第二个文件,如此类推知道最后一个文件还不存在就报fail.使用first_available_file这个关键字便可以到上述效果.

 1---
 2- name: Install an Apache config file
 3  hosts: ansibletest
 4  user: root
 5  tasks:
 6   - name: Get the best match for the machine
 7     copy: dest=/etc/apache.conf src={{ item }}
 8     first_available_file:
 9      - files/apache/{{ ansible_os_family }}-{{ ansible_architecture }}.cfg
10      - files/apache/default-{{ ansible_architecture }}.cfg
11      - files/apache/default.cfg

环境变量

unix命令经常需要依赖环境变量,例如C makefiles,installers,和aws cli工具.很幸运,ansible很容易实现,譬如你现在需要控制远程的机器一个文件到s3,那你许多要配置aws的access key.下面我们的例子演示,安装pip,用pip安装aws cli,并且通过cli上传文件到s3

 1---
 2- name: Upload a remote file via S3
 3  hosts: ansibletest
 4  user: root
 5  tasks:
 6   - name: Setup EPEL
 7     command: rpm -ivh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm creates=/etc/yum.repos.d/epel.repo
 8   - name: Install pip
 9     yum: name=python-pip state=installed
10   - name: Install the AWS tools
11     pip: name=awscli state=present
12   - name: Upload the file
13     shell: aws s3 put-object --bucket=my-test-bucket --key={{ ansible_hostname }}/fstab --body=/etc/fstab --region=eu-west-1
14  environment:
15    AWS_ACCESS_KEY_ID: XXXXXXXXXXXXXXXXXXX
16    AWS_SECRET_ACCESS_KEY: XXXXXXXXXXXXXXXXXXXXX

一些模块例如get_url,yum,和apt是需要使用环境变量配置proxy的.下面一些场景也是需要配置环境变量的

  1. 运行application installers
  2. 当运行shell的时候需要添加一些额外的的变量在path里
  3. 需要load的一些库不在系统的library路径中
  4. 在运行模块时使用LD_PRELOAD hack

External data lookups

ansible在0.9版本开始引进了lookup插件,这些插件运行ansible在外围获取数据.ansible已经提供了几个插件,但它还是支持自己编写插件.这真的让你使用ansible配置更加伸缩自如

lookup是在master机器运行的python程序.下面一个例子是使用lookup插件获取环境变量里面的http_proxy,然后配置在远端机器,确保远端机器使用相同的proxy下载文件

1---
2- name: Downloads a file using the same proxy as the controlling machine
3  hosts: all
4  tasks:
5    - name: Download file
6      get_url: dest=/var/tmp/file.tar.gz url=http://server/file.tar.gz
7      environment:
8       http_proxy: "{{ lookup('env', 'http_proxy') }}"

使用with_*能够使用lookup插件迭代出特别的东西.您可以使用任何这样的插件,但最好是返回一个列表.下面的例子让你自动注册webapp,使用下面的例子会创建出虚拟机并配置它

 1---
 2- name: Registers the app server farm
 3  hosts: localhost
 4  connection: local
 5  vars:
 6    hostcount: 5
 7  tasks:
 8    - name: Register the webapp farm
 9      local_action: add_host name={{ item }} groupname=webapp
10      with_sequence: start=1 end={{ hostcount }} format=webapp%02x

在下面的场景,lookup非常有用

  1. 复制整个目录的apache配置到conf.d
  2. 使用环境变量调整playbook的运行
  3. 从DNS TXT记录中获取配置
  4. 获取一个命令的输出到一个变量中

保存结果

几乎所有的模块都是会outputs一些东西,甚至debug模块也会.大多数我们会使用的结果变量是changed.这个changed变量决定了是否要直接handlers和输出的颜色是什么.然而,结果变量还有其他的用途,譬如我需要保存我的结果变量,然后咋我的playbook的其他地方给使用.在下面的例子我们创建了一个/tmp目录,然后在后面我们创建一个/tmp/subtmp使用和前面目录一样的权限

 1---
 2- name: Using register
 3  hosts: ansibletest
 4  user: root
 5  tasks:
 6   - name: Get /tmp info
 7     file: dest=/tmp state=directory
 8     register: tmp
 9   - name: Set mode on /var/tmp
10     file: dest=/tmp/subtmp mode={{ tmp.mode }} state=directory

一些模块,例如上面的file模块,是能够获取到一些简单的信息.结合register这个功能,可以让你在playbook里面检查你的环境和计算如何进行

register对于数多场景是很有用的

  1. 在一台远端的服务器获取一个目录下的一列表的文件,然后下载这些文件
  2. 在handler执行之前,发现前面一个task发生了changed,然后执行一个指定的task
  3. 获取远端服务器的ssh key的内容,构建出known_hosts文件

debugging playbook

有好几种方法去debug我们的playbook.ansible有verbose模式和debug模式,也可以使用例如fetch和get_url模块来协助debug.当你想学习怎样使用一些模块时,这些debugging技术能够帮助你.

a.debug模块

debug模块使用很简单.它具有两个参数,msg和fail.msg就是打印出来的信息,而当fail参数设置为yes时,会发送失败通知给ansible,然后ansible会停止运行任务.

在下面的例子,配置了使用debug模块去显示远端机器所有的network interface.

 1---
 2- name: Demonstrate the debug module
 3  hosts: ansibletest
 4  user: root
 5  vars:
 6    hostcount: 5
 7  tasks:
 8    - name: Print interface
 9      debug: msg="{{ item }}"
10      with_items: ansible_interfaces

运行上面的配置会出现这样的输出

1PLAY [Demonstrate the debug module] *********************************
2GATHERING FACTS *****************************************************
3ok: [ansibletest]
4TASK: [Print IP address] ********************************************
5ok: [ansibletest] => (item=lo) => {"item": "lo", "msg": "lo"}
6ok: [ansibletest] => (item=eth0) => {"item": "eth0", "msg": "eth0"}
7PLAY RECAP **********************************************************
8ansibletest                : ok=2    changed=0    unreachable=0 failed=0

如你说见,debug模块可以让你很容易看到在playbook运行期间一些变量

b.verbose模式

另外的debug选择是verbose模式.当运行verbose模式时,会打印出所有模块运行后的变量.这对于你要使用register功能时候很重要.只需要在执行playbook命令时加上参数–verbose便可以.ansible-playbook –verbose playbook.yml

c.check模式

除了verbose模式外,ansible还提供了check模式和diff模式.只需要执行playbook时添加参数–check和–diff.check模式运行时,ansible不会真正控制远程机器发生变更.这能够让你获得这次playbook任务中,将会发生changed事件的列表.

很重要的一点是check模式不是完美的.有一些模块是会跳过check模式的.尤其明显的限制是在运行command和shell模块

在diff模式下,当文件发现更变,会打印出变更文件的变更部分.配合check模式使用效果更好

d.pause模块

另外一个debug技巧是使用pause模块,它可以让你需要在某个地方需要检查远程机器的配置的时候暂停playbook的执行.这样可以让先观察一下运行到这里为止的效果,再判断是否继续运行下去.

总结

在这个章节我们更加深入探索了编写playbook的一些细节.现在你应该可以使用一些ansible的特性.例如delegation,looping,conditionals,和fact,registration等等,让你能够更容易的编写和维护你的playbook.我们也看到了如何获取其他host的信息,如何配置环境变量,如何从外围获取到数据.最后我们展示了一些debug技巧,让playbook能按你的预期来执行.

下一章节,我们会学习如何在大规模环境中使用ansible,也会讲到一些方法让你在一些需要运行很久的任务中提高你的性能.我们也会介绍一些特性让你的playbook如何更加可维护,更加解藕,让它们按目的分配到不同的地方.