第5章 Ansible Playbook扩展,ansibleplaybook


5.1 Handlers

    在4.6节LAMP实战中,我们就已经使用了Handlers来实现了重启Apache的功能,该实例中,一些修改Apache配置文件的操作使用notify:restart apache触发Handlers,从而实现了Apache的重启。

   

handlers:
    - name: restart apache
      service: name=apache2 state=restarted
tasks:
    - name: 开启Apache rewrite模块
      apache2_module: name=rewrite state=present
      notify: restart apache

    在某些情况下,你可能需要同时调用多个Handlers,或者需要使用Handlers调用其他Handlers,Ansible可以很简单的实现这些功能。

    下面的例子中,实现了一个任务同时调用多个Handlers。

- name: Rebuild application configuration
    command: /opt/app/rebuild.sh
  notify:
    - restart apache
    - restart memcached

    若要实现Handlers调用Handlers,则直接在Handlers中使用notify选项就可以了,如以下代码所示。

handlers:
    - name: restart apache
      service: name=apache2 state=restarted
      notify: restart memcached
    - name: restart memcached
      service: name=memcached state=restarted

    在使用Handlers的过程中,有以下几点需要格外注意。

  • Handlers只有在其所在的任务被执行时,才会被运行;如果一个任务中定义了notify调用Handlers,但是由于条件判断等原因,该任务未被执行,那么Handlers同样不会被执行。
  • Handlers只会在Play的末尾运行一次;如果想在一个Playbook的中间运行Handlers,则需要使用meta模块来实现,例如:- meta:flush_handlers。
  • 如果一个Play在运行到调用Handlers的语句之前失败了,那么这个Handlers将不会被执行。我们可以使用mega模块的--force-handlers选项来强制执行Handlers,即使是Handlers所在的Play中途运行失败也能执行。

5.2 环境变量

    在Ansible中设置和使用环境变量的方法多种多样。例如,如果我们想为连接远程主机的账号设置一些环境变量,我们可以使用lineinfile模块直接修改远程用户的~/.bash_profile文件,如下代码所示:

- name: 为远程主机上的用户指定环境变量
    lineinfile: dest=~/.bash_profile regexp=^ENV_VAR= line=ENV_VAR=value

- name: 获取刚刚指定的环境变量,并将其保存到自定义变量foo中
    shell: 'source ~/.bash_profile && echo $ENV_VAR'
     register: foo
- name: 打印出环境变量
     debug: msg="The variable is {{ foo.stdout }}"

    我们在第4行使用了"source ~/.bash_profile"命令重读了环境变量配置文件,这样就能确保我们接下来获取的是最新生效的环境变量。在某些情况下,若所有任务都运行在一个特久的或准高速缓存的SSH会话上的话,如果不重读环境变量配置文件,那么我们所定义的新环境变量ENV_VAR可能就不会生效。

    Linux同样也使用文件/etc/environment来读取环境变量,所以我们也可以使用如下方法来指定远程主机上用户的环境变量。

- name: Add a global environment variable.
    lineinfile: dest=/etc/environment regexp=^ENV_VAR= line=ENV_VAR=value
    sudo: yes

    lineinfile模块可以很方便地处理对环境变量设定量较少的情况。当我们需求大量的环境变量设定的时候,copy模块和接下来要讲的模块将会是不错的选择。

预定义环境变量

    对于某一个Play来说,我们可以使用environment选项来为其设置单独的环境变量。比如,我们现在需要为一个下载任务设置http代理。最简单的情况,我们可以这样实现:

- name: 使用指定的代理服务器下载文件
  get_url: url=http://www.example.com/file.tar.gz     dest=~/Downloads/
  environment:
  http_proxy: http://example-proxy:80/

    但是,一旦任务数量增多或者需要其他环境变量时,这种方法就会变得非常笨重。在此例中,我们可以使用playbook中的var区块(或者一个包含变量的外部文件)来传递多个环境变量到play中,如以下代码所示:

vars:
    var_proxy:
    http_proxy: http://example-proxy:80/
    https_proxy: https://example-proxy:443/
    [etc...]
tasks:
    - name: 使用指定的代理服务器下载文件
      get_url: url=http://www.example.com/file.tar.gz dest=~/Downloads/
      environment: var_proxy

    如果要为整个系统设置代理服务器,那么建议使用/etc/environment文件进行定义,如以下代码所示:

vars:
    proxy_state: present
task:
    - name: Configure the proxy
        lineinfile:
            dest: /etc/environment
            regexp: "{{ item.regexp }}"
            line: "{{ item.line }}"
            state: "{{ proxy_state }}"
            with_items:
                - { regexp: "^http_proxy=",line:"http_proxy=http://example-proxy:80/" }
                - { regexp: "^http_proxy=",line:"https_proxy=https://example-proxy:443/" }
                - { regexp: "^ftp_proxy=",line:"ftp_proxy=http://example-proxy:80/" }

    采用这种方法,无论我们使用何种代理协议(http、https或ftp),都可以通过变量proxy_stat来决定是否启用代理服务。当然,我也可以使用类似地方法来设置其他类似系统级别的变量。

    我们可以使用如下命令来检测我们在远程主机设置的环境变量是否生效:

    ansible test -m shell -a 'echo $TEST'

5.3 变量

    Ansible中变量的命名规则与其他语言或系统中变量的命名规则非常相似。在Ansible中,变量以英文小写字母开头,中间可以包含下划线(_)和数字。

    在Inventory文件中,比如Ansible的Hosts文件,我们使用等号"="来为变量赋值,如

    foo=bar

    在Playbook和包含变量设置的配置文件中,我们使用冒号":"来为变量赋值,如:

    foo:bar

5.3.1 Playbook变量

    Ansible中有多种不同的途径来定义变量。

    比如在运行Playbook时,使用--extra-vars选项指定额外的变量。

   

ansible-playbook example.yml --extra-vars "foo=bar"

    我们也可以直接引用JSON或YMAL代码来设置额外变量,或者直接将定义变量的JSON或YAML代码写入一个文件中,然后调用这个文件。比如:

ansible-palybook example.yml --extra-vars "@even_more_vars.json"
ansible-playbook example.yml --extra-vars "@even_more_vars.yml"

    上面是Ad-Hoc方式中设置额外变量的方法。

     在Playbook中,最常见的定义变量的方法是使用vars代码块。如Playbook内容如下:

---
- hosts: example
    vars:
        foo: bar
    tasks:
        # Prints "Variable 'foo' is set to bar".
        - debug: msg="Variable 'foo' is set to {{ foo }}"

    同时,变量的定义也可以在一个独立的文件中完成,当要使用时,在Playbook中使用vars_files代码块来引用这个文件,来看个例子。

    Playbook文件内容如下:

---
-hosts: example
    vars_files:
        - vars.yml
    tasks:
        - debug: msg="Variable 'foo' is set to {{ foo }}"

    定义变量的独立文件vars.yml的内容如下:

---
foo: bar

    上例中使用了vars_file代码块来调用独立文件vars.yml,并且能成功读取其中定义的变量。

    利用Ansible的内置环境变量(即使用setup模块可以查看到的变量),我们还可以实现变量配置文件的有条件导入。

    我们来看以下的应用场景:现在生产环境中都有多台主机,分别安装了CentOS系统和Debian系统,同时我们为两套系统设置了两个变量定义文件:apache_CentOS.yml和apache_default.yml,里面同时定义了同一个变量apache,在apache_CentOS.yml文件中定义为apache:httpd,在apache_default.yml文件中定义为apache:apache2,这样我们就实现了同一个Playbook可以针对不同系统环境实施不同的操作的效果。Playbook内容如下:

---
- hosts: example
    vars_files:
- [ "apache_{{ ansible_os_family }}.yml","apache_default.yml" ]
    tasks:
        - service: name={{ apache }} state=running

    在执行Playbook的过程中,Ansible会主动读取远程主机的Factor信息,从而获取远程主机的ansible_os_family的值,并在vars_files代码块读取该值得到对应名称的变量定义文件,如果没有匹配到合适的文件名,将默认读取apache_default.yml中的设定。

5.3.2 在Inventory文件中定义变量

    在Ansible中,Inventory文件通常是指Ansible的主机和组的定义文件Hosts(默认路径为/etc/ansible/hosts,简称Hosts文件)。在Hosts文件中,变量会被定义在主机名的后面或组名的下方,如下面这个例子所示:

# 为某台主机指定变量,作用范围仅限于当台主机
[shanghai]
app1.example.com proxy_state=present
app2.example.com proxy_state=absent

# 为主机组指定变量,指定范围为整个主机组
[shanghai:vars]
cdn_host=sh.static.example.com
api_version=3.0.

    在执行Ansible命令时,Ansible默认会从/etc/ansible/host_vars/和/etc/ansible/group_vars/两个目录下读取变量定义,如果/etc/ansible下面没有这两个目录,可以直接手动创建,并且可以在这两个目录中创建于Hosts文件中主机名或组名同名的文件来定义变量。

    举例来说,我们现在要给主机app1.example.com设置一组变量,那就可以直接在/etc/ansible/host_vars/目录下创建一个名为app1.example.com的空白文件,然后在文件中以YAML语法来定义所需的变量,如以下代码所示:

---
foo: bar
baz: qux

如此一来,变量foo和baz将自动给主机app1.example.com。

同理,要想针对整个shanghai主机组定义一些变量,则只需在/etc/ansible/group_vars/目录下创建于主机组名的YAML文件来定义变量就可以了。

5.3.3 注册变量

    注册变量,其实就是将操作的结果,包括标准输出和标准错误输出,保存到变量中,然后再根据这个变量的内容来决定下一步的操作,在这个过程中用来保存操作结果的变量就叫注册变量。我们在Playbook中使用register来声明一个变量为注册变量。

   在第4章中,我们就曾使用register来声明注册变量来保存命令运行结果,然后用其来判断是否需要启动Node.js,再来回顾一下那段代码:

- name: 获取正在运行的Node.js app列表
    command: forever list
    register: forever_list
    changed_when: false

- name: 启动Node.js app
    command: forever start {{ node_apps_location }}/app/app.js
    when: "forever_list.stdout.find('{{ node_apps_location }}/app/app.js') == -1"

    在这段代码中,我们使用Python内置的字符串的find方法来查找app.js的路径,如果没找到,程序就会自动启动Node.js。

5.3.4 使用高阶变量

    对于普通变量,例如由Ansible命令行设定的、在Hosts文件中定义的,再或者在Playbook和变量定义文件中定义的,这些变量都被称为简单变量或普通变量,我们可以直接在Playbook中使用双大括号加变量名来读取变量内容,形如{{variable}}。比如下面的例子:

- command: /opt/my-app/rebuild {{ my_environment }}

    当Playbook运行这段命令时,变量my_environment将会自动被替换为其所对应的变量内容。

    Ansible中除了这些普通变量之外,还有数组变量或者叫列表变量。由于Ansible是基于Python语言开发的,所以我们这里就称之为列表。列表的定义方法如下:

foo_list:
    - one
    - two
    - three

    列表定义完成后,要读取其中第一个变量,有以下两种方法:

foo[0]
foo|first

    Ansible内置变量ansible_eth0用来保存远程主机上面eth0接口的信息,包括IP地址和子网掩码等。下面我们使用debug模块来展示一下变量ansible_eth0的内容。

tasks:
    - debug: var=ansible_eth0




    当我们想要读取IPv4地址时,可使用如下两种方法实现:

{{ ansible_eth0.ipv4.address }}
{{ ansible_eth0['ipv4']['address'] }}

5.3.5 主机变量和组变量

    Ansible为用户提供了用于批量定义主机的管理文件,及Hosts文件,默认存放位置是/etc/ansible/hosts。有了这个文件,我们可以非常便捷地在里面为主机分组,极大地简化了多主机的操作。

    在Hosts文件中,我们使用如下格式定义主机组:

    [gorup]

    host1

    host2

     为每个主机定义自己专属变量最直接、最简单的办法就是:在Hosts文件中,在对应主机名的后面直接定义。如下所示:

    [group]

    host1 admin_user=jane

    host2 admin_user=jack

    host3

    这样我们就为host1和host2分别定义了一个变量,host3主机则无法使用该变量。

    如果要对整个主机组设置变量,则采用如下方法:

    [group:vars]

    admin_user=john

    这样一来,变量将会对主机组group下面的所有主机生效,就相当于给其下的每一台主机分别定义了一次变量admin_user。

    以上定义主机变量和主机组变量的方法,在主机或主机组数量较少的情况下非常方便有效。但当我们要为非常多的主机和主机组分别设置不同的变量时,这种方法就会显得比较笨拙。

    1.group_vars和host_vars

     Ansible在运行任务前,都会搜索与Hosts文件同一目录下的两个用于定义变量的目录:group_vars和host_vars。

     我们可以在这两个目录下放一些使用YAML语法编辑的 定义变量的文件,并以对应的主机名和主机组名来命名这些文件,这样在运行Ansible时,Ansible会自动去这两个目录下读取针对不同主机和主机组的变量定义。我们可以通过下面的例子来加深一下理解。

     1)对主机组group设置变量。

    

---
# File: /etc/ansible/group_vars/group
admin_user: john

     2)对主机host1设置变量。

---
# File: /etc/ansible/host_vars/host1
admin_user: jane

    除此以外,我们还可以在group_vars和host_vars两个文件夹定义all文件,来一次性地为所有的主机组和主机定义变量。

    2.巧妙使用主机变量和组变量

    hostvars可以获取从一台远程主机上获取另一台远程主机的变量信息,变量hostvars包含了指定主机上所定义的所有变量。

    比如,我们想获取host1上的变量admin_user的内容,在任意主机上直接使用下面这行代码即可。

   

{{ hostvars['host1']['admin_user'] }}

    Ansible提供了一些非常有用的内置变量,这里我们列举了几个常用的。

  • groups:包含了所有Hosts文件里主机组的一个列表。
  • group_names:包含了当前主机所在的所有主机组名的一个列表。
  • inventory_hostname:通过Hosts文件定义主机的主机名(与ansible_home不一定相同)。
  • inventory_hostname_short:变量inventory_hostname的第1部分,比如inventory_hostname的值是books.ansible.com,那么inventory_hostname_short就是books。
  • play_hosts:将执行当前任务的所有主机。

5.3.6 Facts(收集系统信息)

    1.Facts信息

    在运行任何一个Playbook之前,Ansible默认会先抓取Playbook中所指定的所有主机的系统信息,这些信息我们称之为Facts。在之前我们运行的所有Playbook任务中,都会出现类似下面代码的内容:

ansible-playbook playbook.yml

    上述命令的运行结果如下:


    Facts信息包括(但不仅限于)远程主机的CPU类型、IP地址、磁盘空间、操作系统信息以及网络接口信息等,这些信息对于Playbook的运行

至关重要。我们可以根据这些信息来决定是否要继续运行下一步任务,或者将这些信息写入某个配置文件中。

    我们可以使用setup模块来获取对应主机上面的所有可用的Facts信息。比如:

[root@localhost ~]# ansible proxy -m setup
192.168.230.100 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.230.100"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::c8f4:9dff:fe3d:9953"
        ],
        "ansible_apparmor": {
            "status": "disabled"
        },
        ... 
    在某些用不到Facts信息的Playbook任务中,我们可以在Playbook中设置gather_facts:no来暂时让Ansible在执行Playbook任务之前跳过收集

远程主机Facts信息这一步,这样可以为任务节省几秒钟的时间,如果主机数量多的话,就能节省更多的时间。在Playbook中设置gather_facts的方法如下:

- hosts: db
    gather_facts:no

    2.本地Facts变量

    我们可以把需要定义的变量写进一个以.fact结尾的文件中,这个文件可以是JSON文件或INI文件,或者是一个可以返回JSON代码的可执行文件。

然后将其放置在/etc/ansible/facts.d文件夹中,Ansible在执行任务时会自动到这个文件夹下读取变量信息。

    比如,我们在远程主机上创建了一个.fact文件/etc/ansible/facts.d/settings.fact,文件内容如下:

    [users]

    admin=jane,john

    normal=jim

    接下来,使用setup模块就可以读取到这两个变量,如下所示:


    如果在一个Playbook中,只有部分Playbook任务用到了远程主机自定义的本地Facts,那么我们可以使用下面一段代码来明确的指明只显示这些本地Facts。

- name: 重新获取本地Facts
    setup: filter=ansible_local

5.3.7 Ansible加密模块Vault

    Ansible自带的Vault加密功能,Vault可以将经过加密的密码和敏感数据同Playbook存储在一起。


    Ansible Vault可以为我们提供非常高的安全加密级别,这将很好地帮我们解决后顾之忧。使用如下命令,可以利用Vault给文件加密:

    ansible-vault encrypt api_key.yml

除了encrypt选项之外,关于ansible-vault命令有几个比较常用的选项,列举如下。

  • edit:用于编辑ansible-vault加密过的文件
  • rekey:重新修改已被加密文件的密码
  • create:创建一个新文件,并直接对其进行加密。
  • view:查看经过加密的文件。
  • decrypt:解密文件

    除了手动输入密码进行解密以外,Ansible还提供了以密码文件的形式来解密的认证方式,Ansible Vault将密码文件放置于~/.ansible/,需设置其权限为600。

现在我们可以在~/.ansible/目录下创建一个权限为600的纯文本文件vault_pass.txt,并写入我们的Vault密码,使用如下命令就可非交互式地使用被加密过的Playbook运行任务了。

ansible-playbook test.yml --vault-password-file ~/.ansible/vault_pass.txt

5.3.8 变量优先级

Ansible官方给出了如下由高到低的优先级排序:

  • 在命令行中定义的变量(即用-e定义的变量)
  • 在Inventory中定义的连接变量(比如ansible_ssh_user)
  • 大多数的其他变量(命令行转换、play中的变量、included的变量、role中的变量等)
  • 在Inventory定义的其他变量
  • 有系统通过gather_facts方法发现的Facts
  • "Role默认变量",这个是默认的值,很容易丧失优先权。

5.4 if/then/when--流程控制

5.4.1 Jinja2正则表达、Python内置函数和逻辑判断

    Jinja2支持的数据类型有:字符串型(如"strings")、整数型(如45)、浮点数型(如42.33)、列表(如[1,2,3,4])、元组(与列表类型格式一样,只是内容无法修改)、字典(如{key:value,key2:value2},还有布尔型(如true或false)。

    Jinja2同时也支持基本的数据运算,如加、减、乘、除和比较(==表示相等,!=表示不相等,>=表示大于等于,等等)。逻辑运算,可以使用小括号来对逻辑运算符进行分组使用。

下列表达式的运算结果都为'true':

1 in [1,2,3]
'see' in 'Can you see me?'
foo != bar
(1<2) and ('a' not in 'best')

    除此之外,Jinja2还提供了非常有用的"test"语句。比如,我们可以使用如下语句来判定变量foo是否被定义过。

    foo is defined

    当变量foo被定义过,那么这个表达式的结果就是true,相反则为false。类似地还有:undefined,equalto(与==等效),even(判断对象是否是偶数)以及iterable(判断对象是否可迭代)。

    我们来看如下一个应用场景,目前我们有一款软件版本号为4.6.1,现在有一个任务需要通过判断软件的版本号来确定要不要执行接下来的任务,如果主版本号为4就执行任务,其他版本则不执行。这时,Jinja2表达式将不再适合,我们可以通过Python内置方法,使用点号"."来对版本号进行拆分后取得第1位主版本号,然后用它与数字4进行比较。具体代码如下:

- name: 当软件主版本号为4的时候进行操作
  [task here]
  when: software_version.split('.')[0] == '4'

    通常我们建议尽量使用更为简洁的Jinja2语句来进行判断,但是在涉及变量的复杂操作时,Python的内置方法还是不错的选择。

5.4.2 变量注册器register

    任何一个任务都可以注册一个变量来存储其运行结果,该注册变量在随后的任务中将像其他普通变量一样被使用。

    大部分情况下,我们使用注册器用来接收shell命令的返回结果,结果中包含标准输出(stdout)和错误输出(stderr)。使用下面一段代码即可调用注册器来获取shell命令的返回结果。

- shell: my_command_here
    register: my_command_result

    命令结果获取完成之后,可以使用注册变量的stdout方法来读取标准输出的内容:my_command_result.stdout;使用stderr方法来读取标准输入的内容:my_command_result.stderr。

    如果想查看一个注册变量都有哪些属性,那么在运行一个Playbook的时候,使用-v选项来检查Playbook的运行结果,通常我们会得到如下4种类型的运行结果。

  • changed:任务是否对远程主机造成的变更
  • delta:任务运行所用的时间
  • stdout:正常的输出信息
  • stderr:错误信息

5.4.3 when条件判断

    假设我们的所有服务器上都有一个布尔变量is_db_server,在数据库服务器上,其值为true,其他主机上值为false,我们只需要在数据库服务器上安装MySQL软件包,

- yum: name=mysql-server state=present
    when: is_db_server

    如果我们只在数据库服务器上定义is_db_server变量,其他主机上没有定义这个变量,这里我们就需要增加 一个判断条件,来判断变量是否被定义。

- yum: name=mysql-server state=present
    when: (is_db_server is defined) and is_db_server

    举例来说,我们想检查一个应用的运行状态,并判断返回的状态值,当状态为"ready"时,在执行下一步操作。任务代码如下:

- command: my-app --status
    register: myapp_result
- command: do-something-to-my-app
    when: "'ready' in myapp_result.stdout"



5.4.4 changed_when、failed_when条件判断

    对于Ansible来说,其很难判断一个命令的运行是否符合我们的实际预期,尤其是当我们使用command模块和shell模块时,如果不使用changed_when语句,Ansible将永远返回changed。大部分模块都能正确返回运行结果是否对目标主机产生影响,我们依然可以使用changed_when语句来对返回信息进行重写,根据任务返回结果来判定任务的运行结果是否真正符合我们预期。

    正常情况下,当我们使用PHP Composer来安装项目依赖的时候,无论是否安装或升级的某些软件,Ansible任务的返回结果都是changed。但是当我们使用changed_when语句,并结合注册变量对任务返回结果进行判断后,在来决定是否显示状态为changed,将更加符合我们的实际需求。比如:

- name: Install dependencies via Composer.
    command: "/usr/local/bin/composer global require phpunit/phpunit --prefer-dist"
    register: composer
    changed_when: "'Nothing to install or update' not in composer.stdout"

    由此我们可以看出,当PHP Composer安装或升级了某些软件的时候,也就是其运行结果不包含"Nothing to install or update"字段的时候,Ansible才会返回运行状态为changed,这更符合我们的需要。

    有一些命令会将自己的运行结果写入标准错误输出stderr中,而不是通常的标准输出stdout中,这时可以使用failed_when来对结果进行判断,从而告诉Ansible真正的运行结果到底是成功还是失败。

    在下面的例子中,我们将通过判断Jenkins CLI命令的错误输出来判定命令是否真的运行失败。代码如下:

- name: 通过CLI导入Jenkins任务
    shell: >
       java -jar /opt/jenkins-cli.jar -s http://localhost:8080/
       create-job "My Job" < /usr/local/my-job.xml
    register: import
    failed_when: "import.stderr and 'already exists' not in import.stderr"

     本例我们希望当命令返回错误信息并且返回的错误信息中不包含"already exists"的内容时,再通过Ansible显示命令运行失败。

5.4.5 ignore_errors条件判断

    在有些情况下,一些必须运行的命令或脚本会报一些错误,而这些错误并不一定真的说明有问题,但是经常会给接下来要运行的任务造成困扰,甚至直接导致Playbook运行中断。

    这时候,我们可以在相关任务中添加ignore_errors:true来屏蔽所有错误信息,Ansible也将视该任务运行成功,不再报错,这样就不会对接下来要运行的任务造成额外困扰。但是要注意的是,我们不应过度依赖ingore_errors,因为它会隐藏所有的报错信息,而应该把精力集中在寻找报错的原因上面,这样才能从根本上解决问题。

5.5 任务间流程控制

5.5.1 任务委托

     默认情况下,Ansible的所有任务都是在我们指定的机器上面运行的,当在一个独立的群集环境中配置时,这并没有什么问题。而在有些情况下,比如给某台服务器发送通知或向监控服务中添加被监控主机,这个时候任务就需要在特定的主机上运行,而非一开始指定的所有主机。此时就需要用到Ansible的任务委托功能。

     使用delegate_to关键字便可以配置任务在指定的机器上执行,而其他任务还是在hosts关键字配置的所有机器上运行,当到了这个关键字所在的任务时,就使用委托的机器运行。而facts还使用与当前的host,下面我们演示一个例子,使用Munin在监控服务器中添加一个被监控主机。

---
- hosts: webservers
  tasks:
    - name: Add server to Munin monitoring configuration.
          command: monitor-server webservers {{ inventory_hostname }}
          delegate_to: "{{ monitoring_master }}"
    

    由本例可以看出,我们虽然在Playbook开头指定了操作对象是所有webservers,但是添加监控对象这一任务却只需要在监控服务器上运行,所以我们就使用了delegate_to来指定运行此任务的主机。

    如果我们想将一个任务在Ansible服务器本地运行,除了将任务委托给127.0.0.1之外,还可以全用local_action方法来完成。看下面两个功能一模一样的例子:

- name: Remove server from load balancer.
      command: remove-from-lb {{ inventory_hostname }}
      delegate_to: 127.0.0.1

- name: Remove server from load balancer.
      local_action: command remove-from-lb {{ inventory_hostname }}

5.5.2 任务暂停

    在有些情况下,一些任务的运行需要等待一些状态的恢复,比如某一台主机或者应用刚刚重启,我们需要等待它上面的某个端口开启,此时我们就不得不将正在运行的任务暂停,直到其状态满足我们的需求。先来看下面的例子:

- name: Wait for webserver to start.
  local_action:
      module: wait_for
      host: webserver1
      prot: 80
      delay: 10
      timeout: 300
      state: started

    这个任务将会每10s检查一次主机webservers1上面的80端口是否开启,如果超过300s,80端口仍未开启,将会返回失败信息。

    总结一下,Ansible的wait_for模块常用于如下一些场景中:

  • 使用选项host、port、timeout的组合来判断一段时间内主机的端口是否可用;
  • 使用path选项(可结合search_regx选项进行正则匹配)和timeout选项来判断某个路径下的文件是否存在;
  • 使用选项host、port和state选项的drained值来判断一各给定商品的活动连接是否被耗尽;
  • 使用delay选项来指定在timeout时间内进行检测的时间间隔,时间单位为秒。

5.6 交互式提示

    在少数情况下,Ansible任务运行的过程中需要用户输入一些数据,这些数据要么比较私密不方便保存,或者数据是动态的,不同用户有不同的需求,比如输入用户自己的账号和密码或者输入不同的版本号会触发不同的后续操作等。Ansible的vars_prompt关键字就是用来处理上述这种与用户交互的情况的。

    我们先来看一个例子:我们需要用户提供自己的账号和密码来登录自己的网络账户,并且可以给用户已适当的文字提示。代码如下所示:

---
- hosts:all
  vars_prompt:
    - name: share_user
        prompt: "What is your network username?"
    - name: share_pass
        prompt: "What is your network password?"
    private: yes

    关键字vars_prompt下面几个常用的选项总结如下。
  • private:该值为yes,即用户所有的输入在命令默认都是不可见的;而将其值设为no时,用户输入可见。
  • default:为变量设置默认值,以节省用户输入时间。
  • confirm:特别适合输入密码的情况,如果将值设为yes,则会要求用户输入两次,以增加输入的正确性。

5.7 Tags标签

    默认情况下,Ansible在执行一个Playbook时,会执行Playbook中定义的所有任务。Ansible的标签(Tags)功能可以给角色(Roles)、文件、单独的任务甚至整个Playbook打上标签,然后利用这些标签来指定要运行Playbook中的个别任务,或不执行指定的任务,并且它的语法非常简单。

    在下面这个例子中,我们将展示多种不同的打标签的方法。

---
# 可以给整个Playbook的所有任务打一个标签
- hosts: proxy
  tags: deploy
  roles:
    # 给角色打的标签将会应用于角色下所有的任务
    - { role: tomcat,tags: ['tomcat','app'] }
  tasks:
    - name: Notify on completion.
      local_action:
        module: osx_say
        msg: "{{ inventory_hostname }} is finished!"
        voice: Zarvox
      tags:
        - notifications
        - say
    - include: foo.yml
      tags: foo

     假设我们将上述代码保存在文件tags.yml中,我们可以通过执行下面这条命令来只执行"Notify on completion."任务。
ansible-palybook tags.yml --tags "say"

     如果我们想跳过带有"notifications"标签的任务,可以使用--skip-tags选项。

ansible-playbook tags.yml --skip-tags "notifications"

     正如我们所看到的,只要我们在Playbook中打好完整的标签,我们就可以非常方便地对Playbook中的众多任务进行抽取,有针对地执行任务,或者跳过那些暂时不需要执行的任务。

    我们可以为一个对象添加多个标签,但是在添加多标签时必须使用YAML列表格式。YAML列表格式如下:

# 最简洁的写法
tags: ['one','two','three']
# 最清晰的写法
tags:
    - one
    - two
    - three

# 不正确的写法
tags: one,two,three

5.8 Block块

    Ansible从2.0.0版本开始引入了块功能,块功能可以将任务进行分组,并且可以在块级别上应用任务变量。同时,块功能还可以使用类似于其他编程语言处理异常那样的方法,来处理块内部的任务异常。

    来看一个例子:

---
- hosts: web
  tasks:
    # Install and configure Apache on RedHat/CentOS hosts.
    - block:
        - yum: name=httpd state=present
        - template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
        - service: name=httpd state=started enabled=yes
    when: ansible_os_family == 'RedHat'
    sudo: yes

    # Install and configure Apache on Debian/Ubuntu hosts.
    - block:
        - apt: name=apache2 state=present
        - template: src=httpd.conf.j2 dest=/etc/apache2/apache2.conf
        - service: name=apache2 state=started enabled=yes
    when: ansible_os_family == 'Debian'
    sudo: yes

    在上例中,我们使用了带有when语句的块来指定在不同平台上运行一组不同的安装配置任务,我们可以看到,块将apt,template,service三个模块任务包含在内作为一个整块,这样就不用再每一个模块任务后都跟一个when语句进行操作系统的判断了。由此我们可以看出,块功能非常适合于多个任务共用同一套任务参数的情况。

    块功能也可以用来处理任务的异常。比如有一个Ansible任务时监控一个并不太重要的应用,这个应用的正常运行与否对后续的任务并不产生影响,这时我们就可以通过块功能来处理这个应用的报错。如下代码所示:

tasks:
  - block:
    - name: Shell script to connect the app to a monitoring service.
      script: monitoring-connect.sh
  rescue:
    - name: 只有脚本报错时才执行
      debug: msg="There was an error in the block."
  always:
    - name: 无论结果如何都执行
      debug: msg="This always executes."

    当块中的任意任务出错时,rescue关键字对应的代码块就会被执行,而always关键字对应的代码块无论如何都会被执行。

































相关内容

    暂无相关文章