進擊的 Ansible(二):如何快速搞定生產環境 Ansible 專案佈局?

雲叔_又拍雲發表於2021-12-02

Tips:與前文 《進擊的 Ansible(一):Ansible 快速入門》 一樣,本文使用的 Ansible 版本 2.5.4,專案演示環境 MacOS。由於 Ansible 專案開發活躍版本更新快,很多 API 介面不向後相容,所以對照本文實踐時請確保所用版本一致。

學完前文《進擊的 Ansible(一):Ansible 快速入門》後,用來發布單體專案綽綽有餘。但是實際生產環境中一個服務往往有多個元件,比如部署大資料服務時,常常需要部署一個“大資料全家桶”:Hadoop、 Zookeeper、 Hive、 Mysql、 Flink 等。這時僅靠前文中的知識就有點捉襟見肘了,繁多的 yaml 檔案和其他配置檔案依賴關係複雜,如果不能正確地劃分目錄組織專案結構,對於後期維護非常不利。所以今天的文章著重解決一下這個問題:如何科學正確地劃分 Ansible 應用的目錄結構?

把 Ansible 視為一種程式語言

首先要樹立這樣一個觀念:“把 Ansible 視為一種程式語言”。我們可以將 Ansible 理解為專門用來管理自動化釋出的 DSL,它的基本語法規則約等於 yaml 語言規則,諸如 synchronize、pip、template 等。同時 Ansible 模組可等價為語言的內建函式或內建包,也就是編寫 playbook 就是在寫 Ansible 這門語言的程式碼塊。如此,只要我們沿著程式語言的思路去理解 Ansible,很多疑惑會迎刃而解:比如 Ansible 支援變數,模組 when 就是程式語言的流程控制語句 if, 模組 loop 或 with_* 就是程式語言中的迴圈迭代語句 for 或 while。

使用程式語言進行專案開發的過程中,我們是如何降低專案複雜度的?當然是進行“模組化”。不同的功能封裝到不同的包或檔案中,這樣構成一個業務功能的最小單位我們稱之為“模組”。在專案的入口檔案中,我們通過 import (Python、Golang 等語言中使用此關鍵字) 或 require (Node.js 等語言使用此關鍵字)等關鍵字把需要的模組載入進來,然後就可以進行業務邏輯上的編排。

這樣做的優點顯而易見,一個模組是一個業務功能的具體實現,當後期有修改的需求時只需要修改相關的模組即可,這正是 SOLID 原則中的 “SRP” (單一職責原則) 所提倡的。此外,模組化還支援像“搭積木”一樣根據業務需求靈活地組織業務流程,這樣就能最大限度地複用當前模組,這也符合程式設計原則中的 “DRY” (Don't Repeat Yourself)。

因此在我們將 Ansible 視為一門程式語言(DSL)的現在,我們可以發現在 Ansible 文件中用 Roles 表示“模組”這個概念,用 import_tasks、include_tasks 等表示 import 這個概念。這裡說一個題外話,不知道有沒有人覺得 Ansible 中發明的“playbook”、“roles” 等直譯成“劇本”、“角色”的概念讓人摸不著頭腦?

至關重要的概念:Roles

現在我們樹立了“Roles = 模組”的概念。我們在看一下文件中對 Roles 的定義,豐富一下細節:

Roles let you automatically load related vars, files, tasks, handlers, and other Ansible artifacts based on a known file structure. After you group your content in roles, you can easily reuse them and share them with other users.

“角色允許您基於已知的檔案結構自動載入相關的變數、檔案、任務、處理程式和其他Ansible工件。在將內容按角色分組後,可以輕鬆地重用它們並與其他使用者共享它們。”

直白來講就是 Roles 是對變數、檔案、任務等的封裝,目的是為了模組重用。

如何使用 Roles?

在軟體設計正規化的最佳實踐中有一條叫做 “約定大於配置”,簡單來講就是“軟體中做了一些前提性假設,這些假設就是軟體開發者與軟體使用者的約定。作為軟體使用者你遵守這些約定即可,不再需要(或不支援)對這些約定進行配置。”—— 一定程度上可以理解為軟體中的預設配置。

Ansible 在使用 Roles 時同樣存在 “約定大於配置” 的情況,並且這些約定是硬性的(即不支援在配置檔案中自定義)。

首先,Role 的目錄結構是固定的。

An Ansible role has a defined directory structure with eight main standard directories. You must include at least one of these directories in each role. You can omit any directories the role does not use.

Ansible 角色有一個定義好的目錄結構,其中有八個主要的標準目錄。該已規定好的目錄結構,示例如下:

├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

By default Ansible will look in each directory within a role for a main.yml file for relevant content.

這八個目錄的作用是:

  • tasks/main.yml:放置 role 執行任務時用到的檔案。
  • handlers/main.yml:處理程式,可以在 role 內部或外部使用
  • library/my_module.py:模組,可以在 role 中使用(有關更多資訊,請參見在rroles 中嵌入模組和外掛)。
  • defaults/main.yml:role 的預設變數(有關更多資訊,請參閱使用變數)。這些變數具有所有可用變數中最低的優先順序,並且可以被任何其他變數(包括庫存變數)輕鬆覆蓋。
  • vars/main.yml:role 中的其他變數。(與 Ansible 模組中的 vars 作用一致,只不過這裡的 vars 表示目錄。)
  • files/main.yml:role 部署時用到的檔案。
  • templates/main.yml:role 部署時用到的模板。與 Ansible 模組中的 templates 作用一致,只不過這裡的 templates 表示目錄。)
  • meta/main.yml:role 使用到的後設資料。

在每個角色中必須包含至少一個這樣的目錄,當然我們可以省略角色不使用的任何目錄,但是每個目錄下的 main.yml 檔案是該目錄的入口檔案,Ansible 讀取時會預設查詢該檔案。所以這個 main.yml 檔案不能省略。

其次,儲存和查詢 roles。

上文我們已經瞭解到了一個 role 的內部目錄結構,但是這遠遠不能滿足實際生產的需求。在文章的開始部分我們也以一個大資料專案為例,往往需要部署 Flink、Hadoop 等多個元件。這每一個元件都可以看做是一個 role。那麼 Ansible 是如何查詢 roles 的呢?

預設情況下,有 2 種方式:

  • 在 Ansible 的釋出專案中,建立一個叫做 roles 的目錄。
  • 預設情況下,Ansible 會自動查詢 /etc/ansible/roles 目錄下的 role。

舉個例子,我們要使用 Ansible 建立一個釋出大資料“全家桶”的專案 bigdata,該專案下要包含 Flink、Mysql、Hive 這 3 個 role。那麼 bigdata 這個專案的目錄結構大致如下:

➜  bigdata tree -L 3
.
└── roles
    ├── flink
    │   ├── defaults
    │   ├── files
    │   ├── handlers
    │   ├── meta
    │   ├── tasks
    │   ├── templates
    │   ├── tests
    │   └── vars
    ├── hive
    │   ├── defaults
    │   ├── files
    │   ├── handlers
    │   ├── meta
    │   ├── tasks
    │   ├── templates
    │   ├── tests
    │   └── vars
    └── mysql
        ├── defaults
        ├── files
        ├── handlers
        ├── meta
        ├── tasks
        ├── templates
        ├── tests
        └── vars

很明顯可以看到 roles 下的 Flink、hive、Mysql 的子目錄結構就是上文中提到的八大目錄。

ansible-galaxy

快速建立 Role

從上文可知,每建立一個 role 都必須至少含有八大目錄之一。所以 Ansible 中已內建了一個命令列工具 ansible-galaxy 快速建立 role 的八大目錄,減輕我們的工作量。

假設該 role 名稱是 flink,用如下命令生成相關目錄:

ansible-galaxy init flink

使用 tree 命令看到 ansible-galaxy 生成的目錄正是 role 所要求的標準的八個目錄。

➜  tree flink
flink
├── README.md
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

ansible-galaxy init 其他幾個實用的引數:

  • ansible-galaxy init -force role_name,預設情況下建立的 role 與當前工作目錄下存在的檔案重名的話,會丟擲異常。使用 -force 選項會強制建立 role 目錄,並對 role 目錄下重名的目錄或檔案進行替換。
  • ansible-galaxy init --role-skeleton=/path/to/skeleton role_name,使用過 Maven 的同學應該知道, Maven 支援以其他專案做骨架建立新專案。ansible-galaxy 同樣支援該功能,以 /path/to/skeleton 路徑下的 role 為骨架,把所有的檔案都進行拷貝來建立新的 role。

Galaxy:role 線上分享社群

此外與 Docker Hub、Grafana Dashboards 類似,Ansible Galaxy 也有一個線上社群 Galaxy[https://galaxy.ansible.com/home],上面有開發者分享的各種已經開發好的 roles。方便我們搜尋現成的 role 下載,也可以上傳自己開發的 role 到 Galaxy。

下載或上傳 role 到 Galaxy 網站,同樣需要使用命令列工具 ansible-galaxy。預設情況下 ansible-galaxy 呼叫的 Galaxy 服務端的地址是 https://galaxy.ansible.com, 可以通過 -server 選項或在 ansible.cfg 檔案中重新配置 Galaxy 的地址。

下載 roles

下載 roles 的語法模板是:

$ ansible-galaxy install username.role_name

預設情況下 ansible-galaxy 會把 role 下載到環境變數 ANSIBLE_ROLES_PATH 中,ansible-galaxy 提供了引數 --role_path 指定 role 下載的地址。

ansible-galaxy 其他命令速覽

  • ansible-galaxy search elasticsearch,查詢 Galaxy 網站中的 role elasticsearch
  • ansible-galaxy info username.role_name,檢視 username.role_name 的詳細資訊
  • ansible-galaxy list,檢視已安裝的 roles
  • ansible-galaxy remove username.role_name,解除安裝安裝的 username.role_name
  • ansible-galaxy login,登入 Galaxy 網站

整體佈局:Suit is best

如果你堅持讀完上述部分,那麼你肯定對於如何使用 role 瞭然於心,簡單來講就是當前 Ansible 應用下需要存在一個叫做 roles 的目錄。接下來我們聊聊 Ansible 應用下除了 roles 目錄外,其他目錄該如何佈局呢?Ansible 最佳實踐官方文件[https://docs.ansible.com/ansi...]中是這樣建議的:

Your usage of Ansible should fit your needs, however, not ours, so feel free to modify this approach and organize as you see fit.

One crucial way to organize your playbook content is Ansible’s “roles” organization feature, which is documented as part of the main playbooks page. You should take the time to read and understand the roles documentation which is available here: Roles.

Ansible 整體的目錄結構沒有一定之規,適合你的當前需求就好。但是 Roles 這個概念至關重要。

良心的 Ansible 官方在 Github 上開了一個專案 ansible-examples[https://github.com/ansible/an...]專門用來收集優秀的最佳實踐。大家可以根據實際需求吸收借鑑,下面我分享一下我常用的專案佈局:


.
├── Makefile
├── README.md
├── deploy.retry
├── deploy.yml
├── files
│   ├── apache-maven-3.8.3-bin.tar.gz
│   ├── apache-zookeeper-3.7.0-bin.tar.gz
│   ├── flink-1.14.0-bin-scala_2.11.tgz
│   ├── hadoop-2.7.5.tar
│   ├── hadoop-3.3.1.tar.gz
│   ├── mysql-connector-java-8.0.26-1.el7.noarch.rpm
│   ├── mysql-connector-java-8.0.26.jar
│   └── openjdk-11.0.2_linux-x64_bin.tar.gz
├── inventories
│   └── hosts
└── roles
    ├── flink
    ├── hadoop
    ├── hadoop3
    ├── hive
    ├── java
    ├── linux
    ├── mvn
    ├── mysql
    └── zookeeper
  • Makefile,用來封裝 Ansible 的釋出命令
  • deploy.ym,是執行 Ansible 命令時的入口檔案
  • files,用來存放 role 相關的部署包,一般體積較大,不會使用 git 進行版本管理。
  • inventories,用來管理部署機器。
  • roles,用來部署的元件。從上面的目錄可知,當前主要是用來部署大資料相關的元件。

給自己打個小廣告:在下一個實戰章節將使用這個專案佈局釋出大資料專案,在這個過程中又需要補充哪些 Ansible 的知識呢?為什麼我不直接使用 Galaxy 網站上的 role 而是要自己從頭開發呢?在部署 Hadoop3、Flink 專案中,使用 Ansible 又踩了哪些坑呢?

敬請期待 《進擊的 Ansible(三):Ansible 大資料實踐》

參考資料

  • 使用 Ansible 傳輸檔案的幾種方式:

https://zdyxry.github.io/2019...

  • Ansible:

https://gist.github.com/MrNic...

  • Roles:

https://docs.ansible.com/ansi...

  • Galaxy User Guide:

https://docs.ansible.com/ansi...

  • 約定優於配置:

https://zh.wikipedia.org/wiki...

推薦閱讀

一文讀懂瀏覽器儲存與快取機制

Python Type Hints 從入門到實踐

相關文章