跳到正文
Træfɪk

在 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: DENY
  • X-Content-Type-Options: nosniff
  • X-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 服务。

工作原理

  1. 请求到达 api.docker.localhost/api
  2. 父路由器(api-parent)根据主机和路径匹配请求
  3. BasicAuth 中间件对用户进行身份认证,并将用户名写入 X-Auth-User 请求头
  4. 子路由器(api-admin 或 api-user)根据请求头的值进行匹配
  5. 请求被转发到相应的 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 的基础和高级功能,还可以继续探索:


版权所有 © 头文件软件工作室

文档由 HeadFile Studio 翻译。

Traefik v3.7 中文快照 · 原文版权归 Traefik Labs 及贡献者所有 · MIT 许可证