|
|
@@ -147,4 +147,345 @@ ExecStart=/usr/bin/dockerd \
|
|
|
|
|
|
***
|
|
|
|
|
|
-下一章,我们将正式开始使用 Docker Java SDK,创建 DockerClient 并连接到 Docker daemon。
|
|
|
+## 快速入门
|
|
|
+
|
|
|
+前面我们完成了 Docker 环境的配置,现在让我们正式开始使用 Docker Java SDK。本章将介绍如何创建 DockerClient、连接到 Docker daemon、拉取镜像,以及创建并启动一个容器。
|
|
|
+
|
|
|
+### Maven 依赖
|
|
|
+
|
|
|
+首先,我们需要在项目中引入 Docker Java SDK 的依赖。在 `pom.xml` 中添加以下配置:
|
|
|
+
|
|
|
+```xml
|
|
|
+<dependencies>
|
|
|
+ <!-- Docker Java SDK 核心库 -->
|
|
|
+ <dependency>
|
|
|
+ <groupId>com.github.docker-java</groupId>
|
|
|
+ <artifactId>docker-java</artifactId>
|
|
|
+ <version>3.7.1</version>
|
|
|
+ </dependency>
|
|
|
+ <!-- HTTP Client 传输层实现 -->
|
|
|
+ <dependency>
|
|
|
+ <groupId>com.github.docker-java</groupId>
|
|
|
+ <artifactId>docker-java-transport-httpclient5</artifactId>
|
|
|
+ <version>3.7.1</version>
|
|
|
+ </dependency>
|
|
|
+ <!-- 日志实现 -->
|
|
|
+ <dependency>
|
|
|
+ <groupId>ch.qos.logback</groupId>
|
|
|
+ <artifactId>logback-classic</artifactId>
|
|
|
+ <version>1.5.38</version>
|
|
|
+ </dependency>
|
|
|
+</dependencies>
|
|
|
+```
|
|
|
+
|
|
|
+这里我们需要两个核心依赖:`docker-java` 是 SDK 的核心库,定义了所有 API 接口和模型;`docker-java-transport-httpclient5` 是传输层实现,负责与 Docker daemon 进行 HTTP 通信。
|
|
|
+
|
|
|
+### 创建 DockerClient
|
|
|
+
|
|
|
+DockerClient 是 Docker Java SDK 的核心入口类,所有的 Docker 操作都通过它来执行。SDK 提供了两种创建方式:使用默认配置和自定义配置。
|
|
|
+
|
|
|
+#### 使用默认配置
|
|
|
+
|
|
|
+最简单的方式是使用 `DockerClientBuilder` 的默认配置,它会自动检测本机的 Docker 环境:
|
|
|
+
|
|
|
+```java
|
|
|
+package space.anyi.docker;
|
|
|
+
|
|
|
+import com.github.dockerjava.api.DockerClient;
|
|
|
+import com.github.dockerjava.core.DockerClientBuilder;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Docker Java SDK 快速入门示例
|
|
|
+ * 演示如何使用默认配置创建 DockerClient
|
|
|
+ */
|
|
|
+public class QuickStart {
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(QuickStart.class);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用默认配置连接本地 Docker
|
|
|
+ * 默认配置会自动使用 Unix socket 连接本机 Docker daemon
|
|
|
+ */
|
|
|
+ public void quickStart() {
|
|
|
+ // 使用默认配置创建 DockerClient 实例
|
|
|
+ // 内部会自动检测 Docker 环境并使用 unix:///var/run/docker.sock 连接
|
|
|
+ DockerClient dockerClient = DockerClientBuilder.getInstance().build();
|
|
|
+
|
|
|
+ // 使用 ping 命令测试连接是否成功
|
|
|
+ dockerClient.pingCmd().exec();
|
|
|
+ log.info("Docker client ping 成功!");
|
|
|
+
|
|
|
+ // 列出所有本地镜像,验证连接正常
|
|
|
+ dockerClient.listImagesCmd().exec().forEach(image -> {
|
|
|
+ log.info("Docker 镜像信息: {}", image);
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+这段代码展示了最基本的使用方式:通过 `DockerClientBuilder.getInstance().build()` 创建客户端,然后使用 `pingCmd()` 测试连接,最后列出所有镜像验证功能正常。
|
|
|
+
|
|
|
+**思考:** `DockerClientBuilder.getInstance()` 内部做了什么?它是如何知道 Docker daemon 的地址的?
|
|
|
+
|
|
|
+实际上,`DockerClientBuilder.getInstance()` 会读取环境变量 `DOCKER_HOST` 来确定 Docker daemon 的地址。如果该环境变量未设置,则默认使用 `unix:///var/run/docker.sock`。
|
|
|
+
|
|
|
+#### 使用自定义配置
|
|
|
+
|
|
|
+在实际项目中,我们通常需要自定义连接参数,比如指定 Docker daemon 地址、设置超时时间等:
|
|
|
+
|
|
|
+```java
|
|
|
+package space.anyi.docker;
|
|
|
+
|
|
|
+import com.github.dockerjava.api.DockerClient;
|
|
|
+import com.github.dockerjava.core.DockerClientImpl;
|
|
|
+import com.github.dockerjava.core.DefaultDockerClientConfig;
|
|
|
+import com.github.dockerjava.core.DockerClientConfig;
|
|
|
+import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
|
|
+import com.github.dockerjava.transport.DockerHttpClient;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import java.time.Duration;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Docker Java SDK 自定义配置示例
|
|
|
+ * 演示如何使用自定义参数创建 DockerClient
|
|
|
+ */
|
|
|
+public class QuickStart {
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(QuickStart.class);
|
|
|
+
|
|
|
+ // 连接地址配置
|
|
|
+ private static final String UNIX_HOST = "unix://var/run/docker.sock";
|
|
|
+ private static final String TCP_HOST = "tcp://0.0.0.0:2375";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用自定义配置连接 Docker
|
|
|
+ * 可以指定连接地址、超时时间、最大连接数等参数
|
|
|
+ */
|
|
|
+ public void customStart() {
|
|
|
+ // 步骤1:构建 DockerClientConfig 配置对象
|
|
|
+ DockerClientConfig dockerClientConfig = DefaultDockerClientConfig
|
|
|
+ .createDefaultConfigBuilder()
|
|
|
+ .withDockerHost(UNIX_HOST) // 指定 Docker daemon 地址
|
|
|
+ // 以下为可选的 TLS 配置
|
|
|
+ //.withDockerTlsVerify(true)
|
|
|
+ //.withDockerCertPath("/home/user/.docker")
|
|
|
+ // 以下为可选的 Registry 认证配置
|
|
|
+ //.withRegistryUsername(registryUser)
|
|
|
+ //.withRegistryPassword(registryPass)
|
|
|
+ //.withRegistryEmail(registryMail)
|
|
|
+ //.withRegistryUrl(registryUrl)
|
|
|
+ .build();
|
|
|
+ log.info("Docker 客户端配置: {}", dockerClientConfig);
|
|
|
+
|
|
|
+ // 步骤2:构建 HTTP 传输层实例
|
|
|
+ DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
|
|
|
+ .dockerHost(dockerClientConfig.getDockerHost()) // 使用配置中的地址
|
|
|
+ //.sslConfig(dockerClientConfig.getSSLConfig()) // SSL 配置
|
|
|
+ .maxConnections(100) // 最大连接数
|
|
|
+ .connectionTimeout(Duration.ofSeconds(30)) // 连接超时时间
|
|
|
+ .responseTimeout(Duration.ofSeconds(45)) // 响应超时时间
|
|
|
+ .build();
|
|
|
+ log.info("HTTP 客户端配置: {}", httpClient);
|
|
|
+
|
|
|
+ // 步骤3:根据自定义配置创建 DockerClient
|
|
|
+ DockerClient dockerClient = DockerClientImpl.getInstance(dockerClientConfig, httpClient);
|
|
|
+
|
|
|
+ // 测试连接
|
|
|
+ dockerClient.pingCmd().exec();
|
|
|
+ log.info("自定义配置连接成功!");
|
|
|
+
|
|
|
+ // 列出所有镜像
|
|
|
+ dockerClient.listImagesCmd().exec().forEach(image -> {
|
|
|
+ log.info("Docker 镜像信息: {}", image);
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+自定义配置的创建分为三个步骤:
|
|
|
+1. 构建 `DockerClientConfig`:配置 Docker daemon 地址、TLS、Registry 认证等
|
|
|
+2. 构建 `DockerHttpClient`:配置传输层参数,如连接数、超时时间等
|
|
|
+3. 使用 `DockerClientImpl.getInstance()` 创建客户端实例
|
|
|
+
|
|
|
+### 连接测试
|
|
|
+
|
|
|
+创建 DockerClient 后,我们可以使用 `pingCmd()` 方法测试连接是否正常。如果连接失败,SDK 会抛出 `DockerException` 异常:
|
|
|
+
|
|
|
+```java
|
|
|
+try {
|
|
|
+ dockerClient.pingCmd().exec();
|
|
|
+ log.info("Docker 连接成功!");
|
|
|
+} catch (Exception e) {
|
|
|
+ log.error("Docker 连接失败: {}", e.getMessage());
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+我们还可以获取 Docker daemon 的版本信息,进一步验证连接:
|
|
|
+
|
|
|
+```java
|
|
|
+// 获取 Docker 版本信息
|
|
|
+String version = dockerClient.versionCmd().exec().getVersion();
|
|
|
+log.info("Docker 版本: {}", version);
|
|
|
+
|
|
|
+// 获取 Docker 系统信息
|
|
|
+Info info = dockerClient.infoCmd().exec();
|
|
|
+log.info("Docker 系统信息: {}", info);
|
|
|
+```
|
|
|
+
|
|
|
+### 拉取镜像
|
|
|
+
|
|
|
+拉取镜像是使用 Docker 的基本操作之一。SDK 提供了 `pullImageCmd()` 方法来拉取镜像:
|
|
|
+
|
|
|
+```java
|
|
|
+import com.github.dockerjava.api.command.PullImageCmd;
|
|
|
+import com.github.dockerjava.api.command.PullImageResultCallback;
|
|
|
+import com.github.dockerjava.api.model.PullResponseItem;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 拉取 Docker 镜像
|
|
|
+ * @param imageName 镜像名称,如 "hello-world" 或 "nginx:latest"
|
|
|
+ */
|
|
|
+public void pullImage(String imageName) {
|
|
|
+ try {
|
|
|
+ // 构建并执行拉取命令
|
|
|
+ // exec() 方法会阻塞直到镜像拉取完成
|
|
|
+ dockerClient.pullImageCmd(imageName)
|
|
|
+ .exec(new PullImageResultCallback())
|
|
|
+ .awaitCompletion();
|
|
|
+
|
|
|
+ log.info("镜像 {} 拉取成功!", imageName);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ log.error("镜像拉取被中断: {}", e.getMessage());
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**注意:** `PullImageResultCallback` 是一个异步回调类,我们需要调用 `awaitCompletion()` 方法来等待拉取完成。如果不调用此方法,拉取操作会在后台异步执行,主线程可能无法获取到结果。
|
|
|
+
|
|
|
+### 创建容器并启动
|
|
|
+
|
|
|
+拉取镜像后,我们可以使用该镜像创建并启动一个容器。这个过程分为两步:先创建容器(create),再启动容器(start):
|
|
|
+
|
|
|
+```java
|
|
|
+import com.github.dockerjava.api.command.CreateContainerResponse;
|
|
|
+import com.github.dockerjava.api.model.ExposedPort;
|
|
|
+import com.github.dockerjava.api.model.Ports;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 创建并启动一个容器
|
|
|
+ * @param imageName 镜像名称
|
|
|
+ * @param containerName 容器名称
|
|
|
+ */
|
|
|
+public void createAndStartContainer(String imageName, String containerName) {
|
|
|
+ // 步骤1:创建容器
|
|
|
+ // 创建容器只是分配资源和配置,并不会真正运行
|
|
|
+ CreateContainerResponse container = dockerClient.createContainerCmd(imageName)
|
|
|
+ .withName(containerName) // 设置容器名称
|
|
|
+ .exec();
|
|
|
+
|
|
|
+ log.info("容器创建成功,ID: {}", container.getId());
|
|
|
+
|
|
|
+ // 步骤2:启动容器
|
|
|
+ dockerClient.startContainerCmd(container.getId()).exec();
|
|
|
+ log.info("容器 {} 启动成功!", containerName);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+创建容器时,我们可以通过链式调用设置各种参数:
|
|
|
+
|
|
|
+```java
|
|
|
+// 创建一个带有端口映射和环境变量的容器
|
|
|
+CreateContainerResponse container = dockerClient.createContainerCmd("nginx:latest")
|
|
|
+ .withName("my-nginx")
|
|
|
+ .withEnv("NGINX_HOST=localhost", "NGINX_PORT=80") // 设置环境变量
|
|
|
+ .withExposedPorts(ExposedPort.tcp(80)) // 暴露端口
|
|
|
+ .withPortBindings(new Ports.Binding(8080, 80)) // 端口映射
|
|
|
+ .exec();
|
|
|
+```
|
|
|
+
|
|
|
+### 完整示例
|
|
|
+
|
|
|
+下面是一个完整的示例,展示了从创建 DockerClient 到运行容器的整个流程:
|
|
|
+
|
|
|
+```java
|
|
|
+package space.anyi.docker;
|
|
|
+
|
|
|
+import com.github.dockerjava.api.DockerClient;
|
|
|
+import com.github.dockerjava.api.command.CreateContainerResponse;
|
|
|
+import com.github.dockerjava.core.DefaultDockerClientConfig;
|
|
|
+import com.github.dockerjava.core.DockerClientImpl;
|
|
|
+import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
|
|
+import com.github.dockerjava.transport.DockerHttpClient;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import java.time.Duration;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Docker Java SDK 快速入门完整示例
|
|
|
+ */
|
|
|
+public class QuickStart {
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(QuickStart.class);
|
|
|
+
|
|
|
+ private final DockerClient dockerClient;
|
|
|
+
|
|
|
+ public QuickStart() {
|
|
|
+ // 创建 DockerClient 实例
|
|
|
+ DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
|
|
|
+ DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
|
|
|
+ .dockerHost(config.getDockerHost())
|
|
|
+ .maxConnections(100)
|
|
|
+ .connectionTimeout(Duration.ofSeconds(30))
|
|
|
+ .responseTimeout(Duration.ofSeconds(45))
|
|
|
+ .build();
|
|
|
+ this.dockerClient = DockerClientImpl.getInstance(config, httpClient);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 完整流程演示:连接 -> 拉取镜像 -> 创建容器 -> 启动容器
|
|
|
+ */
|
|
|
+ public void runDemo() {
|
|
|
+ // 1. 测试连接
|
|
|
+ dockerClient.pingCmd().exec();
|
|
|
+ log.info("1. Docker 连接成功");
|
|
|
+
|
|
|
+ // 2. 拉取 hello-world 镜像
|
|
|
+ try {
|
|
|
+ dockerClient.pullImageCmd("hello-world")
|
|
|
+ .exec(new com.github.dockerjava.api.command.PullImageResultCallback())
|
|
|
+ .awaitCompletion();
|
|
|
+ log.info("2. 镜像拉取成功");
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ log.error("镜像拉取被中断");
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 创建并启动容器
|
|
|
+ CreateContainerResponse container = dockerClient.createContainerCmd("hello-world")
|
|
|
+ .withName("demo-container")
|
|
|
+ .exec();
|
|
|
+ log.info("3. 容器创建成功,ID: {}", container.getId());
|
|
|
+
|
|
|
+ dockerClient.startContainerCmd(container.getId()).exec();
|
|
|
+ log.info("4. 容器启动成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void main(String[] args) {
|
|
|
+ new QuickStart().runDemo();
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+运行这段代码,你会看到 Docker 客户端成功连接、拉取镜像、创建并启动容器的完整过程。
|
|
|
+
|
|
|
+**思考:** 为什么创建容器和启动容器要分成两步?直接一步完成不是更简单吗?
|
|
|
+
|
|
|
+将创建和启动分开的设计允许我们在容器启动前进行更多配置,比如设置网络、挂载卷、配置资源限制等。这种设计也符合 Docker 的命令行操作习惯(`docker create` + `docker start`)。
|
|
|
+
|
|
|
+***
|
|
|
+
|
|
|
+下一章,我们将深入学习镜像管理相关的 API,包括列出镜像、查看详情、删除镜像等操作。
|