|
@@ -817,4 +817,237 @@ log.info("找到 {} 个匹配镜像", images.size());
|
|
|
|
|
|
|
|
***
|
|
***
|
|
|
|
|
|
|
|
-下一章,我们将学习容器管理相关的 API,这是 Docker 使用中最频繁的操作。
|
|
|
|
|
|
|
+## 容器管理相关API
|
|
|
|
|
+
|
|
|
|
|
+容器(Container)是 Docker 的另一核心概念,它是镜像的**运行实例**。如果说镜像是"类",那容器就是"对象"。本章将系统学习容器管理的各项 API。
|
|
|
|
|
+
|
|
|
|
|
+### 容器的完整生命周期
|
|
|
|
|
+
|
|
|
|
|
+容器的生命周期可以分为几个阶段:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+创建 (create) → 启动 (start) → 运行中 (running)
|
|
|
|
|
+ ↓
|
|
|
|
|
+ 停止 (stop) / 强制终止 (kill)
|
|
|
|
|
+ ↓
|
|
|
|
|
+ 删除 (remove)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Docker 的设计哲学是:**创建**和**启动**是分离的操作。创建容器只分配资源和配置,容器处于 `created` 状态;启动后进入 `running` 状态。这种分离设计让我们可以在启动前完成网络、卷、资源限制等配置。
|
|
|
|
|
+
|
|
|
|
|
+### createContainerCmd - 创建容器
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+/**
|
|
|
|
|
+ * 创建容器(不启动)
|
|
|
|
|
+ * @param imageName 镜像名称
|
|
|
|
|
+ * @param containerName 容器名称
|
|
|
|
|
+ * @return 创建的容器响应(包含容器 ID)
|
|
|
|
|
+ */
|
|
|
|
|
+public CreateContainerResponse createContainer(String imageName, String containerName) {
|
|
|
|
|
+ // 创建容器:分配资源配置,但不会真正运行
|
|
|
|
|
+ // withName 指定容器名称(可选,不指定时 Docker 自动生成)
|
|
|
|
|
+ CreateContainerResponse response = dockerClient.createContainerCmd(imageName)
|
|
|
|
|
+ .withName(containerName)
|
|
|
|
|
+ .exec();
|
|
|
|
|
+ log.info("容器创建成功,ID: {}, 名称: {}", response.getId(), containerName);
|
|
|
|
|
+ return response;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+创建容器时常用的配置项:
|
|
|
|
|
+
|
|
|
|
|
+| 方法 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| `withName(String)` | 指定容器名称 |
|
|
|
|
|
+| `withEnv(String...)` | 设置环境变量 |
|
|
|
|
|
+| `withExposedPorts(ExposedPort...)` | 声明容器内暴露的端口 |
|
|
|
|
|
+| `withPortBindings(Ports)` | 配置端口映射(宿主机端口 -> 容器内端口) |
|
|
|
|
|
+| `withCmd(String...)` | 覆盖镜像中定义的启动命令 |
|
|
|
|
|
+| `withWorkingDir(String)` | 设置容器内工作目录 |
|
|
|
|
|
+| `withHostConfig(HostConfig)` | 设置资源限制、挂载卷、网络模式等 |
|
|
|
|
|
+
|
|
|
|
|
+创建带环境变量和端口映射的容器:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+// 创建带环境变量的容器
|
|
|
|
|
+dockerClient.createContainerCmd("nginx:latest")
|
|
|
|
|
+ .withName("my-nginx")
|
|
|
|
|
+ .withEnv("NGINX_HOST=localhost", "NGINX_PORT=80")
|
|
|
|
|
+ .exec();
|
|
|
|
|
+
|
|
|
|
|
+// 创建带端口映射的容器
|
|
|
|
|
+dockerClient.createContainerCmd("nginx:latest")
|
|
|
|
|
+ .withName("my-nginx")
|
|
|
|
|
+ .withExposedPorts(ExposedPort.tcp(80))
|
|
|
|
|
+ .withPortBindings(new Ports.Binding(8080, 80))
|
|
|
|
|
+ .exec();
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### startContainerCmd - 启动容器
|
|
|
|
|
+
|
|
|
|
|
+创建容器后,使用 `startContainerCmd()` 启动它:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+/**
|
|
|
|
|
+ * 启动容器
|
|
|
|
|
+ * @param containerId 容器 ID
|
|
|
|
|
+ */
|
|
|
|
|
+public void startContainer(String containerId) {
|
|
|
|
|
+ dockerClient.startContainerCmd(containerId).exec();
|
|
|
|
|
+ log.info("容器 {} 启动成功", containerId);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**思考:** 在快速入门章节我们看到了 `CreateContainerResponse.getId()`,它返回完整的容器 ID。为什么大多数 API 都要求传容器 ID 而不是名称?
|
|
|
|
|
+
|
|
|
|
|
+容器 ID 是容器的唯一标识,不会重复。而容器名称虽然也可以用于操作(Docker daemon 会解析),但在某些场景下名称可能不存在或重复。使用 ID 是最稳妥的方式。SDK 也支持传入短 ID 或名称,Docker daemon 会自动解析。
|
|
|
|
|
+
|
|
|
|
|
+### stopContainerCmd - 停止容器
|
|
|
|
|
+
|
|
|
|
|
+停止容器采用"优雅退出"机制:首先向容器进程发送 `SIGTERM` 信号,等待一段时间后仍未退出则发送 `SIGKILL` 强制终止:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+/**
|
|
|
|
|
+ * 停止容器(等待容器优雅退出)
|
|
|
|
|
+ * @param containerId 容器 ID
|
|
|
|
|
+ * @param timeoutSeconds 等待时长(秒),超过则强制终止
|
|
|
|
|
+ */
|
|
|
|
|
+public void stopContainer(String containerId, Integer timeoutSeconds) {
|
|
|
|
|
+ dockerClient.stopContainerCmd(containerId)
|
|
|
|
|
+ .withTimeout(timeoutSeconds) // SIGTERM 后等待时间
|
|
|
|
|
+ .exec();
|
|
|
|
|
+ log.info("容器 {} 已停止(等待 {} 秒)", containerId, timeoutSeconds);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### killContainerCmd - 强制停止容器
|
|
|
|
|
+
|
|
|
|
|
+与 `stopContainerCmd` 不同,`killContainerCmd` 直接发送 `SIGKILL` 信号,进程立即终止,不给优雅退出的机会:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+/**
|
|
|
|
|
+ * 强制停止容器(立即发送 SIGKILL)
|
|
|
|
|
+ * @param containerId 容器 ID
|
|
|
|
|
+ */
|
|
|
|
|
+public void killContainer(String containerId) {
|
|
|
|
|
+ dockerClient.killContainerCmd(containerId).exec();
|
|
|
|
|
+ log.info("容器 {} 已被强制停止", containerId);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**思考:** 什么场景下应该用 stop,什么场景下应该用 kill?
|
|
|
|
|
+
|
|
|
|
|
+`stop` 会先给进程发 `SIGTERM`,让应用有机会保存数据、释放资源(比如数据库需要执行清理操作);`kill` 则是立即杀死进程,可能导致数据丢失。生产环境中应该优先使用 `stop`,只有在进程无法响应或严格限时的情况下才使用 `kill`。
|
|
|
|
|
+
|
|
|
|
|
+### removeContainerCmd - 删除容器
|
|
|
|
|
+
|
|
|
|
|
+删除容器时需要考虑两个选项:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+/**
|
|
|
|
|
+ * 删除容器
|
|
|
|
|
+ * @param containerId 容器 ID
|
|
|
|
|
+ * @param force 是否强制删除(容器运行中也删除)
|
|
|
|
|
+ * @param removeVolumes 是否同时删除关联的卷
|
|
|
|
|
+ */
|
|
|
|
|
+public void removeContainer(String containerId, boolean force, boolean removeVolumes) {
|
|
|
|
|
+ dockerClient.removeContainerCmd(containerId)
|
|
|
|
|
+ .withForce(force) // 强制删除运行中的容器
|
|
|
|
|
+ .withRemoveVolumes(removeVolumes) // 删除关联数据卷
|
|
|
|
|
+ .exec();
|
|
|
|
|
+ log.info("容器 {} 已删除", containerId);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### listContainersCmd - 列出容器
|
|
|
|
|
+
|
|
|
|
|
+列出容器对应 `docker ps` 命令。默认只显示运行中的容器,`withShowAll(true)` 则显示所有容器(包括已停止的):
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+/**
|
|
|
|
|
+ * 列出所有容器(包括停止的)
|
|
|
|
|
+ * @return 容器列表
|
|
|
|
|
+ */
|
|
|
|
|
+public List<Container> listContainers() {
|
|
|
|
|
+ // withShowAll(true) 展示所有容器(不只运行中的)
|
|
|
|
|
+ List<Container> containers = dockerClient.listContainersCmd()
|
|
|
|
|
+ .withShowAll(true)
|
|
|
|
|
+ .exec();
|
|
|
|
|
+ log.info("本机共有 {} 个容器", containers.size());
|
|
|
|
|
+ return containers;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`Container` 对象包含的信息:
|
|
|
|
|
+
|
|
|
|
|
+| 字段 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| `getId()` | 容器 ID |
|
|
|
|
|
+| `getNames()` | 容器名称列表(以 `/` 开头) |
|
|
|
|
|
+| `getImage()` | 使用的镜像名称 |
|
|
|
|
|
+| `getState()` | 容器状态(running / exited 等) |
|
|
|
|
|
+| `getStatus()` | 状态描述(如 "Up 2 hours") |
|
|
|
|
|
+| `getPorts()` | 端口映射信息 |
|
|
|
|
|
+| `getLabels()` | 容器标签 |
|
|
|
|
|
+
|
|
|
|
|
+### inspectContainerCmd - 获取容器详细信息
|
|
|
|
|
+
|
|
|
|
|
+与镜像类似,容器也有详查方法。它返回容器的完整配置、网络、挂载信息:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取容器详细信息
|
|
|
|
|
+ * @param containerId 容器 ID
|
|
|
|
|
+ * @return 容器详情
|
|
|
|
|
+ */
|
|
|
|
|
+public InspectContainerResponse inspectContainer(String containerId) {
|
|
|
|
|
+ InspectContainerResponse response = dockerClient.inspectContainerCmd(containerId).exec();
|
|
|
|
|
+ log.info("容器 {} 状态: {}, 名称: {}",
|
|
|
|
|
+ containerId, response.getState().getStatus(), response.getName());
|
|
|
|
|
+ return response;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`InspectContainerResponse` 的关键信息:
|
|
|
|
|
+
|
|
|
|
|
+| 字段 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| `getId()` | 容器 ID |
|
|
|
|
|
+| `getName()` | 容器名称 |
|
|
|
|
|
+| `getState()` | 容器状态(含 Running、Status、StartedAt 等) |
|
|
|
|
|
+| `getConfig()` | 容器配置(镜像、环境变量、命令等) |
|
|
|
|
|
+| `getNetworkSettings()` | 网络配置(端口映射、IP 地址等) |
|
|
|
|
|
+| `getMounts()` | 挂载的卷 |
|
|
|
|
|
+| `getHostConfig()` | 宿主机相关配置 |
|
|
|
|
|
+| `getCreated()` | 创建时间 |
|
|
|
|
|
+
|
|
|
|
|
+### 完整生命周期示例
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public void containerLifecycleDemo(String imageName, String containerName) {
|
|
|
|
|
+ // 1. 创建容器
|
|
|
|
|
+ CreateContainerResponse container = createContainer(imageName, containerName);
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 启动容器
|
|
|
|
|
+ startContainer(container.getId());
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 查询状态
|
|
|
|
|
+ inspectContainer(container.getId());
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 停止容器
|
|
|
|
|
+ stopContainer(container.getId(), 10);
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 删除容器
|
|
|
|
|
+ removeContainer(container.getId(), false, false);
|
|
|
|
|
+ log.info("容器 {} 生命周期演示完成", containerName);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**思考:** 查看 `Container` 的 `getNames()` 返回的数组——为什么是一个数组而不是单个字符串?
|
|
|
|
|
+
|
|
|
|
|
+Docker 的设计中,一个容器可以有多个名称(别名),通常是 `/容器名`。当使用 `--link` 方式连接容器时,会为被连接的容器创建额外的名称。虽然现代 Docker 更推荐使用自定义网络,但 `getNames()` 的设计保留了这种能力。
|
|
|
|
|
+
|
|
|
|
|
+***
|
|
|
|
|
+
|
|
|
|
|
+下一章,我们将讲解容器运维相关 API,包括查看日志、在容器中执行命令、文件复制等操作。
|