最近在我的 VPS 服务器升级完操作系统之后,想体验一下使用 Docker Swarm Mode + Traefik 部署应用,这样还可以把自己电脑、NAS 等设备加入 Swarm 集群,方便本地的 Docker 服务通过 overlay 网络在 VPS 服务器上供外部访问。

但由于 Swarm 模式下使用 IPv6 有一些不方便的地方,所以暂时还是考虑使用普通的非 Swarm 模式,通过 docker-compose 部署应用,同时进行了如下几点修改:

  1. 停用 nginx-proxy, 反向代理改用 Traefik
  2. 子域名使用通配符证书
  3. 通过脚本,使自己的 NAS、无线路由器,能够自动从 VPS 服务器中获取 SSL 证书,从而使自己的服务器和设备能够共用同一套证书
Traefik 的 Web 控制台截图,截图中是 HEALTH 界面,里面包含 Uptime, PID, Total Response, Average Response Time, Total Code Count, Code Count 统计计数。下方有两个图标,分别为 Average Response Time 折线图,以及 HTTP 状态码统计信息的柱状图。

之前的方案

之前,我主要使用 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 语言编写的反向代理与负载均衡程序,对我来说主要有以下优点:

  1. 支持 Docker Swarm、Kubernetes 等,方便后续进一步折腾
  2. 有一个漂亮的 web 控制面板(虽然将来可能会被废弃),能够显示统计信息等
  3. 对于 Let’s Encrypt 的支持较为全面,通过几行配置,即可生成通配符证书
  4. 支持 HTTP Basic 认证,对于服务器上非公开的服务,可以设置密码,防止被其他人访问
  5. 支持将一个容器中的多个端口反向代理到不同的域名,例如在使用 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 的方式。

最后修改日期: 2021-05-11

留言

DSM中简单替换system/default证书并重启nginx是不够的。 /usr/syno/etc/certificate/和/usr/local/etc/certificate这两个目录下所有的子目录里的证书都要替换,并且需要分别重启每个涉及到的应用的服务。

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。