在 Docker 上使用 Traefik 暴露服务:高级篇¶
本指南建立在基础指南介绍的概念和配置之上。继续之前,请确保你已完成基础指南,并已在 Docker 上部署可正常运行的 Traefik。
在本高级指南中,你将学习如何通过以下功能增强 Traefik 部署:
- 使用中间件设置安全响应头和访问控制
- 使用 Let's Encrypt 自动管理证书
- 为有状态应用启用粘性会话
- 使用多层路由实现分层路由,并通过复杂的基于身份认证的路由示例加以说明
- 使用服务中间件在服务级别应用中间件
前提条件¶
- 已完成基础指南
- 已安装 Docker 和 Docker Compose
- 基础指南中的 Traefik 部署已正常运行
添加中间件¶
中间件可以在请求或响应经过 Traefik 时对其进行修改。下面添加两种实用的中间件:用于安全防护的 Headers,以及用于访问控制的 IP 允许列表。
在 docker-compose.yml 中为 whoami 服务添加以下标签:
labels:
# Secure Headers Middleware
- "traefik.http.middlewares.secure-headers.headers.frameDeny=true"
- "traefik.http.middlewares.secure-headers.headers.sslRedirect=true"
- "traefik.http.middlewares.secure-headers.headers.browserXssFilter=true"
- "traefik.http.middlewares.secure-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.secure-headers.headers.stsPreload=true"
- "traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000"
# IP Allowlist Middleware
- "traefik.http.middlewares.ip-allowlist.ipallowlist.sourceRange=127.0.0.1/32,192.168.0.0/16,10.0.0.0/8"
# Apply middlewares to whoami router
- "traefik.http.routers.whoami.middlewares=secure-headers,ip-allowlist"
为 whoami-api 服务添加相同的中间件:
labels:
- "traefik.http.routers.whoami-api.middlewares=secure-headers,ip-allowlist"
应用更改:
docker compose up -d
测试中间件¶
现在验证中间件是否正常工作:
测试安全响应头中间件:
curl -k -I -H "Host: whoami.docker.localhost" https://localhost/
在响应头中,你应该看到中间件设置的安全响应头:
X-Frame-Options: DENYX-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=block- 具有相应设置的
Strict-Transport-Security
测试 IP 允许列表中间件:
如果请求来自允许列表中的 IP(例如 127.0.0.1),则应成功:
curl -k -I -H "Host: whoami.docker.localhost" https://localhost/
如果你从允许列表之外的 IP 访问,请求将被拒绝,并返回 403 Forbidden 响应。若要在本地环境中模拟这种情况,可以临时修改中间件配置,将自己的 IP 地址排除在外,然后再次测试。
使用 Let's Encrypt 生成证书¶
Let's Encrypt 提供免费且自动化的 TLS 证书。下面配置 Traefik,使其自动为服务获取和续订证书。
将现有 docker-compose.yml 文件更新为以下配置,以替代自签名证书:
在 Traefik 服务的命令配置部分添加 Let's Encrypt 证书解析器:
command:
- "--api.insecure=false"
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entryPoints.web.address=:80"
- "--entryPoints.websecure.address=:443"
- "--entryPoints.websecure.http.tls=true"
- "--entryPoints.web.http.redirections.entryPoint.to=websecure"
- "--entryPoints.web.http.redirections.entryPoint.scheme=https"
# Let's Encrypt configuration
- "[email protected]" # replace with your actual email
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
添加用于存储 Let's Encrypt 证书的卷:
volumes:
# ...Existing volumes...
- "./letsencrypt:/letsencrypt"
更新服务标签,使用证书解析器:
labels:
- "traefik.http.routers.whoami.tls.certresolver=le"
对其他需要保护的服务执行相同操作:
labels:
- "traefik.http.routers.whoami-api.tls.certresolver=le"
创建用于存储 Let's Encrypt 证书的目录:
mkdir -p letsencrypt
应用更改:
docker compose up -d
需要公共 DNS
Let's Encrypt 可能需要一个可公开访问的域名来验证域名所有权。使用 whoami.docker.localhost 等本地域名测试时,证书仍为自签名证书。在生产环境中,请替换为真实域名,并为其配置可公开访问、指向 Traefik 实例的 DNS 记录。
证书签发后,可以这样验证:
# Verify the certificate chain
curl -v https://whoami.docker.localhost/ 2>&1 | grep -i "server certificate"
你应该看到证书由 Let's Encrypt 签发。
配置粘性会话¶
粘性会话可确保同一用户的请求始终发送到同一台后端服务器,这对维护会话状态的应用至关重要。下面为 whoami 服务实现粘性会话。
首先,添加粘性会话标签¶
在 docker-compose.yml 文件中为 whoami 服务添加以下标签:
labels:
- "traefik.http.services.whoami.loadbalancer.sticky.cookie=true"
- "traefik.http.services.whoami.loadbalancer.sticky.cookie.name=sticky_cookie"
- "traefik.http.services.whoami.loadbalancer.sticky.cookie.secure=true"
- "traefik.http.services.whoami.loadbalancer.sticky.cookie.httpOnly=true"
应用更改:
docker compose up -d
然后,扩容服务¶
为了演示 Docker 中的粘性会话,使用 Docker Compose 的扩容功能:
docker compose up -d --scale whoami=3
这会创建 whoami 服务的多个实例。
在配置更改后扩容
如果扩容后运行 docker compose up -d,whoami 实例数将重置为 1。务必先应用配置更改并启动服务,再进行扩容。
测试粘性会话¶
你可以发送多个请求,并观察它们是否都到达同一个后端容器,以测试粘性会话:
# First request - save cookies to a file
curl -k -c cookies.txt -H "Host: whoami.docker.localhost" https://localhost/
# Subsequent requests - use the cookies
curl -k -b cookies.txt -H "Host: whoami.docker.localhost" https://localhost/
curl -k -b cookies.txt -H "Host: whoami.docker.localhost" https://localhost/
留意每个响应中的 Hostname 字段:使用 cookie 文件时,各次请求中的值应该保持一致,这说明粘性会话正在生效。
作为对比,可以尝试不携带 cookie 发送请求:
# Requests without cookies should be load-balanced across different containers
curl -k -H "Host: whoami.docker.localhost" https://localhost/
curl -k -H "Host: whoami.docker.localhost" https://localhost/
你应该会在这些响应中看到不同的 Hostname 值,因为每个请求都会经负载均衡分发到不同的容器。
浏览器测试
在浏览器中测试时,需要保持同一个浏览器会话,以保留 cookie。为确保安全,cookie 设置了 httpOnly 和 secure 标志,因此只会通过 HTTPS 连接发送,且无法通过 JavaScript 访问。
更多高级配置选项请参阅参考文档。
多层路由¶
多层路由允许路由器之间建立层级关系:父路由器可以先通过中间件处理请求,再由子路由器作出最终的路由决策。这对基于身份认证的路由或分阶段应用中间件特别有用。
提供者要求
多层路由需要 文件提供者,因为 Docker 标签不支持 parentRefs 字段。不过,你可以同时使用 Docker 和 文件提供者:用 Docker 标签发现服务,用 File 配置实现多层路由。
在 Docker 中设置多层路由¶
要在 Docker 中使用多层路由,需要在 Docker 提供者之外同时启用 文件提供者。
更新 docker-compose.yml 中的 Traefik 服务:
services:
traefik:
image: "traefik:latest"
container_name: "traefik"
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--providers.file.directory=/etc/traefik/dynamic" # Enable File provider
- "--entryPoints.web.address=:80"
- "--entryPoints.websecure.address=:443"
- "--entryPoints.websecure.http.tls=true"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./dynamic:/etc/traefik/dynamic:ro" # Mount directory for dynamic config
基于身份认证的路由示例¶
下面创建一个多层路由配置,由父路由器对请求进行身份认证,子路由器则根据用户角色分发流量。
首先,照常通过标签定义 Docker 服务:
# In docker-compose.yml
services:
# ... traefik service from above ...
# Mock authentication service that adds X-User-Role header
auth-service:
image: "traefik/whoami"
networks:
- proxy
environment:
- WHOAMI_NAME=Auth Service
labels:
- "traefik.enable=true"
- "traefik.http.services.auth-service.loadbalancer.server.port=80"
# Admin backend service
admin-backend:
image: "traefik/whoami"
networks:
- proxy
environment:
- WHOAMI_NAME=Admin Backend
labels:
- "traefik.enable=true"
- "traefik.http.services.admin-backend.loadbalancer.server.port=80"
# User backend service
user-backend:
image: "traefik/whoami"
networks:
- proxy
environment:
- WHOAMI_NAME=User Backend
labels:
- "traefik.enable=true"
- "traefik.http.services.user-backend.loadbalancer.server.port=80"
现在在文件中创建多层路由配置。创建 dynamic/mlr.yml:
http:
routers:
# Parent router with authentication middleware
api-parent:
rule: "Host(`api.docker.localhost`) && PathPrefix(`/api`)"
middlewares:
- auth-middleware
entryPoints:
- websecure
# Note: No service and no TLS config - this is a parent router
# Child router for admin users
api-admin:
rule: "HeadersRegexp(`X-Auth-User`, `admin`)"
service: admin-backend@docker # Reference Docker service
parentRefs:
- api-parent@file # Explicit reference to parent in file provider
# Child router for regular users
api-user:
rule: "HeadersRegexp(`X-Auth-User`, `user`)"
service: user-backend@docker # Reference Docker service
parentRefs:
- api-parent@file # Explicit reference to parent in file provider
middlewares:
auth-middleware:
basicAuth:
users:
- "admin:$apr1$DmXR3Add$wfdbGw6RWIhFb0ffXMM4d0"
- "user:$apr1$GJtcIY1o$mSLdsWYeXpPHVsxGDqadI."
headerField: X-Auth-User
生成密码哈希
上面的 密码哈希使用 htpasswd 生成。若要创建自己的用户凭据:
# Using htpasswd (Apache utils)
htpasswd -nb admin yourpassword
跨提供者引用
注意服务名中的 @docker 后缀,以及 @file 后缀(用于 parentRefs)。使用 文件提供者为 Docker 服务编排多层路由时:
- 使用
service-name@docker引用 Docker 服务 - 使用
parent-name@file在parentRefs中引用 文件提供者中的父路由器
@provider 后缀告诉 Traefik 应在哪个提供者的命名空间中查找资源。
应用更改:
docker compose up -d
测试多层路由¶
测试路由行为:
# Request goes through parent router → auth middleware → admin child router
curl -k -u admin:test -H "Host: api.docker.localhost" https://localhost/api
以 admin 身份认证时,你应该看到 admin-backend 服务的响应。尝试使用 user:test 凭据,则会访问 user-backend 服务。
工作原理¶
- 请求到达
api.docker.localhost/api - 父路由器(
api-parent)根据主机和路径匹配请求 - BasicAuth 中间件对用户进行身份认证,并将用户名写入
X-Auth-User请求头 - 子路由器(
api-admin或api-user)根据请求头的值进行匹配 - 请求被转发到相应的 Docker 服务
有关多层路由的更多详细信息,请参阅多层路由文档。
服务中间件¶
服务中间件允许你将中间件应用于服务,而不必逐个配置路由器。这意味着,无论由哪个路由器转发请求,中间件都会对该服务处理的所有请求生效。
当你希望对到达服务的所有流量应用相同的中间件(如响应头、速率限制或身份认证),又不想在每个路由器上分别配置时,这一功能就很有用。
何时使用服务中间件¶
以下情况适合使用服务中间件:
- 多个路由器将流量转发到同一个服务,并且都需要应用相同的中间件
- 你希望确保无论流量如何到达服务,该服务始终应用某个中间件
- 你希望在服务级别集中配置中间件,以便管理
添加服务中间件标签¶
在 docker-compose.yml 中为 whoami 服务添加以下标签:
services:
whoami:
image: traefik/whoami
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls=true"
# Define the middleware
- "traefik.http.middlewares.service-headers.headers.customRequestHeaders.X-Service-Middleware=applied"
# Attach middleware at the SERVICE level (not the router level)
- "traefik.http.services.whoami.middlewares=service-headers"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
服务级别与路由器级别的中间件
- 路由器级别的中间件(
traefik.http.routers.<name>.middlewares):仅在流量匹配该路由器的规则时应用 - 服务级别的中间件(
traefik.http.services.<name>.middlewares):应用于到达服务的所有流量,无论由哪个路由器转发
同时配置两者时,先执行路由器中间件,再执行服务中间件。
应用更改:
docker compose up -d
测试服务中间件¶
验证服务中间件是否正常工作:
curl -k -H "Host: whoami.docker.localhost" https://localhost/
在 whoami 的响应中,你应该看到服务中间件添加的自定义请求头:
X-Service-Middleware: applied
有关服务中间件的更多详细信息,请参阅参考文档。
总结¶
在本高级指南中,你已学会:
- 使用安全响应头、IP 允许列表等中间件增强安全性
- 使用 Let's Encrypt 自动管理证书
- 为有状态应用实现粘性会话
- 设置多层路由,实现基于身份认证的路由
- 在服务级别应用中间件,集中管理中间件配置
这些高级功能使你能够在 Docker 上构建可用于生产的 Traefik 部署。你还可以进一步定制各项功能,以满足具体需求。
后续步骤¶
现在你已掌握 Docker 上 Traefik 的基础和高级功能,还可以继续探索: