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
相关推荐
风清再凯7 小时前
自动化工具ansible,以及playbook剧本
运维·自动化·ansible
IT乌鸦坐飞机7 小时前
ansible部署数据库服务随机启动并创建用户和设置用户有完全权限
数据库·ansible·centos7
遇见火星13 天前
如何使用Ansible一键部署MinIO集群?
ansible
粥周粥13 天前
ANSIBLE
ansible
码农101号13 天前
Linux中ansible模块补充和playbook讲解
linux·运维·ansible
码农101号13 天前
Linux的Ansible软件基础使用讲解和ssh远程连接
ansible
烟雨书信15 天前
ANSIBLE运维自动化管理端部署
运维·自动化·ansible
碎碎-li15 天前
ANSIBLE(运维自动化)
运维·自动化·ansible
@donshu@18 天前
Linux运维-ansible-python开发-获取inventroy信息
linux·运维·ansible
Kendra91921 天前
Ansible
ansible