容器技術已經成為現代應用程式封裝與部署的標準格式,這種標準化是由開放容器倡議推動的開放規範。OCI 作為一個開放治理結構,專門為建立容器格式與執行時的開放行業標準而設立,確保了跨不同作業系統、廠商、平台與雲端環境的可攜性與互操作性。
在我參與的企業容器化專案中,選擇適當的容器映像檔建置工具往往是成功的關鍵。傳統的 Docker 建置流程雖然成熟且廣泛使用,但在某些場景下,無守護程式的替代方案如 Buildah 與 Jib 提供了更好的安全性與效率。同時,Cloud Native Buildpacks 的自動化建置能力讓開發者能夠專注於應用程式邏輯,而不是容器化的細節。
這篇文章將全面探討容器映像檔建置的各種技術方案,從基礎概念到進階實踐,協助團隊選擇最適合的建置策略。
容器映像檔的核心概念與標準
OCI 標準與容器映像檔結構
容器映像檔採用分層結構設計,這是容器技術高效運作的基礎。每個容器映像檔都建立在基礎作業系統之上,然後透過額外的層添加執行時環境、函式庫與應用程式。這種分層架構不僅使映像檔建置更有效率,也大幅降低了儲存與傳輸的成本。
理解容器映像檔的分層機制至關重要。每一層都代表著 Dockerfile 中的一個指令或操作,層與層之間透過聯合檔案系統堆疊在一起,對外呈現為單一的檔案系統。這種設計使得只有變更的部分會形成新的層,大部分層可以在多次建置中重複使用,顯著加快建置速度。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
rectangle "容器映像檔分層結構" {
rectangle "應用程式層\nCOPY app.py" as App
rectangle "相依套件層\nRUN pip install" as Deps
rectangle "執行時環境層\nPython 3.9" as Runtime
rectangle "基礎作業系統層\nUBI 8" as Base
App -down-> Deps
Deps -down-> Runtime
Runtime -down-> Base
}
note right of App
最上層包含應用程式碼
變更最頻繁的部分
end note
note right of Deps
中間層包含相依套件
變更較不頻繁
end note
note right of Base
底層是基礎映像檔
通常來自公開倉儲
可在多個映像檔間共享
end note
@enduml在實務應用中,容器映像檔的關鍵元件包括容器映像檔本身、容器引擎與執行時環境。容器映像檔是應用程式的可執行套件,包含執行所需的一切。容器引擎負責建立與管理容器映像檔,而執行時則提供容器執行的環境。這三者協同工作,構成了完整的容器化技術堆疊。
映像檔層的運作機制
容器映像檔的每一層都是唯讀的,當容器執行時,會在最上層添加一個可寫層。所有對檔案系統的修改都發生在這個可寫層中,不會影響底層的唯讀映像檔層。這種設計確保了映像檔的不可變性,同時允許容器有自己的可寫空間。
層的共享機制是容器技術高效的關鍵。當多個映像檔使用相同的基礎層時,這些層在儲存系統中只需要保存一份。例如,如果十個應用程式都使用相同的 Python 基礎映像檔,這個基礎映像檔的層只需要儲存一次,大幅節省了磁碟空間。
在網路傳輸方面,層的設計同樣帶來優勢。當從容器倉儲拉取映像檔時,只有本地不存在的層需要下載。如果基礎層已經存在於本地快取中,只需要下載新增或變更的層,這顯著加快了映像檔的拉取速度。
Docker 傳統建置流程深度解析
Dockerfile 指令詳解
Dockerfile 是定義容器映像檔建置過程的腳本檔案,它使用一系列指令來描述如何建立映像檔。理解每個指令的作用與最佳實踐是寫好 Dockerfile 的基礎。
FROM 指令指定基礎映像檔,這是 Dockerfile 的起點,必須是第一個非註解指令。選擇適當的基礎映像檔對最終映像檔的大小與安全性有重要影響。在企業環境中,通常會選擇經過安全驗證的基礎映像檔,如 Red Hat 的 Universal Base Image。
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
start
:FROM 指定基礎映像檔;
:ENV 設定環境變數;
:WORKDIR 設定工作目錄;
:COPY 複製相依清單;
note right
先複製相依清單
利用 Docker 快取機制
避免每次都重新安裝
end note
:RUN 安裝相依套件;
note right
在單一 RUN 指令中
完成所有安裝操作
減少映像檔層數
end note
:COPY 複製應用程式碼;
:EXPOSE 聲明連接埠;
:ENTRYPOINT 設定執行程式;
:CMD 設定預設參數;
stop
@endumlENV 指令設定環境變數,這些變數在建置過程與容器執行時都可以使用。EXPOSE 指令聲明容器在執行時監聽的連接埠,雖然這是一個文件性質的指令,但對於理解容器的網路配置很有幫助。
WORKDIR 指令設定工作目錄,後續的 RUN、CMD、ENTRYPOINT、COPY 與 ADD 指令都會在這個目錄下執行。使用 WORKDIR 而不是 RUN cd 是最佳實踐,因為它更清晰且不會在 shell 層面產生副作用。
COPY 與 ADD 指令都用於將檔案從主機複製到映像檔中,但 COPY 是首選,因為它更簡單明確。ADD 具有自動解壓縮與支援遠端 URL 的額外功能,但這些功能在大多數情況下並不需要,反而可能帶來意外的行為。
RUN 指令在映像檔中執行命令並提交結果,這是建置過程中最常用的指令。每個 RUN 指令都會建立一個新的層,因此將多個命令合併到單一 RUN 指令中可以減少層數,最佳化映像檔大小。
ENTRYPOINT 與 CMD 指令共同定義容器啟動時執行的命令。ENTRYPOINT 設定容器的主要執行程式,而 CMD 提供預設參數。這種組合允許容器像可執行檔案一樣運作,同時提供參數覆寫的靈活性。
Docker 建置過程實戰
執行 Docker 建置命令時,Docker 引擎會讀取 Dockerfile 並逐行執行指令。每個指令執行後都會形成一個新的映像檔層,最終生成完整的容器映像檔。這個過程包含了從公開容器倉儲取得基礎層,然後在其上添加自訂層。
建置過程中,Docker 會充分利用快取機制。如果某一層的指令與之前的建置相同,且其之前的所有層也相同,Docker 會直接使用快取的層,而不重新執行指令。這個快取機制大幅加快了重複建置的速度,特別是在開發過程中頻繁建置時。
當基礎映像檔在本地不存在時,Docker 會自動從配置的倉儲下載。下載過程是分層進行的,每個層都會被獨立下載與驗證。這種分層下載不僅提高了傳輸效率,也增強了安全性,因為每一層都可以獨立驗證其完整性。
建置完成後,使用 docker images 命令可以查看本地所有的映像檔。每個映像檔都有唯一的 ID,以及可選的標籤用於識別。標籤通常包含版本資訊,方便管理不同版本的映像檔。
推送映像檔到容器倉儲
建置完成的映像檔需要推送到容器倉儲才能在其他環境中使用。推送過程同樣是分層進行的,只有本地新建立或修改的層會被上傳,已經存在於倉儲中的層會被跳過。
在推送之前,需要先使用 docker login 命令登入目標倉儲。這個命令會提示輸入使用者名稱與密碼,認證資訊會被安全地儲存在本地配置檔案中。在自動化環境中,通常使用服務帳戶或存取權杖來進行認證,避免直接使用使用者密碼。
推送過程會顯示每一層的上傳進度,可以清楚看到哪些層被跳過,哪些層正在上傳。對於大型映像檔,這個過程可能需要一些時間,但由於層的共享機制,後續的推送通常會快得多。
Jib:Java 應用的無 Docker 容器化
Jib 的技術創新與優勢
Jib 是 Google 開發的開放原始碼工具,專為 Java 應用程式設計,實現了無需 Docker 或 Dockerfile 即可建置容器映像檔的目標。這種創新方法徹底改變了 Java 應用的容器化流程,讓開發者可以專注於應用程式開發,而不需要學習容器技術的細節。
Jib 的核心優勢在於其對 Java 應用結構的深度理解。它會自動將應用程式分為多個層,包括類別檔案層、資源檔案層、相依套件層等。這種智慧分層確保了只有變更的部分會形成新層,大幅提升了建置與部署的效率。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
package "Jib 建置流程" {
rectangle "Maven/Gradle\n專案配置" as Config
rectangle "Jib 分析\n應用結構" as Analyze
rectangle "智慧分層\n類別/資源/相依" as Layer
rectangle "直接推送\n到倉儲" as Push
Config -down-> Analyze
Analyze -down-> Layer
Layer -down-> Push
}
package "傳統 Docker 流程" {
rectangle "撰寫\nDockerfile" as DF
rectangle "Docker\n建置" as DB
rectangle "本地\n映像檔" as DI
rectangle "推送\n到倉儲" as DP
DF -down-> DB
DB -down-> DI
DI -down-> DP
}
note right of Layer
自動最佳化分層
相依套件單獨一層
類別檔案單獨一層
變更時只重建必要層
end note
note right of DB
需要 Docker Daemon
需要 root 權限
建置在本地進行
end note
@enduml在實務應用中,Jib 的無 Docker 特性帶來了顯著優勢。CI/CD 環境不需要安裝與維護 Docker daemon,減少了安全風險與資源消耗。建置過程更快,因為 Jib 直接與容器倉儲通訊,跳過了本地映像檔儲存的步驟。
Jib 提供了優秀的可重現性。相同的原始碼與相依套件總是產生相同的映像檔,這對於確保建置的一致性至關重要。這種確定性在除錯與問題追蹤時特別有價值,可以確信部署到不同環境的映像檔是完全相同的。
Maven 整合 Jib 的實戰應用
將 Jib 整合到 Maven 專案非常簡單,只需要在專案的 POM 檔案中添加 Jib 外掛配置。這個配置定義了目標映像檔的名稱、基礎映像檔的選擇、容器的配置等資訊。
最簡單的使用方式是透過命令列執行 Jib 建置。這個命令會編譯 Java 程式碼,分析應用結構,建置容器映像檔,並直接推送到指定的倉儲。整個過程無需任何 Docker 相關的工具或配置。
在企業環境中,通常會在 POM 檔案中完整配置 Jib 外掛,包括映像檔名稱、標籤策略、基礎映像檔選擇、容器配置等。這種配置方式使得建置過程標準化,團隊成員只需要執行標準的 Maven 命令即可完成容器建置。
Jib 支援高度客製化的配置。可以指定 JVM 參數、環境變數、暴露的連接埠、掛載的資料卷等。這些配置都透過 POM 檔案管理,與應用程式原始碼一起進行版本控制,確保配置的可追蹤性。
Jib 建置過程分析
當執行 Jib 建置時,可以在輸出中看到詳細的建置過程。首先是專案掃描與分析,Jib 會識別所有的類別檔案、資源檔案與相依套件。然後是基礎映像檔的取得,Jib 會從配置的倉儲下載基礎映像檔。
建置過程的核心是分層處理。Jib 會將應用程式分為多個邏輯層,每一層都獨立處理。相依套件層通常最大但變更最少,因此在大多數建置中可以重用。類別檔案層包含編譯後的 Java 類別,這一層在程式碼變更時會更新。
最後是映像檔的推送階段。Jib 直接將建置好的映像檔推送到目標倉儲,無需在本地儲存完整的映像檔。這種直接推送的方式節省了磁碟空間,也加快了整個建置流程。
特別值得注意的是,Jib 建置完成後,使用 docker images 命令在本地看不到建置的映像檔。這是因為 Jib 完全繞過了 Docker,映像檔直接被推送到遠端倉儲。這種設計在 CI/CD 環境中特別有價值,因為不需要清理本地映像檔快取。
Buildah:無守護程式的安全建置方案
Buildah 的設計理念與架構
Buildah 是 Red Hat 主導開發的容器映像檔建置工具,其核心設計理念是提供無守護程式的安全建置環境。與 Docker 需要持續執行的 daemon 不同,Buildah 採用輕量級的命令列工具設計,每次建置都是獨立的程序,建置完成後程序結束,不佔用系統資源。
這種無守護程式設計帶來了多個安全優勢。首先是減少了攻擊面,沒有長期執行的特權程序可以被攻擊。其次是支援無根模式,一般使用者可以在自己的使用者命名空間中建置容器,無需 root 權限。這在多租戶環境與企業安全政策嚴格的環境中特別重要。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
package "Buildah 無守護程式架構" {
rectangle "使用者命令\nbuildah from" as Cmd1
rectangle "建立工作容器\n獨立程序" as Proc1
rectangle "執行建置操作\nbuildah run/copy" as Cmd2
rectangle "提交映像檔\nbuildah commit" as Cmd3
Cmd1 -down-> Proc1
Proc1 -down-> Cmd2
Cmd2 -down-> Cmd3
}
package "Docker 守護程式架構" {
rectangle "Docker Daemon\n持續執行" as Daemon
rectangle "使用者命令\ndocker build" as DCmd
rectangle "透過 API\n與 Daemon 通訊" as API
Daemon -down-> API
DCmd -up-> API
}
note right of Proc1
每次建置獨立程序
無需 root 權限
建置完成程序結束
end note
note right of Daemon
需要持續執行
通常需要 root 權限
佔用系統資源
end note
@endumlBuildah 與 Podman、Skopeo 等工具組成了完整的容器工具鏈,這種模組化設計與 Docker 的單體架構形成對比。每個工具專注於特定功能,Buildah 專注於建置,Podman 專注於執行,Skopeo 專注於映像檔傳輸與管理。這種職責分離提供了更好的靈活性與可組合性。
Buildah 的命令式建置方式
Buildah 支援兩種建置方式。第一種是使用 Dockerfile,與 Docker 完全相容。第二種是命令式建置,透過一系列 Buildah 命令逐步構建映像檔。這種命令式方式提供了更大的靈活性,可以根據條件動態調整建置過程。
使用 buildah from 命令從基礎映像檔建立工作容器。這個工作容器是可修改的,後續的所有操作都在這個工作容器中進行。與 Docker 的層層疊加不同,Buildah 允許在同一個工作容器中執行多個操作,然後一次性提交為一個映像檔層。
buildah run 命令在工作容器中執行命令,類似於 Dockerfile 中的 RUN 指令。但不同的是,Buildah 允許多次執行 buildah run 而不創建多個層,所有操作都在同一個工作容器中累積,直到使用 buildah commit 提交時才建立新的映像檔層。
buildah copy 命令將檔案從主機複製到工作容器中,buildah config 命令設定容器的配置,如環境變數、工作目錄、入口點等。這些命令組合起來,提供了完整的映像檔建置能力,同時保持了操作的透明性與可控性。
Buildah 實戰範例
讓我們透過實際案例來理解 Buildah 的使用方式。假設要建置一個簡單的 HTTP 伺服器容器,首先從 CentOS 基礎映像檔開始,建立一個工作容器。Buildah 會自動下載基礎映像檔並建立工作容器實例。
接下來在工作容器中安裝 HTTP 伺服器套件。使用 buildah run 命令執行套件管理器,安裝所需的軟體。這個過程與在一般 Linux 系統上安裝軟體完全相同,非常直覺。
安裝完成後,可以使用 buildah copy 命令將自訂的網頁內容複製到容器中。然後使用 buildah config 命令設定容器的啟動命令與暴露的連接埠。所有這些配置都儲存在工作容器的中繼資料中。
最後使用 buildah commit 命令將工作容器提交為映像檔。這個命令會將工作容器的當前狀態儲存為新的映像檔,並可以指定映像檔的名稱與標籤。提交後,可以使用 buildah push 命令將映像檔推送到容器倉儲。
Cloud Native Buildpacks 自動化建置
Buildpacks 的革命性理念
Cloud Native Buildpacks 代表了容器建置自動化的重大進展,它改變了我們思考容器建置的方式。傳統上,每個應用程式都需要撰寫與維護 Dockerfile,指定如何建置容器映像檔。Buildpacks 則透過自動檢測應用程式類型,選擇適當的建置策略,無需手動撰寫建置腳本。
這種自動化方法在大規模微服務環境中特別有價值。當組織有數百個微服務時,維護數百個 Dockerfile 是個巨大的負擔。每次基礎映像檔更新或安全性修補發布時,都需要更新所有相關的 Dockerfile。Buildpacks 將這個維護負擔集中到 builder 層面,大幅簡化了管理工作。
Buildpacks 的工作流程分為檢測與建置兩個階段。檢測階段分析應用程式原始碼,識別程式語言、框架版本、相依套件管理工具等資訊。建置階段根據檢測結果,選擇適當的 buildpack 組合,自動安裝相依套件、編譯程式碼、設定執行環境。
在實務應用中,Buildpacks 特別適合標準化的應用程式架構。對於使用常見框架的應用程式,如 Spring Boot、Express.js、Django 等,Buildpacks 可以自動處理幾乎所有的容器化細節,讓開發者完全專注於應用程式邏輯。
使用 Pack CLI 建置應用程式
Pack 是 Cloud Native Buildpacks 的命令列工具,提供了簡單直覺的建置介面。安裝 Pack 後,使用 pack builder suggest 命令可以查看可用的 builder,這些 builder 由不同組織提供,支援各種程式語言與框架。
選擇合適的 builder 後,使用 pack build 命令即可開始建置。這個命令會自動執行檢測與建置流程,無需任何額外配置。對於標準的應用程式結構,這個過程完全自動化,從原始碼到可執行的容器映像檔一步完成。
Buildpacks 建置過程會顯示詳細的步驟資訊,包括檢測到的應用程式類型、選擇的 buildpacks、安裝的相依套件等。這種透明性讓開發者可以理解建置過程,在需要時進行客製化調整。
建置完成的映像檔可以直接使用容器執行時工具執行,與使用 Dockerfile 建置的映像檔沒有任何區別。Buildpacks 生成的映像檔完全符合 OCI 標準,具有良好的可攜性與互操作性。
容器映像檔最佳化實踐
多階段建置策略
多階段建置是最佳化容器映像檔大小的重要技術。這種方法在 Dockerfile 中定義多個建置階段,每個階段使用不同的基礎映像檔。編譯階段使用包含完整工具鏈的映像檔,而執行階段則使用最小化的執行時映像檔。
在多階段建置中,可以使用 COPY –from 指令將前一階段建置的產物複製到當前階段。這樣,最終的映像檔只包含執行時需要的檔案,不包含編譯工具、原始碼、中間產物等不必要的內容。
對於編譯型語言如 Go、Rust、C++ 等,多階段建置特別有效。編譯階段可以使用完整的開發環境,而執行階段可以使用 scratch 或 distroless 這樣的極簡映像檔,最終映像檔大小可以縮減到數十 MB。
即使是解釋型語言如 Python、Node.js,多階段建置也有價值。可以在第一階段安裝與編譯原生擴充套件,在第二階段只複製必要的執行時檔案,移除開發工具與建置快取。
層最佳化技巧
合理組織 Dockerfile 指令順序可以充分利用 Docker 的快取機制。將變更頻率低的指令放在前面,變更頻率高的指令放在後面。例如,安裝系統套件的指令應該在複製應用程式碼之前,因為系統套件變更較少。
在一個 RUN 指令中組合多個命令可以減少映像檔層數。使用 && 連接多個命令,並在適當的地方使用反斜線換行,保持 Dockerfile 的可讀性。同時,在同一個 RUN 指令中清理臨時檔案與快取,避免這些檔案被固化到映像檔層中。
選擇適當的基礎映像檔對最終映像檔大小有決定性影響。Alpine Linux 基礎映像檔非常小,但使用 musl libc 可能導致相容性問題。Debian slim 或 Ubuntu minimal 變體提供了較好的相容性,同時保持相對較小的體積。
使用 .dockerignore 檔案排除不需要複製到映像檔中的檔案。這不僅減少了映像檔大小,也加快了建置過程,因為減少了需要傳送到 Docker daemon 的檔案數量。
安全性最佳實踐
容器安全性從基礎映像檔選擇開始。使用官方維護的基礎映像檔,定期更新以獲取安全性修補。避免使用 latest 標籤,而是使用特定版本標籤,確保建置的可重現性與可預測性。
在容器中以非 root 使用者執行應用程式是重要的安全實踐。使用 USER 指令切換到非特權使用者,限制容器內程序的權限。即使容器被攻破,攻擊者也只能獲得有限的權限。
定期掃描容器映像檔以檢測已知的安全漏洞。使用工具如 Trivy、Clair、Anchore 等掃描映像檔,識別存在漏洞的套件。在 CI/CD 流程中整合映像檔掃描,確保只有通過安全檢查的映像檔才能部署到生產環境。
避免在映像檔中包含敏感資訊如密碼、API 金鑰、憑證等。這些資訊應該透過環境變數、掛載的密鑰檔案或專用的密鑰管理服務注入到容器中。使用多階段建置時,確保敏感的建置參數不會洩漏到最終映像檔中。
容器建置工具的選擇策略
在實際專案中,選擇合適的容器建置工具需要考慮多個因素。Docker 作為最成熟的工具,擁有最豐富的文件與社群支援,適合大多數場景。對於需要高度客製化或特殊建置邏輯的專案,Docker 提供了最大的靈活性。
對於 Java 應用程式,Jib 是理想的選擇。它與 Maven 與 Gradle 無縫整合,無需學習容器技術即可完成容器化。Jib 的智慧分層與快速建置特性,在頻繁部署的開發環境中特別有價值。
在安全性要求高的環境中,Buildah 提供了無守護程式與無根建置的優勢。它特別適合 CI/CD 環境,可以在受限的安全環境中安全地建置容器映像檔。Buildah 的命令式建置方式也提供了腳本化自動化的靈活性。
對於大規模標準化應用程式,Cloud Native Buildpacks 可以大幅降低維護成本。透過集中管理 builder,組織可以確保所有應用程式遵循相同的建置標準與安全實踐,同時簡化開發者的容器化工作。
在實務中,往往會組合使用多種工具。例如,使用 Buildpacks 處理標準的微服務,使用 Docker 處理需要特殊配置的服務,使用 Jib 處理 Java 應用程式。這種混合策略結合了各種工具的優勢,為不同類型的應用程式提供最適合的建置方案。
透過本文深入探討的容器映像檔建置技術,從 OCI 標準到 Docker 傳統流程,從 Jib 的 Java 專用方案到 Buildah 的安全建置,再到 Buildpacks 的自動化策略,我們可以根據專案需求選擇最適合的建置工具。結合映像檔最佳化與安全性實踐,可以建立高效、安全、可維護的容器化工作流程,這正是現代雲原生應用開發的基礎。