BuildPack:无需编写 Dockerfile,新一代的企业镜像打包工具
公眾號(hào)關(guān)注?「奇妙的 Linux 世界」
設(shè)為「星標(biāo)」,每天帶你玩轉(zhuǎn) Linux !
過去的工作中,我們使用微服務(wù)、容器化以及服務(wù)編排構(gòu)建了技術(shù)平臺(tái)。為了提升開發(fā)團(tuán)隊(duì)的研發(fā)效率,我們同時(shí)還提供了 CICD 平臺(tái),用來將代碼快速的部署到 Openshift(企業(yè)級(jí)的 Kubernetes) 集群。
部署的第一步就是應(yīng)用程序的容器化,持續(xù)集成的交付物從以往的 jar 包、webpack 等變成了容器鏡像。容器化將軟件代碼和所需的所有組件(庫、框架、運(yùn)行環(huán)境)打包到一起,進(jìn)而可以在任何環(huán)境任何基礎(chǔ)架構(gòu)上一致地運(yùn)行,并與其他應(yīng)用“隔離”。
我們的代碼需要從源碼到編譯到最終可運(yùn)行的鏡像,甚至部署,這一切在 CICD 的流水線中完成。最初,我們?cè)诿總€(gè)代碼倉庫中都加入了三個(gè)文件,也通過項(xiàng)目生成器(類似 Spring Initializer)在新項(xiàng)目中注入:
?Jenkinsfile.groovy:用來定義 Jenkins 的 Pipeline,針對(duì)不同的語言還會(huì)有多種版本?Manifest YAML:用于定義 Kubernetes 資源,也就是工作負(fù)載及其運(yùn)行的相關(guān)描述?Dockerfile:用于構(gòu)建對(duì)象
這個(gè)三個(gè)文件也需要在工作中不斷的演進(jìn),起初項(xiàng)目較少(十幾個(gè))的時(shí)候我們基礎(chǔ)團(tuán)隊(duì)還可以去各個(gè)代碼倉庫去維護(hù)升級(jí)。隨著項(xiàng)目爆發(fā)式的增長,維護(hù)的成本越來越高。我們對(duì) CICD 平臺(tái)進(jìn)行了迭代,將“Jenkinsfile.groovy”和 “manifest YAML”從項(xiàng)目中移出,變更較少的 Dockerfile 就保留了下來。
隨著平臺(tái)的演進(jìn),我們需要考慮將這唯一的“釘子戶” Dockerfile 與代碼解耦,必要的時(shí)候也需要對(duì) Dockerfile 進(jìn)行升級(jí)。因此調(diào)研了一下 buildpacks,就有了今天的這篇文章。
什么是 Dockerfile
Docker 通過讀取 Dockerfile 中的說明自動(dòng)構(gòu)建鏡像。Dockerfile 是一個(gè)文本文件,包含了由 Docker 可以執(zhí)行用于構(gòu)建鏡像的指令。我們拿之前用于測試 Tekton 的 Java 項(xiàng)目[1]的 Dockerfile 為例:
FROM openjdk:8-jdk-alpine RUN mkdir /app WORKDIR /app COPY target/*.jar /app/app.jar ENTRYPOINT ["sh", "-c", "java -Xmx128m -Xms64m -jar app.jar"]鏡像分層
你可能會(huì)聽過 Docker 鏡像包含了多個(gè)層。每個(gè)層與 Dockerfile 中的每個(gè)命令對(duì)應(yīng),比如?RUN、COPY、ADD。某些特定的指令會(huì)創(chuàng)建一個(gè)新的層,在鏡像構(gòu)建過程中,假如某些層沒有發(fā)生變化,就會(huì)從緩存中獲取。
在下面的 Buildpack 中也同樣通過鏡像分層和 cache 來加速鏡像的構(gòu)建。
什么是 Buildpack
BuildPack[2]?是一個(gè)程序,它能將源代碼轉(zhuǎn)換成容器鏡像的并可以在任意云環(huán)境中運(yùn)行。通常 buildpack 封裝了單一語言的生態(tài)工具鏈。適用于 Java、Ruby、Go、NodeJs、Python 等。
buildpacks.ioBuilder 是什么?
一些 buildpacks 按順序組合之后就是?builder,除了 buildpacks, builder 中還加入了?生命周期[3]?和 stack 容器鏡像。
stack 容器鏡像由兩個(gè)鏡像組成:用于運(yùn)行 buildpack 的鏡像 build image,以及構(gòu)建應(yīng)用鏡像的基礎(chǔ)鏡像 run image。如上圖,就是 builder 中的運(yùn)行環(huán)境。
Buildpack 的工作方式
how buildpack works每個(gè) buildpack 運(yùn)行時(shí)都包含了兩個(gè)階段:
phases1. 檢測階段
通過檢查源代碼中的某些特定文件/數(shù)據(jù),來判斷當(dāng)前 buildpack 是否適用。如果適用,就會(huì)進(jìn)入構(gòu)建階段;否則就會(huì)退出。比如:
?Java maven 的 buildpack 會(huì)檢查源碼中是否有?pom.xml?Python 的 buildpack 會(huì)檢查源碼中是否有?requirements.txt?或者?setup.py?文件?Node buildpack 會(huì)查找?package-lock.json?文件。
2. 構(gòu)建階段
在構(gòu)建階段會(huì)進(jìn)行如下操作:
1.設(shè)置構(gòu)建環(huán)境和運(yùn)行時(shí)環(huán)境2.下載依賴并編譯源碼(假如需要的話)3.設(shè)置正確的 entrypoint 和啟動(dòng)腳本。
比如:
?Java maven buildpack 在檢查到有?pom.xml?文件之后,會(huì)執(zhí)行?mvn clean install -DskipTests?Python buildpack 檢查到有?requrements.txt?之后,會(huì)執(zhí)行?pip install -r requrements.txt?Node build pack 檢查到有?package-lock.json?后執(zhí)行?npm install
BuildPack 上手
那到底如何在沒有 Dockerfile 的情況下使用 builderpack 構(gòu)建鏡像的。看了上面這些,大家基本上也都能了解到這個(gè)核心就在 buildpack 的編寫和使用的。
其實(shí)現(xiàn)在有很多開源的 buildpack 可以用,沒有特定定制的情況下無需自己手動(dòng)編寫。比如下面的幾個(gè)大廠開源并維護(hù)的 Buildpacks:
?Heroku Buildpacks[4]?Google Buildpacks[5]?Paketo[6]
但是正式詳細(xì)介紹開源的 buildpacks 之前,我們還是通過自己創(chuàng)建 buildpack 的方式來深入了解 Buildpacks 的工作方式。測試項(xiàng)目呢,我們還是用測試 Tekton 的 Java 項(xiàng)目[7]。
下面所有的內(nèi)容都提交到了?Github[8]?上,可以訪問:https://github.com/addozhang/buildpacks-sample 獲取相關(guān)代碼。
最終的目錄buildpacks-sample結(jié)構(gòu)如下:
├── builders │?? └── builder.toml ├── buildpacks │?? └── buildpack-maven │?? ├── bin │?? │?? ├── build │?? │?? └── detect │?? └── buildpack.toml └── stacks├── build│?? └── Dockerfile├── build.sh└── run└── Dockerfile創(chuàng)建 buildpack
pack buildpack new examples/maven \--api 0.5 \--path buildpack-maven \--version 0.0.1 \--stacks io.buildpacks.samples.stacks.bionic看下生成的?buildpack-maven?目錄:
buildpack-maven ├── bin │?? ├── build │?? └── detect └── buildpack.toml各個(gè)文件中都是默認(rèn)的初試數(shù)據(jù),并沒有什么用處。需要添加些內(nèi)容:
bin/detect:
#!/usr/bin/env bash if [[ ! -f pom.xml ]]; thenexit 100 fi plan_path=$2 cat >> "${plan_path}" <<EOL [[provides]] name = "jdk" [[requires]] name = "jdk" EOLbin/build:
#!/usr/bin/env bash set -euo pipefail layers_dir="$1" env_dir="$2/env" plan_path="$3" m2_layer_dir="${layers_dir}/maven_m2" if [[ ! -d ${m2_layer_dir} ]]; thenmkdir -p ${m2_layer_dir}echo "cache = true" > ${m2_layer_dir}.toml fi ln -s ${m2_layer_dir} $HOME/.m2 echo "---> Running Maven" mvn clean install -B -DskipTests target_dir="target" for jar_file in $(find "$target_dir" -maxdepth 1 -name "*.jar" -type f); docat >> "${layers_dir}/launch.toml" <<EOL [[processes]] type = "web" command = "java -jar ${jar_file}" EOLbreak; donebuildpack.toml:
api = "0.5" [buildpack]id = "examples/maven"version = "0.0.1" [[stacks]]id = "com.atbug.buildpacks.example.stacks.maven"創(chuàng)建 stack
構(gòu)建 Maven 項(xiàng)目,首選需要 Java 和 Maven 的環(huán)境,我們使用?maven:3.5.4-jdk-8-slim?作為 build image 的 base 鏡像。應(yīng)用的運(yùn)行時(shí)需要 Java 環(huán)境即可,因此使用?openjdk:8-jdk-slim作為 run image 的 base 鏡像。
在?stacks?目錄中分別創(chuàng)建?build?和?run?兩個(gè)目錄:
build/Dockerfile
FROM maven:3.5.4-jdk-8-slim ARG cnb_uid=1000 ARG cnb_gid=1000 ARG stack_id ENV CNB_STACK_ID=${stack_id} LABEL io.buildpacks.stack.id=${stack_id} ENV CNB_USER_ID=${cnb_uid} ENV CNB_GROUP_ID=${cnb_gid} # Install packages that we want to make available at both build and run time RUN apt-get update && \apt-get install -y xz-utils ca-certificates && \rm -rf /var/lib/apt/lists/* # Create user and group RUN groupadd cnb --gid ${cnb_gid} && \useradd --uid ${cnb_uid} --gid ${cnb_gid} -m -s /bin/bash cnb USER ${CNB_USER_ID}:${CNB_GROUP_ID}run/Dockerfile
FROM openjdk:8-jdk-slim ARG stack_id ARG cnb_uid=1000 ARG cnb_gid=1000 LABEL io.buildpacks.stack.id="${stack_id}" USER ${cnb_uid}:${cnb_gid}然后使用如下命令構(gòu)建出兩個(gè)鏡像:
export STACK_ID=com.atbug.buildpacks.example.stacks.maven docker build --build-arg stack_id=${STACK_ID} -t addozhang/samples-buildpacks-stack-build:latest ./build docker build --build-arg stack_id=${STACK_ID} -t addozhang/samples-buildpacks-stack-run:latest ./run創(chuàng)建 Builder
有了 buildpack 和 stack 之后就是創(chuàng)建 Builder 了,首先創(chuàng)建?builder.toml?文件,并添加如下內(nèi)容:
[[buildpacks]] id = "examples/maven" version = "0.0.1" uri = "../buildpacks/buildpack-maven" [[order]] [[order.group]] id = "examples/maven" version = "0.0.1" [stack] id = "com.atbug.buildpacks.example.stacks.maven" run-image = "addozhang/samples-buildpacks-stack-run:latest" build-image = "addozhang/samples-buildpacks-stack-build:latest"然后執(zhí)行命令,注意這里我們使用了?--pull-policy if-not-present?參數(shù),就不需要將 stack 的兩個(gè)鏡像推送到鏡像倉庫了:
pack builder create example-builder:latest --config ./builder.toml --pull-policy if-not-present測試
有了 builder 之后,我們就可以使用創(chuàng)建好的 builder 來構(gòu)建鏡像了。
這里同樣加上了?--pull-policy if-not-present?參數(shù)來使用本地的 builder 鏡像:
# 目錄 buildpacks-sample 與 tekton-test 同級(jí),并在 buildpacks-sample 中執(zhí)行如下命令 pack build addozhang/tekton-test --builder example-builder:latest --pull-policy if-not-present --path ../tekton-test如果看到類似如下內(nèi)容,就說明鏡像構(gòu)建成功了(第一次構(gòu)建鏡像由于需要下載 maven 依賴耗時(shí)可能會(huì)比較久,后續(xù)就會(huì)很快,可以執(zhí)行兩次驗(yàn)證下):
... ===> EXPORTING [exporter] Adding 1/1 app layer(s) [exporter] Reusing layer 'launcher' [exporter] Reusing layer 'config' [exporter] Reusing layer 'process-types' [exporter] Adding label 'io.buildpacks.lifecycle.metadata' [exporter] Adding label 'io.buildpacks.build.metadata' [exporter] Adding label 'io.buildpacks.project.metadata' [exporter] Setting default process type 'web' [exporter] Saving addozhang/tekton-test... [exporter] *** Images (0d5ac1158bc0): [exporter] addozhang/tekton-test [exporter] Adding cache layer 'examples/maven:maven_m2' Successfully built image addozhang/tekton-test啟動(dòng)容器,會(huì)看到 spring boot 應(yīng)用正常啟動(dòng):
docker run --rm addozhang/tekton-test:latest. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.2.3.RELEASE)...總結(jié)
其實(shí)現(xiàn)在有很多開源的 buildpack 可以用,沒有特定定制的情況下無需自己手動(dòng)編寫。比如下面的幾個(gè)大廠開源并維護(hù)的 Buildpacks:
?Heroku Buildpacks[9]?Google Buildpacks[10]?Paketo[11]
上面幾個(gè) buildpacks 庫內(nèi)容比較全面,實(shí)現(xiàn)上會(huì)有些許不同。比如 Heroku 的執(zhí)行階段使用 Shell 腳本,而 Paketo 使用 Golang。后者的擴(kuò)展性較強(qiáng),由 Cloud Foundry 基金會(huì)支持,并擁有由 VMware 贊助的全職核心開發(fā)團(tuán)隊(duì)。這些小型模塊化的 buildpack,可以通過組合擴(kuò)展使用不同的場景。
當(dāng)然還是那句話,自己上手寫一個(gè)會(huì)更容易理解 Buildpack 的工作方式。
引用鏈接
[1]?測試 Tekton 的 Java 項(xiàng)目:?https://github.com/addozhang/tekton-test
[2]?BuildPack:?https://buildpacks.io/
[3]?生命周期:?https://buildpacks.io/docs/concepts/components/lifecycle/
[4]?Heroku Buildpacks:?https://github.com/heroku/
[5]?Google Buildpacks:?https://github.com/GoogleCloudPlatform/buildpacks
[6]?Paketo:?https://github.com/paketo-buildpacks
[7]?測試 Tekton 的 Java 項(xiàng)目:?https://github.com/addozhang/tekton-test
[8]?Github:?https://github.com/addozhang/buildpacks-sample
[9]?Heroku Buildpacks:?https://github.com/heroku/
[10]?Google Buildpacks:?https://github.com/GoogleCloudPlatform/buildpacks
[11]?Paketo:?https://github.com/paketo-buildpacks
本文轉(zhuǎn)載自:「云原生指北」,原文:https://tinyurl.com/jwr782uz,版權(quán)歸原作者所有。歡迎投稿,投稿郵箱: editor@hi-linux.com。
你可能還喜歡
點(diǎn)擊下方圖片即可閱讀
一個(gè) TCP 連接可以發(fā)多少個(gè) HTTP 請(qǐng)求?99% 的人可能都不知道!
點(diǎn)擊上方圖片,『美團(tuán)|餓了么』外賣紅包天天免費(fèi)領(lǐng)
更多有趣的互聯(lián)網(wǎng)新鮮事,關(guān)注「奇妙的互聯(lián)網(wǎng)」視頻號(hào)全了解!
總結(jié)
以上是生活随笔為你收集整理的BuildPack:无需编写 Dockerfile,新一代的企业镜像打包工具的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: uniapp开发小程序之上传图片(拍照或
- 下一篇: 保密局计算机网络的安全检查与防护,自治区