在本地 Docker 环境中信任自签名 CA 证书

前言⌗
我经常在内网部署一些服务,通过 Traefik 反向代理,并且使用自签名的 TLS 证书,如果只是本机浏览器访问是没有任何问题的,一切都很丝滑。但是如果部署在容器内的服务之间相互访问,则蛮烦的多,接下来介绍这其中可能遇到的坑!
域名解析⌗
我本地部署的服务主要是我本机访问,很少涉及内网访问,而我使用 Dnsmasq 作为 DNS 服务,将 .test
的 TLD 都解析为 127.0.0.1。
这在本机浏览器上访问时是没有什么问题的,但是如果容期间访问就会存在问题,例如 Outline 要访问 MinIO 服务,而 MinIO 是通过 Traefik 反向代理,域名是 minio.test,其流量如下所示:
`Outline` ---> `Traefik` ---> `MinIO`
这就导致一个问题 Outline 在请求 minio.test 这个域名是,从 Dnsmasq 哪里获取到的解析结果是 127.0.0.1,在容器内就等于是请求自己,无法将流量发送到 Traefik 容器。
为了解决这个问题可行的方案有两种:
- 将 Dnasmasq 的解析记录设置为 Traefik 的 IP;
- 在 Docker Compose 的配置文件中设置
extra_hosts
,等同于硬编码到容器的 /etc/hosts 文件中。
无论那种方式都需要为 Traefik 的容器设置一个固定 IP!
例如我本地使用 Valet 作为 PHP 的开发环境,在 ~/.config/valet/dnsmasq.d
目录中的配置如下:
例如我这里 *.test 的所有流量都走 traefik,Dnsmasq 的配置如下:
这样无论是容器内还是宿主机都是直接请求的 Traefik 这个容器,就不存在容器内和宿主机因解析地址是 loopback 而产生未知问题了!
证书⌗
当 Outline 容器要请求 MinIO 的时候,走的是 HTTPS,因为使用了自签名证书,所以会导致建立 TLS 握手的时候失败,就是 X.509 证书不被信任!解决的方法也很简单,但如果基于不同的镜像可能存在不同的差异!
例如 Linux 系的都是将 CA 证书放到容器内的 /usr/local/share/ca-certificate
目录中,然后进入容器执行如下命令:
如果容器内没有安装 update-ca-certificates
命令的话,那么可以直接将 CA 证书的内容追加到 /etc/ssl/certs/ca-certificates.crt
文件中:
这种方法比较普适,但是存在的问题也很明显,你要么在创建容器收手动执行,要么重写 Dockerfile,将上述步骤在构建阶段就执行。这样不用每次 Recreate Container 的时候手动去执行上面的操作了!
切记更新完证书以后,需要重启容器(不是 Recreate 哦),否则不生效!
除了上述的方案以为,某些特定的服务也支持通过 ENV 来设置:
- Node.JS 的镜像,可以使用
NODE_EXTRA_CA_CERTS
环境变量来指定 CA 所在位置,这样容器内的 Node.JS 进程发起的 Request 就可以自动加载 CA; - Gitlab Runner 可以通过
CA_CERTIFICATES_PATH
和CI_SERVER_TLS_CA_FILE
环境变量来信任 CA 证书。 - Python 的镜像,如果是用
requests
库发起的请求,使用REQUESTS_CA_BUNDLE
如果是httpx
库的请求,则使用SSL_CERT_FILE
环境变量来指定 CA 所在位置,这样容器内的 Python 进程发起的 Request 就可以自动加载 CA;
总结⌗
在本地开发,要模拟和生产一致的体验,坑还是挺多的,需要你有一定的耐心和 Debug 能力。但是只要坚持下来,你发现没有一根头发是白掉的……
I hope this is helpful, Happy hacking…