文章

1Panel 安装 Traefik 与 OpenResty 并存

背景

1Panel 是现代化的开源 Linux 服务器面板,与传统(且流氓)的宝塔相比,其使用 go 编写,安装部署简单。内部完全基于 Docker,安装应用不会污染主机环境,备份与迁移也更简单。可惜面板为了能更细致地控制 web 服务器,实现 WAF 等高级功能,1Panel 强依赖 OpenResty。如果大量 HTTP 应用基于 Docker 安装,则需要手动创建多个反向代理,各自可能还需要单独创建证书,非常麻烦。

Traefik 则是专为云原生时代设计的 web 服务器,它能自动发现基于容器运行的 web 服务,并创建相应的路由(反向代理),而且默认集成了 ACME 协议可自动申请部署 HTTPS 证书。说人话就行,解析好域名,起一个容器,剩下就不用管了。

作为 web 服务器,显然 OpenResty 与 Traefik 默认都占用 80 与 443 端口。这里的解决方案是让 Traefik 作为主服务器处理外部请求,OpenResty 作为普通的容器,受 Traefik 管理。你问我为什么不倒过来?倒过来你不又得自己配置反代了?

安装 OpenResty

这一步比较简单,在面板的应用商店里安装即可,需要注意的是手动指定一下端口避免冲突。这里保持了默认的 host 模式。

specify ports for openresty manually

安装 Traefik

Compose 文件

这里直接使用命令行通过 docker compose 安装 Traefik,不想用命令行的也可以从 1Panel 的 「容器 - 编排」处创建。

compose.yml 文件如下:

services:
  traefik:
    image: traefik:3.1
    restart: unless-stopped
    networks: ["traefik"]
    ports:
      - 443:443
      - 80:80
      - 81:8080 # Traefik dashboard 默认端口
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data:/etc/traefik/
    extra_hosts: ["host.docker.internal:host-gateway"]

networks:
  traefik:
    external: true

这里要注意的是:

  • 没有使用 host 模式,因为 Traefik 只能自动发现与自己在同一网络中的服务。
  • 网络 traefik 需要手动创建,这样可以防止 Traefik 容器意外销毁时网络同步销毁。
  • extra_hosts 用于在容器内部通过 .. 访问宿主机(别忘了我们的 Traefik 是 host 模式)。
  • ./data 目录里存放 Traefik 的配置文件,见下文。

启动之前记得先创建网络(别忘了先做好下面的静态配置再启动):

docker network create traefik

静态配置

创建 ./data/traefik.yml 文件,注意文件名不可更改:

global:
  checkNewVersion: false
  sendAnonymousUsage: false

entryPoints:
  web:
    address: :80

api:
  dashboard: true
  insecure: true

providers:
  file:
    directory: "/etc/traefik"
    watch: true

api.insecure 设置为 true 方便通过 IP 地址访问 Traefik Dashboard。其实这个东西是只读的,并不能修改什么配置,所以问题不大。介意的话后续可以关掉或配置成域名 HTTPS 访问。

providers.file 配置的是动态配置文件的监听目录,与 compose.yml 中的文件映射对应。后面手动创建路由会用到。

HTTPS 配置

要想自动部署 HTTPS 包括两个方面:

  • 自动申请 HTTPS 证书。
  • 监听 HTTPS 端口。

Traefik 会自动申请证书。如果要整合 1Panel 的证书会麻烦一点,自己配置吧。

在静态配置文件中添加下面的内容:

entryPoints:
	# 监听 https 端口
  websecure:
    address: :443
    asDefault: true
    http:
      tls:
        certResolver: letsenc

# 自动申请证书
certificatesResolvers:
  # 名字随便起,与 entryPoints 中的设置对应
  letsenc:
    acme:
      email: "cert@example.com"
      storage: "/etc/traefik/acme.json"
      tlsChallenge: {}

这里默认使用 TLS-ALPN-01 ACME challenge,Traefik 会自动处理一切事务。但如果你的域名使用了 CDN,建议配置为 HTTP challenge,也可使用 DNS challenge,详见 Traefik 文档

创建路由

创建路由其实是 Traefik 的使用问题了,这里只解释如何为 OpenResty 中的网站绑定域名,其他场景请参阅 Traefik 文档

虽然 Traefik 可以自动发现服务,但它并不知道你想给他绑定哪个域名呀。所以还是需要为每个容器设置一些东西。这里有两种方式:

  • Container Label
  • 配置文件

对于 OpenResty 必须使用配置文件方式,因为它是 host 模式,不会被 Traefik 扫描到。而且这个容器是 1Panel 管理的,我们并不方便修改 Label。

方便起见,我们利用动态配置功能来写,可以实时生效,无需重启 Traefik 容器。编辑 Traefik compose 目录中的 ./data/dynamic.yml:

http:
  routers:
    my_website:
      rule: Host(`www.example.com`)
      service: openresty
  services:
    openresty:
      loadBalancer:
        servers:
          - url: 'http://host.docker.internal:8080/'

service 可以理解为后端(上游),这里是我们的宿主机地址,端口则是 OpenResty 的监听端口。

每创建一个站点,记得都要在这里创建一个新的 Router (Service 一个就够)。虽然稍微麻烦一点,但我们既然用了 Traefik,肯定大部分服务都是容器化的,这些不需要手动配置。

如果你配置了 HTTPS,建议确认域名解析生效后再添加 Router,否则将无法申请证书。

常见问题

HTTPS

这里建议 HTTPS 由 Traefik 自动处理(包括 HTTP 自动跳转),手动在 1Panel 或 OpenResty 中创建的站点 (vhost) 使用 HTTP 就好。

注意,即使在 OpenResty 中配置了证书,Traefik 的 HTTPS 也不可省略。因为后者才是真正与客户端握手的程序,OpenResty 的证书只在 Traefik <---> OpenResty 之间的连接起作用,不会影响客户端。鉴于这两个程序都是本地的,所以 HTTPS 意义不大,徒增损耗。

1Panel 证书

针对 HTTP-01 challenge 方式申请的 HTTPS 证书,1Panel 需要 OpenResty 的配合来自动放置验证文件。但我们把 Traefik 放在了 OpenResty 前面,如果你恰好 Traefik 中也配置的 HTTP challenge,那么验证请求将被拦截,无法到达容器(OpenResty)。这里没有太好的解决方法,建议:

  • 放弃 1Panel 的证书功能,或通过 DNS 申请。
  • 修改 Traefik,使用 DNS 或 tls 申请。