天天看點

BuildPack 打包

無需 dockerfile,使用 buildpacks 打包鏡像

書接上文,聰明如你已經發現項目中沒有定義

dockerfile

,但我們依然能打鏡像,是如何做到的呢?正如上面提到的 gradle 的 spring 插件建立了

bootBuildImage

,通過 buildpacks 建構 OCI 鏡像。

概念

buildpacks 讓你可以把源檔案轉換為安全、高效、預生産的容器鏡像。

buildpacks 是什麼?

BuildPack 打包

buildpacks 為應用程式提供架構和運作時支援。buildpacks 檢查你的應用以決定需要哪些依賴,并恰當的配置這些應用以便能在各種雲環境中運作。

工作方式

BuildPack 打包

每個 buildpack 由兩個階段組成。

檢測(detect)階段

檢測階段會檢查你的源碼是否适合使用 buildpack。如果适合,就直接進入建構(build)階段。如果不适合,則直接跳過 buildpack 中的建構階段。

例如:

  • buildpack 如何在 Python 項目中找到

    requirements.txt

    setup.py

    檔案,則通過檢測
  • buildpack 如果在 Node 項目中找到

    package-lock.json

建構(build)階段

建構階段會檢測你的代碼以完成以下操作:

  • 設定建構時和運作時環境
  • 下載下傳依賴然後編譯你的源碼(如果有必要的話)
  • 設定恰當的程式入口和啟動腳本
  • Python 項目中的 buildpack 如果檢測到

    requirements.txt

    檔案,會執行

    pip install -r requirements.txt

    指令,以安裝

    requirements.txt

    檔案中的依賴。
  • Node 項目中的 buildpack 如果檢測到

    package-lock.json

    npm install

    指令。

建構者是什麼樣子?

BuildPack 打包

建構者是多個 buildpack 檔案、基礎建構(build)鏡像、運作(run)鏡像的有序組合。這些建構者參與到你的源碼中然後建構,輸出 app 鏡像。建構鏡像為建構者提供基礎環境(例如,一個帶有建構工具的 Ubuntu Bionic 作業系統鏡像),運作鏡像為 app 鏡像在運作期間提供基礎環境。建構鏡像和運作鏡像的組合叫做棧。

究其根本,建構者使用生命周期(lifecycle)為所有 buildpack 運作檢測階段,以便為所有通過檢測階段的 buildpack 運作建構階段。

這讓我們可以通過一個建構者就可以自動檢測和建構各種各樣的應用程式。

例如,假如 demo-builder 包含 Python 和 Node buildpack。那麼

  • 如果你的項目隻包含

    requirements.txt

    檔案,demo-builder 将隻運作 Python 建構步驟。
  • package-lock.json

    檔案,demo-builder 将隻允許 Node 建構步驟。
  • 如果你的項目同時包含

    package-lock.json

    requirements.txt

    檔案,demo-builder 将同時運作 Python 和 Node 建構步驟。
  • 如果你的項目既不包含

    requirements.txt

    檔案,也不包含

    package-lock.json

    檔案,那麼 demo-builder 将檢測失敗,并退出建構。

元件

建構者

建構者是什麼?

建構者是一個包含執行建構時需要的各種元件的鏡像。建構者鏡像由建構鏡像、生命周期、多個 buildpack、各方面的建構配置檔案(包含 buildpack 檢測順序和運作鏡像的位置)組成。

解剖建構者

建構者由以下元件組成:

  • 多個 buildpack
  • 生命周期
  • 棧建構鏡像

buildpack

buildpack 是什麼?

buildpack 是一個工作單元,該工作單元仔細檢查你的源代碼然後制定建構、運作應用程式的計劃。

通常多個 buildpack 檔案是一個至少包含 3 個檔案的集合:

  • buildpack.toml

    ——提供 buildpack 的中繼資料
  • bin/detect

    ——決定是否可以應用 buildpack
  • bin/build

    ——執行 buildpack 邏輯
元 buildpack

還有一種不同類型的 buildpack,通常稱為元 buildpack。它隻有一個 buildpack.toml 檔案,該檔案包含有序的配置,而配置的内容是對其它 buildpack 的引用。當組合比較複雜的檢測政策時,元 buildpack 特别有用。

解剖 buildpack

有兩個必不可少的階段可以讓多個 buildpack 檔案建立可運作的鏡像。

檢測

平台根據你的源碼順序測試 buildpack 組。第一個認為自己适合你的源碼的 buildpack 組将成為你的 app 的 buildpack 檔案集合。每個 buildpack 的檢測标準不同——例如,NPM buildpack 查找

package.json

檔案,Go buildpack 查找 Go 源檔案。

建構

在建構過程中,buildpack 檔案對最終應用程式鏡像有所貢獻。貢獻内容包括設定鏡像的環境變量、建立包含二進制(例如:node、python、ruby)的層、添加應用程式依賴(例如:運作

npm install

pip install -r requirements.txt

bundle install

)。

分布

buildpack 檔案可以在鏡像系統資料庫或者 Docker 背景程序的基礎上打包為 OCI 鏡像。元 buildpack 檔案也可以做到。

buildpack 組

buildpack 組是什麼?

buildpack 組是一個特定的 buildpack 檔案清單,該清單以适合建構應用程式的順序組合在一起。由于 buildpack 檔案是子產品化并且可重用的,是以 builpack 組可以讓你把多個子產品化 buildpack 檔案連在一起。

例如,你有一個 buildpack 檔案用于安裝 Java,一個 buildpack 檔案使用 Maven 建構應用程式。這兩個 builpack 檔案可以合并到一個組中實作更進階的功能,特别是第一個檔案安裝 Java,第二個檔案使用 Java 運作 Maven,這不就是 Java 的建構建構工具嗎。

因為你在建構者或元 buildpack 中可以有多個 buildpack 組,并且你可以重用 buildpack 檔案,是以你可以再用一個 buildpack 組,該組重用提供 Java 的 buildpack,但是使用 Gradle 提供的 buildpack 建構你的應用程式。如此一來,你在建立進階功能的時候就不需要複制了。

解剖 buildpack 組

buildpack 組是一個 buildpack 條目清單,按順序定義了 buildpack 的順序執行。

buildpack 條目通過 id 和版本進行定義。該條目可以被标記為是可選的。雖然在一個 buildpack 組中可能有一個或多個 buildpack,但在一個建構者或元 buildpack 中可以有多個 buildpack 組。

使用多個 buildpack 組進行檢測

建構者或元 buildpack 可能包含多個 buildpack 組。當生命周期執行檢測過程時,它會按指定的順序執行每一組 buildpack。對于每個 buildpack 組來說,生命周期會執行組中的每個 buildpack 的檢測階段(可以并行執行)然後聚合結果。生命周期會選擇第一個所有必須的 buildpack 檢測通過的組。

例如,如果建構者有 A、B和C buildpack 組。生命周期将通過 A 運作檢測。如果 A 中所有必需的 buildpack 都通過檢測,那麼生命周期就會選擇 A。在那種情況下,B 和 C 不會進行處理。如果 A 在必須的 buildpack 中有任何失敗,生命周期将轉向處理 B。如果 B 在必須的 buildpack 中有任何失敗,那麼生命周期将轉向處理 C。如果 C 有失敗,那麼整體檢測處理将失敗。

如果 buildpack 組隻有元 buildpack,那麼元 buildpack 可能反過來包含更多的 buildpack 組,這些組通過Order Resolution規則展開,是以元 buildpack 中的每個 buildpack 組将與其它 buildpack 組中 buildpack 一起工作。

  • 建構者有 buildpack 組 A,包含 buildpack X,Y 和 Z
  • Y 是元 buildpack 包含 buildpack 組 B 和 C
  • buildpack 組 B 包含 buildpack T 和 U
  • buildpack 組 C 包含 buildpack V 和 W

生命周期将此展開為以下 buildpack 組:

  • X、T、U、Z
  • X、V、W、Z

未包含 Y 的原因是元 buildpack 隻提供組,元 buildpack 不參與建構過程或者建構、檢測程式。

生命周期協調 buildpack 執行,然後将生成的檔案的打包進最終 app 鏡像中。

尋找一個有序的 buildpack 組,以便在建構階段使用。

檢測是生命周期的第一個階段。由檢測儀完成。在本階段中,檢測儀尋找有序的 buildpack 組,以便在建構階段使用。在建構環境中調用檢測儀不需要參數,并且不能使用 root 權限運作。輸入檔案是order.toml,兩個輸出檔案分别是group.toml和plan.toml。

除非傳入某些标志,否則檢測儀使用以下預設配置:

  • 定義順序的路徑:

    /cnb/order.toml

  • 定義輸出組的路徑:

    <layers>/group.toml

  • 輸出建構計劃的路徑:

    <layers>/plan.toml

完整的标志清單和配置看這裡。

order.toml

order.toml 是一個包含組清單的檔案。每個組是一個 buildpack 清單。檢測儀讀取 order.toml 然後尋找第一個通過檢測的組。如果所有的組都失敗了,那麼檢測就失敗了。

組中的 buildpack 要麼被标記會可選的,要麼被标記為必須的。為了通過檢測處理,必須滿足兩個條件:

  • 檢測腳本必須成功(退出代碼是 0)通過所有必須的 buildpack。
  • 檢測儀可以建立建構計劃(寫入 plan.toml),包含所有組中必須的 buildpack。

第一個通過以上兩個步驟的組将寫入 group.toml 中,并将其建構計劃寫入 plan.toml 中。

注意:如果檢測腳本執行可選 buildpack 失敗了,buildpack 組仍然可以通過檢測處理并且可以被選中。該組要被選中,至少要有一個 buildpack (無論是可選的還是必須的)成功的通過檢測。

group.toml

選中的組如果可以建立 plan.toml,則會被寫入 group.toml 中。buildpack 将會與 order.toml 中相同的順序,并且過濾掉所有可選的失敗的 buildpack,然後寫入 group.toml 中。

plan.toml

每個 buildpack 可以定義兩個清單,分别是提供依賴清單和要求依賴清單(或者通過 or 隔離的鍵值對清單)。這些清單(如果不為空)叫做建構計劃。檢測儀從選中的組中讀取 buildpack(在過濾掉執行探測腳本失敗的 buildpack 之後)。檢測儀檢查所有的可選項然後嘗試建立一個包含條目清單的檔案,每個條目都有提供和要求清單以滿足所有 buildpack 的需求。每個可選項都稱為一次試驗,輸出檔案叫做 plan.toml。

提供和要求有兩個限制條件:

  • buildpack 提供的依賴,無論是 buildpack 本身還是 buildpack 所在的組都是必須的。
  • buildpack 要求的依賴,必須通過 buildpack 本身提供或者 buildpack 所在的組的其它 buildpack 提供。

對于必要的 buildpack 來說,上面兩個條件如果有一個失敗,實驗也會失敗并且檢測儀會尋找下一個實驗。對于可選的 buildpack 來說,上面兩個條件如果有一個失敗,那邊在最終計劃中應該排除該 buildpack。如果所有的試驗都失敗了,代表着 buildpack 所在的組失敗了(檢測儀将轉向下一個組)。

退出代碼

退出碼 結果
成功
11 平台 API 不相容錯誤
12 buildpack API 不相容錯誤
1-10,13-19 普通生命周期錯誤
20 所有 buildpack 組都未檢測到 w/o 錯誤
21 所有 buildpack 組都未檢測到 buildpack 發送錯誤
22-29 檢測特定生命周期錯誤

分析

恢複 buildpack 用于優化建構和導出階段的檔案。

20-39 分析特定生命周期錯誤

恢複

從緩存中恢複層。

40-49 恢複特定生命周期錯誤

将應用程式源代碼轉換為可運作的構件,這些構件可以打包到容器中。

51 Buildpack 建構錯誤
50,52-59 建構特定生命周期錯誤

導出

建立最終 OCI 鏡像。

60-69 導出特定生命周期錯誤

鏡像

導出器通過參數的方式接收标志,該标志引用 OCI 鏡像系統資料庫或 Docker 守護程序,并将其寫入 app 鏡像中。

report.toml

導出器還将寫一個 report.toml 檔案,該檔案包含導出鏡像的相關資訊,比如該鏡像的概要以及清單的大小(如果導出的是 OCI 系統資料庫)或識别器,以及 buildpack 提供的建構 BOM。輸出的報告的位置可以通過 -report 标志進行指定;預設位址是

<layers>/report.toml

——注意它不會在導出的鏡像的檔案系統中出現。

建立

在單個指令中運作檢測、分析、恢複、建構以及導出。

20-29
30-39
50-59

啟動

最終 OCI 鏡像的入口。負責啟動應用程式程序。

80-89 啟動特定生命周期錯誤

變基(rebase)

把應用程式的層變基到新的運作鏡像。

70-79 變基特定生命周期錯誤

平台

平台是什麼?

平台使用生命周期、buildpack(打包在建構者中)以及應用程式源碼生成一個 OCI 鏡像。

示例

平台可能包括的示例:

  • 本地指令行工具使用 buildpack 建立 OCI 鏡像。比如 Pack CLI 工具
  • 持續內建服務的插件,該插件使用 buildpack 建立 OCI 鏡像。比如 tekton 提供的 buildpack 插件
  • 雲應用程式平台在部署之前使用 buildpack 建構源碼。比如 kpack 平台

API

平台技術規範詳細的描述了平台的能力,以及平台如何和生命周期、建構者互動。目前平台的 API 版本是 0.4。

棧是什麼?

棧是把兩個打算一起工作的鏡像組合在一起:

  1. 棧中的建構鏡像提供了基礎鏡像,該鏡像構造了建構環境。建構環境是容器化環境,生命周期(進而打包)在該環境中執行。
  2. 棧中的運作鏡像提供了基礎鏡像,應用程式通過該鏡像進行建構。
如果你使用 pack 指令行界面,運作

pack stack suggest

會推薦一個棧(同時還有每個棧相關的建構、運作鏡像)清單,這些棧可以用于執行

pack builder create

使用棧

建構者使用棧,并通過建構者的配置檔案進行配置:

[[buildpacks]]
  # ...

[[order]]
  # ...

[stack]
  id = "com.example.stack"
  build-image = "example/build"
  run-image = "example/run"
  run-image-mirrors = ["gcr.io/example/run", "registry.example.com/example/run"]
           

通過必要的

[stack]

小節,建構者作者可以配置棧的 ID、建構鏡像、運作鏡像(包括任何鏡像)。

run-image-mirrors

run-image-mirrors

為運作鏡像提供了可選位置,以便在

建構

(或

變基

)期間使用。當通過建構者容器運作

建構

時,

打包

會選擇使用 app 鏡像指定的位置(如果在鏡像名稱中沒有指定系統資料庫的主機位址,則将使用 DockerHub)。這在釋出生成的 app 鏡像時很有用(通過

--publish

标志或通過

docker push

),app 的基礎鏡像和 app 的鏡像在同一個系統資料庫中,這将減少推送 app 鏡像所需的資料傳輸量。

在以下示例中,假設建構者配置檔案和上面的一樣,被選中運作的鏡像是

registry.example.com/example/run

$ pack build registry.example.com/example/app
           

當命名 app 不指定系統資料庫時,比如

example/app

,将導緻

example/run

作為 app 的運作鏡像。

$ pack build example/app
           

操作

建構解釋

BuildPack 打包

建構是指通過你的源碼執行一個或多個 buildpack,然後生成一個可運作的 OCI 鏡像。每個 buildpack 檢查都檢查源碼然後提供相關的依賴。然後根據 app 的源碼和那些依賴生成鏡像。

buildpack 相容一個或多個棧。一個棧指定了一個建構鏡像和一個運作鏡像。在建構過程中,建構鏡像是 buildpack 執行的環境,運作鏡像是最終 app 鏡像的基礎鏡像。

多個 buildpack 可以通過指定的棧的建構鏡像綁定在一起,即建構者(注意,是建構者)鏡像。建構者為指定棧的 buildpack 提供最便捷的分布。

變基

變基解釋

當 app 的棧的運作鏡像變更時,變基可以讓 app 開發者或營運商快速地更新 app 鏡像。通過對分層的鏡像變基,我們避免了重新建構 app。

BuildPack 打包

鏡像變基的核心部分是一個簡單的工程。通過檢查 app 鏡像,

變基

可以判定 app 的基礎鏡像是否有新版本(無論是本地還是系統資料庫中)。如果有,

變基

通過更新 app 鏡像的中繼資料層以引用新的基礎鏡像版本。

示例:app 鏡像變基

考慮有一個 app 鏡像

my-app:my-tag

使用預設的構造者進行初次建構。該建構者的棧使用的運作鏡像為

pack/run

。運作以下指令更新

my-app:my-tag

的基礎鏡像為

pack/run

的最新版本。

$ pack rebase my-app:my-tag
           
提示:

pack rebase

有一個

--publish

标志可以用于釋出更新後的 app 鏡像到系統資料庫中。當使用系統資料庫時,與 Docker 守護程序相比,

--publish

是最優的。