Contents

Docker Java API 简介

1. 概述

在本文中,我们将了解另一个成熟的平台特定 API — Java API Client for Docker

在整篇文章中,我们理解了如何连接正在运行的 Docker 守护程序以及 API 为 Java 开发人员提供了哪些类型的重要功能。

2. Maven依赖

首先,我们需要将主要依赖添加到我们的pom.xml文件中:

<dependency>
    <groupId>com.github.docker-java</groupId>
    <artifactId>docker-java</artifactId>
    <version>3.0.14</version>
</dependency>

在撰写本文时,API 的最新版本是 3.0.14。每个版本都可以从GitHub 发布页面Maven 存储库 中查看。

3. 使用 Docker 客户端

DockerClient是我们可以在 Docker 引擎/守护进程和我们的应用程序之间建立连接的地方。

默认情况下,只能通过 unix:///var/run/docker.sock 文件访问 Docker 守护进程。除非另有配置,否则我们可以在本地与侦听 Unix 套接字的 Docker 引擎进行通信。

在这里,我们通过接受默认设置来应用DockerClientBuilder类来创建连接:

DockerClient dockerClient = DockerClientBuilder.getInstance().build();

同样,我们可以分两步打开连接:

DefaultDockerClientConfig.Builder config 
  = DefaultDockerClientConfig.createDefaultConfigBuilder();
DockerClient dockerClient = DockerClientBuilder
  .getInstance(config)
  .build();

由于引擎可以依赖其他特性,因此客户端也可以根据不同的条件进行配置。

例如,构建器接受服务器 URL,也就是说,如果引擎在端口 2375 上可用,我们可以更新连接值

DockerClient dockerClient
  = DockerClientBuilder.getInstance("tcp://docker.blogdemo.com:2375").build();

请注意,我们需要根据连接类型在连接字符串前面加上unix://tcp://

如果我们更进一步,我们可以使用DefaultDockerClientConfig类获得更高级的配置:

DefaultDockerClientConfig config
  = DefaultDockerClientConfig.createDefaultConfigBuilder()
    .withRegistryEmail("[[email protected]](/cdn_cgi/l/email_protection)")
    .withRegistryPassword("blogdemo")
    .withRegistryUsername("blogdemo")
    .withDockerCertPath("/home/blogdemo/.docker/certs")
    .withDockerConfig("/home/blogdemo/.docker/")
    .withDockerTlsVerify("1")
    .withDockerHost("tcp://docker.blogdemo.com:2376").build();
DockerClient dockerClient = DockerClientBuilder.getInstance(config).build();

同样,我们可以使用Properties执行相同的方法:

Properties properties = new Properties();
properties.setProperty("registry.email", "[[email protected]](/cdn_cgi/l/email_protection)");
properties.setProperty("registry.password", "blogdemo");
properties.setProperty("registry.username", "baaldung");
properties.setProperty("DOCKER_CERT_PATH", "/home/blogdemo/.docker/certs");
properties.setProperty("DOCKER_CONFIG", "/home/blogdemo/.docker/");
properties.setProperty("DOCKER_TLS_VERIFY", "1");
properties.setProperty("DOCKER_HOST", "tcp://docker.blogdemo.com:2376");
DefaultDockerClientConfig config
  = DefaultDockerClientConfig.createDefaultConfigBuilder()
    .withProperties(properties).build();
DockerClient dockerClient = DockerClientBuilder.getInstance(config).build();

除非我们在源代码中配置引擎的设置,否则另一种选择是设置相应的环境变量,这样我们就只能考虑在项目中默认实例化DockerClient

export DOCKER_CERT_PATH=/home/blogdemo/.docker/certs
export DOCKER_CONFIG=/home/blogdemo/.docker/
export DOCKER_TLS_VERIFY=1
export DOCKER_HOST=tcp://docker.blogdemo.com:2376

4. 容器管理

API 允许我们在容器管理方面有多种选择。让我们来看看他们中的每一个。

4.1.列出容器

现在我们已经建立了连接,我们可以列出位于 Docker 主机上的所有正在运行的容器:

List<Container> containers = dockerClient.listContainersCmd().exec();

如果显示正在运行的容器不满足需要,我们可以利用提供的选项来查询容器。

在这种情况下,我们显示具有“exited”状态的容器:

List<Container> containers = dockerClient.listContainersCmd()
  .withShowSize(true)
  .withShowAll(true)
  .withStatusFilter("exited").exec()

它相当于:

$ docker ps -a -s -f status=exited
# or 
$ docker container ls -a -s -f status=exited

4.2. 创建一个容器

使用createContainerCmd方法创建容器。我们可以使用以“with”*前缀开头的可用方法*声明更复杂的声明。

假设我们有一个docker create命令,它定义了一个依赖于主机的 MongoDB 容器,该容器在内部侦听端口 27017:

$ docker create --name mongo \
  --hostname=blogdemo \
  -e MONGO_LATEST_VERSION=3.6 \
  -p 9999:27017 \
  -v /Users/blogdemo/mongo/data/db:/data/db \
  mongo:3.6 --bind_ip_all

我们能够以编程方式引导同一个容器及其配置:

CreateContainerResponse container
  = dockerClient.createContainerCmd("mongo:3.6")
    .withCmd("--bind_ip_all")
    .withName("mongo")
    .withHostName("blogdemo")
    .withEnv("MONGO_LATEST_VERSION=3.6")
    .withPortBindings(PortBinding.parse("9999:27017"))
    .withBinds(Bind.parse("/Users/blogdemo/mongo/data/db:/data/db")).exec();

4.3. 启动、停止和终止容器

一旦我们创建了容器,我们就可以分别通过名称或 id 来启动、停止和终止它:

dockerClient.startContainerCmd(container.getId()).exec();
dockerClient.stopContainerCmd(container.getId()).exec();
dockerClient.killContainerCmd(container.getId()).exec();

4.4. 检查容器

inspectContainerCmd方法接受一个String参数,该参数指示容器的名称或 id。使用这种方法,我们可以直接观察容器的元数据:

InspectContainerResponse container 
  = dockerClient.inspectContainerCmd(container.getId()).exec();

4.5. 快照容器

docker commit命令类似,我们可以使用commitCmd方法创建一个新镜像。

在我们的示例中,场景是**,我们之前运行了一个 alpine:3.6 容器,其 id 为*“3464bb547f88”,并在其上安装了git*。**

现在,我们要从容器中创建一个新的镜像快照:

String snapshotId = dockerClient.commitCmd("3464bb547f88")
  .withAuthor("Blogdemo <[[email protected]](/cdn_cgi/l/email_protection)>")
  .withEnv("SNAPSHOT_YEAR=2018")
  .withMessage("add git support")
  .withCmd("git", "version")
  .withRepository("alpine")
  .withTag("3.6.git").exec();

由于我们与git捆绑的新镜像保留在主机上,我们可以在 Docker 主机上搜索它:

$ docker image ls alpine --format "table {{.Repository}} {{.Tag}}"
REPOSITORY TAG
alpine     3.6.git

5. 镜像管理

我们提供了一些适用的命令来管理镜像操作。

5.1.列出镜像

要列出 Docker 主机上所有可用的镜像,包括悬空镜像,我们需要应用listImagesCmd方法:

List<Image> images = dockerClient.listImagesCmd().exec();

如果我们的 Docker 主机上有两个镜像,我们应该在运行时获取它们的Image对象。我们寻找的镜像是:

$ docker image ls --format "table {{.Repository}} {{.Tag}}"
REPOSITORY TAG
alpine     3.6
mongo      3.6

接下来,要查看中间镜像,我们需要明确地请求它:

List<Image> images = dockerClient.listImagesCmd()
  .withShowAll(true).exec();

如果只显示悬空镜像,则必须考虑withDanglingFilter方法:

List<Image> images = dockerClient.listImagesCmd()
  .withDanglingFilter(true).exec();

5.2. 构建镜像

让我们专注于使用 API 构建镜像的方式。buildImageCmd方法从Dockerfile构建 Docker 映像。在我们的项目中,我们已经有一个 Dockerfile,它提供了一个安装了 git 的 Alpine 镜像:

FROM alpine:3.6
RUN apk --update add git openssh && \
  rm -rf /var/lib/apt/lists/* && \
  rm /var/cache/apk/*
ENTRYPOINT ["git"]
CMD ["--help"]

新镜像将在不使用缓存的情况下构建,并且在开始构建过程之前,无论如何,Docker 引擎都会尝试拉取更新版本的alpine:3.6如果一切顺利,我们最终应该会看到具有给定名称 alpine:git 的镜像:

String imageId = dockerClient.buildImageCmd()
  .withDockerfile(new File("path/to/Dockerfile"))
  .withPull(true)
  .withNoCache(true)
  .withTag("alpine:git")
  .exec(new BuildImageResultCallback())
  .awaitImageId();

5.3. 检查镜像

借助 inspectImageCmd方法,我们可以检查有关镜像的低级信息:

InspectImageResponse image 
  = dockerClient.inspectImageCmd("161714540c41").exec();

5.4. 标记镜像

使用docker tag命令向我们的镜像添加标签非常简单,因此 API 也不例外。我们也可以使用tagImageCmd方法实现相同的目的。使用 git 将 id 为161714540c41的 Docker 映像标记到 blogdemo/alpine 存储库中:

String imageId = "161714540c41";
String repository = "blogdemo/alpine";
String tag = "git";
dockerClient.tagImageCmd(imageId, repository, tag).exec();

我们将列出新创建的镜像,它是:

$ docker image ls --format "table {{.Repository}} {{.Tag}}"
REPOSITORY      TAG
blogdemo/alpine git

5.5. 推送镜像

在将镜像发送到注册表服务之前,必须将 docker 客户端配置为与该服务协作,因为使用注册表需要提前进行身份验证。

由于我们假设客户端配置了 Docker Hub,我们可以将blogdemo/alpine镜像推送到 blogdemo DockerHub 账户:

dockerClient.pushImageCmd("blogdemo/alpine")
  .withTag("git")
  .exec(new PushImageResultCallback())
  .awaitCompletion(90, TimeUnit.SECONDS);

我们必须遵守这个过程的持续时间。在示例中,我们等待 90 秒

5.6. 拉取镜像

要从注册表服务下载镜像,我们使用pullImageCmd方法。此外,如果从私有注册表中提取镜像,客户端必须知道我们的凭据,否则该过程会以失败告终。和拉图一样,我们指定一个回调和一个固定的时间来拉取镜像:

dockerClient.pullImageCmd("blogdemo/alpine")
  .withTag("git")
  .exec(new PullImageResultCallback())
  .awaitCompletion(30, TimeUnit.SECONDS);

拉取镜像后检查 Docker 主机上是否存在上述镜像:

$ docker images blogdemo/alpine --format "table {{.Repository}} {{.Tag}}"
REPOSITORY      TAG
blogdemo/alpine git

5.7. 删除镜像

其余的另一个简单函数是removeImageCmd方法。我们可以删除具有短 ID 或长 ID 的镜像:

dockerClient.removeImageCmd("beaccc8687ae").exec();

5.8. 在注册表中搜索

为了从 Docker Hub 中搜索镜像,客户端附带了searchImagesCmd方法,该方法采用一个表示术语的字符串值。在这里,我们探索与Docker Hub 中包含“ Java”的名称相关的镜像:

List<SearchItem> items = dockerClient.searchImagesCmd("Java").exec();

输出返回SearchItem对象列表中的前 25 个相关镜像。

6. 卷管理

如果 Java 项目需要与 Docker 交互以获取卷,我们还应该考虑这一节。简而言之,我们看一下 Docker Java API 提供的卷的基本技术。

6.1.列出卷

包括命名和未命名的所有可用卷都列出:

ListVolumesResponse volumesResponse = dockerClient.listVolumesCmd().exec();
List<InspectVolumeResponse> volumes = volumesResponse.getVolumes();

6.2. 检查卷

inspectVolumeCmd方法是显示卷详细信息的形式。我们通过指定它的短 id 来检查卷:

InspectVolumeResponse volume 
  = dockerClient.inspectVolumeCmd("0220b87330af5").exec();

6.3. 创建卷

API 提供两种不同的选项来创建卷。非参数createVolumeCmd方法创建一个卷,其名称由 Docker 给出:

CreateVolumeResponse unnamedVolume = dockerClient.createVolumeCmd().exec();

与使用默认行为不同,名为withName的辅助方法允许我们为卷设置名称:

CreateVolumeResponse namedVolume 
  = dockerClient.createVolumeCmd().withName("myNamedVolume").exec();

6.4. 删除卷

我们可以使用removeVolumeCmd方法直观地从 Docker 主机中删除一个卷。**需要注意的是,如果某个卷正在从容器中使用,我们将无法删除它。**我们从卷列表中删除卷myNamedVolume

dockerClient.removeVolumeCmd("myNamedVolume").exec();

7. 网络管理

最后一节是关于使用 API 管理网络任务的。

7.1. 列出网络

我们可以使用以 list 开头的传统 API 方法之一显示网络单元列表

List<Network> networks = dockerClient.listNetworksCmd().exec();

7.2. 创建网络

使用createNetworkCmd方法执行相当于docker network create命令。如果我们有三十方或自定义网络驱动程序,除了内置驱动程序之外, withDriver方法可以接受它们。在我们的例子中,让我们创建一个名为blogdemo的桥接网络:

CreateNetworkResponse networkResponse 
  = dockerClient.createNetworkCmd()
    .withName("blogdemo")
    .withDriver("bridge").exec();

此外,使用默认设置创建网络单元并不能解决问题,我们可以申请其他辅助方法来构建高级网络。因此,要使用自定义值覆盖默认子网

CreateNetworkResponse networkResponse = dockerClient.createNetworkCmd()
  .withName("blogdemo")
  .withIpam(new Ipam()
    .withConfig(new Config()
    .withSubnet("172.36.0.0/16")
    .withIpRange("172.36.5.0/24")))
  .withDriver("bridge").exec();

我们可以使用docker命令运行的相同命令是:

$ docker network create \
  --subnet=172.36.0.0/16 \
  --ip-range=172.36.5.0/24 \
  blogdemo

7.3. 检查网络

API 中还介绍了显示网络的低级详细信息:

Network network 
  = dockerClient.inspectNetworkCmd().withNetworkId("blogdemo").exec();

7.4. 删除网络

我们可以使用removeNetworkCmd方法安全地删除具有名称或 id 的网络单元:

dockerClient.removeNetworkCmd("blogdemo").exec();