第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 过滤器对普通变量的操作

  1. default: 为没有定义的变量提供默认值
- name: "变量没有定义的话,输出默认值Default"
  debug: msg="{{ some_undefined_variable | default("Default") }}"
  1. 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"
  1. mandatory: 强制变量必须定义,否则拋错。
    在Ansible默认的配置中,如果变量没有定义,在直接使用变量的时候会报错。但是如果Ansible配置文件中使用了下面的配置,那么遇到未定义的变量时,Ansible就不会拋错。
error_on_undefined_vars = False

而这时候如果想要约束某个变量必须定义,就可以使用mandatory。

--- 
- hosts: localhost
  tasks:
  - name: show vars
    debug: msg="{{ some_undefined_variable|mandatory }}"
  1. bool: 判断变量是否为布尔类型
--- 
- hosts: localhost
  vars:
    test_var = True
  tasks:
  - name: show vars
    debug: msg=test
    when: test_var | bool
  1. 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 过滤器对字符串变量的操作

  1. quote: 给字符串加引号
    略过,前面已经演示过了

  2. base64: 得到字符串的Base64编码。

vars:
  name: pcm
tasks:
- name: get base64
  debug: msg="{{ pcm | b64encode }}"
  1. hash: 获取字符串的哈希值
    计算哈希值的算法很多,如sha1、md5、checksum等。
vars:
  my_passwd= "pass123"
tasks:
- name: "get hash"
  debug: msg="{{ my_passwd |hash('sha1') }}"
  1. 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//"
}

  1. 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 }}"

  1. datetime 将字符串类型的时间转换为时间戳
tasks:
- name: "datetime filter"
  debug: msg="{{ ('2021-01-01 11:11:11' | to_datetime) }}"

4.3.5 过滤器对JSON的操作

  1. format: 将变量的值按照JSON/YAML格式输出
---
- hosts: localhost
  tasks:
  - name:  json
    blockinfile: 
      desk: /tmp/test_ansible_filter_formated
      block: {{ some_variable | to_json }}

...还有一些用法不大清楚,后面再去了解

4.3.6 过滤器对数据结构的操作

Ansible中的过滤器支持以下几种类型的数据结构的操作。

  1. 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"
}
  1. 对List的操作有:
  • min: 取最小值
  • max: 取最大值
  • join: 将List的所有元素连接成一个新的字符串
  • shuffle: 将List做顺序打乱成一个新的List
  • map: 实现对List的映射操作
  1. 对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") }}

这部分还是略过吧,感觉用处不大。

你可能感兴趣的:(第4章 Ansible Playbook杂谈)