第4章 Ansible Playbook杂谈
4.1 再谈Ansible变量
4.1.1 变量的作用域
-
Global,作用域为全局
- Ansibl配置文件中定义的变量
- 环境变量
- ansible/ansible-playbook 命令行中传进来的变量
-
Play,作用域为Play(一个Playbook由多个Play构成)
- Play中vaars关键字下的定义饿变量
- role在文件default/main.yml 和 vars/main.yml 中定义的变量
-
Host,作用域为某个主机
- 定义在主机清单中变量
- 主机的系统变量
- 注册变量
4.1.2 变量的优先级
变量优先级由低到高排序:
- role defaults
- dynamic inventory variables
- inventory variables
- inventory group_vars
- inventory host_vars
- playbook group_vars
- playbook host_vars
- host facts
- registered variables
- set_facts
- paly vars_prompt
- play vars_files
- role variable and include variables
- block variables
- tasks variables
- extra variables
从上面的排序可以看出,除了role defaults 变量外,其他变量的作用域越小越精确,变量的优先级越高。优先级高的变量能覆盖优先级的变量。
4.2 使用lookup访问外部文件或者数据库中的数据
Ansible Playbook允许用户使用自定义的变量,不过当变量过大,或者太复杂时,无论是Playbook中通过vars定义,还是在单独的变量文件中定义,可读性都比较差,而且不够灵活。
有了lookup就能解决这类难题,lookup既能够读取Ansibe管理节点上文件系统的文件内容到Ansible变量中,也可以读取配置的数据库中的内容。
下面是lookup的基本使用方法,将Ansible管理节点上文件data/plain.txt的内容读取出来,并赋值给contents。file高速lookup读取对象的类型是File,直接读取文件内容,无需特别的处理。
---
- hosts: all
vars:
contents: "{{ lookup('file', 'data/plain.txt')}}"
tasks:
- name: show vars
debug: msg="the value of data/plain.txt is {{ contents }}"
下面具体介绍下lookup的功能。
4.2.1 lookup 读取文件
上面的例子使用了file类型的lookup,是最简单的lookup用法。
4.2.2 lookup生成随机密码
第一次执行时,如果密码文件不存在,那么lookup会创建一个,如果已经存在了,那就直接使用。
---
- hosts: localhost
vars:
password: "{{ lookup('password', '/tmp/password/pcm length=8')}}"
tasks:
- name: show password
debug: var=password
4.2.3 lookup 读取环境变量
env类型的lookup可以读取Linux上的环境变量,如下:
- hosts: localhost
tasks:
- name: show env vars
debug: msg="{{ lookup('env', 'HOME')}} is an env variables."
4.2.4 lookup 读取Linux命令行的执行结果
pipe类型的lookup可以将Linux的执行结果读取到Ansible中:
---
- hosts: localhost
tasks:
- name: show env vars
debug: msg="{{ lookup('pipe', 'data')}} is an env variables."
4.2.5 lookup 读取template变量替换后的文件
template类型的lookup可以将一个template文件经过变量替换后的文件内容读取到Ansible中。当然,如果template文件中有未定义的变量,则会报错。
---
- hosts: localhost
vars:
name: pcm
tasks:
- name: show env vars
debug: msg="{{ lookup('template', 'some_template.j2')}} is an env variables."
4.2.6 lookup 读取配置文件
lookup 支持读取两种类型的配置文件:ini和Java的properties。
ini 类型的lookup默认读取配置文件的类型是ini。
假设我们有个 users.ini 的配置文件,内容如下
[intergration]
user=pcm
playbook如下:
---
- hosts: localhost
tasks:
- name: show ini file
debug: msg="User is intergration is {{ lookup('ini', 'user section=intergration file=users.ini') }}"
输出结果:
TASK [show ini file] ***********************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "User is intergrate is pcm"
}
读取properties类型文件时,需要加一个额外的参数来告诉lookup,这是一个properties类型的文件。
- hosts: localhost
tasks:
- name: show ini file
debug: msg="user.name is {{ lookup('ini', 'user.name type=properties file=users.properties') }}"
实际上,每个参数都有默认值的,所以在使用 ini类型的lookup是,每个参数都是可选的。默认的参数如下:
参数名 | 默认值 | 参数含义 |
---|---|---|
type | ini | 文件的类型 |
file | ansible.ini | 加载文件的名字 |
section | global | 默认在哪个section里面查找key |
re | False | key的正则表达式 |
default | empty string | key不存在时的返回值 |
4.27 lookup 读取 CSV文件的指定单元
使用的是csvfile类型,貌似没什么用途,略过。
4.2.8 lookup 读取DNS解析的值
dig 类型的lookup可以向DNS服务器查询指定域名的DNS记录,它可以查询任何的DNS记录,包括正向查询和方向查询。
---
- hosts: localhost
tasks:
- name: show baidu ip
debug: msg="The IPv4 address for baidu.com. is {{ lookup('dig','baidu.com.') }}"
4.2.9 更多的lookup功能
参考Ansible的官方文档,有些lookup的功能需要额外的Python包支持。
4.3 过滤器
过滤器(filter)是Python模版语言Jinja2提供的模版,可以用来操作数据。它在Ansible的管理节点上执行并操作数据,而不是在远程的目标主机上。
过滤器和lookup类似,都是在 {{}} 中使用。不同类型的过滤器功能差距很大。过滤器是Ansible使用模版语言Jinja2的内置共。在Ansible中,不仅可以使用jinja2自带的过滤器,还可以使用Ansible提供的过滤器,以及用户自定义的过滤器。
本节重点介绍Ansible提供的过滤器,Jinja2自带的过滤器请参考jinja2的官方文档。
4.3.1 过滤器使用的位置
下面用一个最简单的过滤器来说明过滤器的语法,quote过滤器的功能是给字符串加上引号。
---
- hosts: localhost
vars:
test_var: "Test string"
tasks:
- name: "quote {{ test_var }}"
debug: msg="echo {{ test_var |quote }}"
输出的结果如下:
TASK [quote Test string] ********************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "echo 'Test string'"
}
4.3.2 过滤器对普通变量的操作
- default: 为没有定义的变量提供默认值
- name: "变量没有定义的话,输出默认值Default"
debug: msg="{{ some_undefined_variable | default("Default") }}"
- omit: 忽略变量的占位符
与default一起使用时,如果某个变量没有定义,那么使用omit占位符,Ansible就会把这个参数按照没有传这个参数的值来处理。
文件 /tmp/foo 没有定义参数mode,所以default(moit)会在没有定义mode时忽略mode变量,Ansible的file模块会按照没有传入mode这个参数来创建文件 /tmp/foo. 文件/tmp/baz 定义了mode为0444,所以文件的权限是0444。
- name: touch files with an optional mode
file: dest={{ item.path }} state=touch mode={{ item.mode|default(omit) }}
with_items:
- path: /tmp/foo
- path: /tmp/baz
mode: "0444"
- mandatory: 强制变量必须定义,否则拋错。
在Ansible默认的配置中,如果变量没有定义,在直接使用变量的时候会报错。但是如果Ansible配置文件中使用了下面的配置,那么遇到未定义的变量时,Ansible就不会拋错。
error_on_undefined_vars = False
而这时候如果想要约束某个变量必须定义,就可以使用mandatory。
---
- hosts: localhost
tasks:
- name: show vars
debug: msg="{{ some_undefined_variable|mandatory }}"
- bool: 判断变量是否为布尔类型
---
- hosts: localhost
vars:
test_var = True
tasks:
- name: show vars
debug: msg=test
when: test_var | bool
- ternary: Playbook的条件表达式
ternary类似于编程语言中的类型表达式,("A?B:C")当条件为真的时候返回B,为假时返回C。如下:
---
- hosts: localhost
vars:
name = 'pcm'
tasks:
- name: "当变量name为pcm时,返回 Mr"
debug: msg="{{ (name == "pcm" ) |termary('Mr','Ms') }}"
4.3.3 过滤器对文件路径的操作
Ansible为了方便对文件及其路径进行操作,提供了一系列的文件目录的操作,包含获取文件名、路径名等等。在Linux下,常用的过滤器有:
- basename: 获取路径中的文件名
- dirname: 获取文件的目录
- expanduser: 拓展为实际的目录
- realpath: 获取链接文件所指文件的真实路径
- relpath 获取相当于某一目录的相对路径
- splitext: 把文件用点号分割成多个部分
使用示例如下:
- hosts: localhost
vars:
linux_path: "/etc/hosts"
tasks:
- name: get basename
debug: msg="{{ linux_path | basename }}"
windows文件路径的操作的过滤器有:
- win_basename: 获取文件名
- win_dirname: 获取文件目录
- win_splitdrive: 把路径分割为多个部分
示例略过。
4.3.4 过滤器对字符串变量的操作
-
quote: 给字符串加引号
略过,前面已经演示过了 -
base64: 得到字符串的Base64编码。
vars:
name: pcm
tasks:
- name: get base64
debug: msg="{{ pcm | b64encode }}"
- hash: 获取字符串的哈希值
计算哈希值的算法很多,如sha1、md5、checksum等。
vars:
my_passwd= "pass123"
tasks:
- name: "get hash"
debug: msg="{{ my_passwd |hash('sha1') }}"
- comment: 把字符串变成代码注释的一部分
comment有很多风格和格式,在下面的例子中,展示了将字符串转化为不同风格和格式注释的使用方法。
---
- hosts: localhost
vars:
my_comment: "This is a test comment"
tasks:
- name: "Simplte comment"
debug: msg="{{ my_comment | comment }}"
- name: "C style comment"
debug: msg="{{ my_comment | comment('c') }}"
运行结果如下:
TASK [Simplte comment] **********************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "#\n# This is a test comment\n#"
}
TASK [C style comment] **********************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "//\n// This is a test comment\n//"
}
- regex: 利用正则表达式对字符串进行替换
---
- hosts: localhost
tasks:
- name: "convert ansible to able"
debug: msg="{{ 'ansible' | regex_replace('^a.*i(.*)$','a\\1') }}"
运行结果如下:
TASK [convert ansible to able] **************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "able"
}
6. ip: 判断字符串是否是合法的IP地址
```yml
---
- hosts: localhost
tasks:
- name: "convert ansible to able"
debug: msg="{{ '192.168.1111.1' |ipaddr }}"
- datetime 将字符串类型的时间转换为时间戳
tasks:
- name: "datetime filter"
debug: msg="{{ ('2021-01-01 11:11:11' | to_datetime) }}"
4.3.5 过滤器对JSON的操作
- format: 将变量的值按照JSON/YAML格式输出
---
- hosts: localhost
tasks:
- name: json
blockinfile:
desk: /tmp/test_ansible_filter_formated
block: {{ some_variable | to_json }}
...还有一些用法不大清楚,后面再去了解
4.3.6 过滤器对数据结构的操作
Ansible中的过滤器支持以下几种类型的数据结构的操作。
- random: 取随机数
取随机数的操作比较常见,random既支持从List中取随机的元素,也支持生成一个随机的数字。
---
- hosts: localhost
tasks:
- name: "List with random"
debug: msg="{{ ['a','b','c'] | random }}"
- name: "number with random"
debug: msg="{{ 59 |random}}"
- name: "random with step"
debug: msg="{{ 100 |random(step=10) }}"
执行的结果如下:
TASK [List with random] *********************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "b"
}
TASK [number with random] *******************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "9"
}
TASK [random with step] *********************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "50"
}
- 对List的操作有:
- min: 取最小值
- max: 取最大值
- join: 将List的所有元素连接成一个新的字符串
- shuffle: 将List做顺序打乱成一个新的List
- map: 实现对List的映射操作
- 对Set的操作有如下的过滤器
- unique: 去重复的元素
- union: 交集
- differentce: 差集
- symmetric_difference: 取对称差
4.3.7 过滤器的链式/连续使用
Ansible的过滤器是支持链式使用的,即在一个{{}} 中使用多个过滤器,就像下面的这样。
- name: "Use multiple filter in chain"
debug: msg="{ [0,2}]| map('extract', ['x','y','z'])|join('|') }}"
在上面的例子中,先用map操作得到['x','z'],再用join得到字符串'x|z'。
4.4 测试变量或者表达式是否符合条件
4.4.1 测试字符串
match 和 search 是用于测试字符串是否符合某一个正则表达式的测试。其中 match 是完全匹配,search 只需要部分匹配。
---
- hosts: localhost
vars:
url: "http://pangcm.com/users/foo/resources/bar"
tasks:
- name: show debug
debug: "msg='matched pattern 1'"
when: url is match("http://pangcm.com/users/.*/resources/.*")
- name: show search debug
debug: "msg='matched pattern 2'"
when: url is search("/users/.*/resources/.*")
4.4.2 比较版本
使用 version (旧版本使用version_compare) 类型的测试来实现版本的比较
---
- hosts: localhost
vars:
version1: "7.8.0"
tasks:
- name: echo the version when it newer than 7.8.0
debug: msg=" {{ version1 is version('7.8.0', '>=') }}"
4.4.3 测试List的包含关系
issuperset 和 issubset 可以分别测试List是否被包含或者包含另外一个List
---
- hosts: localhost
vars:
a: [1,2,3]
b: [1,2]
tasks:
- name: show debug
debug: msg='A include B'
when: a is superset(b)
- name: show debug
debug: msg='A include B'
when: b is subset(a)
4.4.4 测试文件路径
---
- hosts: localhost
vars:
mypath: "/etc"
tasks:
- debug: msg="path is a directory"
when: mypath is directory
- debug: msg="path is a fle"
when: mypath is file
- debug: msg="path is a symlink"
when: mypath is link
- debug: msg="path is exists"
when: mypath is exists
4.4.5 测试任务的执行结果
Ansible提供了一系列的测试,可以用来判断任务的执行结果
---
- hosts: localhost
tasks:
- name: exec shell
shell: /usr/bin/false
register: result
ignore_errors: True
- name: show exec result
debug: msg="it failed"
when: result is failed
- name: show exec result
debug: msg="it changed"
when: result is changed
- name: show exec result
debug: msg="it succeed"
when: result is succeeded
- name: show exec result
debug: msg="it skip"
when: result is skipped
执行结果:
TASK [exec shell] ***************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": "/usr/bin/false", "delta": "0:00:00.008272", "end": "2021-06-13 10:44:14.449719", "msg": "non-zero return code", "rc": 1, "start": "2021-06-13 10:44:14.441447", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
...ignoring
TASK [show exec result] *********************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "it failed"
}
TASK [show exec result] *********************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "it changed"
}
TASK [show exec result] *********************************************************************************************************************************************************************
skipping: [localhost]
TASK [show exec result] *********************************************************************************************************************************************************************
skipping: [localhost]
PLAY RECAP **********************************************************************************************************************************************************************************
localhost : ok=4 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=1
4.5 认识插件
Ansible插件(plugin)并不像模块一样有统一出现的位置和相似的使用方法。它只是对Ansible功能的补充。Ansible插件会因类型的不同而使用不同的方法。如果想对上面提到的lookup写更多的插件,使其功能更加丰富,那么应该使用lookup插件,语法结构如下。
{{ lookup('new_lookup_plugin', "paramters") }}
这部分还是略过吧,感觉用处不大。