Ansible的循环:loop,with_<lookup>和until

环境

  • 管理节点:Ubuntu 22.04
  • 控制节点:CentOS 8
  • Ansible:2.15.6

循环的方法

  • loop
  • with_<lookup>
  • until

用这几种方式都可以实现循环。其中, loop 是推荐的用法,在很多时候能够替换 with_<lookup>

loopwith_<lookup>

with_<lookup> 使用了lookup插件,比如 with_items 使用的是 items lookup。(注:可参见我另一篇文档。)

loop 等同于 with_list 。注意, loop 是作用在list上的,如果用在字符串上会报错。

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      debug:
        msg: "{{ item }}"
      loop: "{{ ['aaa', 'bbb', 'ccc'] }}"

    - name: task2
      debug:
        msg: "{{ item }}"
      with_list: "{{ ['aaa', 'bbb', 'ccc'] }}"

    - name: task3
      debug:
        msg: "{{ item }}"
      with_items: "{{ ['aaa', 'bbb', 'ccc'] }}"

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ccc"
}

TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ccc"
}

TASK [task3] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ccc"
}

可见,本例中 loopwith_listwith_items 的效果是一样的。

但是对于嵌套的list, loopwith_items 并不是完全等同的。

yaml 复制代码
......
    - name: task4
      debug:
        msg: "{{ item }}"
      loop: "{{ ['aaa', ['bbb', ['ddd', 'eee']], 'ccc'] }}"

    - name: task5
      debug:
        msg: "{{ item }}"
      with_items: "{{ ['aaa', ['bbb', ['ddd', 'eee']], 'ccc'] }}"
......

运行结果如下:

powershell 复制代码
TASK [task4] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "aaa"
}
ok: [192.168.1.55] => (item=['bbb', ['ddd', 'eee']]) => {
    "msg": [
        "bbb",
        [
            "ddd",
            "eee"
        ]
    ]
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ccc"
}

TASK [task5] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "bbb"
}
ok: [192.168.1.55] => (item=['ddd', 'eee']) => {
    "msg": [
        "ddd",
        "eee"
    ]
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ccc"
}

可见:

  • loop 只针对最外层的list,不管是否有嵌套。
  • with_items 则是解开了第一层嵌套的list。这个行为比较诡异,要么就不要管嵌套,要么就全部处理,为什么只处理第一层嵌套呢?

实际上,对于 loop ,可用 flatten filter来指定解开嵌套:

(注:flatten是扁平化的意思,这里的扁平化和Java8里扁平化流的概念类似,即把层次结构转换为线性结构)

yaml 复制代码
......
    - name: task6
      debug:
        msg: "{{ item }}"
      loop: "{{ ['aaa', ['bbb', ['ddd', 'eee']], 'ccc'] | flatten }}"

    - name: task7
      debug:
        msg: "{{ item }}"
      loop: "{{ ['aaa', ['bbb', ['ddd', 'eee']], 'ccc'] | flatten(levels=1) }}"
......

运行结果如下:

powershell 复制代码
TASK [task6] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "bbb"
}
ok: [192.168.1.55] => (item=ddd) => {
    "msg": "ddd"
}
ok: [192.168.1.55] => (item=eee) => {
    "msg": "eee"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ccc"
}

TASK [task7] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "bbb"
}
ok: [192.168.1.55] => (item=['ddd', 'eee']) => {
    "msg": [
        "ddd",
        "eee"
    ]
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ccc"
}

可见, flatten 默认会处理所有嵌套,也可以通过 levels 选项,指定处理几层嵌套。

由于 with_items 处理一层嵌套,所以, with_items 就相当于 loop 指定了 flatten(levels=1) 。在本例中,task5和task7的运行结果是一样的。

需要使用 lookup 的循环,多使用 with_<lookup> 语句,而不是 loop 语句。比如:

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      debug:
        msg: "{{ item }}"
      loop: "{{ lookup('fileglob', '/tmp/*.txt', wantlist=True) }}"

    - name: task2
      debug:
        msg: "{{ item }}"
      with_fileglob: "/tmp/*.txt"

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=/tmp/b.txt) => {
    "msg": "/tmp/b.txt"
}
ok: [192.168.1.55] => (item=/tmp/a.txt) => {
    "msg": "/tmp/a.txt"
}

TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => (item=/tmp/b.txt) => {
    "msg": "/tmp/b.txt"
}
ok: [192.168.1.55] => (item=/tmp/a.txt) => {
    "msg": "/tmp/a.txt"
}

显然,此处使用 with_fileglob 比使用 loop 要简洁。

注: fileglob 获取指定目录下符合条件的文件名(不包含子目录)。

循环的种类

简单list

yaml 复制代码
---
- hosts: all
  vars:
    var1: ['aaa', 'bbb', 'ccc']
  tasks:
    - name: task1 # list常量
      debug:
        msg: "{{ item }}"
      loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
      # loop: ['aaa', 'bbb', 'ccc'] 可以简写

    - name: task2 # list常量
      debug:
        msg: "{{ item }}"
      loop:
        - aaa # 引号可以省略
        - "bbb"
        - "ccc"

    - name: task3 # list变量
      debug:
        msg: "{{ item }}"
      loop: "{{ var1 }}"

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ccc"
}

TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ccc"
}

TASK [task3] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ccc"
}

复杂list

yaml 复制代码
---
- hosts: all
  vars:
    var1: [{name: "Tom", age: 20}, {name: "Jerry", age: 18}]
  tasks:
    - name: task1
      debug:
        msg: "Name: {{ item.name }}. Age: {{ item.age }}"
      loop: "{{ [{ 'name': 'Tom', 'age': 20 }, { 'name': 'Jerry', 'age': 18 }] }}"
      #loop: "{{ [{ name: 'Tom', age: 20 }, { name: 'Jerry', age: 18 }] }}" # 报错!说name未定义
      #loop: [{ name: 'Tom', age: 20 }, { name: 'Jerry', age: 18 }] # OK
      #loop: [{ 'name': 'Tom', 'age': 20 }, { 'name': 'Jerry', 'age': 18 }] # OK

    - name: task2
      debug:
        msg: "Name: {{ item.name }}. Age: {{ item.age }}"
      loop:
        - { name: "Tom", age: 20 }
        - { name: "Jerry", age: 18 }

    - name: task3
      debug:
        msg: "Name: {{ item.name }}. Age: {{ item.age }}"
      loop: "{{ var1 }}"

可以看到,对于key要不要加引号,行为好像有点诡异,最好还是加上吧。

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item={'name': 'Tom', 'age': 20}) => {
    "msg": "Name: Tom. Age: 20"
}
ok: [192.168.1.55] => (item={'name': 'Jerry', 'age': 18}) => {
    "msg": "Name: Jerry. Age: 18"
}

TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => (item={'name': 'Tom', 'age': 20}) => {
    "msg": "Name: Tom. Age: 20"
}
ok: [192.168.1.55] => (item={'name': 'Jerry', 'age': 18}) => {
    "msg": "Name: Jerry. Age: 18"
}

TASK [task3] ***************************************************************************************
ok: [192.168.1.55] => (item={'name': 'Tom', 'age': 20}) => {
    "msg": "Name: Tom. Age: 20"
}
ok: [192.168.1.55] => (item={'name': 'Jerry', 'age': 18}) => {
    "msg": "Name: Jerry. Age: 18"
}

dict

如果要遍历一个dict,则需要使用 dict2items filter,将其转换为list:

yaml 复制代码
---
- hosts: all
  vars:
    var1: {name: "Tom", age: 20}
  tasks:
    - name: task1
      debug:
        msg: "Key: {{ item.key }}. Value: {{ item.value }}"
      loop: "{{ {'name': 'Tom', 'age': 20} | dict2items }}"

    - name: task2
      debug:
        msg: "Key: {{ item.key }}. Value: {{ item.value }}"
      loop: "{{ var1 | dict2items }}"

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item={'key': 'name', 'value': 'Tom'}) => {
    "msg": "Key: name. Value: Tom"
}
ok: [192.168.1.55] => (item={'key': 'age', 'value': 20}) => {
    "msg": "Key: age. Value: 20"
}

TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => (item={'key': 'name', 'value': 'Tom'}) => {
    "msg": "Key: name. Value: Tom"
}
ok: [192.168.1.55] => (item={'key': 'age', 'value': 20}) => {
    "msg": "Key: age. Value: 20"
}

本例中,dict为:

yaml 复制代码
{name: "Tom", age: 20}

转为list后:

yaml 复制代码
    [
        {
            "key": "name",
            "value": "Tom"
        },
        {
            "key": "age",
            "value": 20
        }
    ]

循环结果的register变量

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      shell: "echo {{ item }}"
      loop: "{{ ['aaa', 'bbb'] }}"
      register: var1
        
    - name: task2
      debug:
        msg: "{{ var1 }}"

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
changed: [192.168.1.55] => (item=aaa)
changed: [192.168.1.55] => (item=bbb)

TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => {
    "msg": {
        "changed": true,
        "msg": "All items completed",
        "results": [
            {
                "ansible_loop_var": "item",
                "changed": true,
                "cmd": "echo aaa",
                "delta": "0:00:00.002332",
                "end": "2023-11-21 22:29:54.990234",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "echo aaa",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "stdin_add_newline": true,
                        "strip_empty_ends": true
                    }
                },
                "item": "aaa",
                "msg": "",
                "rc": 0,
                "start": "2023-11-21 22:29:54.987902",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "aaa",
                "stdout_lines": [
                    "aaa"
                ]
            },
            {
                "ansible_loop_var": "item",
                "changed": true,
                "cmd": "echo bbb",
                "delta": "0:00:00.002036",
                "end": "2023-11-21 22:29:55.223227",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "echo bbb",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "stdin_add_newline": true,
                        "strip_empty_ends": true
                    }
                },
                "item": "bbb",
                "msg": "",
                "rc": 0,
                "start": "2023-11-21 22:29:55.221191",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "bbb",
                "stdout_lines": [
                    "bbb"
                ]
            }
        ],
        "skipped": false
    }
}

可见,register变量把循环操作的结果放到了叫做 results 的list里。因此,后续可以遍历 results ,做相应处理,比如:

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      shell: "cat {{ item }}"
      loop: "{{ ['/tmp/a.txt', '/tmp/d.txt'] }}"
      register: var1
      ignore_errors: true
        
    - name: task2
      debug:
        msg: "{{ var1 }}"

    - name: task3
      fail:
        msg: "Something is wrong!"
      when: item.rc != 0
      loop: "{{ var1.results }}"

假设 /tmp/d.txt 不存在,则运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
changed: [192.168.1.55] => (item=/tmp/a.txt)
failed: [192.168.1.55] (item=/tmp/d.txt) => {"ansible_loop_var": "item", "changed": true, "cmd": "cat /tmp/d.txt", "delta": "0:00:00.002438", "end": "2023-11-21 22:46:28.216904", "item": "/tmp/d.txt", "msg": "non-zero return code", "rc": 1, "start": "2023-11-21 22:46:28.214466", "stderr": "cat: /tmp/d.txt: No such file or directory", "stderr_lines": ["cat: /tmp/d.txt: No such file or directory"], "stdout": "", "stdout_lines": []}
...ignoring

TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => {
    "msg": {
        "changed": true,
        "failed": true,
        "msg": "One or more items failed",
        "results": [
            {
                "ansible_loop_var": "item",
                "changed": true,
                "cmd": "cat /tmp/a.txt",
                "delta": "0:00:00.003006",
                "end": "2023-11-21 22:46:27.995302",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "cat /tmp/a.txt",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "stdin_add_newline": true,
                        "strip_empty_ends": true
                    }
                },
                "item": "/tmp/a.txt",
                "msg": "",
                "rc": 0,
                "start": "2023-11-21 22:46:27.992296",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "aaaaa\nb\nccccc",
                "stdout_lines": [
                    "aaaaa",
                    "b",
                    "ccccc"
                ]
            },
            {
                "ansible_loop_var": "item",
                "changed": true,
                "cmd": "cat /tmp/d.txt",
                "delta": "0:00:00.002438",
                "end": "2023-11-21 22:46:28.216904",
                "failed": true,
                "invocation": {
                    "module_args": {
                        "_raw_params": "cat /tmp/d.txt",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "stdin_add_newline": true,
                        "strip_empty_ends": true
                    }
                },
                "item": "/tmp/d.txt",
                "msg": "non-zero return code",
                "rc": 1,
                "start": "2023-11-21 22:46:28.214466",
                "stderr": "cat: /tmp/d.txt: No such file or directory",
                "stderr_lines": [
                    "cat: /tmp/d.txt: No such file or directory"
                ],
                "stdout": "",
                "stdout_lines": []
            }
        ],
        "skipped": false
    }
}

TASK [task3] ***************************************************************************************
skipping: [192.168.1.55] => (item={'changed': True, 'stdout': 'aaaaa\nb\nccccc', 'stderr': '', 'rc': 0, 'cmd': 'cat /tmp/a.txt', 'start': '2023-11-21 22:46:27.992296', 'end': '2023-11-21 22:46:27.995302', 'delta': '0:00:00.003006', 'msg': '', 'invocation': {'module_args': {'_raw_params': 'cat /tmp/a.txt', '_uses_shell': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['aaaaa', 'b', 'ccccc'], 'stderr_lines': [], 'failed': False, 'item': '/tmp/a.txt', 'ansible_loop_var': 'item'}) 
failed: [192.168.1.55] (item={'changed': True, 'stdout': '', 'stderr': 'cat: /tmp/d.txt: No such file or directory', 'rc': 1, 'cmd': 'cat /tmp/d.txt', 'start': '2023-11-21 22:46:28.214466', 'end': '2023-11-21 22:46:28.216904', 'delta': '0:00:00.002438', 'failed': True, 'msg': 'non-zero return code', 'invocation': {'module_args': {'_raw_params': 'cat /tmp/d.txt', '_uses_shell': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': [], 'stderr_lines': ['cat: /tmp/d.txt: No such file or directory'], 'item': '/tmp/d.txt', 'ansible_loop_var': 'item'}) => {"ansible_loop_var": "item", "changed": false, "item": {"ansible_loop_var": "item", "changed": true, "cmd": "cat /tmp/d.txt", "delta": "0:00:00.002438", "end": "2023-11-21 22:46:28.216904", "failed": true, "invocation": {"module_args": {"_raw_params": "cat /tmp/d.txt", "_uses_shell": true, "argv": null, "chdir": null, "creates": null, "executable": null, "removes": null, "stdin": null, "stdin_add_newline": true, "strip_empty_ends": true}}, "item": "/tmp/d.txt", "msg": "non-zero return code", "rc": 1, "start": "2023-11-21 22:46:28.214466", "stderr": "cat: /tmp/d.txt: No such file or directory", "stderr_lines": ["cat: /tmp/d.txt: No such file or directory"], "stdout": "", "stdout_lines": []}, "msg": "Something is wrong!"}

本例中,由于 /tmp/d.txt 不存在, results 的第2个元素,其rc值为1。

注意:每一次迭代,其结果就会放到register变量里,而不是整个循环结束后才放的。

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      shell: echo "{{ item }}"
      loop:
        - "aaa"
        - "bbb"
      register: var1
      changed_when: var1.stdout != "aaa"

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa)
changed: [192.168.1.55] => (item=bbb)

可见,第一次迭代没有满足判断条件,而第二次迭代满足判断条件了。

复杂循环

遍历嵌套list

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      debug:
        msg: "{{ item[0] }} {{ item[1] }}"
      loop: "{{ ['Zhang', 'Li'] | product(['San', 'Si']) | list }}"

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=['Zhang', 'San']) => {
    "msg": "Zhang San"
}
ok: [192.168.1.55] => (item=['Zhang', 'Si']) => {
    "msg": "Zhang Si"
}
ok: [192.168.1.55] => (item=['Li', 'San']) => {
    "msg": "Li San"
}
ok: [192.168.1.55] => (item=['Li', 'Si']) => {
    "msg": "Li Si"
}

本例中,把两个list做笛卡尔乘积,生成了一个新的嵌套list:

yaml 复制代码
    [
        [
            "Zhang",
            "San"
        ],
        [
            "Zhang",
            "Si"
        ],
        [
            "Li",
            "San"
        ],
        [
            "Li",
            "Si"
        ]
    ]

然后遍历外层list,并通过 item[0]item[1] 访问内层list的元素。

Retry

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      shell: cat /tmp/a.txt
      register: var1
      until: var1.stdout.find("OK") != -1
      retries: 3
      delay: 5

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
FAILED - RETRYING: [192.168.1.55]: task1 (3 retries left).
FAILED - RETRYING: [192.168.1.55]: task1 (2 retries left).
FAILED - RETRYING: [192.168.1.55]: task1 (1 retries left).
fatal: [192.168.1.55]: FAILED! => {"attempts": 3, "changed": true, "cmd": "cat /tmp/a.txt", "delta": "0:00:00.002228", "end": "2023-11-23 07:53:18.333193", "msg": "", "rc": 0, "start": "2023-11-23 07:53:18.330965", "stderr": "", "stderr_lines": [], "stdout": "aaaaa\nb\nccccc", "stdout_lines": ["aaaaa", "b", "ccccc"]}

在运行过程中,编辑 /tmp/a.txt 文件(注意是在目标机器上),添加 OK 的内容,则运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
FAILED - RETRYING: [192.168.1.55]: task1 (3 retries left).
FAILED - RETRYING: [192.168.1.55]: task1 (2 retries left).
changed: [192.168.1.55]

注:

  • retries 的缺省值是 3delay 的缺省值是 5

遍历inventory

假设 /etc/ansible/hosts 内容如下:

powershell 复制代码
[myvms]
192.168.1.55

[myself]
127.0.0.1
yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      debug:
        msg: "{{ item }}"
      loop: "{{ groups['all'] }}"
      #loop: "{{ groups['myvms'] }}"

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=192.168.1.55) => {
    "msg": "192.168.1.55"
}
ok: [192.168.1.55] => (item=127.0.0.1) => {
    "msg": "127.0.0.1"
}
ok: [127.0.0.1] => (item=192.168.1.55) => {
    "msg": "192.168.1.55"
}
ok: [127.0.0.1] => (item=127.0.0.1) => {
    "msg": "127.0.0.1"
}

可见,打印了所有的主机名。

为什么打印了两次呢?这是因为指定了 hosts: all ,所以在两个目标机器上都运行了一次。

若改为 hosts: myvms ,则运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=192.168.1.55) => {
    "msg": "192.168.1.55"
}
ok: [192.168.1.55] => (item=127.0.0.1) => {
    "msg": "127.0.0.1"
}

如果只想遍历 myvms ,则把 loop: "{``{ groups['all'] }}" 改为 loop: "{``{ groups['myvms'] }}" ,运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=192.168.1.55) => {
    "msg": "192.168.1.55"
}

也可以通过 loop: "{``{ ansible_play_batch }}" 指定遍历当前play的主机:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=192.168.1.55) => {
    "msg": "192.168.1.55"
}

注: groupsansible_play_batch 都是Ansible的特殊变量,参见 https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html

还可以通过 inventory_hostnames lookup来指定遍历的主机:

yaml 复制代码
---
- hosts: myvms
  tasks:
    - name: task1
      debug:
        msg: "{{ item }}"
      loop: "{{ query('inventory_hostnames', 'all') }}"

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=192.168.1.55) => {
    "msg": "192.168.1.55"
}
ok: [192.168.1.55] => (item=127.0.0.1) => {
    "msg": "127.0.0.1"
}

遍历 all ,同时排除 myvms ,则指定:loop: "{``{ query('inventory_hostnames', 'all:!myvms') }}"

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=127.0.0.1) => {
    "msg": "127.0.0.1"
}

loop_control

label

前面我们提到,遍历一个dict:

yaml 复制代码
---
- hosts: all
  vars:
    var1: {name: "Tom", age: 20}
  tasks:
    - name: task1
      debug:
        msg: "Key: {{ item.key }}. Value: {{ item.value }}"
      loop: "{{ {'name': 'Tom', 'age': 20} | dict2items }}"
      #loop_control:
      #  label: "{{ item.key}}"

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item={'key': 'name', 'value': 'Tom'}) => {
    "msg": "Key: name. Value: Tom"
}
ok: [192.168.1.55] => (item={'key': 'age', 'value': 20}) => {
    "msg": "Key: age. Value: 20"
}

注意其中的 (item={'key': 'name', 'value': 'Tom'}) 等,如果item数据量很大,则输出量很大。此处可以使用 label 指定打印的内容(比如只打印key,不打印value),见注释部分。

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=name) => {
    "msg": "Key: name. Value: Tom"
}
ok: [192.168.1.55] => (item=age) => {
    "msg": "Key: age. Value: 20"
}

pause

在每次循环迭代之间,暂停一段时间(秒)。

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      debug:
        msg: "{{ item }}"
      loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
      loop_control:
        pause: 3

index_var

指定下标变量,然后通过该变量获取下标值(从0开始)。

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      debug:
        msg: "{{ idx }}: {{ item }}"
      loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
      loop_control:
        index_var: idx

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "0: aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "1: bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "2: ccc"
}

loop_var

循环的元素名称,默认叫做 item ,而对于嵌套循环,为避免混淆内外循环的item,可用 loop_var 指定item名称。

创建文件 test19.yml 如下:

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      include_tasks: test20.yml
      loop: [1, 2, 3]
      loop_control:
        loop_var: item_outer

创建文件 test20.yml 如下:

yaml 复制代码
---
- name: inner_task1
  debug:
    msg: "Outer item = {{ item_outer }}, Inner item = {{ item_inner }}"
  loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
  loop_control:
    loop_var: item_inner

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
included: /root/temp/temp1121/test20.yml for 192.168.1.55 => (item=1)
included: /root/temp/temp1121/test20.yml for 192.168.1.55 => (item=2)
included: /root/temp/temp1121/test20.yml for 192.168.1.55 => (item=3)

TASK [inner_task1] *********************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "Outer item = 1, Inner item = aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "Outer item = 1, Inner item = bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "Outer item = 1, Inner item = ccc"
}

TASK [inner_task1] *********************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "Outer item = 2, Inner item = aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "Outer item = 2, Inner item = bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "Outer item = 2, Inner item = ccc"
}

TASK [inner_task1] *********************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "Outer item = 3, Inner item = aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "Outer item = 3, Inner item = bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "Outer item = 3, Inner item = ccc"
}

本例中,外部循环的item命名为 item_outer ,而内部循环的item命名为 item_inner

扩展循环变量

添加 extended: true ,则可以访问如下变量:

  • ansible_loop.allitems :所有元素
  • ansible_loop.index :从1开始
  • ansible_loop.index0 :从0开始
  • ansible_loop.revindex :倒数,从1开始
  • ansible_loop.revindex0 :倒数,从0开始
  • ansible_loop.first :是否是第一个元素
  • ansible_loop.last :是否是最后一个元素
  • ansible_loop.length :元素数量
  • ansible_loop.previtem :前一个元素(第一次迭代时未定义)
  • ansible_loop.nextitem :后一个元素(最后一次迭代时未定义)
yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      debug:
        msg: "ansible_loop.allitems = {{ ansible_loop.allitems }}, ansible_loop.index = {{ ansible_loop.index }}, ansible_loop.index0 = {{ ansible_loop.index0 }}, ansible_loop.revindex = {{ ansible_loop.revindex }}, ansible_loop.revindex0 = {{ ansible_loop.revindex0 }}, ansible_loop.first = {{ ansible_loop.first }}, ansible_loop.last = {{ ansible_loop.last }}, ansible_loop.length = {{ ansible_loop.length }}, ansible_loop.previtem = {{ ansible_loop.previtem | default('no previous') }}, ansible_loop.nextitem = {{ ansible_loop.nextitem | default('no next') }}"
      loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
      loop_control:
        extended: true

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
    "msg": "ansible_loop.allitems = ['aaa', 'bbb', 'ccc'], ansible_loop.index = 1, ansible_loop.index0 = 0, ansible_loop.revindex = 3, ansible_loop.revindex0 = 2, ansible_loop.first = True, ansible_loop.last = False, ansible_loop.length = 3, ansible_loop.previtem = no previous, ansible_loop.nextitem = bbb"
}
ok: [192.168.1.55] => (item=bbb) => {
    "msg": "ansible_loop.allitems = ['aaa', 'bbb', 'ccc'], ansible_loop.index = 2, ansible_loop.index0 = 1, ansible_loop.revindex = 2, ansible_loop.revindex0 = 1, ansible_loop.first = False, ansible_loop.last = False, ansible_loop.length = 3, ansible_loop.previtem = aaa, ansible_loop.nextitem = ccc"
}
ok: [192.168.1.55] => (item=ccc) => {
    "msg": "ansible_loop.allitems = ['aaa', 'bbb', 'ccc'], ansible_loop.index = 3, ansible_loop.index0 = 2, ansible_loop.revindex = 1, ansible_loop.revindex0 = 0, ansible_loop.first = False, ansible_loop.last = True, ansible_loop.length = 3, ansible_loop.previtem = bbb, ansible_loop.nextitem = no next"
}

注:如果 ansible_loop.allitems 很大,为了节省内存,可以设置 extended_allitems: false

yaml 复制代码
loop_control:
  extended: true
  extended_allitems: false

获取 loop_var 的值

比如指定了 loop_var: myitem ,则可以通过 {``{ myitem }} 来获取item,也可以通过 {``{ lookup('vars', ansible_loop_var) }} 获取item。

yaml 复制代码
---
- hosts: all
  tasks:
    - name: task1
      debug:
        msg: "{{ myitem }} , {{ lookup('vars', ansible_loop_var) }}"
      loop: [1, 2, 3]
      loop_control:
        loop_var: myitem

运行结果如下:

powershell 复制代码
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=1) => {
    "msg": "1 , 1"
}
ok: [192.168.1.55] => (item=2) => {
    "msg": "2 , 2"
}
ok: [192.168.1.55] => (item=3) => {
    "msg": "3 , 3"
}

可见,二者效果是一样的。

参考

  • https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_loops.html
  • https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
  • https://docs.ansible.com/ansible/latest/inventory_guide/intro_patterns.html
相关推荐
我的运维人生2 天前
利用Python与Ansible实现高效网络配置管理
网络·python·ansible·运维开发·技术共享
qlau20072 天前
基于kolla-ansible在AnolisOS8.6上部署all-in-one模式OpenStack-Train
ansible·openstack
Shenqi Lotus2 天前
Ansible——Playbook基本功能
运维·ansible·playbook
qlau20075 天前
基于kolla-ansible在openEuler 22.03 SP4上部署OpenStack-2023.2
ansible·openstack
水彩橘子5 天前
Semaphore UI --Ansible webui
ui·ansible
happy_king_zi6 天前
ansible企业实战
运维·ansible·devops
码上飞扬6 天前
深入浅出 Ansible 自动化运维:从入门到实战
运维·ansible·自动化运维
theo.wu6 天前
Ansible自动化部署kubernetes集群
kubernetes·自动化·ansible
xidianjiapei0016 天前
Ubuntu Juju 与 Ansible的区别
linux·ubuntu·云原生·ansible·juju
打败4046 天前
ansible_find模块
linux·ansible