在容器化應用程式佈署的過程中,映象構建是不可或缺的一環。Dockerfile 的線性構建流程在簡單場景下固然便捷,但面對複雜的客製化需求時,其侷限性便顯露無疑。Buildah 作為新一代容器映象構建工具,以其高度的靈活性和可控性,為開發者提供了更強大的武器。它允許我們以程式化的方式,精細地控制映象的每一層,實作更複雜的構建邏輯。不同於 Dockerfile 的黑盒操作,Buildah 將映象構建過程透明化,使每一層的變更都清晰可見,極大地簡化了除錯流程。此外,Buildah 與 Podman 的無縫整合,更使其成為容器生態系統中的一把利器,讓開發者得以在同一工具鏈中完成映象構建和容器管理。

容器映象構建新思路:為何我擁抱 Buildah?

在容器化技術的浪潮中,映象構建是至關重要的一環。Podman 作為 Docker 的有力替代者,在簡化容器管理方面表現出色。但當面對更複雜的構建需求時,我發現 Buildah 才是真正的利器。

Dockerfile 的侷限性:我的痛點

傳統的 Dockerfile 方式雖然易於上手,但其線性、指令式的流程在某些場景下顯得捉襟見肘。例如:

  • 靈活性不足:Dockerfile 的指令集相對固定,難以滿足客製化的構建需求。
  • 除錯困難:Dockerfile 的每一步都會產生新的映象層,中間層的錯誤難以追蹤。
  • 無法程式化控制:Dockerfile 本身無法嵌入程式碼邏輯,難以實作複雜的構建流程。

這些痛點促使我開始尋找更靈活、更可控的映象構建方案。

Buildah:容器構建的瑞士軍刀

Buildah 是一個專注於構建容器映象的工具,它與 Podman 出自同一團隊之手,可以看作是 Podman 在映象構建方面的補充。Buildah 提供了更底層、更靈活的 API,讓開發者能夠以程式化的方式構建映象。

Buildah 的核心概念:可程式設計的構建流程

Buildah 的核心思想是將映象構建過程分解為一系列可程式設計的操作。你可以像操作 Git 倉函式庫一樣,對映象層進行增加、修改、提交等操作。

Buildah 的優勢:我為何選擇它?

  • 靈活性:Buildah 允許你使用 Shell 指令碼、Go 程式碼等方式編寫構建邏輯,充分滿足客製化需求。
  • 可控性:Buildah 提供了更細粒度的控制,你可以精確控制每一層的內容。
  • 可除錯性:Buildah 的每一層都清晰可見,方便你追蹤和除錯構建過程中的問題。
  • 與 Podman 無縫整合:Buildah 構建的映象可以直接被 Podman 使用,無需額外轉換。

實戰演練:使用 Buildah 構建 HTTPD 映象

接下來,我將展示如何使用 Buildah 構建一個簡單的 HTTPD 映象。

1. 建立工作目錄

首先,建立一個工作目錄,用於存放構建所需的檔案:

mkdir myhttpd
cd myhttpd

2. 準備 HTTPD 設定檔

建立 index.html 檔案,作為 HTTPD 的首頁內容:

<!DOCTYPE html>
<html>
<head>
    <title>玄貓的 Buildah 示例</title>
</head>
<body>
    <h1>歡迎來到玄貓的 Buildah 示例!</h1>
</body>
</html>

3. 編寫 entrypoint.sh 指令碼

建立 entrypoint.sh 指令碼,用於啟動 HTTPD 服務:

#!/bin/sh
set -euo pipefail

if [ $UID != 0 ]; then
    echo "Running as user $UID"
fi

if [ "$1" == "httpd" ]; then
    echo "Starting custom httpd server"
    exec $1 -DFOREGROUND
else
    echo "Starting container with custom arguments"
    exec "$@"
fi

這個指令碼會檢查當前使用者是否為 root,並根據傳入的引數啟動 HTTPD 服務。

4. 編寫 Dockerfile

FROM fedora:latest

# 安裝 httpd 並清理快取
RUN dnf install -y httpd && dnf clean all

# 設定 httpd 日誌輸出到 stdout 和 stderr
RUN sed -i 's|ErrorLog "logs/error_log"|ErrorLog /dev/stderr|' /etc/httpd/conf/httpd.conf && \
    sed -i 's|CustomLog "logs/access_log" combined|CustomLog /dev/stdout combined|' /etc/httpd/conf/httpd.conf && \
    chown 1001 /var/run/httpd

# 複製網頁內容
COPY index.html /var/www/html

# 定義內容卷
VOLUME /var/www/html

# 複製容器啟動指令碼
COPY entrypoint.sh /entrypoint.sh

# 宣告暴露的連線埠
EXPOSE 8080

# 宣告預設使用者
USER 1001

ENTRYPOINT ["/entrypoint.sh"]
CMD ["httpd"]

5. 使用 Podman 構建映象

podman build -t myhttpd .

6. 檢視映象層

使用 podman inspect 命令檢視映象層:

podman inspect myhttpd --format '{{ .RootFS.Layers }}'

7. 壓縮映象層

使用 --layers=false 選項可以將所有映象層壓縮為一層:

podman build -t myhttpd --layers=false .

Buildah 的更多可能性:我的探索之路

除了基本的映象構建,Buildah 還提供了更多高階功能,例如:

  • 從現有容器建立映象:你可以根據一個正在執行的容器建立映象,方便你將容器的狀態儲存下來。
  • 直接操作映象層:你可以使用 buildah mount 命令將映象層掛載到本地檔案系統,直接修改其內容。
  • 多階段構建:你可以使用 Buildah 實作多階段構建,將構建過程中的中間產物分離開來,減小最終映象的體積。

為何我擁抱 Buildah:從零開始開發容器的藝術

在容器技術日新月異的今天,我們有幸擁抱諸多強大的工具。其中,Buildah 以其獨特的靈活性和底層控制力,吸引了我的目光。它不僅僅是一個容器構建工具,更像是一個容器組裝工廠,讓我們可以精雕細琢每一個細節。

Buildah 的核心:容器與映象的生命週期

Buildah 的設計哲學圍繞著容器映象的生命週期管理。它巧妙地利用了 containers/image 專案來處理映象與 registry 的互動,同時藉助 containers/storage 專案來管理映象和容器的檔案系統層。這種分工合作的方式,使得 Buildah 在構建容器時更加高效和可靠。

突破傳統:Buildah 的進階構建策略

Buildah 最吸引我的地方,在於它對傳統 Dockerfile/Containerfile 構建方式的平行支援,以及根據原生 Buildah 命令驅動的構建方式。這種進階的構建策略,為我們開啟了無限可能。

透過將 Dockerfile 指令轉化為標準命令,Buildah 成為了一個可指令碼化的工具。我們可以將其與自定義邏輯和原生 shell 結構(如條件陳述式、迴圈或環境變數)結合使用。例如,Dockerfile 中的 RUN 指令,可以用 buildah run 命令來替代。

這種靈活性在應對複雜的構建需求時尤為重要。例如,在自動化構建流程中,我們可以根據不同的環境變數,動態地調整構建引數,實作高度定製化的容器映象。

保留傳統:Dockerfile 的構建方式

當然,如果團隊需要保留之前 Dockerfile 中實作的構建邏輯,Buildah 也提供了 buildah build(或其別名 buildah bud)命令,可以從 Dockerfile/Containerfile 中讀取指令並構建映象。

安全至上:無 root 模式執行

從安全形度來看,Buildah 另一個備受歡迎的功能是它可以在無 root 模式下流暢執行以構建映象。無需 Unix socket 即可執行構建。正如本章開頭所解釋的,構建始終根據容器;Buildah 也不例外,並且所有構建都在工作容器內執行,從基礎映象之上開始。

Buildah 常用指令概覽

以下是一些 Buildah 中最常用的指令,它們為我們提供了構建容器的強大能力:

  • buildah from:根據一個基礎映象初始化一個新的工作容器。例如:$ buildah from fedora
  • buildah run:相當於 Dockerfile 的 RUN 指令,在工作容器內部執行命令。例如:buildah run <containerID> -- dnf install -y nginx
  • buildah config:組態映象的中繼資料。這個命令的可用選項與 Dockerfile 中那些不修改檔案系統層,但設定容器中繼資料的指令相關聯。例如:buildah config --entrypoint /entrypoint.sh <containerID>
  • buildah add:相當於 Dockerfile 的 ADD 指令,向容器中增加檔案、目錄,甚至是 URL。例如:buildah add <containerID> index.php /var/www.html
  • buildah copy:與 Dockerfile 的 COPY 指令相同,向容器中增加檔案、URL 和目錄。例如:buildah copy <containerID> entrypoint.sh /
  • buildah commit:從一個工作容器提交一個最終映象。這個命令通常是最後執行的命令。例如:buildah commit <containerID> <myhttpd>
  • buildah build:相當於經典的 Podman build。這個命令接受 Dockerfile 或 Containerfile 作為引數,以及構建目錄路徑。例如:buildah build -t <imageName> .
  • buildah containers:列出參與 Buildah 構建的活動工作容器,以及用作起點的基礎映象。等效的命令是 buildah lsbuildah ps。例如:buildah containers
  • buildah rm:用於移除工作容器。buildah delete 命令是等效的。例如:buildah rm <containerID>
  • buildah mount:可以用於掛載工作容器的根檔案系統。例如:buildah mount <containerID>
  • buildah images:列出本地主機快取中的所有可用映象。例如:buildah images --json

玄貓認為,Buildah 的這些指令為我們提供了構建容器的完整工具集。無論是從零開始構建,還是根據現有的 Dockerfile,Buildah 都能夠勝任。

玄貓帶你認識 Buildah:從零開始開發容器

Buildah 是 Podman 的好夥伴,它能讓你從頭開始建構容器。以下將介紹 Buildah 的常用指令:

  • buildah tag: 為本地儲存的映像檔加上自訂名稱和標籤。語法為 buildah tag <name> <new-name>。例如:buildah tag myapp quay.io/packt/myapp:latest
  • buildah push: 將本地映像檔推播到遠端私有或公開的 registry,或是 Docker 或 OCI 格式的本地目錄。相較於 Podman 或 Docker 的同等指令,這個指令提供更大的彈性。語法為 buildah push [options] <image> [destination]。例如:buildah push quay.io/packt/myapp:latestbuildah push <imageID> docker://<URL>/repository:tagbuildah push <imageID> oci:</path/to/dir>:image:tag
  • buildah pull: 從 registry、OCI 封存檔或目錄中提取映像檔。語法為 buildah pull [options] <image>。例如:buildah pull <imageName>buildah pull docker://<URL>/repository:tagbuildah pull dir:</path/to/dir>

想了解更多指令細節嗎?只要在終端機輸入 man buildah-<command> 就能找到對應的說明檔案。例如,想知道 buildah run 的詳細資訊,輸入 man buildah-run 即可。

以下範例展示 Buildah 的基本功能。我們將以 Fedora 為基礎映像檔,客製化成能執行 httpd 程式的容器:

$ container=$(buildah from fedora)
$ buildah run $container -- dnf install -y httpd; dnf clean all
$ buildah config --cmd "httpd -DFOREGROUND" $container
$ buildah config --port 80 $container
$ buildah commit $container myhttpd
$ buildah tag myhttpd registry.example.com/myhttpd:v0.0.1

上述指令將產生一個符合 OCI 標準、可移植的映像檔,功能與從 Dockerfile 建構的映像檔相同,而與只需幾行程式碼,就能包含在簡單的指令碼中。

現在,讓我們深入瞭解第一個指令:

$ container=$(buildah from fedora)

buildah from 指令會從允許的 registry 中提取 Fedora 映像檔,並從中啟動一個工作容器,然後傳回容器名稱。為了方便後續使用,我們將使用 shell 擴充套件語法將容器名稱儲存在 $container 變數中。如此一來,我們就可以將 $container 變數傳遞給後續指令,讓 build 指令在這個工作容器內執行。這是一個常見的模式,特別適合用於自動化指令碼中的 Buildah 指令。

重要提示

Buildah 和 Podman 在容器概念上存在細微差異。雖然兩者都採用相同的技術來建立容器,但 Buildah 容器是短暫的實體,專為修改和提交而建立,而 Podman 容器則用於執行長時間運作的工作負載。

Buildah 的彈性和可嵌入性非常出色。Buildah 指令可以包含在任何地方,使用者可以選擇全自動化的建構流程,或是更具互動性的流程。

例如,Buildah 可以輕鬆與 Ansible 整合,透過原生連線外掛程式與工作容器通訊,提供自動化的建構。

你也可以將 Buildah 納入 CI 流程(例如 Jenkins、Tekton 或 GitLab CI/CD),以完全掌控建構和整合任務。

Buildah 也包含在雲原生社群的較大型專案中,例如 Shipwright 專案 (https://github.com/shipwright-io/build)。

Shipwright 是一個適用於 Kubernetes 的可擴充套件建構框架,提供使用自訂資源定義和不同建構工具來自訂映像檔建構的彈性。Buildah 是你在設計建構流程時可以選擇的解決方案之一。

接下來將會看到更詳細與豐富的範例。現在我們已經瞭解 Buildah 的功能和使用案例,讓我們開始安裝和準備環境。

環境準備

Buildah 可在不同的發行版上使用,並可使用各自的套件管理器進行安裝。本文提供主要發行版上的一些安裝範例。為了清楚起見,本章的實驗室環境都是根據 Fedora 34:

  • Fedora: 若要在 Fedora 上安裝 Buildah,請執行以下 dnf 指令:

    $ sudo dnf -y install buildah
    
  • Debian: 若要在 Debian Bullseye 或更新版本上安裝 Buildah,請執行以下 apt-get 指令:

    $ sudo apt-get update
    $ sudo apt-get -y install buildah
    
  • CentOS: 若要在 CentOS 上安裝 Buildah,請執行以下 yum 指令:

    $ sudo yum install -y buildah
    
  • RHEL8: 若要在 RHEL8 上安裝 Buildah,請執行以下 yum module 指令:

    $ sudo yum module enable -y container-tools:1.0
    $ sudo yum module install -y buildah
    
  • RHEL7: 若要在 RHEL7 上安裝 Buildah,請啟用 rhel-7-server-extras-rpms 儲存函式庫,然後使用 yum 安裝:

    $ sudo subscription-manager repos --enable=rhel-7-server-extras-rpms
    $ sudo yum -y install buildah
    
  • Arch Linux: 若要在 Arch Linux 上安裝 Buildah,請執行以下 pacman 指令:

    $ sudo pacman –S buildah
    
  • Ubuntu: 若要在 Ubuntu 20.10 或更新版本上安裝 Buildah,請執行以下 apt-get 指令:

    $ sudo apt-get -y update
    $ sudo apt-get -y install buildah
    
  • Gentoo: 若要在 Gentoo 上安裝 Buildah,請執行以下 emerge 指令:

    $ sudo emerge app-emulation/libpod
    
  • 從原始碼建構: Buildah 也可以從原始碼建構。本章將重點放在簡單的佈署方法上,但如果你有興趣,可以參考以下來嘗試自己的建構:

    https://github.com/containers/buildah/blob/main/install.md#building-from-scratch

最後,Buildah 可以佈署為容器,並可以使用巢狀方法在其中執行建構。這個流程將在第 7 章「與現有應用程式建構流程整合」中詳細介紹。

在將 Buildah 安裝到主機後,我們可以繼續驗證安裝。

驗證安裝

安裝 Buildah 後,我們現在可以執行一些基本測試指令來驗證安裝。

若要檢視主機本地儲存中的所有可用映像檔,請使用以下指令:

$ buildah images
# buildah images

映像檔清單會與 podman images 指令輸出的清單相同,因為它們共用相同的本地儲存。

另請注意,這兩個指令分別以非特權使用者和 root 身分執行,分別指向使用者 rootless 本地儲存和系統範圍的本地儲存。

我們可以執行一個簡單的測試建構來驗證安裝。這是一個測試基本建構指令碼的好機會,其唯一目的是驗證 Buildah 是否能夠完全執行完整的建構。

為了本章的目的(以及樂趣),玄貓建立了一個簡單的測試指令碼,用於建立最小的 Python 3 映像檔:

#!/bin/bash
BASE_IMAGE=alpine
TARGET_IMAGE=python3-minimal

玄貓認為,Buildah 作為一個強大的容器建構工具,提供了極大的彈性與控制權。無論是自動化建構流程還是互動式開發,Buildah 都能滿足你的需求。透過與 Ansible 等工具的整合,更能將容器建構融入到現有的 CI/CD 流程中,實作更高效的軟體交付。