ansible playbook

本文最后更新于:2023年12月5日 晚上

https://docs.ansible.com/ansible/latest/playbook_guide/index.html

[TOC]

ansible playbooks

语法 / 结构

playbook 使用yaml语法。

一个 playbook 由一个或多个 ‘play‘ 组成。每个play执行总体目标的一部分,运行一个或多个task,每个task调用一个ansible模块。

执行

playbook 按照从上往下的顺序运行,在每个play中,任务也按照从上往下的顺序运行,可以给play指定运行的机器。每个paly定义了两件事:

  • 执行play的目标节点,使用 pattern
  • 至少执行一个任务

下面的playbook中,有两个play,每个play中都有两个task,每个task都是一个对ansible模块的调用,

第一个play以web servers为目标,第二个play以database servers为目标:

- name: Update web servers
  hosts: webservers
  remote_user: root
  tasks:
    - name: Ensure apache is at the latest version
      ansible.builtin.yum:
        name: httpd
        state: latest
    - name: Write the apache config file
      ansible.builtin.template:
        src: /srv/httpd.j2
        dest: /etc/httpd.conf

- name: Update db servers
  hosts: databases
  remote_user: root
  tasks:
    - name: Ensure postgresql is at the latest version
      ansible.builtin.yum:
        name: postgresql
        state: latest
    - name: Ensure that postgresql is started
      ansible.builtin.service:
        name: postgresql
        state: started

运行playbook

使用 ansible-playbook 命令运行 playbook:

ansible-playbook playbook.yml -f 10

验证

在运行 playbook 之前,可以验证语法错误和其他问题,ansible-playbook提供了几个验证选项,包括 --checkdiff--list-host--list-taskssyntax-check

ansible-lint

在执行 playbook 之前,可以使用 ansible-lint 对 ansible-playbook 进行详细的、特定于Ansible的反馈。

$ ansible-lint verify-apache.yml
[403] Package installs should not use latest
verify-apache.yml:8
Task/Handler: ensure apache is at the latest version

ansible-lint 默认规则 页面描述了每个错误。对于[403],推荐的修复方法是将playbook中的 state: latest 更改为 state: present

使用 playbooks

模板(jinja2)

ansible 使用 jinja2 模板 来启用动态表达式以及对变量(包括facts)的访问。

过滤器

jinja2 内置过滤器列表:

abs() forceescape() map() select() unique()
attr() format() max() selectattr() upper()
batch() groupby() min() slice() urlencode()
capitalize() indent() pprint() sort() urlize()
center() int() random() string() wordcount()
default() items() reject() striptags() wordwrap()
dictsort() join() rejectattr() sum() xmlattr()
escape() last() replace() title()
filesizeformat() length() reverse() tojson()
first() list() round() trim()
float() lower() safe() truncate()

ansible 在原生的jinja2模板基础上,扩展了很多过滤器和测试变量。

处理未定义变量

提供默认值
{{ some_variable | default(5) }}

如果是在 role 中,还可以添加 defaults/main.yml 来定义 role 中的默认值。

从2.8版本开始,访问未定义值的属性,可以定义返回默认值,而不是直接抛出错误,例如:

{{ foo.bar.baz | default('DEFAULT') }}

如果foo或者bar没有定义,则返回 'DEFAULT'

如果变量为false或者空字符,则使用默认值,只需要将 default 函数第二个参数设置为 true

{{ lookup('env', 'MY_USER') | default('admin', true) }}

关于 lookup,见下文。

忽略未定义 omit

上面介绍了,当变量未定义时,使用设置的默认值。

某些变量有系统默认值,如果当变量未定义,使用系统默认值;当变量定义,则使用定义的值,该怎么办?

答案是使用 omit

在 ansible中,omit 关键字的作用是把对应的变量改为 可选,就是忽略未定义的变量。例如:将arg="{{omit}}" 传递给模块,如果arg变量未定义,则相当于根本没传;如果arg变量定义,则使用 arg

下面的示例中:default(omit) 作用和正常的 default('value') 相反,omit 对应 item.mode/tmp/foo/tmp/bar 没有定义modeitem.mode 使用系统默认值;/tmp/baz 定义了mode0444,则item.mode 使用0444

- name: Touch files with an optional mode
  ansible.builtin.file:
    dest: "{{ item.path }}"
    state: touch
    mode: "{{ item.mode | default(omit) }}"
  loop:
    - path: /tmp/foo
    - path: /tmp/bar
    - path: /tmp/baz
      mode: "0444"
设置未定义变量的错误提示信息 undef

defaultundef 都是用于处理变量的关键字。当变量未定义时,使用default 可以为变量设置默认值;而使用 undef 则可以为变量设置一个自定义的错误提示信息,当变量未定义时会抛出这个错误信息。

两者的区别在于,使用default可以为变量设置默认值,而使用undef则不会为变量设置默认值,而是抛出一个自定义的错误提示信息。

galaxy_url: "https://galaxy.ansible.com"
galaxy_api_key: "{{ undef(hint='You must specify your Galaxy API key') }}"

为 true/false/null 定义不同的值 ternary

ternary 是一个三元运算符。

{{ variable | ternary(true_value, false_value) }}

variable 是要进行判断的变量,true_value 是当变量为真时的值,false_value 是当变量为假时的值。

第三个参数是可选,它表示当变量未定义时的默认值,如果省略第三个参数,则默认值为false

{{ enabled | ternary('no shutdown', 'shutdown', omit) }}

管理数据类型

使用 type_debugdict2itemsitems2dict 过滤器来管理数据类型。您还可以使用数据类型本身将值转换为特定的数据类型。

发现数据类型

如果您不确定某个变量的底层Python类型,可以使用 type_debug 过滤器来显示它。这在调试中很有用:

{{ myvar | type_debug }}

这是个很有用的过滤器,可以检查变量中的数据类型是否正确,但是更推荐 类型测试

将字典转换为列表

使用 dict2items 过滤器将字典转换为适合 循环 的项目列表:

{{ dict | dict2items }}

示例:

# 字典数据
tags:
  Application: payment
  Environment: dev
  
# 应用 dict2items 过滤器后
- key: Application
  value: payment
- key: Environment
  value: dev

可以配置key的名称:

{{ files | dict2items(key_name='file', value_name='path') }}
# 字典数据
files:
  users: /etc/passwd
  groups: /etc/group
  
# 应用dict2items过滤器之后
- file: users
  path: /etc/passwd
- file: groups
  path: /etc/group
将列表转换为字典

使用 items2dict 过滤器将列表转换为字典。

{{ tags | items2dict }}

{{ tags | items2dict(key_name='fruit', value_name='color') }}
强制数据类型

可以将值转换为某些类型。

- ansible.builtin.debug:
     msg: test
  when: some_string_value | bool
- shell: echo "only on Red Hat 6, derivatives, and later"
  when: ansible_facts['os_family'] == "RedHat" and ansible_facts['lsb']['major_release'] | int >= 6

格式化数据:yaml 和 json

将模板中的数据结构从JSON或YAML格式切换为JSON或YAML格式,并提供格式化、缩进和加载数据的选项。对调试很有用:

{{ some_variable | to_json }}
{{ some_variable | to_yaml }}

{{ some_variable | to_nice_json }}
{{ some_variable | to_nice_yaml }}
过滤器 to_json 和 unicode 支持

默认情况下to_json和to_nice_json将接收到的数据转换为ASCII,因此:

{{ 'München'| to_json }}

# 返回 'M\u00fcnchen'

要保留Unicode字符,请将参数ensure_ascii=False传递给过滤器:

{{ 'München'| to_json(ensure_ascii=False) }}

'München'

组合和选择数据

合并多个列表中的项目:zipzip_longest

zipzip_longest 是python的原生函数。

使用zip函数将两个列表中的元素一一对应地组合在一起:

- name: Give me list combo of two lists
  ansible.builtin.debug:
    msg: "{{ [1,2,3,4,5,6] | zip(['a','b','c','d','e','f']) | list }}"

# => [[1, "a"], [2, "b"], [3, "c"], [4, "d"], [5, "e"], [6, "f"]]

- name: Give me shortest combo of two lists
  ansible.builtin.debug:
    msg: "{{ [1,2,3] | zip(['a','b','c','d','e','f']) | list }}"

# => [[1, "a"], [2, "b"], [3, "c"]]

使用zip_longest函数将多个列表中的元素一一对应地组合在一起,并使用指定的填充值填充缺失的元素:

- name: Give me longest combo of three lists , fill with X
  ansible.builtin.debug:
    msg: "{{ [1,2,3] | zip_longest(['a','b','c','d','e','f'], [21, 22, 23], fillvalue='X') | list }}"

# => [[1, "a", 21], [2, "b", 22], [3, "c", 23], ["X", "d", "X"], ["X", "e", "X"], ["X", "f", "X"]]

构造字典:

{{ dict(keys_list | zip(values_list)) }}
keys_list:
  - one
  - two
values_list:
  - apple
  - orange
  
# 构造字典
one: apple
two: orange
组合 对象和子元素
{{ users | subelements('groups', skip_missing=True) }}
组合 哈希/字典
{{ {'a':1, 'b':2} | combine({'b':3}) }}

从数组或哈希表中选择值
{{ [0,2] | map('extract', ['x','y','z']) | list }}
{{ ['x','y'] | map('extract', {'x': 42, 'y': 31}) | list }}
组合列表
permutations
- name: Give me largest permutations (order matters)
  ansible.builtin.debug:
    msg: "{{ [1,2,3,4,5] | ansible.builtin.permutations | list }}"

- name: Give me permutations of sets of three
  ansible.builtin.debug:
    msg: "{{ [1,2,3,4,5] | ansible.builtin.permutations(3) | list }}"
combinations
- name: Give me combinations for sets of two
  ansible.builtin.debug:
    msg: "{{ [1,2,3,4,5] | ansible.builtin.combinations(2) | list }}"
product
- name: Generate multiple hostnames
  ansible.builtin.debug:
    msg: "{{ ['foo', 'bar'] | product(['com']) | map('join', '.') | join(',') }}"
选择JSON数据:JSON查询
{
    "domain_definition": {
        "domain": {
            "cluster": [
                {
                    "name": "cluster1"
                },
                {
                    "name": "cluster2"
                }
            ],
            "server": [
                {
                    "name": "server11",
                    "cluster": "cluster1",
                    "port": "8080"
                },
                {
                    "name": "server12",
                    "cluster": "cluster1",
                    "port": "8090"
                },
                {
                    "name": "server21",
                    "cluster": "cluster2",
                    "port": "9080"
                },
                {
                    "name": "server22",
                    "cluster": "cluster2",
                    "port": "9090"
                }
            ],
            "library": [
                {
                    "name": "lib1",
                    "target": "cluster1"
                },
                {
                    "name": "lib2",
                    "target": "cluster2"
                }
            ]
        }
    }
}
- name: Display all cluster names
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query('domain.cluster[*].name') }}"
- name: Display all server names
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query('domain.server[*].name') }}"
- name: Display all ports from cluster1
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
  vars:
    server_name_cluster1_query: "domain.server[?cluster=='cluster1'].port"
- name: Display all ports from cluster1 as a string
  ansible.builtin.debug:
    msg: "{{ domain_definition | community.general.json_query('domain.server[?cluster==`cluster1`].port') | join(', ') }}"
- name: Display all ports from cluster1
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query('domain.server[?cluster==''cluster1''].port') }}"
- name: Display all server ports and names from cluster1
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
  vars:
    server_name_cluster1_query: "domain.server[?cluster=='cluster2'].{name: name, port: port}"
- name: Display all ports from cluster1
  ansible.builtin.debug:
    msg: "{{ domain_definition | to_json | from_json | community.general.json_query(server_name_query) }}"
  vars:
    server_name_query: "domain.server[?starts_with(name,'server1')].port"
- name: Display all ports from cluster1
  ansible.builtin.debug:
    msg: "{{ domain_definition | to_json | from_json | community.general.json_query(server_name_query) }}"
  vars:
    server_name_query: "domain.server[?contains(name,'server1')].port"

随机化数据

"{{ '52:54:00' | community.general.random_mac }}"
# => '52:54:00:ef:1c:03'

"{{ '52:54:00' | community.general.random_mac(seed=inventory_hostname) }}"
"{{ ['a','b','c'] | random }}"
# => 'c'
"{{ 60 | random }} * * * * root /script/from/cron"
# => '21 * * * * root /script/from/cron'
{{ 101 | random(step=10) }}
# => 70
{{ 101 | random(1, 10) }}
# => 31
{{ 101 | random(start=1, step=10) }}
# => 51
"{{ 60 | random(seed=inventory_hostname) }} * * * * root /script/from/cron"

打乱列表

{{ ['a','b','c'] | shuffle }}
# => ['c','a','b']
{{ ['a','b','c'] | shuffle }}
# => ['b','c','a']
{{ ['a','b','c'] | shuffle(seed=inventory_hostname) }}
# => ['b','a','c']

管理列表变量

从集合或列表中选择(集合论)

计算数字(数学)

管理网络交互

IP地址过滤器

测试字符串是否是有效的IP地址:

{{ myvar | ansible.netcommon.ipaddr }}

需要特定的IP协议版本:

{{ myvar | ansible.netcommon.ipv4 }}
{{ myvar | ansible.netcommon.ipv6 }}

IP地址过滤器还可用于从IP地址中提取特定信息。例如,要从CIDR获取IP地址本身,您可以使用:

{{ '192.0.2.1/24' | ansible.netcommon.ipaddr('address') }}
# => 192.0.2.1

更多关于ipaddr 过滤器的信息和完整的使用指南在 ipaddr过滤器

网络CLI过滤器
网络XML过滤器
网络VLAN过滤器

散列和加密字符串和密码

获取字符串的sha1哈希值:

{{ 'test1' | hash('sha1') }}
# => "b444ac06613fc8d63795be9ad0beaf55011936ac"

获取字符串的md5哈希值:

{{ 'test1' | hash('md5') }}
# => "5a105e8b9d40e1329780d62ea2265d8a"

获取字符串校验和:

{{ 'test2' | checksum }}
# => "109f4b3c50d7b0df729d299bc6f8e9ef9066971f"

获取sha512密码哈希(随机盐):

{{ 'passwordsaresecret' | password_hash('sha512') }}
# => "$6$UIv3676O/ilZzWEE$ktEfFF19NQPF2zyxqxGkAceTnbEgpEKuGBtk6MlU4v2ZorWaVQUMyurgmHCh2Fr4wpmQ/Y.AlXMJkRnIS4RfH/"

获取带有特定盐的sha256密码哈希:

{{ 'secretpassword' | password_hash('sha256', 'mysecretsalt') }}
# => "$5$mysecretsalt$ReKNyDYjkKNqRVwouShhsEqZ3VOE8eoVO4exihOfvG4"

为每个系统生成唯一哈希的幂等方法是使用运行之间一致的盐:

{{ 'secretpassword' | password_hash('sha512', 65534 | random(seed=inventory_hostname) | string) }}
# => "$6$43927$lQxPKz2M2X.NWO.gK.t7phLwOKQMcSq72XxDZQ0XzYV6DlL1OD72h417aj16OnHTGxNzhftXJQBcjbunLEepM0"

操作文本

有几个过滤器可以处理文本,包括URL、文件名和路径名。

向文件添加注释
URLEncode变量
分割URL
使用正则表达式搜索字符串
管理文件名和路径名

操作字符串

管理UUID

处理日期和时间

获取k8s资源名称

测试

测试 是评估模板表达式并返回True或False的一种方式。Jinja附带了许多这样的工具。请参阅官方Jinja模板留档中的 内置测试

测试和过滤器之间的主要区别是Jinja测试用于比较,而过滤器用于数据操作,并且在jinja中有不同的应用。测试也可以用于列表处理过滤器,如map()select()来选择列表中的项目。

像所有模板一样,测试总是在Ansible控制器上执行,而不是在任务的目标上执行,因为它们测试本地数据。

除了这些Jinja2测试之外,Ansible还提供了更多测试,用户可以轻松创建自己的测试。

查找 lookups

Lookup plugins 从外部来源检索数据,如文件、数据库、键/值存储、API和其他服务。与所有模板一样,查找在Ansible控制机器上执行和评估。Ansible使用标准模板系统使查找插件返回的数据可用。在Ansible 2.5之前,查找主要间接用于循环的with_<lookup>构造。从Ansible 2.5开始,查找更明确地用作输入loop关键字的Jinja2表达式的一部分。

在变量中使用 lookups

vars:
  motd_value: "{{ lookup('file', '/etc/motd') }}"
tasks:
  - debug:
      msg: "motd value is {{ motd_value }}"

有关 ansible-core 中 lookups 插件的详细信息和列表,请参阅 使用插件

您可以使用命令 ansible-doc -l -t lookup 安装在控制机器上的查找插件列表。

模板中的python3

now函数:获取当前时间

在 Ansible 中,您可以使用 now 函数来获取当前时间。now 函数支持两个参数:

  • utc:指定为 True 以获取 UTC 中的当前时间。默认为 False。
  • fmt:接受一个 strftime 字符串,返回格式化的日期时间字符串。

以下是一个示例,演示如何使用 now 函数获取当前时间:

- name: Get the current time
  debug:
    msg: "{{ now() }}"

您还可以使用 Ansible 的 to_datetime 过滤器将字符串转换为日期时间对象。以下是一个示例:

- name: Convert string to datetime object
  debug:
    msg: "{{ '2023-06-20 12:00:00' | to_datetime('%Y-%m-%d %H:%M:%S') }}"

循环 loops

比较 loopwith_*

  • with_<lookup> 关键字依赖 Lookup plugins - 甚至items 是一个 lookup。

  • loop 关键字相当于 with_list,是简单循环的最佳选择。

  • loop 关键字不接受字符串作为输入。

  • with_items更改为loop要小心,因为with_items会自动展开。您可能需要使用flatten(1)

    with_items:
      - 1
      - [2,3]
      - 4
    
    # 
    loop: "{{ [1, [2, 3], 4] | flatten(1) }}"
  • lookup不能在loop中使用,例如下面的写法是不对的:

    loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}"

    应该使用 with_*更合适:

    with_fileglob: '*.txt'

标准loops

迭代简单列表
- name: Add several users
  ansible.builtin.user:
    name: "{{ item }}"     # item 关键字是固定的,代表列表中的当前元素
    state: present
    groups: "wheel"
  loop:
     - testuser1
     - testuser2

可以在变量文件或者play 的vars部分定义list,然后在任务中直接引用:

loop: "{{ somelist }}"
迭代哈希列表
- name: Add several users
  ansible.builtin.user:
    name: "{{ item.name }}"
    state: present
    groups: "{{ item.groups }}"
  loop:
    - { name: 'testuser1', groups: 'wheel' }
    - { name: 'testuser2', groups: 'root' }

与条件语句循环组合时,when 将为每个项目单独处理。

在字典上迭代
- name: Using dict2items
  ansible.builtin.debug:
    msg: "{{ item.key }} - {{ item.value }}"
  loop: "{{ tag_data | dict2items }}"
  vars:
    tag_data:
      Environment: dev
      Application: payment

使用loop循环注册变量

register关键字:注册变量,运行命令并将该命令的结果注册为变量,每个register值在整个playbook执行期间都是有效的。

10. 使用变量 - Ansible wiki (leops.cn)

将循环的输出注册为变量。例如:

- name: Register loop output as a variable
  ansible.builtin.shell: "echo {{ item }}"
  loop:
    - "one"
    - "two"
  register: echo

echo {{ item }} 的输出会注册到 echo 变量。

使用带循环的register时,放置在变量中的数据结构将包含一个results属性,该属性是来自模块的所有响应的列表。这与使用不带循环的register时返回的数据结构不同。

对已注册变量进行后续循环以检查结果可能如下所示:

- name: Fail if return code is not 0
  ansible.builtin.fail:
    msg: "The command ({{ item.cmd }}) did not have a 0 return code"
  when: item.rc != 0
  loop: "{{ echo.results }}"

复杂loop

在嵌套list上迭代

您可以使用Jinja2表达式遍历复杂的列表。例如,循环可以组合嵌套列表。

- name: Give users access to multiple databases
  community.mysql.mysql_user:
    name: "{{ item[0] }}"
    priv: "{{ item[1] }}.*:ALL"
    append_privs: true
    password: "foo"
  loop: "{{ ['alice', 'bob'] | product(['clientdb', 'employeedb', 'providerdb']) | list }}"
重试任务直到满足条件

使用until关键字重试任务,直到满足特定条件。下面是一个例子:

- name: Retry a task until a certain condition is met
  ansible.builtin.shell: /usr/bin/foo
  register: result
  until: result.stdout.find("all systems go") != -1
  retries: 5
  delay: 10

此任务最多运行5次,每次尝试之间的延迟为10秒。如果任何尝试的结果在其标准输出中显示 “all systems go”,则任务成功。“retries”的默认值为3,“delay”为5。

在 inventory 上循环

要遍历 inventory 或仅遍历 inventory 的子集,可以使用带有 ansible_play_batchgroups 变量的常规 loop

- name: Show all the hosts in the inventory
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ groups['all'] }}"


- name: Show all the hosts in the current play
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ ansible_play_batch }}"

还有一个特定的查找插件inventory_hostnames可以这样使用:

- name: Show all the hosts in the inventory
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ query('inventory_hostnames', 'all') }}"

- name: Show all the hosts matching the pattern, ie all but the group www
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ query('inventory_hostnames', 'all:!www') }}"

queryloopup

loop 需要一个list作为输入,但是lookup默认返回一串都好分割的值,ansible2.5引入query,它总是返回一个list。

下面的两个例子做同样的事情:

loop: "{{ query('inventory_hostnames', 'all') }}"

loop: "{{ lookup('inventory_hostnames', 'all', wantlist=True) }}"

向loops添加控件(control)

关键字loop_control可以让您管理循环。

精简输出 label

循环处理复杂的数据结构时,任务的控制台输出可能会很大。要限制显示的输出,请使用loop_controllabel指令。

- name: Create servers
  digital_ocean:
    name: "{{ item.name }}"
    state: present
  loop:
    - name: server1
      disks: 3gb
      ram: 15Gb
      network:
        nic01: 100Gb
        nic02: 10Gb
        ...
  loop_control:
    label: "{{ item.name }}"

输出将仅显示每个itemname字段,而不是多行{{ item }}变量的全部内容。

这是为了使控制台输出更具可读性,而不是保护敏感数据。如果loop中有敏感数据,请在任务上设置no_log: yes以防止泄露。
循环间隔 pause

控制任务循环中每个项目执行之间的时间(以秒为单位),请使用带有loop_controlpause指令。

# main.yml
- name: Create servers, pause 3s before creating next
  community.digitalocean.digital_ocean:
    name: "{{ item }}"
    state: present
  loop:
    - server1
    - server2
  loop_control:
    pause: 3
跟踪进度 index_var

该指令指定一个变量名来包含当前循环索引。

- name: Count our fruit
  ansible.builtin.debug:
    msg: "{{ item }} with index {{ my_idx }}"
  loop:
    - apple
    - banana
    - pear
  loop_control:
    index_var: my_idx
指定循环的变量名 loop_var

循环的变量名默认是 item,如果有嵌套循环的情况,可以使用loop_var指定循环的变量名。

# main.yml
- include_tasks: inner.yml     # `include_tasks` 嵌套两个循环任务
  loop:
    - 1
    - 2
    - 3
  loop_control:
    loop_var: outer_item

# inner.yml
- name: Print outer and inner items
  ansible.builtin.debug:
    msg: "outer item={{ outer_item }} inner item={{ item }}"
  loop:
    - a
    - b
    - c

注意:loop_var定义的变量名不要于已有的变量名冲突。

使用 ansible_loop_var变量,访问 loop_var 定义的变量名。

扩展循环变量
loop_control:
  extended: true

开启 loop_control.extended 后,会消耗更多的内存。

从 with_x 迁移到loop

在绝大数情况下,循环推荐使用loop而不是with_xloop 推荐使用过滤器,而不是更复杂的 queryloopup

下面的示例展示了如何将常见的 with_ 转换为 loop + 过滤器:

  • with_list 直接替换为 loop

    - name: with_list
      ansible.builtin.debug:
        msg: "{{ item }}"
      with_list:
        - one
        - two
    
    - name: with_list -> loop
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop:
        - one
        - two
  • with_items 替换为 loop + flatten过滤器

    - name: with_items
      ansible.builtin.debug:
        msg: "{{ item }}"
      with_items: "{{ items }}"
    
    - name: with_items -> loop
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop: "{{ items|flatten(levels=1) }}"
  • with_indexed_items 替换为 loop + flatten过滤器 + loop_control.index_var

    - name: with_indexed_items
      ansible.builtin.debug:
        msg: "{{ item.0 }} - {{ item.1 }}"
      with_indexed_items: "{{ items }}"
    
    - name: with_indexed_items -> loop
      ansible.builtin.debug:
        msg: "{{ index }} - {{ item }}"
      loop: "{{ items|flatten(levels=1) }}"
      loop_control:
        index_var: index
  • with_flattened

  • with_together

  • with_dict

  • with_sequence

  • with_subelements

  • with_nested / with_cartesian 替换为 loop + product过滤器

  • with_random_choice 替换为 random过滤器,不需要 loop

    - name: with_random_choice
      ansible.builtin.debug:
        msg: "{{ item }}"
      with_random_choice: "{{ my_list }}"
    
    - name: with_random_choice -> loop (No loop is needed here)
      ansible.builtin.debug:
        msg: "{{ my_list|random }}"
      tags: random

任务委派

默认情况下,ansible 收集 facts 并在与 playbook hosts line 匹配的机器上执行所有任务。如果过程中需要对另外的机器执行操作,就需要用到 任务委派 功能。

在任务上使用 delegate_to 关键字可以实现委派功能:

- hosts: webservers
  serial: 5

  tasks:
    - name: Take out of load balancer pool
      ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
      delegate_to: 127.0.0.1

    - name: Actual steps would go here
      ansible.builtin.yum:
        name: acme-web-stack
        state: latest

    - name: Add back to load balancer pool
      ansible.builtin.command: /usr/bin/add_back_to_pool {{ inventory_hostname }}
      delegate_to: 127.0.0.1

local_action

delegate_to: 127.0.0.1 就是将任务委派到本地执行,因为这种委派到本地的操作很常见,所以ansible提供了 local_action 关键字,等效于 delegate_to: 127.0.0.1

---
# ...

  tasks:
    - name: Take out of load balancer pool
      local_action: ansible.builtin.command /usr/bin/take_out_of_pool {{ inventory_hostname }}

# ...

    - name: Add back to load balancer pool
      local_action: ansible.builtin.command /usr/bin/add_back_to_pool {{ inventory_hostname }}


---
# ...

  tasks:
    - name: Recursively copy files from management server to target
      local_action: ansible.builtin.command rsync -a /path/to/files {{ inventory_hostname }}:/path/to/target/

请注意,您必须配置无密码SSH密钥或ssh代理才能使其工作,否则rsync会请求密码。

要指定更多参数,请使用以下语法:

---
# ...

  tasks:
    - name: Send summary mail
      local_action:
        module: community.general.mail
        subject: "Summary Mail"
        to: "{{ mail_recipient }}"
        body: "{{ mail_body }}"
      run_once: True

注意:如果要委派到 inventory 中没有的主机,最好使用 add_host 模块:

- name: Add host to multiple groups
  ansible.builtin.add_host:
    hostname: '{{ new_ip }}'
    groups:
    - group1
    - group2

add_host 有两个参数:

  • groups: 可选,字符串的list,将主机动态的添加到指定的组,可以是inventory中已定义的组,也可以是临时自定义的组。

    别名:groupnamegroup

  • name:主机hostname或者IP地址。

    别名:hostnamehost

本地运行 connection

---
- hosts: 127.0.0.1
  connection: local

如果 ansible_connection 设置为local,这请务必设置ansible_python_interpreter,否则模块将在/usr/local/python下运行,而不是在 {{ ansible_playbook_python }} 下。

委派 facts

收集的facts默认分配给 inventory_hostname (当前主机),而不是产生fatcs的主机,要将收集的事实分配给委托主机而不是当前主机,请将delegate_facts设置为true

---
- hosts: app_servers

  tasks:
    - name: Gather facts from db servers
      ansible.builtin.setup:
      delegate_to: "{{ item }}"
      delegate_facts: true
      loop: "{{ groups['dbservers'] }}"

此任务收集dbserver组中机器的facts,并将facts分配给这些机器,即使 play 的目标是app_servers组。

条件语句

基本条件 when

最简单的条件语句,适用于单个任务。when子句是没有双花括号的原始jinja2表达式(参见 group_by_module)。

例如,在开启了SELinux的机器上安装mysql:

tasks:
  - name: Configure SELinux to start mysql on any port
    ansible.posix.seboolean:
      name: mysql_connect_any
      state: true
      persistent: true
    when: ansible_selinux.status == "enabled"
    # all variables can be used directly in conditionals without double curly braces

基于ansible_facts的条件语句

通常希望根据facts执行或跳过任务。facts是各个主机的属性,包括IP地址、操作系统、文件系统的状态等等。基于facts的条件:

  • 只有当操作系统是特定版本时,才能安装某个包;
  • 内网主机跳过配置防火墙;
  • 当文件系统已满时,才能执行清理任务

常用facts:https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_conditionals.html#commonly-used-facts

并非所有主机都存在所有facts,例如lsb_major_release尽在目标主机上

Handlers: running operations on change

playbooks 中的错误处理

设置远程环境

使用特定语言的版本管理器

重复使用ansible artifacts

roles 角色

模块默认值

交互式输入:提示

使用变量

发现变量:facts 和 magic 变量

playbook示例:持续交付 和 滚动升级


ansible playbook
http://blog.lujinkai.cn/运维/运维自动化/ANSIBLE/ansible playbook/
作者
像方便面一样的男子
发布于
2023年12月4日
更新于
2023年12月5日
许可协议