写点什么

Ansible 最佳实践之 Playbook 高级循环任务如何操作

作者:山河已无恙
  • 2022-12-02
    内蒙古
  • 本文字数:7957 字

    阅读完需:约 26 分钟

写在前面



  • 今天和小伙伴分享一些 ansible 剧本中数据迭代的笔记

  • 博文内容比较简单

  • 主要介绍的常见的迭代对比

  • 使用过滤器和查找插件在复杂数据结构上实施迭代循环

  • 食用方式:了解 Ansible 基础语法

  • 理解不足小伙伴帮忙指正


傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。--------王小波



循环和查找插件

Ansible 在 2.5 中引入了loop关键字。以前任务迭代通过使用with_开头并以查找的名称结尾的关键字的方法。与 loop 等效的是 with_list,设计用于在简单的扁平列表中进行迭代,对于简单的列表来讲,loop 是最佳语法。


以下三种语法具有相同的结果,其中第一个使用的loop是首选:


$ cat loop_demo.yaml---- name: loop Play  hosts: servera  gather_facts: no  vars:    - mylist:        - li        - rui        - long  tasks:    - name: using loop      debug: msg={{ item }}      loop: "{{ mylist }}"    - name: using with_list      debug: msg={{ item }}      with_list: "{{ mylist }}"    - name: using lookup plugin      debug: msg={{ item }}      loop: "{{ lookup('list',mylist) }}"$
复制代码


运行剧本是一样的效果,这里第三种方式通过,lookup 插件的的方式实现的,lookup 插件是 Jinja2 模板引擎的 Ansible 扩展。通过插件使 Ansible 能够使用外部来源的数据,我们这里使用 lookup 来将一个数据转化为 list


$ ansible-playbook  loop_demo.yaml
PLAY [loop Play] *********************************************************************************************
TASK [using loop] ********************************************************************************************ok: [servera] => (item=li) => { "msg": "li"}ok: [servera] => (item=rui) => { "msg": "rui"}ok: [servera] => (item=long) => { "msg": "long"}
TASK [using with_list] ***************************************************************************************ok: [servera] => (item=li) => { "msg": "li"}ok: [servera] => (item=rui) => { "msg": "rui"}ok: [servera] => (item=long) => { "msg": "long"}
TASK [using lookup plugin] ***********************************************************************************ok: [servera] => (item=li) => { "msg": "li"}ok: [servera] => (item=rui) => { "msg": "rui"}ok: [servera] => (item=long) => { "msg": "long"}
PLAY RECAP ***************************************************************************************************servera : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$
复制代码


使用 loop 关键字替代 with_* 样式的循环具有以下优点:


  • 无需记住或查找 with_* 样式关键字来满足当前迭代场景。

  • 专注于学习 Ansible 中提供的插件和过滤器,适用性比单纯的迭代更广泛。

  • 可以通过ansible-doc -t lookup命令访问查找插件文档,结合插件文档实现复杂遍历


使用 loop 关键字和 dict 插件替换"with_dict"关键字


---- hosts: testB  remote_user: root  gather_facts: no  vars:    users:      alice: female      bob: male  tasks:  - debug:      msg: "{{item.key}} is {{item.value}}"    loop: "{{ lookup('dict',users) }}"
复制代码


在 2.6 版本的官网手册中,官方开始推荐使用 loop加filter 的方式来替代"loop 加 lookup"的方式,


---- hosts: testB  remote_user: root  gather_facts: no  vars:    users:      alice: female      bob: male  tasks:  - debug:      msg: "{{item.key}} is {{item.value}}"    loop: "{{ users | dict2items }}"
复制代码


loop 方式还提供了 loop_control属性


可以用于控制循环的行为,添加索引之类,比如,使用 loop_control 的 index_var 选项,就能在遍历列表时,将元素对应的索引写入到指定的变量中,除了 index_var 选项,loop_control 还有一些其他的选项可用,此处我们就来总结一下这些选项。


pause 选项能够让我们设置每次循环之后的暂停时间,以秒为单位,换句话说就是设置每次循环之间的间隔时间,示例如下

迭代场景示例

在列表的列表上迭代

对于需要多级迭代的嵌套数据,使用传统的循环方式,往往获取不到子数据,即不能实现数据的扁平化处理。


---- name: loop Play  hosts: servera  gather_facts: no  vars:    - mylist:        - ['l','i']        - rui        - long  tasks:    - name: using loop      debug: msg={{ item }}      loop: "{{ mylist }}"    - name: using with_list      debug: msg={{ item }}      with_list: "{{ mylist }}"    - name: using lookup plugin      debug: msg={{ item }}      loop: "{{ lookup('list',mylist) }}"
复制代码


上面迭代的数据为一个嵌套的 list,使用前面所讲的迭代方式不能对嵌套的子数组进行迭代。


三种不同方式的测试,都不能做的扁平化的迭代


$ ansible-playbook  loop_demo.yaml
PLAY [loop Play] *********************************************************************************************
TASK [using loop] ********************************************************************************************ok: [servera] => (item=['l', 'i']) => { "msg": [ "l", "i" ]}ok: [servera] => (item=rui) => { "msg": "rui"}ok: [servera] => (item=long) => { "msg": "long"}
TASK [using with_list] ***************************************************************************************ok: [servera] => (item=['l', 'i']) => { "msg": [ "l", "i" ]}ok: [servera] => (item=rui) => { "msg": "rui"}ok: [servera] => (item=long) => { "msg": "long"}
TASK [using lookup plugin] ***********************************************************************************ok: [servera] => (item=['l', 'i']) => { "msg": [ "l", "i" ]}ok: [servera] => (item=rui) => { "msg": "rui"}ok: [servera] => (item=long) => { "msg": "long"}
PLAY RECAP ***************************************************************************************************servera : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
复制代码


这个时候,我们可以使用 with_items 关键字来迭代复杂的列表,实现列表数据的扁平化处理


$ cat loop_demos.yaml---- name: loop Play  hosts: servera  gather_facts: no  tasks:    - name: using with_list      debug:        msg: "{{ item }}"      with_items:        - [ 1,2,4 ]        - [ r,u ]
复制代码


$ ansible-playbook  loop_demos.yaml
PLAY [loop Play] *********************************************************************************************
TASK [using with_list] ***************************************************************************************ok: [servera] => (item=1) => { "msg": 1}ok: [servera] => (item=2) => { "msg": 2}ok: [servera] => (item=4) => { "msg": 4}ok: [servera] => (item=r) => { "msg": "r"}ok: [servera] => (item=u) => { "msg": "u"}
PLAY RECAP ***************************************************************************************************servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
复制代码

获取序列

有时候希望在剧本里获取一些序列,可以通过 whit_sequence 关键字实现


$ cat loop_demo_sq.yaml---- name: liruilong demo  hosts: servera  gather_facts: n  tasks:    - name: test whit_sequence      debug: msg={{ item }}      with_sequence:        start=1        end=5        stride=1
复制代码


$ ansible-playbook  loop_demo_sq.yaml
PLAY [liruilong demo] ****************************************************************************************
TASK [test whit_sequence] ************************************************************************************ok: [servera] => (item=1) => { "msg": "1"}ok: [servera] => (item=2) => { "msg": "2"}ok: [servera] => (item=3) => { "msg": "3"}ok: [servera] => (item=4) => { "msg": "4"}ok: [servera] => (item=5) => { "msg": "5"}
PLAY RECAP ***************************************************************************************************servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
复制代码

flatten 代替 with_items

若希望重构旧的剧本里的 with_items 任务以使用 loop 关键字,可使用 flatten 过滤器。


$ cat loop_demos.yaml---- name: loop Play  hosts: servera  gather_facts: no  tasks:    - name: using with_list      debug:        msg: "{{ item }}"        #with_items:      loop:        - [ 1,2,4,[3,4,5,[6]] ]        - [ r,u ]
复制代码


$ ansible-playbook loop_demos.yaml
PLAY [loop Play] *********************************************************************************************TASK [using with_list] ***************************************************************************************ok: [servera] => (item=[1, 2, 4, [3, 4, 5, [6]]]) => { "msg": [ 1, 2, 4, [ 3, 4, 5, [ 6 ] ] ]}ok: [servera] => (item=['r', 'u']) => { "msg": [ "r", "u" ]}
PLAY RECAP ***************************************************************************************************servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0$
复制代码


  • flatten 过滤器将以递归方式搜索嵌入式列表,并从发现的值创建一个列表。

  • flatten 过滤器接受 levels 参数,用于指定搜索嵌入式列表的整数的级别数,levels = 1 与 with_items 隐式进行的相同的一级扁平化。


$ cat loop_demos.yaml---- name: loop Play  hosts: servera  gather_facts: no  tasks:    - name: using with_list      debug:        msg: "{{ item }}"      loop: "{{ numList | flatten(levels=3) }}"      vars:              numList:                 - [ 1,2,4,[3,4,5,[6]] ]                 - [ r,u ]
$
复制代码


执行测试


$ ansible-playbook loop_demos.yaml
PLAY [loop Play] *********************************************************************************************
TASK [using with_list] ***************************************************************************************ok: [servera] => (item=1) => { "msg": 1}ok: [servera] => (item=2) => { "msg": 2}ok: [servera] => (item=4) => { "msg": 4}ok: [servera] => (item=3) => { "msg": 3}ok: [servera] => (item=4) => { "msg": 4}ok: [servera] => (item=5) => { "msg": 5}ok: [servera] => (item=6) => { "msg": 6}ok: [servera] => (item=r) => { "msg": "r"}ok: [servera] => (item=u) => { "msg": "u"}
PLAY RECAP ***************************************************************************************************servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

复制代码

迭代嵌套列表

subelements 过滤器从 names 变量数据中创建一个新列表。列表中的每一项本身是一个两元素列表。


$ cat  subelements.yaml---- name: liruilong demo  hosts: servera  vars:    names:      - liruilong:            - key: li      - liruilong:            - key: rui      - liruilong:            - key: long  tasks:    - name: loop demo      debug: msg={{ item }}      loop: "{{ names | subelements('liruilong') }}"
复制代码


执行测试


$ ansible-playbook subelements.yaml -b
PLAY [liruilong demo] ****************************************************************************************
TASK [Gathering Facts] ***************************************************************************************ok: [servera]
TASK [loop demo] *********************************************************************************************ok: [servera] => (item=[{'liruilong': [{'key': 'li'}]}, {'key': 'li'}]) => { "msg": [ { "liruilong": [ { "key": "li" } ] }, { "key": "li" } ]}ok: [servera] => (item=[{'liruilong': [{'key': 'rui'}]}, {'key': 'rui'}]) => { "msg": [ { "liruilong": [ { "key": "rui" } ] }, { "key": "rui" } ]}ok: [servera] => (item=[{'liruilong': [{'key': 'long'}]}, {'key': 'long'}]) => { "msg": [ { "liruilong": [ { "key": "long" } ] }, { "key": "long" } ]}
PLAY RECAP ***************************************************************************************************servera : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
复制代码


  • 第一个元素包含对每个用户的引用。

  • 第二个元素包含该用户的 liruilong 字典中的单个条目的引用。

迭代字典

在 Ansible 2.5 之前,必须使用 with_dict 关键字来迭代字典中的键值对。


item 变量具有两个属性:key 和 value。


key 属性包含一个字典键的值,而 value 属性则包含与字典关联的数据


$ cat dist_demo.yaml---- name: 字典 demo  hosts: servera  vars:    users:      xiaoming:         name: xiaoming      xiaoli:         name: xiaoli  tasks:    - name: dist demo      debug: msg={{ item.value.name }}      loop: "{{ users | dict2items }}"    - name: dist demo1      debug: msg={{ item.key }}      with_dict : "{{ users  }}"$
复制代码


这里使用 dict2item 可以实现相同的功能


$ ansible-playbook dist_demo.yaml
PLAY [字典 demo] *************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************ok: [servera]
TASK [dist demo] ***********************************************************************************************************ok: [servera] => (item={'key': 'xiaoming', 'value': {'name': 'xiaoming'}}) => { "msg": "xiaoming"}ok: [servera] => (item={'key': 'xiaoli', 'value': {'name': 'xiaoli'}}) => { "msg": "xiaoli"}
TASK [dist demo1] **********************************************************************************************************ok: [servera] => (item={'key': 'xiaoming', 'value': {'name': 'xiaoming'}}) => { "msg": "xiaoming"}ok: [servera] => (item={'key': 'xiaoli', 'value': {'name': 'xiaoli'}}) => { "msg": "xiaoli"}
PLAY RECAP *****************************************************************************************************************servera : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$
复制代码

迭代文件通配模式

通过fileglob查找插件构建循环,以迭代与所提供的文件通配模式的文件列表:


$ cat file_loop.yaml---- name: demo  hosts: serverc  tasks:          - name: file demo            debug: msg={{lookup('fileglob','~/.bash*')}}
复制代码


执行测试


$ ansible-playbook  file_loop.yaml
PLAY [demo] ****************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************ok: [serverc]
TASK [file demo] ***********************************************************************************************************ok: [serverc] => { "msg": "/home/student/.bash_logout,/home/student/.bash_profile,/home/student/.bashrc,/home/student/.bash_history"}
PLAY RECAP *****************************************************************************************************************serverc : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
复制代码


可以使用 query 代替 lookup, 以列表的形式展示


$ sed 's/lookup/query/' file_loop.yaml -i$ ansible-playbook  file_loop.yaml
PLAY [demo] ****************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************ok: [serverc]
TASK [file demo] ***********************************************************************************************************ok: [serverc] => { "msg": [ "/home/student/.bash_logout", "/home/student/.bash_profile", "/home/student/.bashrc", "/home/student/.bash_history" ]}
PLAY RECAP *****************************************************************************************************************serverc : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$
复制代码

博文参考



《Red Hat Ansible Engine 2.8 DO447》


用户头像

InfoQ写作平台签约作者,RHCE、CKA认证 2022-01-04 加入

Java 后端一枚,技术不高,前端、Shell、Python 也可以写一点.纯种屌丝,不热爱生活,热爱学习,热爱工作,喜欢一直忙,不闲着。喜欢篆刻,喜欢吃好吃的,喜欢吃饱了晒太阳。

评论

发布
暂无评论
Ansible最佳实践之Playbook高级循环任务如何操作_12月月更_山河已无恙_InfoQ写作社区