Traefik 全自动 HTTPS 方案
Traefik 是一个用于 Docker 中服务以不同子域名公开到服务器 80/443 端口的工具.
- 可以自动实现源站 HTTPS.
- 配合 Cloudflare 的泛域名解析可以自动化子域名建立.
- 实现各种附加功能 (Basic Auth, 负载均衡, etc.)
但是, Traefik 由于配置文件编写比较复杂, 使用的人并不多. 这里分享一个简单的解决方案.
原理
Traefik 启动时会读取配置文件和 Docker Compose 配置中的各个服务. 请求进入时候, 将会按这样一个顺序解析:
- Entrypoint (入口, 80/443)
-> Router (规则, 例如子域名为 www)
-> Service (服务)
-> LoadBalancer (源地址, 这里可以设定用 Docker 容器内的哪个端口)
同时, 启动时会根据你设定的域名, 向 Let’s Encrypt 申请泛域名 ACME 证书, 用于 HTTPS.
安装配置
首先当然需要 Docker 和 Docker Compose (Tutorial).
然后, 新建一个文件夹, 新建一个 docker-compose.yml:
services:
traefik:
container_name: traefik
image: traefik
restart: unless-stopped
ports:
- 80:80
- 443:443
security_opt:
- no-new-privileges:true
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/static.yml:/traefik.yml:ro
- ./traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
- ./traefik/acme.json:/acme.json:rw
- ./traefik/traefik.log:/var/log/traefik.log:rw
- /etc/localtime:/etc/localtime:ro
env_file:
- ./.env/traefik.env
labels:
- traefik.enable=true
- traefik.http.routers.traefik.service=api@internal
- traefik.http.routers.traefik.middlewares=user-auth@file
然后创建一个文件夹 traefik
存配置和日志, 一个文件夹 .env
存敏感的密钥:
mkdir traefik
touch traefik/{static.yml, dynamic.yml, acme.json, traefik.log}
mkdir .env
touch .env/traefik
这几个文件分别的作用及内容:
traefik/static.yml
: 静态配置, 改了需要重启 Traefik.traefik/dynamic.yml
: 动态配置, 改了不需要重启 Traefik.traefik/acme.json
: 申请 HTTPS 时自动存储的密钥.traefik/traefik.log
: 日志文件..env/traefik
: 密钥存储文件.
接下来需要填入三个文件:
第一个文件 traefik/static.yml
(请将example.com
替换为你的域名):
log:
level: INFO
filePath: "/var/log/traefik.log"
api:
dashboard: true
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
http:
tls:
certResolver: le-dns
domains:
- main: example.com
sans:
- "*.example.com"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: web
defaultRule: Host(`{{if index .Labels "com.docker.compose.service" }}{{ index .Labels "com.docker.compose.service" }}.example.com{{else}}{{ trimPrefix `/` .Name }}.example.com{{end}}`)
file:
filename: "/etc/traefik/dynamic.yml"
certificatesResolvers:
le-dns:
acme:
storage: "/acme.json"
email: [email protected]
dnsChallenge:
provider: cloudflare
这里需要解释一下:
defaultRule
这一行, 表示当启用 traefik 时, 默认使用 docker-compose 中的服务名作为子域名.provider
这一行, 如果不使用 cloudflare 作为 DNS 服务商, 可以自行根据WIKI修改.
第二个文件 traefik/dynamic.yml
:
http:
routers:
all-https:
rule: "HostRegexp(`{host:.+}`)"
entryPoints:
- web
tls: true
middlewares:
- to-https
priority: 1000
service: noop@internal
middlewares:
to-https:
redirectScheme:
scheme: https
user-auth:
basicAuth:
users:
- "user:$2y$05$A72U7Ngb6LYYBV.PUawrFOII9tAMMiV.fk8tJQwsHts1Q6yrxLpTS"
这里需要解释一下:
all-https
这一行, 表示在 web 入口 (80端口, 也就是 HTTP) 请求任何子域名时, 使用 to-https 中间件 (也就是重定向到 https). 该条目优先级为 1000, 也就是我们如果设定某个服务优先级为 1000 以上, 就可以不自动跳转 HTTPS.user-auth
这一行, 表示使用user-auth
这个中间件的服务, 将启用 Basic Auth 认证, 用户列表可以通过如下命令生成:
docker run --entrypoint htpasswd httpd:2 -Bbn user 123456
第三个文件 .env/traefik
:
CLOUDFLARE_DNS_API_TOKEN=<Cloudflare 40 位 Token>
这个主要是用于启用 HTTPS 时, 需要通过 Let’s Encrypt 的所有权认证, Traefik 会自动新建一个临时的 DNS 解析记录用于认证. 怎么获得?
进入 Cloudflare 控制台, 进入 My Profile 中的 API Tokens, 创建一个 Token, 需要在 All zones
具有 Zone.Zone, Zone.DNS
权限, 完成后复制Token填入.
然后, 创建一个 Cloudflare 的泛解析记录到服务器:
如果你的 DNS 服务商不支持, 你也可以一个一个子域名创建.
这里解释一下, 如果你有其他的子域名:
- *.example.com -> 1.1.1.1
- abc.example.com -> 2.2.2.2
那么 abc.example.com
的解析优先, 不会被泛解析记录覆盖.
最后, 使用任何 Docker 服务并自动部署!
以 chatgpt-next-web
为例:
在 docker-compose.yaml
中的services 中新建一个 next
服务:
services:
traefik:
...
next:
image: yidadaa/chatgpt-next-web
container_name: next
restart: unless-stopped
labels:
- traefik.enable=true
- traefik.http.services.next.loadbalancer.server.port=3000
然后在 docker-compose.yml
同级目录运行:
docker-compose up -d
这样, 这个网站就会自动部署到 next.example.com
, 并连接到容器内服务的 3000
端口.
如果你需要自定义子域名:
services:
traefik:
...
next:
image: yidadaa/chatgpt-next-web
container_name: next
restart: unless-stopped
labels:
- traefik.enable=true
- traefik.http.routers.next.rule=Host(`nextchat.example.com`)
- traefik.http.services.next.loadbalancer.server.port=3000
这样就会自动部署到 nextchat.example.com
其他功能
如果有其他功能的需求, 可以回复更新.