無需 dockerfile,使用 buildpacks 打包映象

書接上文,聰明如你已經發現專案中沒有定義 dockerfile,但我們依然能打映象,是如何做到的呢?正如上面提到的 gradle 的 spring 外掛建立了 bootBuildImage,通過 buildpacks 構建 OCI 映象。

概念

buildpacks 讓你可以把原始檔轉換為安全、高效、預生產的容器映象。

buildpacks 是什麼?

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

工作方式

每個 buildpack 由兩個階段組成。

檢測(detect)階段

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

例如:

  • buildpack 如何在 Python 專案中找到 requirements.txtsetup.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 檔案、基礎構建(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.jsonrequirements.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 installpip install -r requirements.txtbundle 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.tomlplan.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 所在的組失敗了(檢測儀將轉向下一個組)。

退出程式碼

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

分析

恢復 buildpack 用於優化構建和匯出階段的檔案。

退出程式碼

退出碼 結果
0 成功
11 平臺 API 不相容錯誤
12 buildpack API 不相容錯誤
1-10,13-19 普通生命週期錯誤
20-39 分析特定生命週期錯誤

恢復

從快取中恢復層。

退出程式碼

退出碼 結果
0 成功
11 平臺 API 不相容錯誤
12 buildpack API 不相容錯誤
1-10,13-19 普通生命週期錯誤
40-49 恢復特定生命週期錯誤

構建

將應用程式原始碼轉換為可執行的構件,這些構件可以打包到容器中。

退出程式碼

退出碼 結果
0 成功
11 平臺 API 不相容錯誤
12 buildpack API 不相容錯誤
1-10,13-19 普通生命週期錯誤
51 Buildpack 構建錯誤
50,52-59 構建特定生命週期錯誤

匯出

建立最終 OCI 映象。

退出程式碼

退出碼 結果
0 成功
11 平臺 API 不相容錯誤
12 buildpack API 不相容錯誤
1-10,13-19 普通生命週期錯誤
60-69 匯出特定生命週期錯誤

映象

匯出器通過引數的方式接收標誌,該標誌引用 OCI 映象登錄檔或 Docker 守護程序,並將其寫入 app 映象中。

report.toml

匯出器還將寫一個 report.toml 檔案,該檔案包含匯出映象的相關資訊,比如該映象的概要以及清單的大小(如果匯出的是 OCI 登錄檔)或識別器,以及 buildpack 提供的構建 BOM。輸出的報告的位置可以通過 -report 標誌進行指定;預設地址是<layers>/report.toml——注意它不會在匯出的映象的檔案系統中出現。

建立

在單個命令中執行檢測、分析、恢復、構建以及匯出。

退出程式碼

退出碼 結果
0 成功
11 平臺 API 不相容錯誤
12 buildpack API 不相容錯誤
1-10,13-19 普通生命週期錯誤
20-29 檢測特定生命週期錯誤
30-39 分析特定生命週期錯誤
40-49 恢復特定生命週期錯誤
50-59 構建特定生命週期錯誤
60-69 匯出特定生命週期錯誤

啟動

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

退出程式碼

退出碼 結果
11 平臺 API 不相容錯誤
12 buildpack API 不相容錯誤
80-89 啟動特定生命週期錯誤

變基(rebase)

把應用程式的層變基到新的執行映象。

退出程式碼

退出碼 結果
0 成功
11 平臺 API 不相容錯誤
12 buildpack API 不相容錯誤
1-10,13-19 普通生命週期錯誤
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,然後生成一個可執行的 OCI 映象。每個 buildpack 檢查都檢查原始碼然後提供相關的依賴。然後根據 app 的原始碼和那些依賴生成映象。

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

多個 buildpack 可以通過指定的棧的構建映象繫結在一起,即構建者(注意,是構建者)映象。構建者為指定棧的 buildpack 提供最便捷的分佈。

變基

變基解釋

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

映象變基的核心部分是一個簡單的工程。通過檢查 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 是最優的。