抬头仰望星空,是否能发现自己的渺小。

伪斜杠青年

人们总是混淆了欲望和理想

Portainer 使用 TLS 加密连接远端节点上的 Docker

背景

Docker 环境分布:

  • Mac 上一个 Docker Desktop
HOST:lckiss-mac.com
Desktop Version 3.5.1 (3.5.1.7)
Client: Cloud integration: 1.0.17
Version: 20.10.7
  • 树莓派上一个 Docker Engine
HOST:lckiss-docker.com
Client: Docker Engine - Community
Version: 20.10.7

需求:将树莓派上的 Docker 以 Endpoint 的形式添加到 Mac 端的 Portainer 中进行统一管理。

普通连接

此方法适用于局域网内部,或者仅个人使用的网络环境中。主要步骤就是在需要被管理的主机中将启动命令追加上一个端口暴露,拿自己的举例。

1. 登录树莓派,编辑 Docker 启动命令:

vi /usr/lib/systemd/system/docker.service

2. 找到 ExecStart 字段:

#默认
#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

改为(追加 -H tcp://0.0.0.0:2376 ):

ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H fd:// --containerd=/run/containerd/containerd.sock

3. 重启 docker :

systemctl daemon-reload && systemctl restart docker

至此树莓派的修改结束.

随后在 MacOS 上登录 Portainer ,并找到 Endpoints 选项,点击 Add endpoint。配置项有三个:

Name:给命个名,比如 Rpi 。
Endpoint URL:host 和 ip 选一个填了就行。树莓派处于内网环境,host 和 ip 在路由做了关联,就直接填了 HOST。
Public IP:这个是在 Portainer 容器列表中点击暴露的端口时自动跳转用到的,IP 或者 HOST 都行,追加上容器端口可以访问到容器即可。

如果这个 Portainer 和 远端的 docker 在同一个网段 则点击连接即可,很可惜我不是(应该没人会是),所以需要特殊处理,后面再说。

TLS 连接

对于一个人使用的内网,其实可以随意暴露不必管连接是否加密,但对于外网,不加密的连接就等于将主机送给别人。但 Docker 的 TLS 极其繁琐,需要一系列的证书、私钥生成,在 Mac 上或者 Linux 上生成都可,Windows 应该是不行。

服务器证书

  • 新建一个目录,因为生成的文件太多了
mkdir tls
cd tls
  • 生成根证书 RSA 私钥,过程中需要设置密码,待会儿需要用到,不要忘记。
openssl genrsa -aes256 -out ca-key.pem 4096
  • 生成 ca 证书(这里指定证书有效期365天,根证书 HOST 指定为 lckiss-docker.com ,以自己的私钥签发的证书,也就是自己给自己签发证书)。
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -subj "/CN=lckiss-docker.com" -out ca.pem
  • 生成服务器私钥
openssl genrsa -out server-key.pem 4096

此时目录结构为:

openssl req -subj "/CN=lckiss-docker.com" -sha256 -new -key server-key.pem -out server.csr
  • 生成服务器证书
openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem
out:
 Signature ok
 subject=/CN=lckiss-docker.com 
 Getting CA Private Key
 Enter pass phrase for ca-key.pem: 

服务器的证书告一段落,用自己的私钥创建了一个 ca 根证书,再用 ca 证书签发了一个服务器证书。依次为根证书私钥、ca 证书、服务器证书私钥、服务器证书

客户端证书

  • 生成客户端证书私钥
openssl genrsa -out key.pem 4096
  • 生成客户端证书请求(host 为 lckiss-mac.com)
openssl req -subj '/CN=lckiss-mac.com' -new -key key.pem -out client.csr
  • 使用最开始的 ca 证书来生成客户端证书(需要输入 ca 证书密码)
openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem \
  -CAcreateserial -out cert.pem
  • 删除无用文件
rm -v client.csr server.csr ca.srl 

密钥生成完了,主要是生成了这两个文件:

Docker TLS 证书配置

将下列文件上传 docker 主机(这里指树莓派)的 root 目录

1. 配置 docker 启动文件,附带上 TLS 相关证书配置

vi /usr/lib/systemd/system/docker.service

2. 找到 ExecStart 字段:

#默认
#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

改为:

ExecStart=/usr/bin/dockerd --tlsverify --tlscacert=/root/tls/ca.pem --tlscert=/root/tls/server-cert.pem --tlskey=/root/tls/server-key.pem -H tcp://0.0.0.0:2376 -H fd:// --containerd=/run/containerd/containerd.sock

也就是追加了这部分:

--tlsverify --tlscacert=/root/tls/ca.pem --tlscert=/root/tls/server-cert.pem --tlskey=/root/tls/server-key.pem -H tcp://0.0.0.0:2376

3. 重启 docker :

systemctl daemon-reload && systemctl restart docker

随后在 MacOS 上登录 Portainer ,并找到 Endpoints 选项,点击 Add endpoint。与普通连接不同的是,需要开启 TLS,然后上传 ca 证书,客户端证书,客户端证书私钥,点击添加即可。当然这里还是指 Portainer 和 远端的 docker 在同一个网段,如果不是,往下看。

使用 extra_hosts 字段配置网络

上面两种方式都是到添加 Endpoint 就点到为止。但实际过程中,不会这么一帆风顺,因为一般情况下不会有谁的容器和另一台主机的 docker 是在同一网段的。像我 Potainer 网段在 172 的虚拟网段,树莓派则在 192 的路由网段:

要解决这个问题,一般粗暴一点的做法是,将 Portainer 的容器网络配置为 host 模式,或者 macvlan 模式来让其直接暴露在主机所处的路由网络。但说到这里就很气愤,对于 Mac 上的 network 配置几乎是摆设,不论是 host 还是 macvlan 都无法访问,host 模式的问题原因没找到,macvlan 模式参考 https://github.com/docker/for-mac/issues/3926,总之就是目前没解。

在没找到解决方案的情况下,去翻了下官方文档,找到了一个字段 extra_hosts ,官方文档:https://docs.docker.com/compose/compose-file/compose-file-v3/#extra_hosts

最后将 Portainer 加上了这个字段,再进行连接便可以连接成功,也不需要修改原有网络模式。

最后上图:

整个过程非常的繁琐,因为 TLS 本身就是一件很繁琐的事,想了解的可以去看看 阮一峰 – SSL/TLS协议运行机制的概述

如果连接失败可通过查看 portainer 容器的日志得知问题原因,比如之前尝试的时候出现的证书 host 不匹配问题:

每日一问

现在是一对一(本机的 docker 不算),如果外加 2 台服务器,一对多该怎么配置呢?

答案就是:把上面的都全部再来一次(可复用根证书私钥,然后给不同 host 签发根证书),对于每个节点启用不同证书配置(因为每个节点的 host 不同,ca 也就不同,原则上 ca 证书的签发是按 host 算的,一一对应,就像网站的 https 证书申请),当然也可以用一套证书,但 ca 的 host 校验就不可用了,上面连接时的选项就应该选择 TLS with client verification only 了。换个简单说法就是,我不管你是哪个 host 的 ca 但现在的服务器证书,客户端证书都是这个 ca 签发的,那我们就可以通信。(可能存在描述错误,如果有大佬清楚,还请指正)

2021.8.29 更新X509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0)

参考

官方文档:

https://docs.docker.com/engine/security/protect-access/

https://documentation.portainer.io/v2.0/endpoints/docker/


本站由以下主机服务商提供服务支持:

0条评论

发表评论