如何用 Traefik 的 Nomad Provider 根据服务标签自动生成路由
如果你的服务跑在 HashiCorp Nomad 上,并且希望 Traefik 能够根据服务自身的标签(tags)自动发现并生成 HTTP 路由,那么需要启用 Traefik 的 Nomad Provider。完成这篇文章的操作后,Traefik 会轮询 Nomad API 获取服务列表,把每个 Nomad service 的 tags 解析成 Traefik 的动态路由配置:新增、删除或修改服务标签时,路由随之自动更新,不需要再手动维护文件式路由规则。
前提条件:
- 一个正在运行的 Nomad 集群,Traefik 能够访问其 API(默认连接
http://127.0.0.1:4646); - Traefik 的静态配置可修改(配置文件或启动参数均可)。
启用 Nomad Provider
在 Traefik 的静态配置中开启 provider 即可,文档支持三种等价写法:
# File (YAML)
providers:
nomad: {}
# File (TOML)
[providers.nomad]
# CLI
--providers.nomad=true
Provider 完整选项说明见 Nomad Provider 文档。常用的几个配置项:
| 配置项 | 作用 | 默认值 |
|---|---|---|
providers.nomad.endpoint.address |
Nomad 服务器地址 | http://127.0.0.1:4646 |
providers.nomad.endpoint.token |
Nomad 开启 ACL 时使用的每请求 ACL token(对应权限为 read-job) |
"" |
providers.nomad.refreshInterval |
轮询间隔,开启 watch 后此选项被忽略 |
15s |
providers.nomad.watch |
启用 watch 模式,按事件刷新配置 | false |
providers.nomad.exposedByDefault |
是否默认暴露所有 Nomad 服务 | true |
providers.nomad.namespaces |
限定在哪些 Nomad namespace 中发现服务 | "" |
默认情况下,Provider 每 15s 轮询一次 Nomad API;服务变更会在这个周期内反映到路由上。如果希望按事件即时刷新,可以开启 watch 模式(此时可配合 providers.nomad.throttleDuration 限制事件处理频率,该选项只在 watch 启用时可用)。
一个启用 Provider 并指向远程 Nomad 节点的完整示例:
providers:
nomad:
endpoint:
address: "http://192.168.1.10:4646"
refreshInterval: 5s
在 Nomad Job 中通过 tags 定义路由
这是整个流程的核心:Traefik 会把 Nomad service 的 tags 当作配置来源。文档给出的最小示例是在 job 文件的 service 段里给服务加上路由规则 tag(文档示例):
job "my-service" {
datacenters = ["dc1"]
group "web" {
task "app" {
driver = "docker"
service {
name = "my-service"
tags = [
"traefik.http.routers.my-service.rule=Host(`example.com`)",
]
}
}
}
}
规则生效后的效果:
- Traefik 为每个发现的 Nomad service 自动创建一个同名的 Traefik service,并为每个服务实例注册一个 server;
- 同时自动生成一个默认路由,规则为服务名(默认模板是
Host({{ normalize .Name }}),即Host(my-service)); - 如果 tags 中通过
traefik.http.routers.<名称>.rule显式定义了规则(如上例),则以该规则覆盖默认规则。
也就是说,如果什么都不配,my-service 也会有一条 Host(my-service) 的路由指向它。想让某个域名指向该服务,就加一条 rule tag。
指定转发端口:避免 502
默认情况下,Traefik 使用该容器暴露的第一个端口。当服务实际监听端口不是第一个端口时,会出现 HTTP/502 Gateway Error。解决方法是用 tag 覆盖目标端口:
service {
name = "my-service"
tags = [
"traefik.http.routers.my-service.rule=Host(`example.com`)",
"traefik.http.routers.my-service.service=my-service",
"traefik.http.services.my-service.loadbalancer.server.port=12345",
]
}
traefik.http.services.<名称>.loadbalancer.server.port 会覆盖“取第一个端口”的默认行为。此外,如果只按 Nomad Service Discovery 的默认流程暴露端口(例如在 job 中暴露 :1337),Traefik 也能直接检测到并使用该端口。
一个容器挂多个路由和服务
需要把多个域名转发到同一容器的不同端口时,用 service 参数把 router 指到各自的服务,再分别为每个服务定义端口:
service {
name = "my-service"
tags = [
"traefik.http.routers.www-router.rule=Host(`example-a.com`)",
"traefik.http.routers.www-router.service=www-service",
"traefik.http.services.www-service.loadbalancer.server.port=8000",
"traefik.http.routers.admin-router.rule=Host(`example-b.com`)",
"traefik.http.routers.admin-router.service=admin-service",
"traefik.http.services.admin-service.loadbalancer.server.port=9000",
]
}
这样 http://example-a.com 转发到容器 8000 端口,http://example-b.com 转发到 9000 端口。
用 tags 声明并引用 Middleware
中间件同样可以用 tags 声明,前缀是 traefik.http.middlewares.<名称>.,然后由 router tag 引用。例如声明一个 HTTPS 重定向中间件:
# 声明 middleware
traefik.http.middlewares.my-redirect.redirectscheme.scheme=https
# router 引用它
traefik.http.routers.my-service.middlewares=my-redirect
注意文档中明确的限制:如果有多个同名字中间件但参数不同,中间件会声明失败。
Router 与 Service 可配置的全部 tag 选项(entrypoints、middlewares、tls、priority、healthcheck、sticky.cookie 等)见 Nomad 路由配置文档。
控制哪些服务会被发现
exposedByDefault 默认为 true,即 Traefik 会为所有检测到的 Nomad 服务创建路由。如果只想暴露部分服务,把它设为 false,并在需要暴露的服务上加 traefik.enable=true tag:
providers:
nomad:
exposedByDefault: false
service {
name = "my-service"
tags = [
"traefik.enable=true",
"traefik.http.routers.my-service.rule=Host(`example.com`)",
]
}
traefik.enable 会覆盖 exposedByDefault 的取值。另一种更细粒度的方式是 constraints 表达式,用 Tag() / TagRegex() 函数匹配服务标签,例如:
[providers.nomad]
# 只包含带有标签 a.tag.name=foo 的服务
constraints = "Tag(`a.tag.name=foo`)"
文档提醒:traefik.* 是保留标签命名空间,不能用作自定义 constraints 的键。两种方式的完整说明见 Provider 概览文档中的 Restrict the Scope of Service Discovery 一节。
另外,如果 Nomad 启用了 namespace,可以用 providers.nomad.namespaces 限定发现范围;此时发现的对象名会带后缀,格式为 <resource-name>@nomad-<namespace>。文档同时警告:namespaces 与 namespace 两个选项只能二选一。
验证路由是否生成
Traefik 自带 API(默认在 traefik entrypoint 的 8080 端口),可以用来核对动态路由是否按预期生成,端点定义见 API & Dashboard 文档:
# 列出所有 HTTP 路由,确认 my-service 路由存在且 rule 正确
curl http://<traefik-host>:8080/api/http/routers
# 列出所有 HTTP 服务,确认服务及其 server 地址、端口
curl http://<traefik-host>:8080/api/http/services
判断标准:修改 job 的 tags 并让 Nomad 生效后(轮询模式下最长等一个 refreshInterval 周期),上述接口中应出现对应的 router/service 条目,rule 与 tags 中定义的 Host(example.com) 一致。文档未给出这些接口的固定示例输出,实际返回字段以接口输出为准。
限制与注意事项
- tags 大小写不敏感,但值里的域名等配置按字符串解析。
- 不要把敏感数据放进 tags。文档明确建议:证书、凭据等应存放在 secrets 或文件等更安全的位置,而不是服务标签里。
- 声明 TCP/UDP Router 或 Service 的副作用:一旦为某个 Nomad 服务声明了 TCP(或 UDP)Router/Service,Traefik 就不会再为它自动创建 HTTP Router/Service。HTTP 与 TCP 路由可以共存,但必须手动分别声明。
defaultRule模板限制:自定义默认规则必须是合法的 Go template(支持 sprig 函数),通过Name标识符访问服务名,并可访问该服务上所有带prefix(默认traefik)的标签。文档还提示:如果 Traefik 自身也被暴露且命中默认规则,可能产生指向自己的路由环,Traefik 会加内部中间件拒绝来自同一路由的转发以防死循环。- 多 Provider 共存时的优先级:未显式配置
providers.precedence时,nomad在默认优先级表中排第 12 位,低于kubernetesgateway、kubernetescrd、kubernetes、swarm、docker、file等;它只在两个 Provider 的路由规则相同且priority数值相等时起 tiebreaker 作用。
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 StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00