Ansible 入門筆記(ㄧ) - 核心概念及架構

Posted by MingLun Allen Wu on Monday, September 27, 2021

TL;DR

Ansible是適用於Python環境的自動化組態管理工具,組態(Configuration Management)意味著環境的設定及部署。

部署環境時,需要花費大量的時間來安裝套件、設定Config檔案、調整dependency,使用 Ansible 可以將這些「制式化的系統操作」自動化,讓開發者將注意力投注在值得關注的事物上。

撰寫好「部署的流程」(例如安裝套件、複製檔案…)及「遠端主機的連線資訊」後,運行Ansible的主機即可連線至其他主機,進行自動部署,提高部署的效率、降低安裝過程中人為失誤的機率。

1. Why Ansible ?

Ansible的優勢在於:

  1. 使用Python生態系:

    近期許多機器學習框架皆是建構於Python生態系

  2. 使用SSH連線,不需額外安裝工具:

    使用 Ansible 對遠端主機進行操作時,是透過 SSH 進行遠端操作,意味著不需要在遠端主機額外安裝套件,僅需開啟SSH連接阜即可。 在某些難以安裝套件的環境(例如醫院、銀行)相當方便。

2. 使用 Ansible 的目的

  • 提升 Production 環境的穩定性及可靠性

    使用檔案管理「部署內容」,達到 Infrastructure as Code,管理上也相對單純,可以使用版本控制進行進退版操作。

  • 減少服務中斷時間

    若發生服務中斷,需要將節點下線重新部署,使用Ansible進行部署能夠按照「預先設計好的流程」重新部署,不會受限於人員的精神狀況及操作穩定度。

  • 確保環境間(開發、測試、正式)設定對齊

    如果同時有多台機器,使用 Ansible 統一部署,可確保在部署過程中統一參考組態檔案,不會發生人為失誤(例如手誤輸入錯誤的資訊),或是不同部署人員部署順序不一致。


3. 檔案結構 & 安裝

3.1 安裝

雖然在先前曾提過 Ansible 不需要額外安裝套件,但這是指被操控的機器,我們仍然需要在主要操作的主機上安裝 Ansible,使用 pip 即可輕易安裝:

python -m pip install --user ansible

3.2 檔案結構

在學習一個工具時,我習慣先確認「專案的結構該長怎麼樣」,在後續的文章我們將會以此專案結構來作為範例。

以下是一個 Ansible 專案的大致樣貌:

ansible_project
| - README.md
| - INVENTORY_FILE_1  # 概念 - Inventory
| - INVENTORY_FILE_2
| main.yml # 概念 - Playbook
|
| - group_vars/
    | - GROUP1.yml
    | - GROUP2.yml
| - roles/
    | - common/
        | - tasks/
            | - main.yml
        | - handlers/
            | - main.yml

備註: 這是用來說明的專案結構,更多Ansible的專案結構可以參考 Best Practices - Ancible Documentation

4. Playbook - 設定工作的流程及步驟

在上方架構中,首先看到 main.yml,這是 Ansible 專案中的一個 Playbook(劇本),顧名思義在這個檔案中記錄了「做事的方式」及「做事的流程」。

Playbook 的範例如下:

# main.yml
- name: The example playbook # 階層1 - Play
  hosts: localhost # 定義執行此Play的機器
  vars: # 設定變數
    dynamic_word: "Hello World"

  tasks: # 設定 task
  - name: generate the hello_world.txt file   # 定義 task-1
    lineinfile:  # 概念 - Module
      path: /tmp/hello_world.txt
      state: present
      line: "{{ dynamic_word }}"
      create: yes

  - name: show file context   # 定義 task-2
    command: cat /tmp/hello_word.txt
    register: result

  - name: print file result   # 定義 task-3
    debug:
      msg: "{{ result.stdout_lines }}"

4.1 Playbook的階層

Playbook 中包含幾個不同的概念:

  • Play: 定義了「角色」(hosts) 該執行什麼樣的「任務」(task)

  • Task: 一個具體的工作,可能是一個具體的 bash 指令 (例如: echo $PATH)

  • 在一個 Playbook 中可能包含了數個不同的 Play:

    分別定義「扮演DB-Server角色」的機器該完成哪些任務、「扮演Web-Server」的機器又該完成哪些任務。

  • 在一個 Play 中可能定義了數個 Task:

    「扮演DB-Server角色」的機器可能需要「建立本地資料夾」、「安裝PostgreDB」、「初始化DB」等三項具體的任務。

以上方的 main.yml 來說,我們建立了一個 Play (The example playbook),執行這個Play的角色是localhost(hosts: localhost),而這個 Play 裡面包含了三項具體的 Task : + generate the hello_world.txt file + show file context + print file result

4.2 Task 的定義

接著我們來看如何定義 Task,從上方 main.yml 中節錄 Task 的部分:

tasks: # 設定 task
  - name: generate the hello_world.txt file   # 定義 task-1
    lineinfile:  # 概念 - Module
      path: /tmp/hello_world.txt
      state: present
      line: "{{ dynamic_word }}"
      create: yes

  - name: show file context   # 定義 task-2
    command: cat /tmp/hello_word.txt
    register: result

  - name: print file result   # 定義 task-3
    debug:
      msg: "{{ result.stdout_lines }}"

這三個 Task 語法看起來很不一樣,感覺相當複雜,但涵蓋了大部分的概念,理解這三個Task,就大致了解 Ansible Task了!

首先可以看到三個 Task 都有一個共通的 name 欄位,定義此 Task 的名稱。

接下來我們將三個任務分開來講:

4.2.1 - Command (Task2)

command 欄位放的是單純的 bash script,舉凡 ls /tmp, cat /xxx/yyy.txt,是最為單純的使用方式。

task-2 (show file context) 使用 command 來顯示特定位置的檔案:

- name: show file context   # 定義 task-2
    command: cat /tmp/hello_word.txt
    register: result

如果需要將 terminal 執行的資訊儲存下來,則需要透過 register 語法來註冊變數,以上方yml檔為例,將cat /tmp/hello_world.txt 的內容儲存至 result 變數中。

4.2.2 - Module (Task1 & Task3)

除了單純使用 command 下語法外, Ansible 還有提供許多內建的語法,讓使用者能用簡單的語法執行複雜的任務(例如檔案新增、權限變動、Git下載等等)。 接下來我們分別以 Task1Task3 來介紹兩個常用的 Module

4.2.2.1 - debug

Task2 我們將輸出結果儲存至 result 變數中,接下來我們就透過 AnsibleModule - Debug 來輸出結果,每個不同的 Module 會自帶不同的參數,這些參數可以在 Ansible文件查詢,得到參數 msg 是放置要輸出的值。

所以在 Task3 中,我們將剛剛註冊的變數放入 msg 中:

- name: print file result   # 定義 task-3
debug:
    msg: "{{ result.stdout_lines }}"

在稍後執行 Playbook 的過程中,即可將 Task2 的結果輸出。

4.2.2.2 - fileinline

除了 debug 之外,Ansible還提供許多模組,例如檔案的處理(Module-file)、Git操作(Module-git)等等…

第二個介紹的模組是Module-fileinline,將內容寫入檔案時使用,參考官網的文件可以得到各項參數的意義,以 task1 為例:

- name: generate the hello_world.txt file   # 定義 task-1
    lineinfile:  # 概念 - Module
      path: /tmp/hello_world.txt
      state: present
      line: "{{ dynamic_word }}"
      create: yes
  • path: 要寫入檔案的路徑
  • state: present(寫入)、absent(移除)
  • line: 要寫入的資訊 (對於 Jinja 語法不熟悉的朋友,別緊張,請繼續看下去)
  • create: 檔案若不存在,是否要創建

透過 Module - lineinfiletask1 將會創建 /tmp/hello_world.txt,並將 dynamic_word 變數寫入檔案中。 (請見下節)

4.3 Variable

Playbook 中,我們可以自定義變數,以 main.yml 為例:

# main.yml
- name: The example playbook # 階層1 - Play
  hosts: localhost # 定義執行此Play的機器
  vars: # 設定變數
    dynamic_word: "Hello World"

我們在 Play 中定義了一個變數 dynamic_word,在此Play的各項任務中,如果需要使用此變數,可以使用 Jinja2 的渲染語法來取得 :

- name: generate the hello_world.txt file   # 定義 task-1
    lineinfile:  # 概念 - Module
      path: /tmp/hello_world.txt
      state: present
      line: "{{ dynamic_word }}" # 使用 {{ 變數名稱 }} 來取得變數 
      create: yes

這樣的好處在於: 多個task都使用 Jinja 方式取得變數,未來該變數的值如果有變動,可以直接更換 vars 欄位,不需要每一個 task 都更動

4.4 執行 Playbook

恭喜! 我們成功設定好第一個 Playbook 了,快速總結這個Playbook的目的:

扮演「localhost」角色的主機,需要進行三個任務,依序是建立檔案、顯示資訊、輸出資訊

設定好劇本後,該開始執行了!

ansible-playbook main.yml

應該可以看到 Ansible 開始依序執行三項任務,且最後輸出結果將會如下圖:

在執行的過程中,也可以透過 -e 來覆蓋原先設定的變數,舉例來說,原先在 main.yml 中設定的 dynamic_word 變數為 hello world ,我們可以透過:

ansible-playbook -e dynamic_word=minglunwu main.yml

透過上述指令將 dynamic_word 變數覆寫為 minglunwu,執行結束時,應可在debug的介面看到檔案內容由 hello world 變為 minglunwu了。

5. Inventory - 紀錄機器的群組及連線資訊

Playbook 中我們定義了不同角色的機器需要進行什麼樣的行為,接下來我們將介紹 Inventory File,這個檔案的用意是

定義機器的連線資訊以及所屬的角色

# INVENTORY_FILE_1
[test-server] # 群組(角色)名稱
localhost ansible_connection=local

[web-server] # 群組(角色)名稱
# 連線資訊
# 機器名稱/連線方式/使用者帳號/使用者密碼(不建議)
webserver ansible_connection=ssh ansible_user=myuser ansible_ssh_pass=password
webserver-2 ansible_connection=ssh ansible_user=myuser-2 ansible_ssh_pass=password2

INVENTORY_FILE_1中我們可以看到 [test-server][web-server] 兩個群組(角色),這些群組中可包含不同的機器及連線資訊。

對於 Inventory FileGroup,我個人的理解是:

  • Inventory File 是用來管理不同的環境(例如 SIT、UAT、PROD)
  • Group: 同一個環境中,則用Group來區別不同類型的服務(例如: DB、Web-Server)。Playbook 在設定task時,可以透過 hosts 來篩選不同Group的機器該做什麼任務。

你可能會有兩個不同的 Inventory File,分別代表 UATPROD 環境的機器連線資訊,而兩個 Inventory File 中可能都會有 db-serverweb-server的群組。

在編寫 Playbook 時,你可以透過設定 hosts 參數來設定各個角色要做的事情:

- name: task-db
  hosts: db-server
  tasks:
    - name: example-1
      command: pip install MySQL-python

- name: task-web
  hosts: web-server
  tasks:
    - name: example-2
      command: pip install flask

被歸類在 db-server 群組的機器需要安裝MySQL-python、被歸類在 web-server 群組的機器則需要安裝 flask

這是以「群組」去區分不同機器該做什麼事情 (功能層級)

在執行 Playbook 時,也可以透過 -i 來指定 Inventory file

    ansible-playbook -i INVENTORY_FILE_1 main.yml

不同的 Inventory File 可能會定義相同的Group(Ex: web-server, db-server),但這些群組中包含著不同的節點 (Prod 環境的 web-server 是節點A、UAT環境的 web-server 則是節點B)

單純切換 Inventory File 即可在多個環境間部署多台節點,且每個節點根據自身被賦予的角色,進行不同的任務。

ansible-playbook -i UAT main.yml # 僅更新UAT環境
ansible-playbook -i PROD main.yml # 僅更新PROD環境
ansible-playbook -i UAT -i PROD main.yml # 兩個環境同時更新

使用Group來區分不同性質的服務

使用Inventory來區分不同環境

透過 Inventory FileGroupAnsible 能根據使用者需求快速的部署對應的機器。

6. 小結

本篇筆記對於 Ansible 進行入門的介紹,包含幾個重要觀念:

  • Playbook 中的元件及階層
  • 如何使用 Ansible 內建的模組
  • 使用 Group 區分不同功能的節點
  • 使用 Inventory File 區分不同環境
  • Ansible 如何執行 Playbook
  • 執行時如何覆寫變數、如何選擇 Inventory File

在下篇筆記中,我們將會繼續介紹 Ansible 的應用,包含:

  • 設定不同Group專屬的變數
  • 如何使用 role 來提高 Ansible 專案的程式碼重用率
  • 使用 Ansible-Galaxy 來取得眾多開源Playbook

筆記連結: Ansible 入門筆記(二) - Variable

感謝您的閱讀!如果有任何問題歡迎透過電子郵件聯繫我!

我們下次見!



See Also

監控資料品質的旅程