iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 18
0

現代 IT 人一定要知道的 Ansible 自動化組態技巧

18. 如何在 Playbooks 使用 loops?

在 Shell Script 裡,我們會使用 for 和 while 等迴圈 (loop) 來簡化重複的程式碼,而在 Ansible 我們也可用 loop 來簡化重複的任務 (Tasks) 。以下凍仁將介紹常見的 loop 語法。

automate_with_ansible_practice-23.jpg
▲ 圖片來源:http://screencastsolutions.ca/looping-for-continuous-play/ 。

標準迴圈 (Standard Loops)

首先讓我們以簡單的重複印出 3 筆訊息為例。

Shell Script

先複習一下 Shell Script 的寫法。

  1. 建立 for loop 的 Script。

    #!/bin/bash
    $ vi bash_loop.sh
    for X in 0 1 2; do
      echo Loop $X
    done
    
    • 在第 3 行,我們用了 for,並代入了 0, 1, 2 三個值到 $X 變數。
    • 在第 4 行時,則用了 echo,印出訊息和 $X 變數。
  2. 執行 Script:可以看到底下跑了 3 次的 loop。

    $ ./bash_loop.sh
    Loop 0
    Loop 1
    Loop 2
    

Ansible Playbooks

我們需透過 itemwith_items 來使用 Ansible 的 loop,其 item 為預設名,一般情況下不可修改。

在 Ansible 2.1 新增了 Loop Control 的語法,可透過 loop_controlloop_var 來自訂 item 的名字,這在多重 loop 等較複雜的環境下會有很大的幫助。[^1]

  1. 建立 loop 的 playbook。

    $ vi playbook_loop.yml
    ---
    - name: a basic loop with playbook
      hosts: localhost
      tasks:
        - name: print loop message
          debug:
            msg: "Loop {{ item }}"
          with_items:
            - 0
            - 1
            - 2
    
    • 在第 7, 8 行裡,我們用了 debug module 來印出訊息,並定義 item
    • 在第 9 ~ 12 行裡,則用了 with_items 將 0, 1, 2 的值傳入 item
  2. 執行 Playbook:可以看到 print loop message 的 task 跑了 3 次的 loop。

    $ ansible-playbook playbook_loop.yml
    
    PLAY [a basic loop with playbook] *********************************
    
    TASK [setup] *******************************************************
    ok: [localhost]
    
    TASK [print loop message] ******************************************
    ok: [localhost] => (item=0) => {
        "item": 0,
        "msg": "Loop 0"
    }
    ok: [localhost] => (item=1) => {
        "item": 1,
        "msg": "Loop 1"
    }
    ok: [localhost] => (item=2) => {
        "item": 2,
        "msg": "Loop 2"
    }
    
    PLAY RECAP *********************************************************
    localhost      : ok=2    changed=0    unreachable=0failed=0
    
  3. 凍仁常用此手法來安裝多個套件,接著以建立 chusiang/ansible-jupyter Docker image 的 setup_jupyter.yml 為例。 [^2]

    $ vi setup_jupyter_yml
    01 ---
    02 - hosts: localhost
    03
    04   vars:
    05     # Same package on GNU/Linux.
    06     same_packages:
    07      - bash
    08      - bash-completion
    09      - ca-certificates
    10      - curl
    11      - git
    12      - openssl
    13 
    14    # Alpine Linux.
    15    apk_packages:
    16      - openssh-client
    17      - vim
    18 
    19    # Debian, Ubuntu.
    20    apt_packages: "{{ apk_packages }}"
    21    ...
    22 
    23  tasks:
    24    # General Linux.
    25    - name: install same packages
    26      package: name={{ item }} state=present
    27      with_items: "{{ same_packages }}"
    28      when:
    29        - same_packages is defined
    30        - ansible_pkg_mgr != "portage"
    31
    32    # Alpine Linux.
    33    - name: install apk packages
    34      apk: name={{ item }} state=present
    35      with_items: "{{ apk_packages }}"
    36      when:
    37        - apk_packages is defined
    38        - ansible_pkg_mgr == "apk"
    39
    40    # Debian, Ubuntu.
    41    - name: install apt packages
    42      apt: name={{ item }} state=present
    43      with_items: "{{ apt_packages }}"
    44      when:
    45        - apt_packages is defined
    46        - ansible_pkg_mgr == "apt"
    47    ...
    
    • 在第 6, 15, 20 行裡,分別宣告 same_packages, apk_packagesapt_packages 變數,並傳入了幾個套件名稱。
    • 在第 26, 27, 34, 35, 42, 43 行裡,定義了 item,並將 same_packages 變數傳入。換句話說就是 install same packages task 會安裝 same_packages 定義的所有套件。
    • 由於此例中 apk, apt 的套件名稱皆相同,故在第 20 行用了 apt_packages: "{{ apk_packages }}" 的手法讓 apt_packages = apk_packages

進階迴圈 (Advanced Loops)

如有數個變數需求,可用 item.first, item.second 類似屬性的方式定義 items。

  1. 建立擁有兩個 item 屬性的 loop 的 playbook。

    $ vi playbook_loop_adv1.yml
    ---
    - name: a advanced loop with playbook
      hosts: localhost
      tasks:
        - name: print loop message
          debug:
            msg: "Loop {{ item.num }}: {{ item.str }}"
          with_items:
            - { num: '0', str: 'automate' }
            - { num: '1', str: 'with' }
            - { num: '2', str: 'ansible' }
    
  2. 執行 Playbook:這次除了跑 3 次 loop 以外,還代入 numstr 屬性的 items。

    $ ansible-playbook playbook_loop_adv1.yml
    
    PLAY [a advanced loop with playbook] *******************************
    
    TASK [setup] *******************************************************
    ok: [localhost]
    
    TASK [print loop message] ******************************************
    ok: [localhost] => (item={u'num': u'0', u'str': u'automate'}) => {
        "item": {
            "num": "0",
            "str": "automate"
        },
        "msg": "Loop 0: automate"
    }
    ok: [localhost] => (item={u'num': u'1', u'str': u'with'}) => {
        "item": {
            "num": "1",
            "str": "with"
        },
        "msg": "Loop 1: with"
    }
    ok: [localhost] => (item={u'num': u'2', u'str': u'ansible'}) => {
        "item": {
            "num": "2",
            "str": "ansible"
        },
        "msg": "Loop 2: ansible"
    }
    
    PLAY RECAP *********************************************************
    localhost      : ok=2    changed=0    unreachable=0    failed=0
    
  3. 這部份在新增多個使用者、多個軟連結 (soft link) 時都會用到。

    $ vi playbook_loop_adv2.yml
    ---
    - name: a advanced loop with playbook
      hosts: localhost
      tasks:
        - name: create multiple soft link
          file:
            src: "~/vcs/4.docs/automate-with-ansible/lab/ch18/{{ item.src }}"
            dest: "/tmp/{{ item.dest }}"
            state: link
          with_items:
            - { src: 'playbook_loop.yml', dest: 'loop0.yml' }
            - { src: 'playbook_loop_adv1.yml', dest: 'loop1.yml' }
            - { src: 'playbook_loop_adv2.yml', dest: 'loop2.yml' }
    
    # vim: ft=ansible :
    
    • 第 8 行的 src絕對路徑會因環境而有變動,還請特別留意一下。

後語

以個人經驗而言,掌握這兩個技巧就可以解決大多的迴圈需求!若想深入了解這部份,請研讀官方的 Loops | Ansible Documentation 文件 [^3]。

相關連結

[^1]: 更多 Loop Control 的介紹請參考 http://docs.ansible.com/ansible/playbooks_loops.html#loop-control

[^2]: 在友人的提醒下補了行號以利閱讀,若想複製該範例,請直接上 GitHub 取用。

[^3]: 凍仁從 Ansible 1.9 開始踏入 Ansible 的世界,在 Ansible 2.0 之後新增的 loop 語法凍仁至今 (2016.12.18) 還未完全使用過。


上一篇
17. Ansible 如何發送 notification 到 HipChat?
下一篇
19. 如何維護大型的 Ansible Playbooks?
系列文
現代 IT 人一定要知道的 Ansible 自動化組態技巧31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
卡斯
iT邦研究生 1 級 ‧ 2016-12-18 23:42:58

Shell Script /images/emoticon/emoticon33.gif

看更多先前的回應...收起先前的回應...
凍仁翔 iT邦新手 3 級 ‧ 2016-12-18 23:50:23 檢舉

本來想拿 Chef (另一套組態管理工具) 來比較的,但時間有限就算了。XDD

卡斯 iT邦研究生 1 級 ‧ 2016-12-18 23:53:32 檢舉

哈哈!! Shell Script 都還給老師,竟然都忘光光了,是該開始學習了/images/emoticon/emoticon02.gif

凍仁翔 iT邦新手 3 級 ‧ 2016-12-18 23:57:47 檢舉

會基本的 Shell Script 就夠用了,如果要比較複雜的 script 凍仁會建議改用 Playbook 或 Python。/images/emoticon/emoticon77.gif

卡斯 iT邦研究生 1 級 ‧ 2016-12-18 23:58:25 檢舉

/images/emoticon/emoticon12.gif

0
凍仁翔
iT邦新手 3 級 ‧ 2016-12-19 10:09:23

想用 刪除線 的 markdown 語法時才發現這功能是假的 …。/images/emoticon/emoticon06.gif

http://ithelp.ithome.com.tw/markdown

凍仁翔 iT邦新手 3 級 ‧ 2016-12-23 18:18:59 檢舉

咦咦咦?刪除線哪時回來了?XDD

我要留言

立即登入留言