最近在我的 VPS 服务器升级完操作系统之后,想体验一下使用 Docker Swarm Mode + Traefik 部署应用,这样还可以把自己电脑、NAS 等设备加入 Swarm 集群,方便本地的 Docker 服务通过 overlay 网络在 VPS 服务器上供外部访问。
但由于 Swarm 模式下使用 IPv6 有一些不方便的地方,所以暂时还是考虑使用普通的非 Swarm 模式,通过 docker-compose 部署应用,同时进行了如下几点修改:
- 停用 nginx-proxy, 反向代理改用 Traefik
- 子域名使用通配符证书
- 通过脚本,使自己的 NAS、无线路由器,能够自动从 VPS 服务器中获取 SSL 证书,从而使自己的服务器和设备能够共用同一套证书
之前的方案
之前,我主要使用 HyperApp 来安装、管理我的 VPS 服务器上的应用,包括我的 WordPress 博客。
HyperApp 是 @waylybaye 开发的一款 iOS App, 能够方便地在 Linux 服务器上自动化部署应用,同时提供了服务器资源监控、SSH 终端等功能。如果你在使用 Linux 服务器、NAS、Raspberry Pi、OpenWrt 无线路由器等设备,强烈推荐使用 HyperApp 进行远程管理。
HyperApp 在部署应用的时候,默认使用 nginx-proxy 提供反向代理,使用 docker-letsencrypt-nginx-proxy-companion 自动从 Let’s Encrypt 获取 SSL 证书,并在此基础上安装各种基于 Docker 的应用。通过 nginx-proxy,即可在外部以不同的域名进行访问。
由于 HyperApp 主要考虑到便利性,能够让没有技术背景的新用户也可以快速部署应用,所以在灵活程度上有一定的缺失。例如 HyperApp 中的 WordPress 直接使用了 Docker 官方的镜像,其中 PHP 的 display-errors 选项默认为打开状态,无法关闭。而根据 PHP 文档,该选项在生产环境中尽量应该关闭。
所以这次服务器升级操作系统后,打算仅使用 HyperApp 做为服务器监控工具,而安装和部署应用,则直接通过 docker-swarm 进行。
通过 Traefik 实现 HTTP 反向代理
Traefik 是一个用 go 语言编写的反向代理与负载均衡程序,对我来说主要有以下优点:
- 支持 Docker Swarm、Kubernetes 等,方便后续进一步折腾
- 有一个漂亮的 web 控制面板(虽然将来可能会被废弃),能够显示统计信息等
- 对于 Let’s Encrypt 的支持较为全面,通过几行配置,即可生成通配符证书
- 支持 HTTP Basic 认证,对于服务器上非公开的服务,可以设置密码,防止被其他人访问
- 支持将一个容器中的多个端口反向代理到不同的域名,例如在使用 frp 的时候,可能会在多个端口提供多个 HTTP 服务,这一功能就显得比较有用了
所以最终,我选择使用 Traefik 代替 nginx-proxy 做为 Web 服务的反向代理。 (虽然 Nginx 本身功能比较丰富,但 nginx-proxy 将 Nginx 经过了封装,功能就没那么多了。)
IPv6 支持
在之前的一篇文章中,我通过 robbertkl/docker-ipv6nat 实现了 Docker 环境的 IPv6 支持。在改用 Traefik 和 docker-compose 之后,依旧可以使用类似的方法。
首先创建一个支持 IPv6 的 Docker 网络:
docker network create --ipv6 --subnet=fd00:dead:beef::/48 traefik-bridge
然后在 docker-compose.yml
中,将该网络指定为 default 网络,启动 traefik 和 ipv6nat 服务即可:
version: "3.2"
services:
# traefik 反向代理
traefik:
restart: always
image: traefik
command:
# traefik 命令,此处省略
environment:
# traefik 环境变量,此处省略
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./acme:/etc/traefik/acme
ports:
- target: 80
published: 80
- target: 443
published: 443
# IPv6 NAT
ipv6nat:
restart: always
image: robbertkl/ipv6nat
privileged: true
network_mode: "host"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /lib/modules:/lib/modules:ro
labels:
- "traefik.enable=false"
networks:
# 使用刚刚创建的支持 IPv6 的 bridge 网络
default:
external:
name: traefik-bridge
由于自己的服务器上还需要运行其他服务,暂时不方便公开完整的 docker-compose.yml
,详细的配置,请参考 Traefik 官方文档。
参考链接:https://github.com/containous/traefik/issues/977
在外部安全地访问 Traefik 控制面板
Traefik 提供了一个 Web 控制面板,能够显示工作状态等信息。默认情况下,这个控制面板能够在 8080 端口以 HTTP 协议访问。
在 VPS 上,为了能够使自己在外部安全地访问 Traefik 控制面板,我为其增加了 HTTP Basic 认证,并限制只能通过 HTTPS 协议访问。
实现的的思路比较简单:通过 Traefik,反向代理其自身的 HTTP 控制面板。在 docker-compose.yml
的 Traefik 服务中添加相关的 labels
即可:
services:
traefik:
restart: always
image: traefik
# 省略 command、ports、environment、volumes
expose:
# Web 控制面板的端口
- 8080
labels:
# 启用 traefik
traefik.enable: true
# 指定控制面板的子域名
traefik.frontend.rule: "Host:traefik.example.com"
# 指定 HTTP Basic 认证的用户名和密码,可使用 htpasswd 命令生成
traefik.frontend.auth.basic: "user:password"
# 指定 Web 控制面板的端口
traefik.port: 8080
参考链接:Secure Traefik dashboard with https and password in docker
与 NAS 和路由器共用通配符证书
我的 VPS 上除了个人博客,还运行了其他多个服务,包括 OpenGrok,和一些仅供自己访问的私有服务。这些服务分别反向代理到不同的子域名。之前,每个子域名有一个单独的 SSL 证书。而有了 Traefik 之后,就可以方便地使用 Let’s Encrypt 通配符证书了。具体方法请参考官网文档,本文不再介绍。
对于我的 NAS 和无线路由器,也使用了 DDNS 使其能够通过子域名在外部访问。而且对于我的 RT1900ac 无线路由器,部分服务也需要通配符证书才能正常启动。所以,在这次更新个人网站架构后,同时打算通过脚本,在路由器和 NAS 上,自动从 VPS 服务器获取证书并更新。
将证书由 acme.json 转换为通用格式
对于 Traefik,在使用 Let’s Encrypt 的情况下,SSL 证书保存在 acme.json
文件中。如果需要和其他程序共用 Traefik 生成的证书,就需要通过程序从该文件中提取出证书,并保存为通用格式。
SvenDowideit/traefik-certdumper 镜像提供了这一功能,能够在 acme.json
文件变化时,将其保存为 .key
, .crt
和 .pem
格式。使用方法也比较简单,只需要在 docker-compose.yml
中增加如下服务:
certdumper:
restart: always
image: svendowideit/traefik-certdumper:latest
volumes:
# ./acme 同时映射到 Traefik 容器,用于保存 acme.json 和生成的证书
- ./acme:/traefik
NAS 和无线路由器通过脚本自动获取证书
我的 NAS 使用的是 DiskStation Manager (DSM) 操作系统,无线路由器使用的是 Synology Router Manager (SRM) 操作系统。两者均支持通过 HTTPS 访问 Web 管理页面。
这两个操作系统默认通过 Web 界面上传 SSL 证书。如果需要通过脚本自动更新证书,需要知道证书保存的位置。这一步可以参考 acme.sh 这个项目的文档中,文档中已经告诉了我们这两个操作系统中,证书的保存路径:
可知在 DSM 和 SRM 中,证书分别存放在如下位置:
# SRM
/usr/syno/etc/ssl/
# DSM
/usr/syno/etc/certificate/system/default/
在 DSM 中添加计划任务,通过脚本使用 scp 或 rsync 命令定期从 VPS 服务器上获取证书,替换 DSM 中原有的证书。然后通过 ssh 和 scp 命令将 DSM 中的证书传输到 SRM,替换 SRM 中的证书文件即可。
另外,参考 acme.sh 的文档,替换证书后,还需要在脚本中重启 Web 服务器进程:
# SRM
/usr/syno/sbin/synoservicecfg --restart httpd-sys
# DSM
/usr/syno/sbin/synoservicectl --reload nginx
这部分目前还没完全完成。后续完成后将在本文更新自己所使用的脚本。
Update (2019-11-10): 最后我还是直接在 NAS 上运行 acme.sh
,为 NAS 单独申请证书的。其实 Let’s Encrypt 的 Rate Limit 非常宽松,没有必要使用前文中在 VPS 上申请,然后同步回 NAS 的方式。
DSM中简单替换system/default证书并重启nginx是不够的。
/usr/syno/etc/certificate/和/usr/local/etc/certificate这两个目录下所有的子目录里的证书都要替换,并且需要分别重启每个涉及到的应用的服务。
多谢,还没开始折腾,过段时间准备试试