Traefik 仪表盘自定义路径暴露:多 Provider 完整配置与 api@internal 源码原理
本文围绕 Traefik 仓库中的官方示例片段 include-dashboard-custom-path-examples.md 展开,讲解如何把 Traefik 内置的 API 与 Dashboard 从默认路径(/api、/dashboard)迁移到一个自定义路径前缀(示例中使用 /traefik),并叠加 Basic Auth 认证;覆盖 Docker、Docker Swarm、Kubernetes CRD、Consul Catalog、File(YAML/TOML)六种 Provider 的完整可复制配置,并结合 internal provider 源码 与 Basic Auth 中间件源码 剖析 api@internal 这一内置服务的真实作用与认证校验流程。读完本文,你可以独立完成仪表盘路径改造、理解自定义 Router 与内部 Router 的优先级关系,并掌握 apr1 哈希用户文件的生成与转义规则。
一、背景:Traefik 内置 API/Dashboard 的默认路由
在自定义之前,先弄清楚 Traefik 默认把 Dashboard 放在哪里。internal provider 会在启动时根据静态配置自动注入一组内置 Router。从 apiConfiguration 实现 可以看到:
- 当且仅当静态配置中
api.insecure = true时,才会创建默认路由:apiRouter:规则PathPrefix(\/api`),服务api@internal,优先级math.MaxInt - 1`;dashboardRouter(当api.dashboard = true时):规则PathPrefix(\/`),服务dashboard@internal,优先级math.MaxInt - 2,并挂上两个内部中间件dashboard_redirect@internal(把根路径 301 重定向到/dashboard/)与dashboard_stripprefix@internal(剥离/dashboard/` 前缀);debugRouter(当api.debug = true时):规则PathPrefix(\/debug`)`。
- 这些默认路由都绑定在名为
traefik的默认入口点上。
也就是说,默认情况下 Dashboard 位于 traefik 入口点下的 /(经重定向落到 /dashboard/),JSON API 位于 /api。当你希望把整个 Dashboard 收拢到 https://traefik.example.com/traefik 这样一条独立路径下——例如避免与后端业务路径冲突、或配合统一网关的路径规划——就需要用一条自己的 Router 重新指到内部服务 api@internal 上。这正是官方示例片段要解决的问题。
二、核心思路:一条规则 + api@internal + auth 中间件
官方示例给出的自定义路径配置,其结构在六个 Provider 中完全一致,核心要素只有三个:
- 路由规则:
Host(\traefik.example.com`) && PathPrefix(`/traefik`)——按域名 + 自定义前缀匹配,取代默认的PathPrefix(`/api`) || PathPrefix(`/dashboard`)`; - 服务:
api@internal——直接复用 internal provider 注入的内置服务,不经过后端转发,@internal后缀表示这是跨 Provider 引用的内部服务(Kubernetes Gateway API 路径下同样支持该引用,参见 httproute.go 中的注释); - 认证中间件:
auth(Basic Auth),用户以用户名:apr1 哈希形式配置。
请求处理链在 internalhandler.go 中收口:服务名 api@internal 会被分发到内部 API handler,返回 Traefik 自身的 JSON API 与嵌入式 Web 控制台前端。
三、六种 Provider 的完整配置示例
以下示例完整继承自 include-dashboard-custom-path-examples.md,可直接复制使用(traefik.example.com 与用户哈希请替换为实际值)。
3.1 Docker(普通容器,labels 方式)
# Dynamic Configuration
labels:
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
3.2 Docker Swarm(deploy.labels 方式)
# Dynamic Configuration
deploy:
labels:
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
# Dummy service for Swarm port detection. The port can be any valid integer value.
- "traefik.http.services.dummy-svc.loadbalancer.server.port=9999"
Swarm 模式下多出的 dummy-svc 是官方注释中说明的"端口探测占位服务":Swarm 依靠容器发布的端口做服务发现,加一个任意合法端口的假服务可以让 provider 正确识别该任务。
3.3 Kubernetes(IngressRoute + Middleware CRD)
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard
spec:
routes:
- match: Host(`traefik.example.com`) && PathPrefix(`/traefik`)
kind: Rule
services:
- name: api@internal
kind: TraefikService
middlewares:
- name: auth
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: auth
spec:
basicAuth:
secret: secretName # Kubernetes secret named "secretName"
Kubernetes 与其他 Provider 的显著差异:用户名哈希不直接写在 Middleware 里,而是引用名为 secretName 的 Kubernetes Secret(secret 字段),把凭据从 CRD 中隔离出来。
3.4 Consul Catalog(key-value 标签方式)
# Dynamic Configuration
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
3.5 File Provider(YAML 动态配置)
# Dynamic Configuration
http:
routers:
dashboard:
rule: Host(`traefik.example.com`) && PathPrefix(`/traefik`)
service: api@internal
middlewares:
- auth
middlewares:
auth:
basicAuth:
users:
- "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
- "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"
3.6 File Provider(TOML 动态配置)
# Dynamic Configuration
[http.routers.my-api]
rule = "Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
service = "api@internal"
middlewares = ["auth"]
[http.middlewares.auth.basicAuth]
users = [
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
]
注意 Docker/Consul 的 label 值里写的是
$$apr1$$,而 File 配置里是单$。这是 Docker 对$的转义约定:$$在解析后得到字面量$,最终传给 Traefik 的都是test:$apr1$H6uskkkW$...这一形式的 apr1 哈希。
四、源码级原理佐证
4.1 为什么自定义 Router 能接管 Dashboard
api@internal 服务由 internal provider 无条件注册:internal.go 中 cfg.HTTP.Services["api"] = &dynamic.Service{} 在 api 非 nil 时总是执行(无论 insecure 与否)。而默认的两条 Router(api、dashboard)仅在 api.insecure = true 时创建,且优先级分别被设为 MaxInt - 1 与 MaxInt - 2。你自定义的 dashboard Router 未显式指定 priority,从源码结构看,其优先级由规则复杂度计算得出;若出现与默认路由的匹配重叠,可在自己的 Router 上显式声明更大的 priority 来确保自定义路径优先生效。
4.2 Basic Auth 的校验实现
auth 中间件的实现位于 basic_auth.go:
- 通过
getUsers解析users列表(或usersFile文件),要求至少存在一个用户,否则中间件创建直接报错; - 采用
goauth.CheckSecret做常量时间的哈希比对,且当用户不存在时仍会用首个哈希做一次"影子计算"(notFoundSecret),源码注释说明这是为了防止时序攻击(timing attack); - 请求经
req.BasicAuth()提取用户名/口令后进入比对流程,失败则返回 401 并触发WWW-Authenticate头(Realm 可配置,默认值见defaultRealm常量)。
示例中的 $apr1$H6uskkkW$... 是 Apache MD5-Crypt(htpasswd 的 apr1 算法)格式哈希:$apr1$ 标记算法、H6uskkkW 为 8 字符盐、其余为哈希体,可用 htpasswd -nb -B 用户名 口令 离线生成后填入配置,无需在 Traefik 中保存明文口令。
五、验证与注意事项
- 验证路由生效:改造完成后,可通过内部 API 查看动态路由,确认
dashboard规则与auth中间件已挂载:GET http://<traefik-host>/api/http/routers/dashboard(API 需先关闭insecure或本身暴露时才可达,此时可经自定义路径/traefik/api间接访问); - 默认路径与自定义路径的关系:本示例只新增/覆盖一条 Router,不会删除默认的
api/dashboard路由。若希望仅保留自定义路径这一入口,从源码行为看,可将静态配置的api.insecure置为false以抑制内部注入的默认 Router(apiConfiguration中的默认路由创建分支依赖该开关),仅保留自定义路由引用api@internal的路径; - 与默认示例的差异:仓库中另一份官方示例 include-dashboard-examples.md 使用的规则是
PathPrefix(\/api`) || PathPrefix(`/dashboard`),即保持默认双路径;本文对应的 custom-path 示例将其收敛为单一前缀PathPrefix(`/traefik`)`,这是两者唯一的规则差异,认证与服务的接法完全一致; - TOML 示例的 Router 命名:File (TOML) 示例中 Router 名为
my-api,其余 Provider 示例中为dashboard,二者无功能差别,命名可自定,但建议与语义(dashboard)保持一致以便排障时检索。
相关仓库路径:internal provider 默认路由注入、内部服务分发、Basic Auth 中间件、Gateway API 对 api@internal 的引用支持、静态配置示例。
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