Traefik + Docker Swarm 进阶实战:中间件、Let's Encrypt、Sticky Sessions 与多层路由
本文基于 Traefik 官方文档 docs/content/expose/swarm/advanced.md 展开,是 Docker Swarm 基础指南(docs/content/expose/swarm/basic.md)的进阶篇。假设你已完成基础指南,拥有一个可运行的 Traefik + Swarm 环境(whoami 服务跑在 3 副本上,whoami-api 服务跑在 2 副本上)。读完后你将掌握五类生产级能力:用 Headers 与 IP Allowlist 中间件加固响应与访问控制、用 Let's Encrypt 自动化证书管理、用 Sticky Sessions 支撑有状态应用、用 Multi-Layer Routing 实现基于认证的分级路由、以及用 Service Middlewares 把中间件配置集中到服务层面。
一、前置条件
- 已完成基础指南(Basic Guide)中的部署
- Docker Swarm 集群已初始化
- Traefik 通过 Swarm 正常发现并路由
whoami/whoami-api服务
二、添加中间件(Middlewares)
中间件允许请求/响应在穿过 Traefik 时被修改。这里添加两个最常用的中间件:用于安全响应头的 Headers 与用于访问控制的 IP Allowlist。
在 docker-compose.yml 的 whoami 服务的 deploy 部分追加以下 labels(Swarm 模式下 labels 必须写在 deploy 段,而不是 container 级):
deploy:
# ... existing configuration ...
labels:
# ... existing 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 the middlewares
- "traefik.http.routers.whoami.middlewares=secure-headers,ip-allowlist"
各参数含义(详见 headers 参考文档):
| Label 参数 | 效果 |
|---|---|
frameDeny=true |
响应加 X-Frame-Options: DENY,防止页面被 iframe 嵌套(点击劫持) |
sslRedirect=true |
HTTP 请求 301 跳转到 HTTPS |
browserXssFilter=true |
加 X-XSS-Protection: 1; mode=block |
contentTypeNosniff=true |
加 X-Content-Type-Options: nosniff,禁止浏览器嗅探 MIME 类型 |
stsIncludeSubdomains / stsPreload / stsSeconds=31536000 |
下发 Strict-Transport-Security(HSTS),有效期 1 年,覆盖子域并声明可预加载 |
ipallowlist.sourceRange=... |
仅放行 CIDR 列表内的来源 IP(这里覆盖本机与常见内网段),其余返回 403 |
对 whoami-api 服务做同样的挂载:
deploy:
# ... existing configuration ...
labels:
# ... existing labels ...
- "traefik.http.routers.whoami-api.middlewares=secure-headers,ip-allowlist"
应用变更:
docker stack deploy -c docker-compose.yml traefik
验证中间件生效
验证 Secure Headers:
curl -k -I -H "Host: whoami.swarm.localhost" https://localhost/
响应头中应看到中间件设置的:
X-Frame-Options: DENYX-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=block- 带相应设置的
Strict-Transport-Security
验证 IP Allowlist:请求来源 IP 在允许列表内(如 127.0.0.1)时请求成功:
curl -k -I -H "Host: whoami.swarm.localhost" https://localhost/
若来源 IP 不在列表内,请求会被拒绝并返回 403 Forbidden。本地环境可以临时修改 sourceRange 把自己排除掉来模拟被拒绝的场景,测完再改回来。
源码视角:中间件如何被执行
Swarm provider 在 SwarmProvider.Provide 中周期性(默认 RefreshSeconds 15 秒,见 SetDefaults)调用 listServices 拉取 Docker Swarm 的 service 列表与 overlay 网络,把每个 service 的 deploy.labels 解析成动态配置。labels 中 traefik.http.middlewares.* 定义中间件、traefik.http.routers.<name>.middlewares 把它们按顺序挂到路由链上,最终由 http.Router 的 Middlewares 字段 承载。
三、用 Let's Encrypt 自动生成证书
Let's Encrypt 提供免费且自动续期的 TLS 证书。不再使用基础指南中的自签证书,把配置改为自动签发。
在 Traefik 服务的 command 段加入证书解析器配置:
command:
# ... existing commands ...
# Let's Encrypt configuration
- "--certificatesresolvers.le.acme.email=your-email@example.com" # replace with your actual email
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
三项参数的作用:
email:ACME 账户注册邮箱(替换为你的真实邮箱);storage=/letsencrypt/acme.json:证书与账户状态持久化位置,需要挂卷保证容器重建后不丢;httpchallenge.entrypoint=web:HTTP-01 验证走web(80 端口)入口点,Traefik 会临时监听/.well-known/acme-challenge/路径回应 Let's Encrypt 的验证请求。
添加 Let's Encrypt 证书卷(服务引用 + 顶层命名卷):
volumes:
# ...Existing volumes...
- letsencrypt:/letsencrypt
volumes:
# ... existing volumes ...
letsencrypt:
driver: local
更新路由 labels 指定使用 le 证书解析器:
labels:
# ... existing labels ...
- "traefik.http.routers.whoami.tls.certresolver=le"
其他需要 HTTPS 的服务同理:
labels:
# ... existing labels ...
- "traefik.http.routers.whoami-api.tls.certresolver=le"
应用变更:
docker stack deploy -c docker-compose.yml traefik
重要:需要公网 DNS。 Let's Encrypt 验证域名所有权要求域名可公网访问。对于
whoami.swarm.localhost这类本地域名,证书仍会保持自签状态。生产环境请替换为真实域名,并让 DNS A 记录指向你的 Traefik 实例。
证书签发后可以验证:
# Verify the certificate chain
curl -v https://whoami.swarm.localhost/ 2>&1 | grep -i "server certificate"
应能看到证书签发方为 Let's Encrypt(本地测试域则仍是自签证书)。
四、配置 Sticky Sessions(会话粘滞)
Sticky sessions 保证同一用户的请求始终落到同一个后端容器,对有状态应用必不可少。Swarm 中 whoami 已跑多个副本,现在给它加粘滞配置:
deploy:
# ... existing configuration ...
labels:
# ... existing labels ...
# Sticky Sessions Configuration
- "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"
参数说明(结构定义见 Sticky / Cookie 类型,完整参考见 load balancer 文档):
sticky.cookie=true:启用基于 Cookie 的粘滞;name=sticky_cookie:自定义 Cookie 名,Traefik 在其中写入所选后端标识;secure=true:Cookie 仅通过 HTTPS 传输;httpOnly=true:JavaScript 无法读取该 Cookie,降低被脚本篡改的风险。
应用变更:
docker stack deploy -c docker-compose.yml traefik
测试粘滞效果
# First request - save cookies to a file
curl -k -c cookies.txt -H "Host: whoami.swarm.localhost" https://localhost/
# Subsequent requests - use the cookies
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 的请求会在不同容器间做负载均衡:
# Requests without cookies should be load-balanced across different containers
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 访问。
源码层面可以印证粘滞的实现位置:sticky 字段同时存在于 LoadBalancer(普通服务)与 WeightedRoundRobin 等加权负载均衡器 结构中,Traefik 在服务选择后端 server 时才评估 sticky cookie,因此它与 loadbalancer.server.port 等负载均衡配置处于同一层级(traefik.http.services.<name>.loadbalancer.*)。
五、Multi-Layer Routing(多层路由)
多层路由允许路由器之间建立层级关系:父路由器先通过中间件处理请求,子路由器再做最终路由决策。特别适合基于认证的分级路由或分阶段中间件应用。
前提约束: 多层路由依赖 File provider——Docker Swarm 的 labels 不支持
parentRefs字段(在 http.Router 定义 中该字段标注label:"-",即禁止通过 label 写入)。但可以同时启用 Docker Swarm 和 File 两个 provider:用 Swarm labels 做服务发现,用 File 配置做多层路由编排。
启用 File provider
在 docker-compose.yml 中更新 Traefik 服务,把 File provider 与 Swarm provider 一起打开,并通过 Swarm config 注入动态配置文件:
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" # 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
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
注意 v3 的 provider 命名: 从当前仓库的 deprecation 检查 看,v3 已把
--providers.docker.swarmMode=true标记为移除项,提示改用独立的 Swarm provider(providers.swarm 配置段,即--providers.swarm=true)。原文示例为教学场景写作;在 v3 正式部署中建议按 Swarm provider 参考文档 使用providers.swarm.*配置项。node.role == manager约束是因为 Swarm API 只在 manager 节点暴露(见 Swarm provider 文档的 Docker API Access 一节)。
基于认证的父/子路由示例
场景:父路由器先做 BasicAuth 认证,子路由器再依据认证出的用户角色把流量导向不同服务。
第一步,Swarm 侧照常定义两个后端服务(labels 声明服务发现信息):
# In docker-compose.yml
services:
# ... traefik service from above ...
# Admin backend 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 backend 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(通过上面的 Swarm config 挂载到 /etc/traefik/dynamic/mlr.yml):
http:
routers:
# Parent router with authentication middleware
api-parent:
rule: "Host(`api.swarm.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@swarm # Reference Swarm 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@swarm # Reference Swarm 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
要点解读:
- 父路由器
api-parent:只有 rule、middlewares 和 entryPoints,没有 service 也没有 TLS——这是父路由器的特征,它不直接转发流量,只负责"认证 + 派发给子路由"; headerField: X-Auth-User:BasicAuth 中间件(见 basicAuth 参考)认证成功后,把用户名写入X-Auth-User请求头,子路由的规则正是基于这个头做匹配;- 子路由器 通过
parentRefs声明父引用,流量必须先从父路由器"流下来"才进入子路由的规则评估。
密码哈希: 上文的
$apr1$...是 MD5-crypt 格式哈希,可用 Apache 工具生成:htpasswd -nb admin yourpassword
跨 Provider 引用的
@后缀: 注意服务名上的@swarm和parentRefs里的@file。@provider后缀告诉 Traefik 去哪个 provider 命名空间查找资源:引用 Swarm 服务发现的服务写service-name@swarm;在 File provider 里引用 File provider 定义的父路由器写parent-name@file。
部署 stack:
docker stack deploy -c docker-compose.yml traefik
测试多层路由
# Request goes through parent router → auth middleware → admin child router
curl -k -u admin:test -H "Host: api.swarm.localhost" https://localhost/api
以 admin:test 认证时应看到 admin-backend(whoami 输出中标注 Admin Backend)的响应;改用 user:test 则会命中 user-backend。
工作原理拆解
- 请求到达
api.swarm.localhost/api - 父路由器
api-parent按 Host + PathPrefix 规则匹配 auth-middleware(BasicAuth)完成认证,把用户名写入X-Auth-User请求头- 子路由器
api-admin或api-user按该头的值匹配 - 请求被转发到对应的 Swarm 服务
更多机制细节见 Multi-Layer Routing 参考文档。
六、Service Middlewares(服务级中间件)
Service middlewares 把中间件挂在服务而不是路由上,效果是该服务处理的所有请求都会经过它,不管流量是从哪个路由进来的。
适用场景:
- 多个路由转发到同一个服务,且都应应用同一套中间件;
- 希望确保无论流量从哪条路径到达,中间件一定生效;
- 把中间件配置集中到服务层面,便于统一维护。
在 docker-compose.yml 的 whoami 服务上追加 labels:
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"
# 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"
注意两条 labels 的区别:
- 路由级
traefik.http.routers.<name>.middlewares:仅当流量匹配该路由的规则时才生效; - 服务级
traefik.http.services.<name>.middlewares:对该服务的所有入站流量生效,与经由哪个路由转发无关。
两者同时配置时,路由级中间件先执行,然后才执行服务级中间件。
部署:
docker stack deploy -c docker-compose.yml traefik
验证:
curl -k -H "Host: whoami.swarm.localhost" https://localhost/
whoami 会回显收到的请求头,其中应能看到服务级中间件注入的自定义请求头:
X-Service-Middleware: applied
更多 service 中间件细节见 service 参考文档的 Middlewares 小节。
七、Swarm Provider 底层机制速览
结合仓库源码,理解上面这些配置为什么在 Swarm 环境下可行:
- 标签作用域:Swarm 模式下 Traefik 读取的是 service 级 labels(
deploy.labels),而非单个容器标签——这正是所有示例把traefik.*labels 放在deploy:下的原因(见 Swarm provider 文档); - 端口必须显式声明:Swarm API 不向 Traefik 暴露端口探测信息,因此
traefik.http.services.<name>.loadbalancer.server.port是必填的,这也是为什么每个示例服务都写了loadbalancer.server.port=80; - 任务到后端服务器的映射:listTasks / parseTasks 通过 Docker API 枚举每个 service 下处于 running 状态的 task,再依据 task 的网络附加信息拿到各副本的内网 IP,最终组成负载均衡池——这解释了 sticky sessions 中
Hostname会在不同任务/节点间变化的现象; - 配置刷新节奏:provider 以
RefreshSeconds(默认 15s)轮询 service 列表(pswarm.go 中的 ticker 逻辑),因此docker stack deploy后配置生效通常有一个小的轮询延迟; - 部署位置约束:Swarm API 只在 manager 节点暴露,所以 Traefik 服务要加
node.role == manager约束,或通过 TCP/SSH endpoint 将 API 暴露到任意节点(安全影响见 provider 文档的 Security Note)。
八、小结与延伸
本文覆盖了 Swarm 进阶部署的五个关键能力:
- 用 secure headers 与 IP allowlisting 中间件加固服务;
- 用 Let's Encrypt 自动管理证书(需公网可达域名);
- 用 sticky sessions 支撑有状态应用;
- 用 Swarm + File 双 provider 组合实现基于认证的 multi-layer routing;
- 用 service middlewares 把中间件配置集中到服务层面。
如需进一步深入,可继续参考仓库内的这些文档:
- Advanced routing options(规则与优先级):query 参数匹配、基于头的路由等;
- Middlewares 总览:认证、限流、请求改写等全部中间件;
- Metrics 可观测性:监控与调试 Traefik 部署;
- TCP services 与 UDP services:暴露非 HTTP 服务;
- Docker Swarm provider 完整文档:Swarm 集成的所有配置项(endpoint、constraints、defaultRule 等)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00