使用 Traefik 作为 Docker 的反向代理
本文主要介绍如何使用 Traefik 作为 Docker 的反向代理,以及如何使用 Traefik 配置自动 HTTPS。但是,在写这篇文章时有感而发,先聊聊我对建网站,或者说自学 linux 的一些历程吧。想直达重点的点这里跳转。
还记得最开始使用云服务器的时候,租了个阿里云的学生机,当时就想建个博客玩玩,一顿搜索,发现网上全在推荐使用 WordPress。但是当时还是小白啊,只会 ls
cat
的那种,安装 PHP 和 Apache 几乎是不可能的,再加上网上一堆教程动不动就手动编译安装,我当时连 yum
都不会用,怎么可能编译安成果嘛。
所以我就取了个巧,直接拿阿里云镜像社区的别人装好了 WordPress 的系统(基于CentOS 8)。能用是能用了,随后又花了一个月备案。但这是我想到一个问题:一个服务器只有一个 443 端口,难道只能建一个网站吗?于是我就第一次听说了反向代理,以及著名的 Nginx
。
然而,Nginx
的配置文件显然也不是当时的我能看懂的。经过近半年的折腾,我会用 Nginx
了,可这时问题又来了,我的 SSL 证书过期了……当时陆陆续续搞了3个网站,结果换域名太麻烦了。难道不能自动化完成这些吗?难道不能通过图形化的界面生成 Nginx
的配置文件吗?这一次进入我视野的是 Nginx Proxy Manager
,简称 NPM
,但是人家教程里的安装方式当时只有 Docker
和使用 npm
安装,可我当时还不会用 Docker
,也不会用 npm
。
不会怎么办?学呗!这么一想我当时还真离谱……于是折腾 Docker
,用上了 Let's Encrypt + Nginx Proxy Manager。
随着我也会写了点小程序,我需要将演示站挂到网上。但 Nginx Proxy Manager
的配置任然麻烦,需要 配置一堆 Docker 容器的 endpoint。在一次逛 GitHub 时,我发现了 Traefik
,也就是今天的主角。它彻底解决了我上述的所有需求!
- 稳定
- 配置一次,以后全自动
- 图形化面板
- 基于容器的
label
配置
下面正文开始。
Traefik 安装与配置
因为使用 Docker
安装,所以安装过程不再赘述,直接上 docker-compose.yml
文件(需要先创建一个名为 proxy
的网络)
version: "3.8"
services:
traefik:
container_name: proxy
image: traefik:v2.9
environment: # 我使用了阿里云的 DNS 服务,所以需要配置阿里云的 AccessKey
ALICLOUD_ACCESS_KEY: ""
ALICLOUD_SECRET_KEY: ""
ALICLOUD_REGION_ID: "cn-hangzhou"
ports:
- "80:80"
- "443:443"
volumes:
- /etc/localtime:/etc/localtime:ro # 使用宿主机的时区
- /var/run/docker.sock:/var/run/docker.sock:ro # traefik 需要监听容器的启动和停止, 只读即可
- ./config:/etc/traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entryPoints=websecure"
- "traefik.http.routers.traefik.rule=Host(`proxy.example.com`)" # 用于访问 Traefik 面板的域名, 本身也由 Traefik 管理
- "traefik.http.routers.traefik.middlewares=user-auth@file" # 简单的 HTTP Basic Auth
- "traefik.http.routers.traefik.service=api@internal"
networks:
- proxy
networks:
proxy:
external: true
但是仅仅这样还不够,还需要一些配置文件。假设以上 docker-compose.yml
文件在 ${APP}
目录下。
Traefik 的配置文件分为两种,一种是 static
,一种是 dynamic
。static
配置文件是不会自动加载的,需要重启 Traefik 容器,而 dynamic
配置文件会自动加载。此外,我们需要在 static
配置文件中配置 dynamic
配置文件的路径。
静态配置文件
api:
dashboard: true
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
filename: /etc/traefik/dynamic.yml
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
websecure:
address: ":443"
http:
middlewares:
- secureHeaders@file
- compressConfig@file
tls:
certResolver: letsencrypt
certificatesResolvers:
letsencrypt:
acme:
email: [email protected] # 一个用于申请 let's encrypt 证书的邮箱
storage: /etc/traefik/acme.json
dnsChallenge:
provider: alidns # 阿里云的 AccessKey 来自 `docker-compose.yml` 文件中配置的环境变量
# AccessKey 需要有 DNS 权限
下面解释一下这个静态配置文件的内容:
-
api.dashboard
用于开启 Traefik 面板
-
providers.docker
开启了 Docker 集成,将容器的
label
作为动态配置文件 -
providers.file
指定将
/etc/traefik/dynamic.yml
作为额外的动态配置文件( 等下配置) -
entryPoints
配置入口点,这里配置了两个入口点,一个是
web
,一个是websecure
,分别监听 80 和 443 端口 -
entryPoints.web.http.redirections.entryPoint.to
配置了
web
入口点的重定向,即将所有http
请求重定向到websecure
入口点因为我为我的所有域名开启了 HSTS Preload,重定向规则已经被写进浏览器源代码了,所以这里配不配都差不多,大家可以酌情修改
-
entryPoints.websecure.http.middlewares
配置了
websecure
入口点的中间件,这里配置了两个中间件,一个是secureHeaders
,一个是compressConfig
,分别用于配置安全头和压缩,这两个中间件的具体内容在下面的动态配置文件中,方便我们随时修改 -
entryPoints.websecure.http.tls
配置了
websecure
入口点的 TLS 证书,这里使用了letsencrypt
证书,下一行配置指定了letsencrypt
证书的获取方式 -
certificatesResolvers.letsencrypt.acme
配置了
letsencrypt
证书的获取方式,这里使用了dnsChallenge
,即通过 DNS 验证域名所有权。其实使用 HTTP 验证也可以,但是有两个原因使我不得不使用 DNS 验证。原因在后面的 FAQ 中会提到
动态配置文件
接下来是动态配置文件:
http:
# services:
# demo:
# loadBalancer:
# servers:
# - url: http://web:80
# routers:
# demo:
# rule: Host(`demo.com`)
# entryPoints: [websecure]
# middlewares:
# - balala@file
# service: demo@file
middlewares:
nofloc:
headers:
customResponseHeaders:
Permissions-Policy: "interest-cohort=()"
secureHeaders:
headers:
sslRedirect: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 63072000
compressConfig:
compress:
minResponseBodyBytes: 1024
excludedContentTypes: []
cacheHeaders:
headers:
customResponseHeaders:
Cache-Control: "public, max-age=604800"
user-auth:
basicAuth:
users:
- "" # 一个用户名和密码,使用 htpasswd 生成
同样的,下面解释一下这个动态配置文件的内容:
-
http.middlewares
配置了一些中间件,这些中间件可以在任何地方使用,包括静态配置或 Docker 容器的
label
中。-
secureHeaders
配置了安全头,这里配置了 HSTS,即强制使用 HTTPS,以及 HSTS Preload,即将域名加入浏览器的 HSTS Preload 列表中,这样浏览器就不会发起 HTTP 请求了,有效防止第一次访问时的中间人攻击。
-
compressConfig
配置了压缩,这里配置了最小压缩字节数为 1024,即只有大于 1024 字节的响应才会被压缩,这样可以避免小文件被压缩后反而变大。如果有一些文件不想被压缩,可以在
excludedContentTypes
中添加 MIME 类型。 -
cacheHeaders
配置了缓存,这里配置了缓存时间为 7 天,即 604800 秒。注意,并不是所有文件都应该缓存!!!而 且缓存头应该由服务器返回,而不是由反向代理,因为反向代理并不知道文件是否被修改过。这里只是一个示例,如果不会用忽略即可。
-
user-auth
一个最简单的 HTTP Basic Auth 实现,可以保护一些本身不带身份验证的服务,比如 Traefik 面板。使用
htpasswd
生成用户名和密码,然后将生成的内容复制到dynamic.yml
中即可。网上也有在线生成的网站,自行搜索。 -
nofloc
配置了 Permissions-Policy,即禁用 FLoC,这是 Google Chrome 的一个新特性,用于替代第三方 Cookie,但是这个特性有很多问题,比如会泄露用户的隐私,所以我禁用了它。
-
我在注释部分还写了一个示例的 service
和 router
,可以用于非 Docker 的服务。其实 docker 容器的 label 也是照着这个写的,只是格式不同而已。就是一个路由对应一个服务,中间添加中间件就行了。
示例: 使用 Traefik 反向代理 mediawiki
这里我使用一个具体的例子,即使用 Traefik 反向代理 mediawiki
+ Nginx
,全部使用 Docker
安装。(其实这个例子适合任何 PHP
程序。这次任然使用 ${APP}
作为项目根目录,但注意,这里的 ${APP}
不是上面的 ${APP}
,而是一个新的目录。