三个层面学playbook(核心)
三个层面学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
- inventory_hostname和inventory_hostname_short
分表代表的是inventory中被控节点的主机名和主机名的第⼀部分,如果定义的是主机别名,则变量的值也是别名 - groups和group_names
group_names返回的是主机所属主机组,如果该主机在多个组中,则返回多个组,如果它不在组中,则返回ungrouped这个特殊组
groups变量则是返回其所在inventory⽂件中所有组和其内主机名。注意,该变量对每个控制节点都返回⼀次,所以返回的内容可能⾮常多 - hostvars
该变量⽤于引⽤其他主机上收集的facts中的数据,或者引⽤其他主机的主机变量、主机组变量。其key为主机名或主机组名。但注意,在引⽤其他主机facts中数据时,要求被引⽤主机进⾏了facts收集动作,或者有facts缓存。否则都没收集,当然⽆法引⽤其facts数据。也就是说,当被引⽤主机没有facts缓存时,ansible的控制节点中必须同时包含引⽤主机和被引⽤主机。
除了引⽤其他主机的facts数据,还可以引⽤其他主机的主机变量和主机组变量,且不要求被引⽤主机有facts数据,因为主机变量和主机组变量是在ansible执⾏任务前加载的 - play_hosts和inventory_dir
play_hosts代表的是当前play所涉及inventory内的所有主机名列表
inventory_dir是所使⽤inventory所在的⽬录 - 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的选项
--list-tags # list all available tags
t TAGS, --tags=TAGS # only run plays and tasks tagged with these values
--skip-tags=SKIP_TAGS # only run plays and tasks whose tags do not match these values
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到此介绍,基础部分相信能够搞懂了,至少版本新特性,进阶高级用法等等,对于掌握规则来说不难,规则掌握之后,跟着官网例子学习就可以了