第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是,每个参数都是可选的。默认的参数如下:

    使用的是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