目錄

提升你的 Ansible 技能的幾個建議

前言

先說本文僅代表個人撰寫 Ansible Playbook及 Ansible AWX 之經驗累積。

Ansible 本身具有幾個很顯著的特點: 好寫, 易讀, Agentless,然而,說到好寫,也因為可以寫出多種風格,因此特別撰寫此文,記錄我在編寫 Ansible Playbook 時需要注意的一些事項。當然,其中一些內容會參考 Ansible 官方列出的 Ansible tips and tricks ,但大多數仍以我的個人經驗為出發點。

Suggestion 1: 建議遵循 Ansible Direcory Layout 的最佳實踐

經常編寫 Ansible 的人都知道,通過修改 ansible.cfg 文件,可以讓許多 playbook 的變數呼叫和路徑變得非常靈活。然而,雖然可以進行修改,但強烈不建議這樣做。原因是你的同事或接手的工程師可能不了解你的個人習慣,因此建議遵循最佳實踐和推薦的文件夾命名方式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
production_inventory         # production servers 主機目錄
staging_inventory            # staging environment 主機目錄

group_vars/
   group1                 # 以 Group 為單位,賦予對應之變數
   group2
host_vars/
   hostname1              # 以 host 為單位,賦予對應之變數
   hostname2

library/                  # 任何自己寫或自己維護 (也就是非 Built-in) 的 module 都建議放這邊
module_utils/             # 個人很少在用
                          # 可參考 `/usr/lib/python2.7/site-packages/ansible/module_utils` 的內容
filter_plugins/           # 任何自己寫或自己維護 (也就是非 Built-in) 的filter 都建議放這邊

Do_something_for_someone_1.yml       # 為了某個服務做某事,後面會有詳細建議
Do_something_for_someone_2.yml
Do_something_for_someone_3.yml

roles/
    common/               # 其中一個 role 的名字 `common`
        tasks/            # common 的 tasks 資料夾
            main.yml      # common 相關的 tasks action 請先寫在這
        handlers/         # common 裡的事件處理
            main.yml      # common 相關的 event handler 請先寫在這
        templates/        # common 相關的 jinja2 模板
            ntp.conf.j2   # common 相關的 file template 請先寫在這
        files/            # common 相關的檔案庫
            bar.txt       # common 相關的檔案
            foo.sh        # common 相關的 script 放這邊
        vars/             #
            main.yml      # 跟這個 common 有相關的區域變數宣告請放這邊
        defaults/         # common 相關的預設變數
            main.yml      # 可以寫變數預設值在這邊,但我個人比較推薦寫到 tasks 裡面的 playbook 裡,後面會有詳細建議和寫法
        meta/             # common 相關的其他角色依賴
            main.yml      # 宣告角色依賴
        library/          # 針對 common role 的函式庫
        module_utils/     # 個人很少在用
        lookup_plugins/   # 個人很少在用

    webtier/              # 呈上的結構
        ...
        ...
        ...

Suggestion 2: Playbook 命名法建議

在命名 Playbook 時,遵循 Do something for someone 的格式。我知道很多人會直接用 someone 作為 Playbook 的命名,但基於後續會提到的 Ansible AWX 帶來的 Workflow 概念——多個 Playbook 序列化執行,以及期望維護者能一眼看出這份 Playbook 主要是在針對 什麼目標做什麼事情,因此建議採用此命名規範。

1
2
3
4
5
6
7
#不建議
webserver.yml
database.yml

#建議
deploy-webserver.yml
deploy-database.yml

Suggestion 3: 一個專案對應一個業務目標

Ansible 最頂端的資料夾稱為 專案 (Project),其次是 Playbook 及 roles 等文件結構。

假設今天要部署一個 HTTP 伺服器,需要包含 lb、www、db,我會將 Playbook 分為以下幾個:

  • Deploy_http_server
    • install_lb.yml
    • install_<www.yml>
    • install_db.yml
    • check_www_service.yml

在這個案例中,專案名稱為 Deploy_http_server,裡面包含上述所列的 Playbook,以一個專案一個業務目標為核心進行 Playbook 的撰寫。

Suggestion 4: 變數無給值時,預設使用 default 值

常寫 Shell Script 的人應該熟悉以下寫法:

1
FOO=${VARIABLE:-default} # 如果 VARIABLE 無給值,及使用 default 值

在 Ansible 中也支援這種寫法:

1
2
3
4
5
---
- hosts: "{{ variable_host | default('all') }}"
  tasks:
    - name: ...
    ...

這個使用情境我個人應用在部署新 VM 或更新系統時,針對特定主機時會用到。過去要針對新部署的機器進行一些修正時,都需要修改 hosts 裡面的值,現在用這個寫法可以直接這樣操作:

1
ansible-playbook -i inventory deploy_new_vm_env --extra-vars='variable_host=192.168.50.1'

這樣就可以針對 192.168.50.1 這台機器進行部署或上 patch,如果我沒有指定任何 extra-vars 的話,則會直接對 inventory 中的所有機器(all)進行處理。

這個寫法也可以結合 Ansible AWX 裡 Survey 功能進行 Self-service 應用。

Suggestion 5: Ansible Workflow 實作提示

這個概念源自於 Ansible AWX,它將多個 Playbook 序列化執行。這種做法適用於由多個業務目標所組成的更大業務目標。你可以將其類比為 Agile 專案術語中的 Epic / Story / Task 對應到 Ansible 的 Workflow / Playbook / Roles。

Workflow 具體寫法如下:

1
2
3
4
5
cat deploy_openshift_cluster.yml
---
- import_playbook: prerequisite_env.yml
- import_playbook: deploy_core_componets.yml
- import_playbook: check_componets.yml

如果你需要的話,也可以將 workflow 嵌套在 workflow 中,但除非你對抽象化程度非常有把握,否則建議最多只做一層 workflow,以應付大多數需求。

此外,這個功能主要是為了 Ansible AWX 設計的,不僅具備可視化 Workflow 的能力,還增加了更多錯誤處理功能。例如,當 Playbook 執行時出現問題,可以跳轉到錯誤處理步驟進行應對,比如發送通知等。

Suggestion 6: 使用版本控制系統(Version Control System)管理

在這裡的版本控制系統可以是任何一家方案,例如 git、svn、mercurial 等等。沒有最好的方案,只有最適合自己的方案。我個人強烈推薦使用 git。無論你是採用集中式還是分散式的管理方式,都可以使用 git 來實現,非常好用。

遵循 Infrastructure as Code 的思想,應該對所有源代碼的變動進行記錄。使用版本控制有以下幾個好處:

  • 允許使用者將檔案恢復到某個時間點的狀態
  • 追蹤不同時間點的修改
  • 確認在某個時間點進行了哪些變更,並查找導致問題的責任人
  • 容易進行備份和管理

在 Ansible AWX 中,其專案內容的更新也是需要基於版本控制進行設定檔的變動,這樣便於進行 Playbook 的內容管理。

Suggestion 7: 單一 host 可同時屬於多個 group

host 是唯一值,而 group 可以將多個 host 定義在一起,並且可以多重定義 host。也就是說,你可以把 group 當作 tag 來使用,根據不同的角色、位置、任務來進行定義,這在使用 --limit 參數時會非常好用。

具體的 inventory 寫法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[infra:children]
db
www
lb

[db]
tw-db.divazone.local

[www]
tw-www.divazone.local

[lb]
tw-lb.divazone.local

[tw]
tw-db.divazone.local
tw-lb.divazone.local
tw-www.divazone.local

Suggestion 8: 避免使用 ansible_connection=local,建議改用 ansible_connection=ssh

其實這裡不是說不能用 local,而是設計時請優先考慮使用 SSH 連線,有其他特殊需求再考慮使用 local,因為這主要牽涉到 Ansible 的連線行為。你可以使用 -vvv 選項來觀察連線過程。先前遇到過問題是使用 local 執行時,會有權限和環境變數的問題,經研究 issue 後,發現改成 SSH 連線後就沒有這些問題。此外,Ansible 預設的連線方式是 ansible_connection=ssh,所以在 inventory 中不用特別明寫。

具體寫法如下:

1
2
3
4
5
# 不建議
localhost ansible_connection=local

# 建議
localhost ansible_connection=ssh ansible_host=localhost

Suggestion 9: 多利用 ping 模組進行連線測試

在執行任何 Playbook 之前,我會先針對我要執行的機器跑 ping,以確保機器連接狀況正常。有時候可以通過這個動作提前發現網路和執行問題。多數時候,我會把這個工作寫成一份通用 Playbook,並搭配 Workflow 來進行檢查。此外,Ansible 的 ping 是一個 Ansible 模組,而不是單純的 Linux 工具 ping

具體的 ping Playbook 寫法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cat pingall.yml
---
- hosts: "{{ variable_host | default('all') }}"
  tasks:
    - name: Test connectivity
      ping:
      register: result
    - name: Print result
      debug:
        msg: "{{ result }}"

Suggestion 10: 關閉 gather_facts 以加速執行速度

因為 Ansible 是 agentless,執行時預設會先去收集 host 上的環境資訊(例如 IP、hostname、OS 版本等)。然而,當管理的機器過多時,會發現收集這些資訊的時間過長。這時可以選擇將 gather_facts 設置為 false,以節省收集資訊的時間。

1
2
3
4
5
6
7
---
- hosts: all
  gather_facts: false
  tasks:
    - name: ...
      ...
      ...

Suggestion 11: 設定 fact_cache 以使用快取的環境變數

假設你真的需要用到 gather_facts 收集的環境資訊,但又不想在短時間內多次等待收集資訊的時間,可以在 ansible.cfg 中使用 fact_caching 來作為快取。這樣可以在一定時間內重用已收集的資訊,從而提高執行效率。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cat /etc/ansible/ansible.cfg

[defaults]
...
# 除了支援JSON file 以外,還另外支援 redis、memcached,在管大量機器的時候才需要搭配這方面的方案
fact_caching = jsonfile
fact_caching_connection = /tmp/facts
# hardtimeout
fact_caching_timeout = 600
...

Suggestion 12: 增加 ssh 參數以提升連線效率

這部分偏向於 SSH 的傳輸調教,主要是 ControlPersistPipelining 這兩個選項。 ControlPersist 可以讓 SSH 建立的連線一直保持著連線,建議開啟以減少重複連線的時間。 而 Pipelining 則可以減少 Ansible 控制節點到主機之間的連線數量,也有助於提升執行效率。

1
2
3
4
5
6
7
8
cat /etc/ansible/ansible.cfg

[ssh_connection]
...
ssh_args = -o ControlMaster=auto -o ControlPersist=300s
control_path = %(directory)s/%%h-%%r
pipelining = True
...

Suggestion 13: 收集 tasks 的處理時間

如果每次執行時都不知道哪個 task 跑得最久或卡在哪裡,可以使用 Ansible 插件 profile_tasks 來統計各個 task 的處理時間。這樣可以精確了解每個 task 的耗時,尤其是 Gathering Facts 所消耗的時間。

1
2
3
4
5
6
/etc/ansible/ansible.cfg

[defaults]
...
callback_whitelist = profile_tasks
...

Suggestion 14: 盡可能提供完整的 Inventory 資訊

盡可能在 Inventory 中提供明確的資訊,而不採用隱晦的資訊,尤其是 ansible_host。這樣可以確保每個人看到這份 Inventory 時的理解是一致的。

1
2
3
4
5
# 不建議
db1

# 建議
db1 ansible_host=10.1.2.75

Suggestion 15: 對敏感工作禁用 Log

若不希望在log中留下任何敏感性資料,可以在該 Task 多加一個 no_log: True 來避免記錄敏感資訊。

1
2
3
- name: Secret Task
  shell: /usr/bin/gg --value={{ secret_value }}
  no_log: True

References