|
@@ -695,4 +695,126 @@ api.removeImage("my-nginx:v1.0", false);
|
|
|
|
|
|
|
|
***
|
|
***
|
|
|
|
|
|
|
|
-下一章,我们将学习如何使用 Docker Java SDK 从 Dockerfile 构建自定义镜像。
|
|
|
|
|
|
|
+## 镜像构建相关API
|
|
|
|
|
+
|
|
|
|
|
+前面我们学习了镜像的基本管理操作,本章将介绍如何使用 Docker Java SDK **构建自定义镜像**。镜像构建是 Docker 最强大的功能之一,它允许我们将应用打包成标准化的镜像,实现"一次构建,到处运行"。
|
|
|
|
|
+
|
|
|
|
|
+### buildImageCmd - 从 Dockerfile 构建镜像
|
|
|
|
|
+
|
|
|
|
|
+`buildImageCmd()` 对应 `docker build` 命令,它需要指定构建上下文(包含 Dockerfile 和构建所需的文件)。
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+/**
|
|
|
|
|
+ * 从 Dockerfile 构建镜像
|
|
|
|
|
+ * @param dockerfilePath Dockerfile 文件路径
|
|
|
|
|
+ * @param imageName 构建目标镜像名称(repository:tag)
|
|
|
|
|
+ * @return 构建成功返回镜像 ID,失败返回 null
|
|
|
|
|
+ */
|
|
|
|
|
+public String buildImage(String dockerfilePath, String imageName) {
|
|
|
|
|
+ File dockerfile = new File(dockerfilePath);
|
|
|
|
|
+ if (!dockerfile.exists()) {
|
|
|
|
|
+ log.error("Dockerfile 不存在: {}", dockerfilePath);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // withDockerfile 指定 Dockerfile 文件位置
|
|
|
|
|
+ // withBaseDirectory 设置构建上下文(当前目录)
|
|
|
|
|
+ // withTag 指定构建后的镜像名称和标签
|
|
|
|
|
+ // exec() 返回异步回调,awaitImageId() 阻塞等待构建完成并返回镜像 ID
|
|
|
|
|
+ String imageId = dockerClient.buildImageCmd()
|
|
|
|
|
+ .withDockerfile(dockerfile)
|
|
|
|
|
+ .withBaseDirectory(dockerfile.getParentFile())
|
|
|
|
|
+ .withTag(imageName)
|
|
|
|
|
+ .exec(new BuildImageResultCallback())
|
|
|
|
|
+ .awaitImageId();
|
|
|
|
|
+ log.info("镜像 {} 构建成功,镜像 ID: {}", imageName, imageId);
|
|
|
|
|
+ return imageId;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("镜像构建失败: {}", e.getMessage());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+构建过程的关键参数:
|
|
|
|
|
+
|
|
|
|
|
+| 方法 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| `withDockerfile(File)` | 指定 Dockerfile 文件路径 |
|
|
|
|
|
+| `withBaseDirectory(File)` | 指定构建上下文目录(Dockerfile 中 COPY 指令从此目录读取文件) |
|
|
|
|
|
+| `withTag(String)` | 指定构建出的镜像名称和标签(如 `sdk-demo/hello:latest`) |
|
|
|
|
|
+| `withRemove(boolean)` | 构建完成后是否移除中间层容器 |
|
|
|
|
|
+| `withForcerm(boolean)` | 是否强制移除中间层容器(当构建失败时) |
|
|
|
|
|
+| `withBuildArg(String, String)` | 传递构建参数(对应 ARG 指令) |
|
|
|
|
|
+
|
|
|
|
|
+`BuildImageResultCallback` 是构建过程的异步回调,各个方法的含义:
|
|
|
|
|
+
|
|
|
|
|
+| 方法 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| `onNext(BuildResponseItem)` | 每收到一个构建事件时回调(如缓存命中、步骤执行等) |
|
|
|
|
|
+| `onError(Throwable)` | 构建出错时回调 |
|
|
|
|
|
+| `awaitImageId()` | 阻塞等待构建完成,返回镜像 ID(超时则抛出异常) |
|
|
|
|
|
+| `awaitCompletion()` | 阻塞等待构建完成,无返回值 |
|
|
|
|
|
+
|
|
|
|
|
+**思考:** 构建上下文(BaseDirectory)的作用是什么?为什么需要单独指定 Dockerfile 的位置?
|
|
|
|
|
+
|
|
|
|
|
+构建上下文是 Docker 构建时能"看到"的所有文件所在的目录。当 Dockerfile 中使用 `COPY app.jar /app/` 时,这个 `app.jar` 就是从构建上下文中寻找的。将 Dockerfile 放在构建上下文之外(通过 `withDockerfile` 指定)是允许的,但 COPY 指令仍然只能访问构建上下文内的文件。
|
|
|
|
|
+
|
|
|
|
|
+### 构建流程详解
|
|
|
|
|
+
|
|
|
|
|
+构建一个镜像的过程大致如下:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+Dockerfile(构建指令)
|
|
|
|
|
+ |
|
|
|
|
|
+ v
|
|
|
|
|
+构建上下文 (BaseDirectory) ──► Docker daemon 执行每个指令
|
|
|
|
|
+ |
|
|
|
|
|
+ | 1. FROM alist666/alist:latest ← 拉取/复用基础镜像
|
|
|
|
|
+ | 2. RUN echo "..." > /info.txt ← 执行命令,创建新 Layer
|
|
|
|
|
+ |
|
|
|
|
|
+ v
|
|
|
|
|
+构建完成的镜像 (imageName:tag)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+每次 `RUN`、`COPY` 等指令都会创建一个新的镜像 Layer,所有 Layer 叠加在一起就是最终的镜像。这个分层机制使得镜像可以被高效地缓存和复用。
|
|
|
|
|
+
|
|
|
|
|
+### 示例:构建一个带标识文件的自定义镜像
|
|
|
|
|
+
|
|
|
|
|
+假设我们有如下 Dockerfile:
|
|
|
|
|
+
|
|
|
|
|
+```dockerfile
|
|
|
|
|
+FROM registry.fit2cloud.com/halo/halo-pro:2.24
|
|
|
|
|
+# 构建时添加一个标识文件,演示 Dockerfile 的 RUN 指令
|
|
|
|
|
+RUN echo "Built by Docker Java SDK" > /build-info.txt
|
|
|
|
|
+# 容器启动时输出构建信息
|
|
|
|
|
+CMD ["sh", "-c", "cat /build-info.txt"]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+使用我们封装好的 `ImageBuildAPI` 构建并验证:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+ImageBuildAPI api = new ImageBuildAPI();
|
|
|
|
|
+String imageId = api.buildImage("src/test/resources/Dockerfile", "sdk-demo/hello:latest");
|
|
|
|
|
+log.info("构建结果: {}", imageId);
|
|
|
|
|
+
|
|
|
|
|
+// 验证镜像已存在
|
|
|
|
|
+List<Image> images = api.findImageByName("sdk-demo/hello:latest");
|
|
|
|
|
+log.info("找到 {} 个匹配镜像", images.size());
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+运行测试可以看到构建的输出:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+镜像 sdk-demo/hello:latest 构建成功,镜像 ID: sha256:xxxx
|
|
|
|
|
+镜像 sdk-demo/hello:latest 是否存在: true
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**思考:** 为什么 `withRemove(true)` 和 `withForcerm(true)` 的组合在 CI/CD 流水线中很重要?
|
|
|
|
|
+
|
|
|
|
|
+在自动化构建过程中,如果每次构建都留下大量的中间层容器,会逐渐耗尽磁盘空间。`withRemove(true)` 确保正常完成后清理中间层,`withForcerm(true)` 则在构建失败时也能强制清理,避免残留。
|
|
|
|
|
+
|
|
|
|
|
+***
|
|
|
|
|
+
|
|
|
|
|
+下一章,我们将学习容器管理相关的 API,这是 Docker 使用中最频繁的操作。
|