三个层面学playbook(核心)

ansible-playbook是ansible工具中的核心,对比ad-hoc(ansible)命令,可以把playbook理解为一系列动作的组成,结果传递、判断等等,这些是ansible命令无法完成的。

ansible中如何使用yaml编写playbook,这对于很多人来说不容易搞懂,特别是层级缩进关系方面。

我打算从三个层面讲playbook编写,正如4.7所说,按照playbook、play、tasks三个层面理解

playbook关键字

附上官网关关键字链接,本文对其中一部分举例,注意只是一部分介绍,我也不懂全部,介绍这些只为了理解playbook书写规则,规则掌握了,后续学习基本不会迷路

# 先附上一部分综合例子,便于后面理解
---
- hosts: lzcx
# gather_facts: False
connection: local
become: yes
remote_user: root
vars:
var1: value1
var2: value2
tasks:
- debug: msg="{{var2}} {{var3}}"
vars:
var2: value3
var3: value4
when: ansible_distribution_major_version == "7"
- name: print something
shell: echo {{item}}
with_items:
- zhangsan
- lisi
- wangwu
register: hi
ignore_errors: True
- debug: msg="{% for i in hi.results %} {{i.stdout}} {% endfor %}"

5.1、playbook层面

playbook层面,因为一个playbook一般一个play,便于维护,这里不作详细介绍。

5.2、play层面(重要)

play层面,对应元素hosts, tasks, 变量vars, connection, remote_user, become、name, 等等,这里介绍常见的元素。

5.2.1、hosts指定主机

指定inventory中的主机或主机组,一个play对应一个hosts,一般yaml文件里面就一个hosts,定义在开头

取值方面,多个值按逗号","分割,支持2.5节里面的匹配,具体查看2.5节

5.2.2、gether_facts

获取远程主机信息,关闭时能加快playbook执行速度,前提是不需要获取主机信息,当需要调用主机信息相关变量,可以用setup模块查看,例如"ansible_all_ipv4_addresses",不能关闭该选项

本章开始的gether_facts注释打开,则when语句行需要注释

5.2.3、connection

说明文档原文"connection type to use (default=smart)",默认会根据环境自动设置,一般不用自己设置

5.2.4、become

是否切换为其他用户,值为yes|no

5.2.5、become_user

切换的用户

5.2.6、vars定义变量

需要注意的是vars是可以存在了play层面和tasks层面的元素,同时tasks层面的vars变量会覆盖play层面的同名变量,这一点比较特殊,参考 5.3.1.4、 vars定义变量

5.3、tasks层面(重要)

5.3.1、变量

ansible中定义变量的⽅式有很多种,⼤致有:(1)将模块的执⾏结果注册为变量;(2)直接定义字典类型的变量;(3)role中⽂件内定义变量;(4)命令⾏传递变量;(5)借助with_items迭代将多个task的结果赋值给⼀个变量;(6)inventory中的主机或主机组变量;(7)内置变量

5.3.1.2、register注册变量

使⽤register选项,可以将当前task的输出结果赋值给⼀个变量,注意,模块的输出结果是json格式的,所以,引⽤变量时要指定引⽤的对象

---
- hosts: lzcx
tasks:
- shell: echo 'hahaha'
register: temp
- debug: var=temp.stdout

5.3.1.3、 set_fact定义变量

set_fact和register的功能很相似,也是将值赋值给变量。它更像shell中变量的赋值⽅式,可以将某个变量的值赋值给另⼀个变量,也可以将字符串赋值给变量

---
- hosts: lzcx
tasks:
- shell: echo haha
register: say_ha
- set_fact: var1="{{say_ha.stdout}}"
- set_fact: var2="your name is"
- debug: msg="{{var2}} {{var1}}"

5.3.1.4、 vars定义变量

可以在play或task层次使⽤vars定义字典型变量。如果同名,则task层次的变量覆盖play层次的变量

---
- hosts: lzcx
vars:
var1: value1
var2: value2
tasks:
- debug: msg="{{var1}} {{var2}}"
vars:
var2: value2.2 # tasks层次的变量会覆盖play层次的同名变量

5.3.1.5、 roles中的变量

由于role是整合playbook的,它有默认的⽂件组织结构。其中有⼀个⽬录vars,其内的main.yml⽤于定义变量。还有defaults⽬录内的main.yml则是定义role默认变量的,默认变量的优先级最低

5.3.1.6、 命令行传递变量

ansible和ansible-playbook命令的"-e"选项都可以传递变量,传递的⽅式有两种: -e key=value 和 -e @var_file 。注意,当key=value⽅式传递变量时,如果变量中包含特殊字符,必须防⽌其被shell解析

ansible localhost -m shell -a "echo {{say_hi}}" -e 'say_hi="hello world"'
ansible localhost -m shell -a "echo {{say_hi}}" -e @/tmp/var_file1.yml
# /tmp/var_file1.yml 内容
---
say_hi: zhangsan lisi

5.3.1.7、 借助with_items叠加变量

ansible中可以借助with_items实现列表迭代的功能,作⽤于变量注册的⾏为上,就可以实现将多个结果赋值给同⼀个变量

例如下⾯的playbook中,给出了3个item列表,并在shell模块中通过固定变量"{{item}}"分别迭代,第⼀次迭代的是haha,第⼆次迭代的是heihei,第三次迭代的是hehe,也就实现了3次循环。最后,将结果注册为变量hi_var。还可以使⽤for循环遍历列表

#
---
- hosts: lzcx
connection: local
remote_user: root
become: yes
tasks:
- name: test with items
shell: echo "{{item}}"
with_items:
- haha
- heihei
- hehe
register: hi_var
- debug: var=hi_var.results[0].stdout
- debug: var=hi_var.results[1].stdout
- debug: var=hi_var.results[2].stdout
- debug: msg="{% for i in hi_var.results %} {{i.stdout}} {% endfor %}"

每次迭代的过程中,调⽤item的模块都会将结果保存在⼀个key为results的数组中。因此,引⽤迭代后注册的变量时,需要在变量名中加上results,并指定数组名。例如上⾯的 hi_var.results[N].stdout

建议使用循环输出格式

5.3.1.8、 inventory中的主机变量和组变量

在inventory⽂件中可以为主机和主机组定义变量,不仅包括内置变量赋值,还包括⾃定义变量赋值。

主机变量优先级⾼于主机组变量,给定的主机组变量优先级⾼于all特殊组

# 自定义临时清单
cat /tmp/hosts
192.168.1.214 ansible_ssh_port=22 var1=1 # 主机内置变量,这类变量不能调用
[centos7]
192.168.1.214 var1=2 # 主机变量,优先级最高
[centos7:vars]
var1=2.2 # 主机组变量,优先级次之
var2=3
[all:vars]
var2=4 # 所有组变量,优先级最低
# 执行查看变量优先级
ansible 192.168.1.214 -i /tmp/hosts -m shell -a 'echo "{{var1}} {{var2}}"'
# 结果
192.168.1.214 | CHANGED | rc=0 >>
2 3

5.3.1.9、内置变量

ansible除了inventory中内置的⼀堆不可被引⽤的设置类变量,还有⼏个全局都可以引⽤的内置变量,主要有以下⼏个:

inventory_hostname、inventory_hostname_short、groups、groups_names、hostvars、play_hosts、inventory_dir、ansible_version

  1. inventory_hostname和inventory_hostname_short

    分表代表的是inventory中被控节点的主机名和主机名的第⼀部分,如果定义的是主机别名,则变量的值也是别名
  2. groups和group_names

    group_names返回的是主机所属主机组,如果该主机在多个组中,则返回多个组,如果它不在组中,则返回ungrouped这个特殊组

    groups变量则是返回其所在inventory⽂件中所有组和其内主机名。注意,该变量对每个控制节点都返回⼀次,所以返回的内容可能⾮常多
  3. hostvars

    该变量⽤于引⽤其他主机上收集的facts中的数据,或者引⽤其他主机的主机变量、主机组变量。其key为主机名或主机组名。但注意,在引⽤其他主机facts中数据时,要求被引⽤主机进⾏了facts收集动作,或者有facts缓存。否则都没收集,当然⽆法引⽤其facts数据。也就是说,当被引⽤主机没有facts缓存时,ansible的控制节点中必须同时包含引⽤主机和被引⽤主机。

    除了引⽤其他主机的facts数据,还可以引⽤其他主机的主机变量和主机组变量,且不要求被引⽤主机有facts数据,因为主机变量和主机组变量是在ansible执⾏任务前加载的
  4. play_hosts和inventory_dir

    play_hosts代表的是当前play所涉及inventory内的所有主机名列表

    inventory_dir是所使⽤inventory所在的⽬录
  5. ansible_version

5.3.2、循环

ansible中的循环都是借助迭代来实现的。基本都是以"with_"开头。以下是常见的⼏种循环

5.3.2.1、with_items迭代列表(重要)

参考 5.3.1.7,

5.3.2.2、with_dict迭代字典项

使⽤"with_dict"可以迭代字典项。迭代时,使⽤"item.key"表⽰字典的key,"item.value"表⽰字典的值

另⼀种情况,字典是已存储好的。例如ansible facts中的ansible_eth0.ipv4,这种情况下,with_dict处可以直接指定该字典的key

5.3.2.3、with_fileglob迭代⽂件

例如,拷贝⼀堆⽤通配符匹配出来的⽂件到各远程主机上,注意,通配符⽆法匹配"/",因此⽆法递归到⼦⽬录中,也就⽆法迭代⼦⽬录中的⽂件

5.3.2.4、with_lines迭代⾏(重要)

可以将命令⾏的输出结果按⾏迭代。例如,find⼀堆⽂件出来,copy⾛

---
- hosts: localhost
tasks:
- copy: src="{{item}}" dest=/tmp/yaml
with_lines:
- find /tmp -type -f -name "*.yml"

5.3.2.4、 with_nested嵌套迭代

嵌套迭代是指多次迭代列表项

---
- hosts: localhost
tasks:
- debug: msg="{{item[0]}} & {{item[1]}}"
with_nested:
- [a, b]
- [1, 2, 3]

5.3.3、 条件判断when

在ansible中,只有when语句可以实现条件判断。when判断的对象是task,所以和task在同⼀列表层次。它的判断结果决定它所在task是否执⾏,⽽不是它下⾯的task是否执⾏。when中引⽤变量的时候不需要加{{ }}符号。

tasks:
- name: config the yum repo for centos 6
yum_repository:
name: epel
description: epel
baseurl: http://mirrors.aliyun.com/epel/6/$basearch/
gpgcheck: no
when: ansible_distribution_major_version == "6"

when语句支持逻辑操作,或(or)、与(and)也可以分行写、非(not)

支持直接引用定义了布尔值的变量

可以使⽤jinja2的 defined 来测试变量是否已定义,使⽤ undefined 可以取反表⽰未定义

tasks:
- shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
when: foo is defined
- fail: msg="Bailing out. this play requires 'bar'"
when: bar is undefined

5.3.4、报错处理failed_when

该选项类似编程语言中的try语句,能够抓取报错信息,让playbook执行完成

---
- hosts: lzcx
tasks:
- name: this command prints FAILED when it fails
command: /usr/bin/example-command -x -y -z
register: command_result
failed_when: "FAILED in command_result.stdout"

很明显,例子中的命令不存在,即执行失败,failed_when通过调用注册变量打印错误信息,此时会生成yaml文件同名的retry后缀格式文件

5.3.5、命名name

用于给模块自定义命名,如果没有设置,默认输出模块名

5.3.6、ignore_errors

忽略报错信息

---
- hosts: lzcx
tasks:
- name: this command prints FAILED when it fails
command: /usr/bin/example-command -x -y -z
register: command_result
ignore_errors: True
failed_when: "FAILED in command_result.stdout"

此时不会生成yaml文件同名的retry后缀格式文件

5.3.7、tag标签

可以为playbook中的每个任务都打上标签,标签的主要作⽤是可以在ansible-playbook中设置只执⾏哪些被打上tag的任务或忽略被打上tag的任务,以下是ansible-playbook中关于tag的选项

tasks:
- name: make sure apache is running
service: name=httpd state=started
tags: apache
- name: make sure mysql is runnig
service: name=mysqld state=started
tags: mysql

5.3.8、include

include的设计是为了让playbook模块化,将任务中的playbook分拆,细化各个部分,便宜维护和复用

可以将task列表和handlers独⽴写在其他的⽂件中,然后在某个playbook⽂件中使⽤include来包含它们。除此之外,还可以写独⽴的playbook⽂件,使⽤include来包含这个⽂件。也即是说,include可以导⼊两种⽂件:导⼊task、导⼊playbook。除此之外还可以传入变量给include

注意:nsible2.4之前,引入外部task或play只有include方法,在2.4之后,新增了includes和imports方法,具体查看官网说明includes和imports

用法一

# 引入task列表文件,a.yml
---
- name: execute ntpdate
shell: /usr/sbin/ntpdate ntp1.aliyun.com
- name: execute the variables
debug: msg="{{hi}} {{haha}}"

同级目录下调用a.yml

---
- hosts: localhost
tasks:
- include: a.yml hi="Hello world" # 传入参数
vars:
haha: "ni hao ya" # 用vars传递参数

用法二

# 引入整个playbook,由于是引入整个playbook,加载里面的play,需要注意层级关系
- name: this is a play at the top level of a file
hosts: localhost
become: yes
remote_user: root
tasks:
- name: say hi
tags: foo
shell: echo "hahaha"
- include: webserver.yml # 注意层级关系
- include: dbserver.yml

从上面两个例子可以看出,include和vars元素一样,是可以跨层级出现的,两者跨的层级不同,vars是跨play和tasks;include是跨tasks和playbook

5.3.9、notify和handlers

notify:ansible中⼏乎所有的模块都具有幂等性,这意味着被控主机的状态是否发⽣改变是能被捕捉的,即每个任务的changed=true或changed=false

ansible在捕捉到changed=true时,可以触发notify组件,notify是⼀个组件,并⾮⼀个模块,它可以直接定义action,其主要⽬的是调⽤handler

notify这个action可用于在每个play的最后被触发,这样可以避免多次有改变发生时每次都执行指定的操作,取而代之,仅在所有的变化发生完成后一次性地执行指定操作

handlers:handlers是一些 task 的列表,通过名字来引用,它们和一般的 task 并没有什么区别,handlers里面的name必须要和notify一致,最佳的应用场景是用来重启服务,或者触发系统重启操作

tasks:
- name: template configuration file
template: src=template.j2 dest=/etc/foo.conf
notify:
- restart memcached
- restart apache
handlers:
- name: restart memcached
service: name=memcached state=restarted
- name: restart apache
service: name=apache state=restarted

5.3.10、总结

ansible中的playbook到此介绍,基础部分相信能够搞懂了,至少版本新特性,进阶高级用法等等,对于掌握规则来说不难,规则掌握之后,跟着官网例子学习就可以了

05-20 10:23