iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 13
0

Handler

看完 task,接下來來看一下 handler,大概可以翻成處理器,不過還是用原文吧。若 task A 執行後要觸發 task B,就用 handler 來處理。那麼 task A 什麼時候會執行呢?待會會有一個 condition,可以在 task 中設定什麼樣的條件發生可以執行,或者前面看到 command 模組的 creates 參數可以用某個檔案是否存在來決定要不要執行,不過這裡要說的是另一種情況,叫做等冪性 (idempotent),這是 Ansible 的重要性質,無論 playbook 執行多少次,都能確保主機的狀態是一致的。這也是組態工具的一個重要概念喔,重點並不是你做了什麼,而是它的結果(最終狀態)是什麼。我的理解是這樣,在為了確保等冪性的狀況下,並不是所有的 task 都會被執行,如果一個 task 在執行前,主機的狀態會和執行後相同,已經與預期的結果一致,那就不會執行。例如某個 task 是要安裝一個套件,如果這個套件已經安裝,它就不會執行,或者是要複製一個檔案,但目的檔案已經存在,且內容和來源檔案相同,那它也不會複製。所以並不是所有 task 都會被執行。

回到 handler,如果 task A 執行後要觸發 task B,可以在 task A 最後加上 notify,參數是它要觸發的 handler 的 name。handler 和一般 task 沒什麼不同,只是它要被列在 handlers 這個 directive 下,而 handlers 和 tasks 是在同一階層的。文件中的範例如下:

tasks:
   - name: template configuration file
     template:
       src: template.j2
       dest: /etc/foo.conf
     notify:
       - restart memcached
       - restart apache

handlers:
    - name: restart memcached
      service:
        name: memcached
        state: restarted
    - name: restart apache
      service:
        name: apache
        state: restarted

當 "template configuration file" 這個 task 被執行後,會「通知」handlers 中 restart memcached 和 restart apache 這兩個 task 執行。文件說,handler 只會被觸發一次,即使有很多 task 都來通知它也一樣。

另外有一個 listen 的用法,它有點像是 notify 和 handler 的中介者,範例如下:

handlers:
    - name: restart memcached
      service:
        name: memcached
        state: restarted
      listen: "restart web services"
    - name: restart apache
      service:
        name: apache
        state:restarted
      listen: "restart web services"

tasks:
    - name: restart everything
      command: echo "this task will restart the web services"
      notify: "restart web services"

"restart everything" 這個 task 會通知 "restart web services",然後 restart memcached 和 restart apache 這兩個 handler task 會 listen "restart web services",所以 restart everything 執行後就會通知 restart memcached 和 restart apache。

Variable

在繼續介紹 loop 和 conditional 之前,先說明 Ansible 的變數,因為 loop 和 conditional 的使用都跟變數有關。文件中關於變數的介紹在 https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html,篇幅很長,範圍很雜,例子很多,但我覺得缺乏一些統整的範例,所以看完之後還是不太知道實際上該如何使用…。我們還是來看看文件中的說明吧。

Ansible 中的變數大致上可分為三種,一種是自行定義的變數,可以定義在 inventory、playbook 或者外部檔案再引入使用,第二種是 Ansible 搜集而來的機器資訊,稱為 fact,像是 IP、作業系統種類、版本等,第三種用來儲存在遠端機器執行的指令結果,稱為 register。變數名稱可以使用英數字及底線,但開頭一定要是英文字符。

在 inventory 中定義的變數已經介紹過,如果要在 playbook 中定義變數,要放在 vars 這個 directive 之下,例如:

- hosts: webservers
  vars:
    http_port: 80

register 在前面講到 command 模組有介紹過。

- name: return motd to registered var
  command: cat /etc/motd
  register: mymotd

fact 的部分,最常用到的應該是 ansible_facts,它的結構類似 map,以下節錄文件中 ansible_facts 的部分內容:

{
    "ansible_all_ipv4_addresses": [
        "REDACTED IP ADDRESS"
    ],
    "ansible_all_ipv6_addresses": [
        "REDACTED IPV6 ADDRESS"
    ],
    "ansible_architecture": "x86_64",
    "ansible_bios_date": "09/20/2012",
    "ansible_bios_version": "6.00",
    "ansible_cmdline": {
        "BOOT_IMAGE": "/boot/vmlinuz-3.5.0-23-generic",
        "quiet": true,
        "ro": true,
        "root": "UUID=4195bff4-e157-4e41-8701-e93f0aec9e22",
        "splash": true
    },

(以下省略)

知道了要如何定義變數,那要怎麼使用呢?文件中說變數代換,背後用的是 Jinja2 的樣版功能引擊,可以用 {{ }} 這種雙大括號的方式引用,例如:

template: src=foo.cfg.j2 dest={{ remote_install_path }}/foo.cfg

所以昨天的範例中,在 j2 樣版中的 {{ ansible_managed }} 就是要引用 ansible_mananged 這個變數,它的值是一個字串 "Ansible managed"。

Loop

有時候會想要在一個 task 中重覆作某件事,例如新增若干個使用者、安裝數個套件,這個時候就可以使用 loop。我們直接來看看範例:

- name: add several users
  user:
    name: "{{ item }}"
    state: present
    groups: "wheel"
  loop:
     - testuser1
     - testuser2

- name: add several users
  user:
    name: "{{ item.name }}"
    state: present
    groups: "{{ item.groups }}"
  loop:
    - { name: 'testuser1', groups: 'wheel' }
    - { name: 'testuser2', groups: 'root' }

在 loop 中可以陣列的方式表示要重覆的項目,而 {{ item }} 是這個陣列中迭代用的變數。雖然 YAML 字串不需要雙引號包住,但若值有使用到 {{ }} 來表示變數,要用雙引號包住,以避免 YAML 解析時出錯。文件中還有很多 loop 的範例及用法,請自行參考。

Conditional

conditional 是條件的意思,在程式語言中差不多是 if 的功能。文件中這一部分在討論 play、task、variable(變數)之間的相依狀況,以及 inventory 中 group 根據某些 host 的條件而變化。感覺起來有點複雜,因為文件中這一節把 conditional、loops、register、variable 什麼的都混在一起了,所以它的範例有的看起來有點複雜。

基本上 conditioanl 是由 when 這個 directive 來負責條件的引入,這個還蠻直覺的,就是 when 後面的狀況成立時就執行這個 task,請看下面的範例:

tasks:
  - name: "shut down Debian flavored systems"
    command: /sbin/shutdown -t now
    when: ansible_facts['os_family'] == "Debian"
    # note that all variables can be directly in conditionals without double curly braces

tasks:
  - name: "shut down CentOS 6 and Debian 7 systems"
    command: /sbin/shutdown -t now
    when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") or
          (ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "7")

這裡用到 andible_facts。文件上還有一些範例,例如配合 Jinja2 的 test 或 filter 作為 when 的判斷條件,這部分請再參考文件。接下來是 conditional 和 loop 互相搭配的範例:

tasks:
    - command: echo {{ item }}
      loop: [ 0, 2, 4, 6, 8, 10 ]
      when: item > 5

關於 register 和 when 的搭配請看下面範例。

- name: test play
  hosts: all

  tasks:

      - shell: cat /etc/motd
        register: motd_contents

      - shell: echo "motd contains the word hi"
        when: motd_contents.stdout.find('hi') != -1

今天主要介紹了 inventory 和 playbook 的設置方式,以及變數的使用,因為可供設定的內容非常多,所以只是簡單地看一下文件中的說明,大概知道有這些東西就可以了,等到真正要使用的時候再回來查閱吧。


上一篇
[Day 12] Ansible (3)
下一篇
[Day 14] Ansible (5)
系列文
30 天準備 LPI DevOps Tools Engineer 證照30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言