适用于Java开发人员的Docker:基于HTTP / REST的Docker

本文是我们学院课程中名为《 面向Java开发人员的Docker教程 》的一部分。

在本课程中,我们提供了一系列教程,以便您可以开发自己的基于Docker的应用程序。 我们涵盖了广泛的主题,从通过命令行的Docker到开发,测试,部署和持续集成。 通过我们简单易懂的教程,您将能够在最短的时间内启动并运行自己的项目。 在这里查看

1.简介

从本教程的前面的部分中,我们已经知道Docker不仅具有出色的命令行工具,而且还公开了功能丰富的Docker Engine API 。 到目前为止,已经为Go和Python语言提供了官方支持的客户端。 当然,对于Java开发人员来说,这不是一个令人鼓舞的消息,但是隧道尽头有一个亮点。

很久以前, Spotify开始了自己的旅程,为Docker Engine API开发Java客户端(更广泛地称为Spotify Docker Client )。 此后,该项目一直在积极维护中,以跟随Docker / Engine API领域中的所有最新发展,并且是当今基于JVM的应用程序的实际选择。 它确实可以很好地完成工作,但您需要注意一个警告:因为这是一项社区工作,而不是Docker的官方Java客户端,所以花一些时间才能赶上Docker频繁发布的所有变化。

在本教程的这一部分中,我们将介绍一些关键的Docker Engine API,并演示如何使用Spotify Docker Client从Java应用程序中使用这些API

2.版本控制

在开始之前,我们必须了解Docker Engine API版本,以及如何匹配Docker版本及其支持的API版本。 我们需要docker命令行工具的version命令。

$ docker version

Client:
 Version:      17.06.1-ce
 API version:  1.30
 Go version:   go1.8.3
 Git commit:   874a737
 Built:        Thu Aug 17 22:48:20 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.06.1-ce
 API version:  1.30 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   874a737
 Built:        Thu Aug 17 22:54:55 2017
 OS/Arch:      linux/amd64
 Experimental: false

输出非常简单。 我们可以很容易地发现我们的Docker版本是17.06.1-ce ,支持的API版本是1.30 。 由此,让我们将对Docker Engine API v1.30文档引用添加为书签, 并套上袖子。

3.入门

首先,我们需要将各自的Spotify Docker Client依赖项引入我们的项目。 对于Apache Maven ,这很容易:

<dependency>
	<groupId>com.spotify</groupId>
	<artifactId>docker-client</artifactId>
	<version>8.9.1</version>
</dependency>

Docker生态系统中的其他所有内容一样, Engine API的发展非常快,因此强烈建议您依赖最新的Spotify Docker Client版本。 截至目前, 8.9.1是我们可以使用的最新版本。

太棒了,依赖存在,因此我们可以继续创建DockerClient类的实例。 有两种方法可以做到这一点,并且实质上,这实际上取决于您使用的是哪个操作系统(在开发,持续集成或生产环境中)。

最快,最安全和最可移植的方法可能是使用DefaultDockerClient.fromEnv方法,该方法将从环境变量中收集所需的详细信息,并且可在一系列Docker安装中使用,包括较早的Mac和Windows系统,而本地不支持Docker (诸如Docker Toolboxboot2docker之类的东西)。

final DockerClient client = DefaultDockerClient
	.fromEnv()
	.build();

在这里,我们仅假设已使用默认的Docker配置,但是您可以完全控制自定义它,包括连接URI,注册表身份验证,连接池等。重要的是要记住,完成客户端实例后,请关闭它,例如:

client.close();

很好,我们有Docker客户端实例。 调用众多Docker Engine API是一个单一的切入点,让我们看看我们可以用它完成什么。

4.图片

我们要看的第一个引擎APIImages APISpotify Docker客户端通过一系列方法支持其大多数功能。 为了说明这一点,让我们从教程的上一部分中借用Dockerfile并将其内容存储在src/main/resources文件夹中,文件名为Dockerfile

FROM openjdk:8u131-jdk-alpine
CMD ["java", "-version"]

现在,让我们使用Spotify Docker Client从其构建映像:

final URL dockerfile = getClass().getResource("/");
		
final String imageId = client.build(Paths.get(dockerfile.toURI()), 
	BuildParam.name("base:openjdk-131-jdk"));

容易,但是我们如何检查图像是否在那里? 我们的老伙计泊坞窗命令行工具进入脑海的第一,但为什么不使用Spotify的码头工人的客户端是什么?

final ImageInfo imageInfo = client.inspectImage(imageId);

很好,如何获取所有可用图像?

final List allImages = client.listImages();

如果我们需要在图像上添加其他标签怎么办?

client.tag(imageId, "openjdk");

也可以获取图像历史记录,例如:

final List history = client.history(imageId);

如果我们不再需要该图像,可以很容易地将其删除:

final List removedImages = client.removeImage(imageId, true, false);

我们还可以在Docker Hub注册表中搜索图像。 例如,让我们查找所有可用的JDK映像:

final List jdkImages = client.searchImages("jdk");

要与注册中心交互,有拉和推方法。 第一个从注册表中获取图像,而第二个则将其上载到注册表。

client.pull("base:openjdk-131-jdk");
client.push("base:openjdk-131-jdk");

为了获得与图像操作有关的更多详细信息,请查阅官方的Spotify Docker Client文档

5.容器

我们将要讨论的下一个引擎APIContainers API ,从功能角度来看,它可以说是最丰富的API

为了使用例更加现实,让我们从mysql:8.0.2镜像中生成MySQL容器实例,传递一些环境变量并公开至少一个端口。

final ContainerCreation container = client.createContainer(ContainerConfig
	.builder()
	.image("mysql:8.0.2")
	.env(
		"MYSQL_ROOT_PASSWORD=p$ssw0rd", 
		"MYSQL_DATABASE=my_app_db"
	)
	.exposedPorts("3306")
	.hostConfig(
		HostConfig
			.builder()
			.portBindings(
				ImmutableMap.of(
					"3306", 
					ImmutableList.of(
						PortBinding.of("0.0.0.0", 3306)
					)
				)
			)
			.build()
	)
	.build()
);

为了使容器创建成功,我们需要在本地提供mysql:8.0.2 。 如果不确定是否有一个,最好在使用前先拉出图像。

client.pull("mysql:8.0.2");

此时,容器已创建但尚未启动,因此让我们运行它:

client.startContainer(container.id());

容器启动并运行后,可以完成很多事情。 我们可以通过检查获得有关容器的所有详细信息:

final ContainerInfo info = client.inspectContainer(container.id());

非常简单,但是如果我们需要从容器中捕获日志或标准输出,该怎么办? 这可以通过附加到正在运行的容器来实现,例如:

client
	.attachContainer(container.id(), AttachParameter.values())
	.attach(System.out, System.err, false);

需要注意的是,最后一个附加调用将阻塞调用线程,等待容器的输出。 这可能不是我们大多数人所期望的,因此我们最好将调用卸载到专用线程。

executor.submit(
        () -> {
		client
			.attachContainer(container.id(), AttachParameter.values())
			.attach(System.out, System.err, false);
		return null;
	});

您可以通过暂停/取消暂停来控制容器中进程的状态,例如:

client.pauseContainer(container.id());
client.unpauseContainer(container.id());

还提供了一些API,以获取有关容器的运行时统计信息或获取正在运行的容器进程的列表。

final ContainerStats stats = client.stats(container.id());
final TopResults top = client.topContainer(container.id());

最后,有一个专用的API来获取和记录容器的日志,这与我们上面讨论的attachContainer方法非常相似。

client
    .logs(container.id(), LogsParam.stdout(), LogsParam.stderr(), LogsParam.tail(10))
    .attach(System.out, System.err, false);

一旦不再需要该容器,则可以将其停止并立即终止。

client.stopContainer(container.id(), 5 /* wait 5 seconds before killing */);
client.removeContainer(container.id());

为了获得与容器操作有关的更多详细信息,请查阅官方的Spotify Docker Client文档

因此,我们知道如何在Spotify Docker Client的帮助下使用纯Docker Engine API处理图像和容器。 让我们谈论同样重要的主题,例如管理端口,网络,容量和资源限制。

6.港口

每个容器都可以通过镜像Dockerfile指令或通过createContainer方法调用的参数公开要在运行时监听的端口。 Spotify Docker Client不提供获取端口及其映射的专用方法,但是可以从ContainerInfo轻松提取此信息,例如:

final ContainerInfo info = client.inspectContainer(container.id());
final ImmutableMap<String, List> mappings = info.hostConfig().portBindings();

实际上,一种更可靠的方法是使用info.networkSettings().ports()因为该容器可能不一定使用端口映射,而只暴露某些端口。

final ContainerInfo info = client.inspectContainer(container.id());
final ImmutableMap<String, List> mappings = info.networkSettings().ports();

7.卷

在本教程上一部分中,我们了解了作为持久化容器使用的数据的首选机制。 Spotify Docker客户端通过一系列方法全面包装了Volumes API 。 让我们开始创建一个新的卷:

final Volume volume = client.createVolume();

可以获取有关现有卷的元数据:

final Volume info = client.inspectVolume(volume.name());

另外,您可以获取所有可用的卷(并在必要时对其进行过滤),例如:

final VolumeList volumes = client.listVolumes();

一旦不再需要该卷,则可以将其删除。

client.removeVolume(volume.name());

为了获得与卷操作有关的更多详细信息,请查阅官方的Spotify Docker Client文档

8.网络

很难高估Docker中 用户定义的网络的重要性,因为它们在容器之间如何通信方面起着至关重要的作用。 毫不奇怪, Spotify Docker Client几乎全力支持Networks API

创建新网络只是一种方法:

final NetworkCreation network = client.createNetwork(
	NetworkConfig			
		.builder()
		.name("test-network")
		.driver("bridge")
		.build()
	);

如果您知道网络标识符,则可以获取其元数据:

final Network info = client.inspectNetwork(network.id());

如果您不知道网络标识符或仅对查询(和过滤)所有网络感兴趣,则也可以这样做:

final List networks = client.listNetworks();

一旦不再需要网络,就可以轻松删除它。

client.removeNetwork(network.id());

Spotify Docker客户端为任何正在运行的(或刚刚创建的)容器提供了连接到网络或与网络断开连接的方法,例如:

final ContainerCreation container = client.createContainer(ContainerConfig
		.builder()
		.image("mysql:8.0.2")
		.env(
			"MYSQL_ROOT_PASSWORD=p$ssw0rd", 
			"MYSQL_DATABASE=my_app_db"
		)
		.exposedPorts("3306")
		.build()
	);

client.startContainer(container.id());
client.connectToNetwork(container.id(), network.id());

要么:

client.disconnectFromNetwork(container.id(), network.id());

到目前为止, 官方的Spotify Docker Client文档尚未包括专门用于网络操作的部分。

9.资源限制

Spotify Docker客户端还支持动态调整容器配置(主要,资源限制,例如内存,CPU或块I / O)的功能,例如:

final ContainerUpdate update = client.updateContainer(container.id(), 
	HostConfig
		.builder()
		.memory(268435456L /* limit to 256Mb */)
		.build()
	);

10.入门

为了完成本节,最好有一个实际的示例,在此示例中,到目前为止我们所看过的所有API都可以无缝地协同工作。 正如我们一直在使用MySQL一样 ,作为Java(且不仅是)应用程序的关系数据存储,这是非常受欢迎的选择,因此端到端的演示将基于它。 那么我们的目标是什么?

  1. 使用mysql:8.0.2 image创建一个容器实例
  2. 公开TCP端口3306并将其绑定到随机主机端口(这样我们就可以根据需要运行任意数量的容器,而不会出现端口冲突)
  3. 确保容器已启动,并且MySQL服务器进程已准备就绪,可以处理我们的查询
  4. 使用适用于MySQL的JDBC驱动程序连接到MySQL实例并列出所有目录(数据库)

听起来需要做很多工作,但是…可能我们了解了Spotify Docker Client ,所以让我们一次迈出一步来实现奇迹。

final DockerClient client = DefaultDockerClient
	.fromEnv()
	.build();

// Pull the image first
client.pull("mysql:8.0.2");
		
// Create container
final ContainerCreation container = client.createContainer(ContainerConfig
	.builder()
	.image("mysql:8.0.2")
	.env(
		"MYSQL_ROOT_PASSWORD=p$ssw0rd", 
		"MYSQL_DATABASE=my_app_db"
	)
	.exposedPorts("3306")
	.healthcheck(
		Healthcheck
			.create(
				// MySQL image doesn't have `nc` available
				Arrays.asList(
					"CMD-SHELL", "ss -ltn src :3306 | grep 3306"
                  	), 
			 	5000000000L, /* 5 seconds, in nanoseconds */ 
			 	3000000000L, /* 3 seconds, in nanoseconds */
				5
			)
	)
	.hostConfig(
		HostConfig 
			.builder()
			.portBindings(
				ImmutableMap.of(
					"3306", 
					ImmutableList.of(
						PortBinding.of("0.0.0.0", 0 /* use random port */)
					)
				)
			)
			.build()
	)
	.build()
);

除了可能要进行健康检查的部分之外,此代码段应该看起来很熟悉。 我们为什么需要它? 好吧,如果Docker不仅告诉我们容器启动的时间,还告诉容器内的应用程序何时完全运行,那将是很棒的。 这正是健康检查的目的。 对于MySQL而言 ,本书的秘诀是验证是否有进程在监听3306端口,而这正是我们在这里使用ss -ltn src :3306 | grep 3306 ss -ltn src :3306 | grep 3306 shell命令。

// Start the container
client.startContainer(container.id());
			
// Inspect the container's health
ContainerInfo info = client.inspectContainer(container.id());		
while (info.state().health().status().equalsIgnoreCase("starting")) {
	// Await for container's health check to pass or fail
	Thread.sleep(1000);
			
	// Ask for container status
	info = client.inspectContainer(container.id());

	// Along with health, better to check the container status as well
	if (info.state().status().equalsIgnoreCase("exited")) {
		LOG.info("The container {} has exited unexpectedly ...", container.id());
		break;
	}
}

上面的这段代码也没什么复杂的。 我们会定期轮询容器并检查其健康状况,我们可能在此处获得的值是startinghealthyunhealthy 。 请注意,我们还咨询了容器的状态,因为在某些情况下,运行状况检查可能无法反映容器的实际状态。

// Check if container is healthy
if (info.state().health().status().equalsIgnoreCase("healthy")) {
    // ...
}

Docker向我们报告该容器运行状况良好之后 ,就可以打开与MySQL实例的JDBC连接了。 但是在此之前,我们必须弄清楚要使用哪个端口。

final PortBinding binding = info
	.networkSettings()
	.ports()
	.get("3306/tcp")
	.get(0);
			
final int port = Integer.valueOf(binding.hostPort());

有了它,让我们将端口直接传递给JDBC连接字符串,我们就快到了! 我们只需要输入难题的最后一部分,即要连接的主机的名称。 还是应该是localhost ? 答案是:确实取决于。 在大多数具有本地Docker支持的操作系统上, localhost是非常安全的选择。 但是,最好通过调用client.getHost()来问Spotify Docker Client使用什么才是合适的localhost ,它可能不会一直返回localhost (例如在boot2docker的情况下)。

final String url = String.format(
    "jdbc:mysql://%s:%d/my_app_db?user=root&password=p$ssw0rd&verifyServerCertificate=false", 
        client.getHost(), port);

try (Connection connection = DriverManager.getConnection(url)) {
	try(ResultSet results = connection.getMetaData().getCatalogs()) {
		while (results.next()) {
			LOG.info("Found catalog '{}'", results.getString(1));
		}	
	}
} catch (SQLException ex) {
	LOG.error("MySQL connection problem", ex);
}

太棒了,如果一切正常(应有的话),您将在控制台输出中看到类似的内容(请注意,我们的数据库my_app_db也位于此处):

...
10:25:19.439 [main] INFO  Found catalog 'information_schema'
10:25:19.439 [main] INFO  Found catalog 'my_app_db'
10:25:19.439 [main] INFO  Found catalog 'mysql'
10:25:19.439 [main] INFO  Found catalog 'performance_schema'
10:25:19.439 [main] INFO  Found catalog 'sys'

最后但并非最不重要的一点是,请不要忘记终止容器并在最后关闭Spotify Docker Client实例。

// Stop the container
client.stopContainer(container.id(), 5);
client.removeContainer(container.id());		
client.close();

我们完成了! 您能仅使用Docker Engine API来完成所有这些工作吗? 当然,但是为此您需要编写的支持Java代码的数量会让您感到不愉快。

11.结论

公平地讲, Docker Engine API只是一组REST(ful)服务 ,尽管非常丰富。 当然,任何人都可以使用通用的HTTP客户端从任何Java应用程序访问它们。 那怎么办?

我个人将强调明确的合同,可维护性和类型安全性。 您将拥有一个稳定的基于Java的API,并具有严格的功能和期望,而无需处理URI,查询或路径参数,用于请求和响应有效负载的原始JSON

稍后我们将看到, Spotify Docker Client为自动化和高级测试技术奠定了坚实的基础,尤其是在Java开发的上下文中。 感谢Spotify回报社区!

12.接下来是什么

在下一节中,我们将讨论将构建管道迁移到Docker的方法 ,这样就消除了在每个主机或每个环境中复制和维护工具的需求。

完整的项目资源可供下载

翻译自: https://www.javacodegeeks.com/2017/10/docker-java-developers-docker-http-rest.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值