你也许知道如何在一台安装了 docker 的机器上创建、停止、删除容器。但是如果发号施令的程序和 docker 不在同一台机器上该怎么办?
本文将介绍如何开启 docker api 远程调用、使用 http api 或者 java sdk 操作另一台机器上的 docker、以及如何确保 docker api 不被他人调用。
默认 docker 只允许本机使用docker命令进行操作,我们现在来开启 http api 调用,使得其他机器可以通过 http 请求来操作 docker。
注意:这种方法不对调用者权限进行校验,也就是说互联网上任何一个人都可以控制你的 docker。我们这里为了快速体验临时使用这种方法,长期使用请务必使用 tls 来鉴权,具体方法下文会介绍。
vim /lib/systemd/system/docker.service 编辑 docker 启动命令,找到 service 模块下的 ExecStart=xxx 这一行,在其末尾添加`-H=tcp://0.0.0.0:2375
其中,0.0.0.0 意为允许任何来源的访问,docker 将会在 2375 端口监听 http 请求。(记得开放安全组或者防火墙)
重启 docker:
systemctl daemon-reload
sudo service docker restart
然后你就可以通过 http 请求远程操控 docker 了,例如通过curl http://ip:2375/images/json 来获取镜像列表。同样你也可以通过各种编程语言的 sdk 来发起请求,详见:https://docs.docker.com/engine/api/sdk/
使用 java sdk 远程调用 docker api
我们使用 star 最多的 sdk:https://github.com/docker-java/docker-java
我的使用的 maven 依赖如下,其他版本可能会报错:
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>3.3.4</version>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java-transport-httpclient5</artifactId>
<version>3.3.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
假设我有一台安装了 docker 的 ubuntu 服务器,下面这段代码将初始化一个 dockerClient,然后通过远程调用获取 docker 的镜像列表。
DockerClientConfig config = DefaultDockerClientConfig
.createDefaultConfigBuilder()
.withDockerHost("tcp://服务端 ip 地址:2375")
.withDockerTlsVerify(false)
.build();
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
.dockerHost(config.getDockerHost())
.sslConfig(config.getSSLConfig())
.maxConnections(100)
.connectionTimeout(Duration.ofSeconds(30))
.responseTimeout(Duration.ofSeconds(45))
.build();
dockerClient = DockerClientImpl.getInstance(config, httpClient);
dockerClient.listImagesCmd().exec().forEach(System.out::println);
现在我们对 docker api 远程调用有了一个基本认识,接下来我们将使用 tls 自签证书为 docker api 开启鉴权,只有持有配对证书的客户端和服务端才可以通信,防止被他人调用。
找一个目录存放等下要生成的证书文件,本文以/root/docker/cert/ 为例。
进入这个目录,逐一运行以下命令:
# 这里要你设定一个密码,找个地方存下来,等下运行其他命令还要再输入这个密码
openssl genrsa -aes256 -out ca-key.pem 4096
# 这一步的问题可以不回答,一路回车即可
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
openssl genrsa -out server-key.pem 4096
# 把$HOST替换成运行着 docker 的服务器的 ip 或者域名
openssl req -subj "/CN=$HOST" -sha256 -new -key server-key.pem -out server.csr
# 把$HOST替换成运行着 docker 的服务器的域名,把$IP替换成 ip。二者至少填一个,不填的就删掉,总之你最终要通过 ip 远程调用就填 ip,通过域名调用就填域名
echo subjectAltName = DNS:$HOST,IP:$IP >> extfile.cnf
echo extendedKeyUsage = serverAuth >> extfile.cnf
openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out server-cert.pem -extfile extfile.cnf
openssl genrsa -out key.pem 4096
openssl req -subj '/CN=client' -new -key key.pem -out client.csr
echo extendedKeyUsage = clientAuth > extfile-client.cnf
openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out cert.pem -extfile extfile-client.cnf
# 可选,删除以后没用的配置文件
rm -v client.csr server.csr extfile.cnf extfile-client.cnf
现在这个目录里应该会有一些 .pem 文件,其中,ca.pem, server-cert.pem, server-key.pem
是服务端要用的,ca.pem, cert.pem, key.pem
是客户端要用的。
vim /lib/systemd/system/docker.service
编辑 docker 启动命令,找到我们之前编辑过的 service 模块下的ExecStart=xxx 这一行,把之前的-H tcp://0.0.0.0:2375改成-H tcp://0.0.0.0:2376 ,(2376是 docker 在启用 https 情况下的惯用端口),然后再在后面追加--tlsverify --tlscacert=/path/to/ca.pem tlscert=/path/to/server-cert.pem --tlskey=/path/to/server-key.pem
,以指定启用 ssl 及其用到的证书文件路径(根据自己情况修改)
下面的代码示例指定了服务端所需要的证书文件,并监听 2376 端口接收远程调用,允许所有 ip 来源(但是客户端必须持有配对的证书)
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2376 --tlsverify --tlscacert=/root/docker/cert/ca.pem --tlscert=/root/docker/cert/server-cert.pem --tlskey=/root/docker/cert/server-key.pem
重启 docker:
systemctl daemon-reload
sudo service docker restart
我们从服务端把ca.pem, cert.pem, key.pem这三个文件拷贝到客户端的一个目录里放下,然后修改客户端的代码:
DockerClientConfig config = DefaultDockerClientConfig
.createDefaultConfigBuilder()
.withDockerHost("tcp://服务端 ip 地址:2376")
.withDockerTlsVerify(true)
.withDockerCertPath("你存放客户端证书的目录路径")
.build();
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
.dockerHost(config.getDockerHost())
.sslConfig(config.getSSLConfig())
.maxConnections(100)
.connectionTimeout(Duration.ofSeconds(30))
.responseTimeout(Duration.ofSeconds(45))
.build();
dockerClient = DockerClientImpl.getInstance(config, httpClient);
dockerClient.listImagesCmd().exec().forEach(System.out::println);
如果你能够正常获取服务端的 docker 镜像列表,说明你成功地实现了安全远程调用 docker api
本文介绍了如何开启 docker api 远程调用,并用 java sdk 控制远程 docker 服务器,以及如何通过自签 tls 证书来确保 docker api 不被他人调用
https://docs.docker.com/engine/security/protect-access/#use-tls-https-to-protect-the-docker-daemon-socket
https://www.youtube.com/watch?v=cjm_NqteLLA&ab_channel=CodeWithRajRanjan