书接上文,聪明如你已经发现项目中没有定义 dockerfile
,但我们依然能打镜像,是如何做到的呢?正如上面提到的 gradle 的 spring 插件创建了 bootBuildImage
,通过 buildpacks 构建 OCI 镜像。
buildpacks 让你可以把源文件转换为安全、高效、预生产的容器镜像。
buildpacks 为应用程序提供框架和运行时支持。buildpacks 检查你的应用以决定需要哪些依赖,并恰当的配置这些应用以便能在各种云环境中运行。
每个 buildpack 由两个阶段组成。
检测阶段会检查你的源码是否适合使用 buildpack。如果适合,就直接进入构建(build)阶段。如果不适合,则直接跳过 buildpack 中的构建阶段。
例如:
requirements.txt
或 setup.py
文件,则通过检测package-lock.json
文件,则通过检测构建阶段会检测你的代码以完成一下操作:
例如:
requirements.txt
文件,会执行 pip install -r requirements.txt
命令,以安装 requirements.txt
文件中的依赖。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.json
和 requirements.txt
文件,demo-builder 将同时运行 Python 和 Node 构建步骤。requirements.txt
文件,也不包含 package-lock.json
文件,那么 demo-builder 将检测失败,并退出构建。构建者是一个包含执行构建时需要的各种组件的镜像。构建者镜像由构建镜像、生命周期、多个 buildpack、各方面的构建配置文件(包含 buildpack 检测顺序和运行镜像的位置)组成。
构建者由以下组件组成:
buildpack 是一个工作单元,该工作单元仔细检查你的源代码然后制定构建、运行应用程序的计划。
通常多个 buildpack 文件是一个至少包含 3 个文件的集合:
buildpack.toml
——提供 buildpack 的元数据bin/detect
——决定是否可以应用 buildpackbin/build
——执行 buildpack 逻辑还有一种不同类型的 buildpack,通常称为元 buildpack。它只有一个 buildpack.toml 文件,该文件包含有序的配置,而配置的内容是对其它 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 文件是模块化并且可重用的,因此 builpack 组可以让你把多个模块化 buildpack 文件连在一起。
例如,你有一个 buildpack 文件用于安装 Java,一个 buildpack 文件使用 Maven 构建应用程序。这两个 builpack 文件可以合并到一个组中实现更高级的功能,特别是第一个文件安装 Java,第二个文件使用 Java 运行 Maven,这不就是 Java 的构建构建工具吗。
因为你在构建者或元 buildpack 中可以有多个 buildpack 组,并且你可以重用 buildpack 文件,所以你可以再用一个 buildpack 组,该组重用提供 Java 的 buildpack,但是使用 Gradle 提供的 buildpack 构建你的应用程序。如此一来,你在创建高级功能的时候就不需要复制了。
buildpack 组是一个 buildpack 条目列表,按顺序定义了 buildpack 的顺序执行。
buildpack 条目通过 id 和版本进行定义。该条目可以被标记为是可选的。虽然在一个 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 组:
未包含 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 是一个包含组列表的文件。每个组是一个 buildpack 列表。检测仪读取 order.toml 然后寻找第一个通过检测的组。如果所有的组都失败了,那么检测就失败了。
组中的 buildpack 要么被标记会可选的,要么被标记为必须的。为了通过检测处理,必须满足两个条件:
第一个通过以上两个步骤的组将写入 group.toml 中,并将其构建计划写入 plan.toml 中。
注意:如果检测脚本执行可选 buildpack 失败了,buildpack 组仍然可以通过检测处理并且可以被选中。该组要被选中,至少要有一个 buildpack (无论是可选的还是必须的)成功的通过检测。
选中的组如果可以创建 plan.toml,则会被写入 group.toml 中。buildpack 将会与 order.toml 中相同的顺序,并且过滤掉所有可选的失败的 buildpack,然后写入 group.toml 中。
每个 buildpack 可以定义两个列表,分别是提供依赖列表和要求依赖列表(或者通过 or 隔离的键值对列表)。这些列表(如果不为空)叫做构建计划。检测仪从选中的组中读取 buildpack(在过滤掉执行探测脚本失败的 buildpack 之后)。检测仪检查所有的可选项然后尝试创建一个包含条目列表的文件,每个条目都有提供和要求列表以满足所有 buildpack 的需求。每个可选项都称为一次试验,输出文件叫做 plan.toml。
提供和要求有两个限制条件:
对于必要的 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 文件,该文件包含导出镜像的相关信息,比如该镜像的概要以及清单的大小(如果导出的是 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 | 启动特定生命周期错误 |
把应用程序的层变基到新的运行镜像。
退出码 | 结果 |
---|---|
0 | 成功 |
11 | 平台 API 不兼容错误 |
12 | buildpack API 不兼容错误 |
1-10,13-19 | 普通生命周期错误 |
70-79 | 变基特定生命周期错误 |
平台使用生命周期、buildpack(打包在构建者中)以及应用程序源码生成一个 OCI 镜像。
平台可能包括的示例:
平台技术规范详细的描述了平台的能力,以及平台如何和生命周期、构建者交互。当前平台的 API 版本是 0.4。
栈是把两个打算一起工作的镜像组合在一起:
如果你使用 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
为运行镜像提供了可选位置,以便在构建
(或变基
)期间使用。当通过构建者容器运行构建
时,打包
会选择使用 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 镜像 my-app:my-tag
使用默认的构造者进行初次构建。该构建者的栈使用的运行镜像为 pack/run
。运行以下命令更新 my-app:my-tag
的基础镜像为 pack/run
的最新版本。
$ pack rebase my-app:my-tag
提示:
pack rebase
有一个--publish
标志可以用于发布更新后的 app 镜像到注册表中。当使用注册表时,与 Docker 守护进程相比,--publish
是最优的。
原文:https://www.cnblogs.com/Zhang-Xiang/p/15176823.html