返回文章列表

SaltStack Ansible 容器化 Python 自動化佈署

本文探討如何使用 SaltStack 和 Ansible 自動化管理和佈署 Python 應用程式,包含 SaltStack 模組編寫、Ansible playbook 撰寫,以及容器化 Python 應用程式的最佳實務,涵蓋基礎映像選擇、Python 直譯器安裝、應用程式安裝、

DevOps Web 開發

SaltStack 和 Ansible 是兩種主流的自動化組態管理工具,在 Python 應用程式的佈署和維護中扮演著重要的角色。SaltStack 允許使用 Python 程式語言編寫模組,擴充套件其功能並更有效率地管理複雜組態。Ansible 則透過 SSH 和 Playbooks 簡化系統管理任務,並支援 Docker 等容器化技術。容器化技術如 Docker 和 Kubernetes,提供隔離的執行環境,確保應用程式及其依賴項的一致性。選擇合適的基礎映像、正確安裝 Python 直譯器和應用程式,以及妥善管理虛擬環境和依賴項,是容器化 Python 應用程式的關鍵步驟。本文將探討如何結合 SaltStack、Ansible 和容器化技術,開發更穩健、高效的 Python 應用程式自動化佈署流程。

SaltStack 組態管理系統的擴充套件與實踐

SaltStack 是一個根據 Python 的組態管理系統,允許使用者透過 Python 程式語言來定義和管理系統組態。在處理非簡單組態時,使用 Python 可以比範本化 YAML 檔案更有效率。此外,SaltStack 也支援透過 Python 擴充套件其功能,以定義新的基本操作。

10.4 編寫 SaltStack 模組

在 SaltStack 中,模組是用於擴充套件其功能的關鍵元件。這些模組包括 state 模組、execution 模組和 utility 模組,分別用於定義狀態、執行特定任務和提供共用功能。

10.4.1 State 模組

State 模組用於定義系統的期望狀態。它們通常包含一個或多個函式,這些函式接受一個名稱引數,並傳回一個字典,描述狀態變更的結果。

def enforce_no_mean_files(name):
    mean_files = __salt__['file.find'](name, path="*mean*")
    if mean_files == []:
        return dict(
            name=name,
            result=True,
            comment='No mean files detected',
            changes=[],
        )
    changes = dict(
        old=mean_files,
        new=[],
    )
    if __opts__['test']:
        return dict(
            name=name,
            result=None,
            comment=f"The state of {name} will be changed",
            changes=changes,
        )
    for fname in mean_files:
        __salt__['file.remove'](fname)
    return dict(
        name=name,
        changes=changes,
        result=True,
        comment=f"The state of {name} was changed",
    )

內容解密:

  1. enforce_no_mean_files 函式檢查是否存在符合特定模式的檔案,並在必要時刪除它們。
  2. 使用 __salt__['file.find'] 來查詢檔案,這是 SaltStack 的 execution 模組功能。
  3. 如果找到檔案,則計算變更前後的狀態,並根據 test 模式傳回不同的結果。
  4. 在非 test 模式下,使用 __salt__['file.remove'] 刪除找到的檔案。

10.4.2 Execution 模組

Execution 模組提供了一些函式,可以在 minion 上執行特定的任務。它們通常用於簡化 state 模組的邏輯。

def multiremove(files):
    for fname in files:
        __salt__['file.remove'](fname)

內容解密:

  1. multiremove 函式接受一個檔案列表,並刪除這些檔案。
  2. 它透過呼叫 __salt__['file.remove'] 來執行檔案刪除操作。
  3. 這簡化了 state 模組中的邏輯,使得程式碼更易於管理和重用。

10.4.3 Utility 模組

Utility 模組提供了一些共用的功能,可以被 state 和 execution 模組呼叫。

def return_value(name, old_files):
    if len(old_files) == 0:
        comment = "No changes made"
        result = True
    elif __opts__['test']:
        comment = f"{name} will be changed"
        result = None
    else:
        comment = f"{name} has been changed"
        result = True
    changes = dict(old=old_files, new=[])
    return dict(
        name=name,
        comment=comment,
        result=result,
        changes=changes,
    )

內容解密:

  1. return_value 函式根據輸入引數和當前 SaltStack 的執行模式,傳回一個標準化的結果字典。
  2. 它簡化了 state 模組的邏輯,使得結果的計算和傳回更加統一和易於管理。

Ansible 自動化工具詳解

Ansible 是一種無需客戶端代理的組態管理系統,主要透過 SSH 進行遠端操作,也支援 Docker 或本地執行模式。與 Puppet 或 Salt 類別似,Ansible 能夠簡化系統管理任務,但其運作方式更為靈活。

Ansible 基礎操作

安裝 Ansible 可透過 pip install ansible 在虛擬環境中完成。安裝後,最簡單的測試方法是使用 ansible localhost -m ping 對本機進行連線測試。此指令能夠驗證 SSH 連線、SSH 金鑰組態以及主機金鑰是否正確。

$ ansible localhost -m ping

Ansible 預設使用本地 SSH 指令進行連線,若本地指令不適用,則會退而使用 Paramiko 函式庫。最佳實踐是使用本地加密私鑰並將其載入 SSH 代理中,以確保 Ansible 正常運作。

執行特定指令

$ ansible localhost -a "/bin/echo hello world"
$ ansible 10.40.32.195 -m ping

這些範例展示瞭如何對特定主機執行自定義指令或簡單的連線測試。

Inventory 組態

Ansible 的主機清單(inventory)定義了其可管理的目標主機。Inventory 可以透過靜態檔案(INI 或 YAML 格式)定義,或使用動態指令碼生成。

動態 Inventory 指令碼範例

#!/usr/bin/env python3
# save as simple.inv
import sys
import json

if '--host' in sys.argv[1:]:
    print(json.dumps({}))
else:
    print(json.dumps(dict(all='localhost')))

使用自定義 Inventory

$ chmod +x ./simple.inv
$ ./simple.inv
$ ansible -i simple.inv all -m ping

內容解密:

  1. 動態 Inventory 指令碼:此指令碼根據 Ansible 的引數輸出 JSON 格式的主機清單或主機變數。
  2. --host 引數:當指令碼接收到 --host 引數時,輸出指定主機的變數(此例為空字典)。
  3. --list 引數:預設行為是輸出所有主機清單,此例中僅包含 localhost
  4. JSON 輸出格式:Ansible 需要 JSON 格式的輸出以解析主機資訊。

Playbooks 與 Roles

Ansible 的核心功能是執行 Playbooks,它們是以 YAML 格式定義的任務集合。

簡單 Playbook 範例

---
- hosts: all
  tasks:
    - name: hello printer
      shell: echo "hello world"

執行 Playbook:

$ ansible-playbook -i simple.inv echo.yml

使用 Roles 分離任務

Roles 能夠將複雜的任務分解為可重用的模組。

---
- hosts: all
  roles:
    - common

roles/common/tasks/main.yml 中定義具體任務:

---
- name: hello printer
  shell: echo "hello world" >> /etc/hello
  creates: /etc/hello

內容解密:

  1. Playbook 結構:定義了目標主機 (hosts) 和要執行的任務 (tasks)。
  2. shell 模組:用於執行 Shell 命令,此例中輸出 “hello world”。
  3. creates 引數:確保任務的冪等性,若 /etc/hello 已存在則跳過執行。
  4. Roles 的優勢:便於管理和重複使用任務,特別是在多主機環境中。

Ansible 的進階概念

使用 Vault 管理機密

Ansible Vault 能夠加密敏感資訊,並透過密碼解密。

Jinja2 範本與變數

Playbooks 和 Roles 使用 Jinja2 範本語言,支援變數插值和篩選器。

使用者與許可權管理

Ansible 允許組態遠端使用者和許可權提升方法,例如使用 become 切換至 root 使用者。

---
- hosts: all
  remote_user: your_user
  become: yes
  become_user: root
  tasks:
    - name: Example task
      shell: echo "Task executed as root"

內容解密:

  1. remote_user:定義連線遠端主機的使用者。
  2. become:啟用許可權提升,預設為 False
  3. become_user:定義提升許可權後的使用者,預設為 root
  4. become_method:定義許可權提升的方法(如 sudo)。

容器技術與Ansible自動化佈署

容器技術已經成為現代應用程式佈署的主流方式。容器提供了一種隔離環境,讓應用程式能夠在獨立的環境中執行,具備所有必要的依賴項。Kubernetes和Docker等流行的容器執行環境都使用了containerd作為底層容器執行時。

建構容器映像檔

要將應用程式作為容器執行,需要將其封裝成符合Open Container Initiative(OCI)標準的映像檔。建構映像檔有多種方式,其中最常見的是使用buildctldocker buildnerdctl build等工具,這些工具都根據BuildKit。

BuildKit內部使用了一種稱為Low-Level Builder(LLB)的格式來描述建構過程,但LLB並不是設計給人類直接編寫的。通常,會使用像是Dockerfile這樣的前端工具來編寫建構規格。

Dockerfile撰寫最佳實踐

撰寫一個好的Dockerfile對於建構Python應用程式的容器映像檔至關重要。一個好的Dockerfile需要考慮多個因素,包括映像檔大小、建構時間、可重複建構性以及安全性更新的難易度。

這些目標之間存在一定的衝突,因此需要做出適當的取捨。瞭解Dockerfile中不同指令的使用方式及其對Python應用程式安裝的影響是撰寫好Dockerfile的關鍵。

選擇基礎映像檔

Dockerfile的第一行通常是FROM指令,用於指定基礎映像檔。大多數情況下,會選擇從一個流行的Linux發行版開始。幾乎所有的Linux發行版都有官方的容器映像檔。

GNU C函式庫支援

由於Python的可攜式二進位wheel(manylinux wheel)僅支援GNU C函式庫(glibc),因此通常選擇根據glibc的發行版。Alpine Linux是一個流行的非glibc發行版,雖然可以根據Alpine建構Python應用程式的容器映像檔,但會比較困難。

長期支援

容器映像檔中安裝的套件不可避免地會存在錯誤或安全問題。當需要整合修補程式時,需要重新建構容器映像檔。最小化對容器映像檔的更改可以加快佈署速度,並減少測試的需求。

Ansible與容器佈署

Ansible是一種簡單易用的組態管理工具,可以用於自動化佈署和管理容器。透過撰寫Ansible Playbook,可以定義容器的佈署流程,包括建立、組態和管理容器。

使用Ansible管理容器

- name: Deploy container
  hosts: servers
  become: true
  tasks:
    - name: Pull container image
      docker_image:
        name: myapp:latest
        source: pull

    - name: Run container
      docker_container:
        name: myapp
        image: myapp:latest
        state: started

內容解密:

  1. Deploy container: 定義了一個名為"Deploy container"的Playbook,用於在指定的主機上佈署容器。
  2. hosts: servers: 指定了Playbook執行的主機群組為"servers"。
  3. become: true: 表示需要提升許可權以執行任務。
  4. Pull container image: 使用docker_image模組提取名為"myapp:latest"的容器映像檔。
  5. Run container: 使用docker_container模組執行名為"myapp"的容器,並指定使用的映像檔為"myapp:latest"。

容器化Python應用程式的最佳實踐

容器化已成為現代軟體開發和佈署的重要組成部分。對於Python應用程式來說,選擇合適的基礎映像、安裝Python直譯器以及正確組態應用程式的安裝步驟都是至關重要的。

避免基礎映像的意外變更

官方發行版映像會定期更新修復漏洞,這可能會導致根據這些映像的容器發生變化。為了避免這種情況,可以透過指定映像的digest來確保使用特定的映像版本。

FROM registry.hub.docker.com/library/debian@sha256:8a71adf557086b1f0379142a24cbea50

然而,這種方法並不能保證digest對應的映像始終可用。因此,更好的做法是將基礎映像儲存在本地控制的倉函式庫中,並定期更新。

安裝Python直譯器

有多種方法可以在容器中安裝Python直譯器,包括使用conda、第三方倉函式庫以及直接從原始碼構建。

使用conda

conda是一個流行的Python環境管理工具,可以用來安裝Python直譯器。如果團隊已經在使用conda,那麼在容器中使用conda安裝Python是一個不錯的選擇。

使用第三方倉函式庫

一些第三方倉函式庫,如deadsnakes for Ubuntu,提供了預構建的Python版本。可以將這些倉函式庫新增到系統中,並從中安裝Python。

直接從原始碼構建

可以使用pyenv或python-build等工具直接從原始碼構建Python。這種方法需要更多的構建依賴,但可以提供更大的靈活性。

# 使用pyenv安裝Python
pyenv install 3.9.5

使用官方Python映像

如果使用Debian作為基礎映像,可以使用Docker Hub上的官方Python映像。

FROM python:3.9-slim

安裝Python應用程式

在容器中安裝Python應用程式需要建立一個虛擬環境,並在其中安裝應用程式及其依賴項。

建立虛擬環境

可以使用venv模組建立虛擬環境。

python -m venv /app/venv

安裝依賴項

可以從requirements.txt檔案中安裝依賴項。

pip install -r requirements.txt

安裝應用程式

可以透過構建wheel並安裝它來安裝應用程式。

python -m build
pip install --no-deps dist/my_app-1.0.0-py3-none-any.whl

最佳實踐

  • 將基礎映像儲存在本地控制的倉函式庫中,並定期更新。
  • 使用虛擬環境安裝應用程式及其依賴項。
  • requirements.txt檔案納入版本控制,以確保可重複的構建。
  • 在連續整合系統中新增容器映像構建步驟,以確保應用程式的正確安裝。

內容解密:

  1. 使用虛擬環境可以簡化應用程式的安裝和管理。
  2. requirements.txt檔案納入版本控制,可以確保可重複的構建。
  3. 在連續整合系統中新增容器映像構建步驟,可以確保應用程式的正確安裝。