Skip to content

高级 ​

在 Docker Swarm 上使用 Traefik 暴露 Service - 高级 ​

本指南基于 Basic Guide 的概念和设置。在继续之前,请确保你已完成基本指南并具有一个可工作的 Traefik + Docker Swarm 设置。

在本高级指南中,你将学习如何通过以下方式增强 Traefik 部署:

  • 用于安全 headers 和访问控制的中间件
  • 用于自动证书管理的 Let's Encrypt
  • 用于有状态应用程序的粘性会话
  • 用于复杂身份验证场景的多层路由
  • 用于在 service 级别应用中间件的Service 中间件

先决条件 ​

  • 已完成 Basic Guide
  • Docker Swarm 集群已初始化
  • 基本指南中的可工作 Traefik 设置

添加中间件 ​

中间件允许你在请求和响应通过 Traefik 时修改它们。让我们添加两个有用的中间件:Headers 用于安全性,以及 IP allowlisting 用于访问控制。

在 docker-compose.yml 中的 whoami service 部署部分添加以下 labels:

yaml
deploy:
  # ... 现有配置 ...
  labels:
    # ... 现有 labels ...

    # 安全 Headers 中间件
    - "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 白名单中间件
    - "traefik.http.middlewares.ip-allowlist.ipallowlist.sourceRange=127.0.0.1/32,192.168.0.0/16,10.0.0.0/8"

    # 应用中间件
    - "traefik.http.routers.whoami.middlewares=secure-headers,ip-allowlist"

将相同的中间件添加到 whoami-api service:

yaml
deploy:
  # ... 现有配置 ...
  labels:
    # ... 现有 labels ...
    - "traefik.http.routers.whoami-api.middlewares=secure-headers,ip-allowlist"

应用更改:

bash
docker stack deploy -c docker-compose.yml traefik

测试中间件 ​

测试 Secure Headers 中间件:

bash
curl -k -I -H "Host: whoami.swarm.localhost" https://localhost/

在响应 headers 中,你应该看到由中间件设置的安全 headers:

  • X-Frame-Options: DENY
  • X-Content-Type-Options: nosniff
  • X-XSS-Protection: 1; mode=block
  • Strict-Transport-Security 及相应设置

测试 IP 白名单中间件:

如果你的请求来自白名单中的 IP(例如 127.0.0.1),它应该成功:

bash
curl -k -I -H "Host: whoami.swarm.localhost" https://localhost/

如果你尝试从不在白名单中的 IP 访问,请求将以 403 Forbidden 响应被拒绝。

使用 Let's Encrypt 生成证书 ​

Let's Encrypt 提供免费的自动化 TLS 证书。让我们配置 Traefik 自动获取和续订我们的 service 的证书。

更新现有的 docker-compose.yml 文件:

将 Let's Encrypt 证书解析器添加到 Traefik service 的 command 部分:

yaml
command:
  # ... 现有 commands ...
  # Let's Encrypt 配置
  - "[email protected]" # 替换为你的实际邮箱
  - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
  - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"

为 Let's Encrypt 证书添加卷:

yaml
volumes:
  # ...现有 volumes...
  - letsencrypt:/letsencrypt

更新 service labels 以使用证书解析器:

yaml
labels:
  # ... 现有 labels ...
  - "traefik.http.routers.whoami.tls.certresolver=le"

为任何你想保护的其他 service 执行相同操作:

yaml
labels:
  # ... 现有 labels ...
  - "traefik.http.routers.whoami-api.tls.certresolver=le"

通过添加到 volumes 部分创建用于存储 Let's Encrypt 证书的命名卷:

yaml
volumes:
  # ... 现有 volumes ...
  letsencrypt:
    driver: local

应用更改:

bash
docker stack deploy -c docker-compose.yml traefik

需要公共 DNS

Let's Encrypt 可能需要一个可公开访问的域来验证域所有权。对于使用 whoami.swarm.localhost 等本地域的测试,证书将保持自签名。在生产环境中,将其替换为具有指向你的 Traefik 实例的可公开访问 DNS 记录的真实域。

一旦证书颁发,你可以验证它:

bash
# 验证证书链
curl -v https://whoami.swarm.localhost/ 2>&1 | grep -i "server certificate"

你应该看到你的证书由 Let's Encrypt 颁发。

配置粘性会话 ​

粘性会话确保用户的请求始终转到同一后端服务器,这对于维护会话状态的应用程序至关重要。让我们为 whoami service 实现粘性会话。

Docker Swarm 已有多个副本运行;现在我们添加粘性会话配置。更新 docker-compose.yml 文件中的 whoami service:

添加粘性会话配置 ​

在 docker-compose.yml 文件中的 whoami service 添加以下 labels:

yaml
deploy:
  # ... 现有配置 ...
  labels:
    # ... 现有 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"

应用更改:

bash
docker stack deploy -c docker-compose.yml traefik

测试粘性会话 ​

你可以通过发出多个请求并观察它们都转到同一后端容器来测试粘性会话:

bash
# 第一次请求 - 将 cookie 保存到文件
curl -k -c cookies.txt -H "Host: whoami.swarm.localhost" https://localhost/

# 后续请求 - 使用 cookie
curl -k -b cookies.txt -H "Host: whoami.swarm.localhost" https://localhost/
curl -k -b cookies.txt -H "Host: whoami.swarm.localhost" https://localhost/

注意每个响应中的 Hostname 字段 - 当使用 cookie 文件时,它应该在所有请求中保持相同,确认粘性会话正常工作。

作为对比,尝试不使用 cookie 发出请求:

bash
# 不带 cookie 的请求应负载均衡到不同的容器
curl -k -H "Host: whoami.swarm.localhost" https://localhost/
curl -k -H "Host: whoami.swarm.localhost" https://localhost/

你应该看到这些响应中不同的 Hostname 值,因为每个请求被负载均衡到不同的容器。

浏览器测试

在浏览器中测试时,你需要使用同一浏览器会话来维护 cookie。Cookie 设置了 httpOnly 和 secure 标志以确保安全,因此它将仅通过 HTTPS 连接发送,并且无法通过 JavaScript 访问。

有关更多高级配置选项,请参阅 参考文档。

多层路由 ​

多层路由在 routers 之间启用层次关系,父 routers 可以在子 routers 做出最终路由决策之前通过中间件处理请求。这对于基于身份验证的路由或分阶段中间件应用特别有用。

Provider 要求

多层路由需要 File provider,因为 Docker Swarm labels 不支持 parentRefs 字段。但是,你可以同时使用 Docker Swarm 和 File providers - Swarm labels 用于 service 发现,File 配置用于多层路由。

使用 Docker Swarm 设置多层路由 ​

要在 Docker Swarm 中使用多层路由,你需要在 Docker provider 之外启用 File provider。

更新 docker-compose.yml 中的 Traefik service:

yaml
services:
  traefik:
    image: traefik:v3.4
    command:
      - "--api.dashboard=true"
      - "--providers.docker.swarmMode=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=traefik_proxy"
      - "--providers.file.directory=/etc/traefik/dynamic"  # 启用 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
    configs:
      - source: mlr-config
        target: /etc/traefik/dynamic/mlr.yml
    networks:
      - traefik_proxy
    deploy:
      placement:
        constraints:
          - node.role == manager

configs:
  mlr-config:
    file: ./dynamic/mlr.yml

networks:
  traefik_proxy:
    external: true

基于身份验证的路由示例 ​

让我们创建一个多层路由设置,其中父 router 对请求进行身份验证,子 routers 根据用户角色定向流量。

首先,照常使用 labels 定义你的 Docker Swarm services:

yaml
# 在 docker-compose.yml 中
services:
  # ... 上述 traefik service ...

  # Admin 后端 service
  admin-backend:
    image: traefik/whoami
    networks:
      - traefik_proxy
    environment:
      - WHOAMI_NAME=Admin Backend
    deploy:
      replicas: 2
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.admin-backend.loadbalancer.server.port=80"

  # User 后端 service
  user-backend:
    image: traefik/whoami
    networks:
      - traefik_proxy
    environment:
      - WHOAMI_NAME=User Backend
    deploy:
      replicas: 2
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.user-backend.loadbalancer.server.port=80"

现在在文件中创建多层路由配置。创建 dynamic/mlr.yml:

yaml
http:
  routers:
    # 带身份验证中间件的父 router
    api-parent:
      rule: "Host(`api.swarm.localhost`) && PathPrefix(`/api`)"
      middlewares:
        - auth-middleware
      entryPoints:
        - websecure
      # 注意:没有 service 也没有 TLS 配置 - 这是一个父 router

    # 管理员用户的子 router
    api-admin:
      rule: "HeadersRegexp(`X-Auth-User`, `admin`)"
      service: admin-backend@swarm  # 引用 Swarm service
      parentRefs:
        - api-parent@file  # 显式引用 file provider 中的父

    # 普通用户的子 router
    api-user:
      rule: "HeadersRegexp(`X-Auth-User`, `user`)"
      service: user-backend@swarm  # 引用 Swarm service
      parentRefs:
        - api-parent@file  # 显式引用 file provider 中的父

  middlewares:
    auth-middleware:
      basicAuth:
        users:
          - "admin:$apr1$DmXR3Add$wfdbGw6RWIhFb0ffXMM4d0"
          - "user:$apr1$GJtcIY1o$mSLdsWYeXpPHVsxGDqadI."
        headerField: X-Auth-User

生成密码哈希

上面的密码哈希使用 htpasswd 生成。要创建你自己的用户凭据:

bash
# 使用 htpasswd (Apache utils)
htpasswd -nb admin yourpassword

跨 Provider 引用

注意 service 名称上的 @swarm 后缀和 parentRefs 中的 @file 后缀。当使用 File provider 来编排与 Swarm services 的多层路由时:

  • 使用 service-name@swarm 来引用 Swarm services
  • 在 parentRefs 中使用 parent-name@file 来引用 file provider 中的父 router

@provider 后缀告诉 Traefik 在哪个 provider 命名空间中查找资源。

部署 stack:

bash
docker stack deploy -c docker-compose.yml traefik

测试多层路由 ​

测试路由行为:

bash
# 请求通过父 router → auth 中间件 → admin 子 router
curl -k -u admin:test -H "Host: api.swarm.localhost" https://localhost/api

使用 admin:test 凭据时,你应该看到来自 admin-backend service 的响应。尝试使用 user:test 凭据访问 user-backend service。

工作原理 ​

  1. 请求到达 api.swarm.localhost/api
  2. 父 router (api-parent) 根据 host 和 path 匹配
  3. BasicAuth 中间件 对用户进行身份验证,并使用用户名设置 X-Auth-User header
  4. 子 router (api-admin 或 api-user) 根据 header 值匹配
  5. 请求被转发 到适当的 Swarm service

有关多层路由的更多详细信息,请参阅 多层路由文档。

Service 中间件 ​

Service 中间件允许你将中间件应用于 service 而不是单个 router。这意味着中间件将对 service 处理的所有请求生效,无论哪个 router 转发请求。

当你希望将相同的中间件(如 headers、rate limiting 或身份验证)应用于到达 service 的所有流量而无需在每个 router 上配置它时,这很有用。

何时使用 Service 中间件 ​

在以下情况下使用 service 中间件:

  • 多个 routers 转发流量到同一 service,并且都应应用相同的中间件
  • 你希望确保中间件始终应用于 service,无论流量如何到达
  • 你正在 service 级别集中中间件配置以便更轻松地管理

添加 Service 中间件 Labels ​

在 docker-compose.yml 中的 whoami service 部署部分添加以下 labels:

yaml
services:
  whoami:
    image: traefik/whoami
    networks:
      - traefik_proxy
    deploy:
      replicas: 2
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.whoami.rule=Host(`whoami.swarm.localhost`)"
        - "traefik.http.routers.whoami.entrypoints=websecure"
        - "traefik.http.routers.whoami.tls=true"
        # 定义中间件
        - "traefik.http.middlewares.service-headers.headers.customRequestHeaders.X-Service-Middleware=applied"
        # 在 SERVICE 级别附加中间件(不是 router 级别)
        - "traefik.http.services.whoami.middlewares=service-headers"
        - "traefik.http.services.whoami.loadbalancer.server.port=80"

Service 级别与 Router 级别中间件

  • Router 级别中间件 (traefik.http.routers.<name>.middlewares):仅当流量匹配该特定 router 的规则时应用
  • Service 级别中间件 (traefik.http.services.<name>.middlewares):应用于到达 service 的所有流量,无论哪个 router 转发它

当两者都配置时,router 中间件首先执行,然后是 service 中间件。

部署 stack:

bash
docker stack deploy -c docker-compose.yml traefik

测试 Service 中间件 ​

验证 service 中间件是否正常工作:

bash
curl -k -H "Host: whoami.swarm.localhost" https://localhost/

在 whoami 的响应中,你应该看到由 service 中间件添加的自定义 header:

X-Service-Middleware: applied

有关 service 中间件的更多详细信息,请参阅 参考文档。

结论 ​

在本高级指南中,你已学习如何:

  • 使用 secure headers 和 IP allowlisting 等中间件添加安全性
  • 使用 Let's Encrypt 自动化证书管理
  • 为有状态应用程序实现粘性会话
  • 为基于身份验证的路由设置多层路由
  • 在 service 级别应用中间件以集中管理中间件

这些高级功能允许你使用 Docker Swarm 构建生产就绪的 Traefik 部署。每个功能都可以进一步自定义以满足你的特定要求。

后续步骤 ​

现在你已经掌握了 Docker Swarm 上 Traefik 的基本和高级功能,你可能想探索:

  • 高级路由选项,如查询参数匹配、基于 header 的路由等
  • 用于身份验证、速率限制和请求修改的其它中间件
  • 用于监控和调试 Traefik 部署的可观测性功能
  • 用于暴露 TCP service 的 TCP services
  • 用于暴露 UDP service 的 UDP services
  • Docker Swarm provider 文档

基于 MIT 协议发布