Dokcer 的远程连接和部署

让远程部署和本地一样简单!

TL;DR

  • Docker 是 C/S 架构的,服务端和客户端可以在不同主机运行
  • 无加密的 TCP 连接配置简单,但不安全
  • TLS 加密的连接需要创建和配置证书,最常用
  • SSH 连接使用方便,但是 IDE 不大兼容,比较局限
  • 命令行、IDEA、VS Code 怎么连接 Docker daemon
  • 一个小 demo,用 Dokcer client 部署 Spring Boot 项目

Docker 的架构

最近在学习用 Docker 来部署项目。我按照这个教程来把 Java 项目部署到了服务器的 Docker 上面。IDEA 的 Dokcer 插件真的是特别方便,配置好之后一键就能部署上去,而且 Windows 本机上也不需要安装几百兆的 Docker Desktop,不用开启 Hyper-V。这是怎么实现的呢?

从 Docker 的官方文档里面可以看到,Docker 使用的是 C/S 架构的系统,分为客户端和服务端。客户端(Docker client)使用命令和服务端(Dokcer daemon,守护进程)进行交互。守护进程负责干重活,比如构建、运行和分发你的容器。客户端和守护进程可以运行在同一台主机,也就是我们常规的安装;也可以让客户端连接到一台远程的守护进程。客户端和守护进程使用 REST API 进行交互,通过 UNIX sockets 或者网络接口进行传输。

Docker的架构 | Docker Documentation

所以我们如果只需要连接远程服务器的 Docker 的话,就没有必要在本地安装完整的 Docker,不需要安装 Docker daemon,只需要有能与 Docker daemon 进行交互的客户端,比如 Docker client,就可以了。

连接 Docker daemon

在默认的设置下,Docker daemon 只监听来自本地的 UNIX socket 连接。

/lib/systemd/system/docker.service 下面可以看到 dockerd,即 Docker daemon 的启动参数:

1
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

我们需要修改这个 systemd 的启动参数来开启外部访问。

使用无加密 TCP 连接

  1. 使用 sudo systemctl edit docker.service 来编辑 docker.service,这会在它的基础上重写配置。

  2. 写上下面的内容,保存文件。

    1
    2
    3
    [Service]
    ExecStart=
    ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375

    上面这个 ExecStart= 的作用是清空原来项目的值,然后用新的值替代。官方文档这么写的,我之前还以为是笔误……

  3. 更新 systemctl 配置和重启 Docker。

    1
    2
    sudo systemctl daemon-reload
    sudo systemctl restart docker.service
  4. 检查 dockerd 是否运行起来

    1
    sudo netstat -lntp | grep dockerd

现在我们已经开放了 TCP 的 2375 端口,只要在执行命令的时候加上参数就可以了:

1
docker -H tcp://docker.beanbang.cn:2375 version

也可以设置环境变量 DOCKER_HOST,这样就不用每次都带参数:

1
2
export DOCKER_HOST=tcp://docker.beanbang.cn:2375
docker version

注意:使用这种连接方式是不安全的,端口并没有加密,这意味者任何人都可以连接你的 Docker daemon,往里面运行容器。虚拟机用还好,云服务器上要是被别有用心的人发现了不是纯白给了。所以加密你的 Docker 端口还是很有必要的。

使用 TLS 加密的 TCP 连接

需要使用 OpenSSL 生成密钥,证书。客户端和守护进程使用这些证书和密钥来进行认证。只要照着官方文档的这篇文章,一步一步做下来就可以了:

Protect the Docker daemon socket | Docker Documentation

生成证书的大致过程是,生成 CA 的密钥和 CA 证书,生成客户端和服务端的密钥;生成客户端和服务端的签名请求,然后 CA 分别对客户端和服务端的签名请求生成证书。

跟着教程一顿操作之后,可以得到下面的几个文件。

文件 描述
ca-key.pem CA 密钥
ca.pem CA 证书
server-key.pem 服务端密钥
server-cert.pem 服务端证书
key.pem 客户端密钥
cert.pem 客户端证书

然后我们接着修改 docker.service。虽然可以这么写:

1
sudo systemctl edit docker.service
1
2
3
4
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --tlsverify --tlscacert=/home/ubuntu/certificate/ca.pem --tlscert=/home/ubuntu/certificate/server-cert.pem --tlskey=/home/ubuntu/certificate/server-key.pem \
-H tcp://0.0.0.0:2376 -H fd://

不过,这样大长串的参数很不直观。下面我们换成使用 Docker 提供的另一种配置 dockerd 的方式,就是 daemon.json 文件。

1
sudo vim /etc/docker/daemon.json
1
2
3
4
5
6
7
8
9
10
{
"hosts": [
"fd://",
"tcp://0.0.0.0:2376"
],
"tlsverify": true,
"tlscacert": "/home/ubuntu/certificate/ca.pem",
"tlscert": "/home/ubuntu/certificate/server-cert.pem",
"tlskey": "/home/ubuntu/certificate/server-key.pem"
}

原本的 docker.service 只保留一点东西:

1
sudo vim /etc/docker/daemon.json
1
2
3
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd

然后重启 Docker 就可以了。

注意:Docker 在启动的时候会同时使用 systemddaemon.json 文件。如果两个文件的配置项冲突了,会造成无法启动。所以配置要么写 docker.service 里面,要么写 daemon.json 里面,不要两个都写,防止出现问题。

注意2:按照惯例,不加密的 TCP 连接使用 2375 端口,TLS 加密的 TCP 连接使用 2376 端口。

运行起来后,客户端连接需要 ca.pem,cert.pem,key.pem 三个文件。我们可以把它们从主机上拷贝下来,放在自己用户目录的 .docker 目录下,这是证书文件的默认查找目录。

纯命令:

1
2
docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem \
-H=docker.beanbang.cn:2376 version

使用环境变量:

1
2
3
export DOCKER_HOST=tcp://docker.beanbang.cn:2376 DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=$HOME/.docker # 证书在默认查找目录可以不用指定
docker version

使用 SSH 连接

从 Docker 的 18.09 版本开始,Docker client 支持通过 SSH 来连接远程 daemon 了。但是到了今天(2020年8月),我能直接下载到的最新 Dokcer client for Windows 版本是 17.09。IDEA 和 VS Code 这些 IDE 我也试过,同样不能使用 SSH 协议来连接。所以这种方法虽然特别方便,但是目前还是比较有局限性的。

要使用 SSH 协议来连接 Docker daemon,你可以使用 Linux 或者 WSL 安装新版的 docker-cli。

服务端不需要任何配置,只需要在客户端执行命令:

1
docker -H ssh://ubuntu@192.168.43.220 version

注意,这边主机名只能使用 IP 地址,不能使用域名。

这种方式是使用 SSH 密钥登录和认证的,如果你没有使用密钥来登录主机,可以现配置一个:

  1. 检查一下你的用户目录下的 .ssh 文件夹下面有没有 id_rsaid_rsa.pub 文件。如果你在用 Git,可能已经创建过密钥了。

    没有的话就创建:

    1
    ssh-keygen -t rsa -C "youremail@example.com"
  2. 把你的公钥添加到远程主机:

    1
    ssh-copy-id ubuntu@192.168.43.220

    Windows 没有这个命令的话就手动把 id_rsa.pub 里面的内容添加到远程主机用户的 ~/.ssh/authorized_keys 里面。

这就行了。同样可以把这个连接串设置到环境变量中,方便使用:

1
export DOCKER_HOST=ssh://ubuntu@192.168.43.220

客户端配置

命令行

Windows

直接安装 Docker desktop 是可以的,安装完成之后就可以使用 docker 命令了。但是假如我们本地只想用客户端的功能,不想安装它那厚重的 Docker Engine,可以只下载几个二进制可执行文件。下面是下载地址:

Docker CLI Windows:https://download.docker.com/win/static/stable/x86_64/

Docker compose:https://github.com/docker/compose/releases

下载完成后,重命名成 docker.exedocker-compose.exe,丢进设置过 Path 环境变量的文件夹里就可以了。

Linux

Linux 客户端只需要安装 docker-ce-cli。这里推荐一下清华 tuna 镜像源,提供了 docker-ce 的镜像和安装教程。

IntelliJ IDEA

IDEA 里面图形化的设置很方便。如果使用了加密的 TCP,要选定证书的文件夹,并且 URL 要写成 https://docker.beanbang.cn:2376 这样的以 https 开头的形式。

IDEA 添加 Docker 连接

VS Code

首先安装 Docker 插件,然后修改配置文件。

1
2
3
4
5
6
// settings.json
{
"docker.host": "tcp://docker.beanbang.cn:2376",
"docker.tlsVerify": "1",
"docker.certPath": "C:\\Users\\GYM\\.docker"
}
VS Code 配置连接 Docker

试试看!

连接上远程 Docker 之后,我们就可以尝试远程部署了。这是一个例子,在 Windows 10 上用 Dokcer CLI 部署一个 Spring Boot 项目到远程服务器上。我使用的是 TLS 加密的 TCP,就是上面的提到的配置。

环境变量:

1
2
3
$ set | findstr DOCKER
DOCKER_HOST=tcp://docker.beanbang.cn:2376
DOCKER_TLS_VERIFY=1

在 Spring initializr 上面创建项目并下载到本地。

使用 Spring initializr 来初始化项目

添加一个控制器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.beanbang.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

@GetMapping("/hello")
public String hello(@RequestParam(defaultValue = "world") String name) {
return String.format("Hello, %s!", name);
}
}

执行 mvnw package 命令打包。会在 target 目录下生成 rest-test-0.0.1-SNAPSHOT.jar 文件。

在项目的目录下创建 Dockerfile 文件:

1
2
3
4
FROM openjdk:8-jre
ADD target/rest-test-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]

然后可以开始部署了。

创建容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ docker build -t rest-test .
Sending build context to Docker daemon 16.78MB
Step 1/4 : FROM openjdk:8-jre
---> 2e2653debbe9
Step 2/4 : ADD target/rest-test-0.0.1-SNAPSHOT.jar app.jar
---> a80c846520e1
Step 3/4 : EXPOSE 8080
---> Running in 68400e62b5cd
Removing intermediate container 68400e62b5cd
---> fb69206fbe08
Step 4/4 : ENTRYPOINT ["java","-jar","/app.jar"]
---> Running in 3e03c7f8804e
Removing intermediate container 3e03c7f8804e
---> df0e8cc51568
Successfully built df0e8cc51568
Successfully tagged rest-test:latest
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.

运行容器:

1
2
$ docker run --name rest-demo -p 8081:8080 -d rest-test
dd164ec7f237ba8736f2253ab2b226a66700733ed85e52cacee52345ec28f71b
显示容器日志和测试接口

这样,一个简单的 Java Web 服务就搭建起来了。在执行 docker build 命令的时候,Docker 会把构建所需要的文件上传到服务端的 daemon,然后 daemon 负责创建镜像和后续的容器运行等流程。


参见: