Traefik 与 Docker:基于容器标签的动态路由与负载均衡实战指南
【免费下载链接】traefikThe Cloud Native Application Proxy项目地址: https://gitcode.com/GitHub_Trending/tr/traefik
Traefik(Cloud Native Application Proxy)最具代表性的特性之一,就是把路由与负载均衡的配置权下放到应用层:借助 Docker 容器的 labels,开发者在部署应用的同时即可声明域名规则、端口、中间件、TLS 等完整的动态配置。本文以 Docker 路由参考文档 为主线,完整梳理 Docker Provider 的配置模型、每一类标签的语义与取值,并结合本仓库中 Docker Provider 实现 的源码说明其底层行为。读完本文,你将能够熟练使用 compose labels 为容器声明 Router/Service/Middleware,正确解决端口探测、多端口暴露、健康检查、会话保持以及 TCP/UDP 代理等真实场景问题。
阅读前说明:本文介绍的"标签路由"是动态配置手段;启用 Docker Provider 本身的连接参数、
watch、exposedByDefault、defaultRule等静态配置项不在本文逐条展开,可参考 Docker Provider 配置页。
一、核心思路:把路由配置下沉到容器本身
常规反向代理需要运维在一个集中式配置文件中维护"域名 ↔ 后端"的映射关系;而 Traefik 将这条映射关系声明式地写在了每个容器的标签里:
- Traefik 通过 Docker API 持续感知宿主机上的容器;
- 读取容器上以
traefik.开头的标签; - 依据标签为每个容器自动生成对应的Service(负载均衡器,每个容器实例对应一个 server)与Router(入口规则),详见 Service 文档 与 规则与优先级文档。
从源码结构看,这条"标签 → 动态配置"的流水线集中在 pkg/provider/docker/config.go 的DynConfBuilder.build方法中:逐个inspect容器 → 用label.DecodeConfiguration解析标签 → 分别构建 HTTP/TCP/UDP 三类配置 → 最后provider.Merge合并为全局动态配置。
安全第一:标签不是存放机密的地方
!!! warning "Labels 与敏感数据" 官方明确建议:不要在标签中存放敏感数据(证书、凭据等)。标签对具备 Docker 读取权限的进程可见,应把这类信息放到 Secret、独立文件等更安全的存储中。
几条通用约定
- 标签名不区分大小写(读取与匹配时统一处理);
- Router 名(
<router_name>)、Service 名(<service_name>)、Middleware 名中不允许出现@字符——@是 Traefik 中标识 Provider 来源(如foo@file)的保留分隔符; - 若定义了 TCP/UDP Router 或 Service,容器将不再被自动创建 HTTP Router/Service(详见后文)。
二、快速上手:最简单的"域名 → 容器"映射
2.1 启用 Docker Provider
在静态配置中开启 Docker Provider(三种写法等价):
providers: docker: {}[providers.docker]--providers.docker=true2.2 给容器打上路由标签
在 docker compose 文件中为服务声明标签,即可完成"域名 → 容器"的暴露:
services: my-container: # ... labels: - traefik.http.routers.my-container.rule=Host(`example.com`)启动后,访问http://example.com的流量会被路由到my-container。
三、Service 的自动创建与自动绑定规则
在基于标签的配置中有两条"偷懒"规则(源自 docs/content/includes/service-by-label.md,被本文档直接引用),理解它们能解释后续所有示例为何"只写一半"也能跑通:
- 规则一(自动绑定):标签声明了 Router(例如通过
rule定义),也声明了 Service(例如通过loadbalancer.server.port隐式创建),但 Router 没有通过service=指定使用哪个 Service —— 此时该 Service 会被自动绑定到这个 Router; - 规则二(自动创建):标签声明了 Router,却完全没有定义任何 Service —— Traefik 会自动创建一个 Service并绑定给该 Router。
而一旦 Router 通过service=显式指定了 Service,则无论是否还有其它 Service 被定义,都只会使用指定的那一个。
这三条规则在 config.go 中都能找到对应实现:buildServiceConfiguration在configuration.Services为空时会自动以"服务名"创建默认ServersLoadBalancer;provider.BuildRouterConfiguration负责把 rule 模板、默认 rule 与模型渲染进 Router。
3.1 三种声明的完整示例
示例 A:只声明 Service,Router 自动绑定(对应规则一)
labels: - "traefik.http.routers.myproxy.rule=Host(`example.net`)" # 下面的 service myservice 会自动绑定到 router myproxy - "traefik.http.services.myservice.loadbalancer.server.port=80"示例 B:只声明 Router,Service 自动创建(对应规则二)
labels: # 没有声明任何 service,Traefik 会自动创建并绑定到 router myproxy - "traefik.http.routers.myproxy.rule=Host(`example.net`)"示例 C:显式绑定 Router 与 Service
labels: - traefik.http.routers.www-router.rule=Host(`example-a.com`) # 显式建立 router 与 service 的关联 - traefik.http.routers.www-router.service=www-service - traefik.http.services.www-service.loadbalancer.server.port=8000关于"默认自动创建的 Service 指向哪个端口/地址",见下一节
loadbalancer.server.port的说明——不指定端口时 Traefik 默认取容器第一个暴露的端口。
四、HTTP 层标签详解
4.1 Routers(HTTP 路由器)
要为某个容器上自动生成的 Router 改配置,添加以traefik.http.routers.<name-of-your-choice>.开头、后跟想修改的选项名的标签即可。例如修改规则:
labels: - traefik.http.routers.my-container.rule=Host(`example.com`)| Label | Description | Value |
|---|---|---|
traefik.http.routers.<router_name>.rule | 路由匹配规则,详见 Rules | Host(`example.com`) |
traefik.http.routers.<router_name>.ruleSyntax | 按 Router 指定规则解析语法。⚠️ruleSyntax已弃用,将在下一个大版本移除,请改用 v3 语法书写规则 | v3 |
traefik.http.routers.<router_name>.entrypoints | 该 Router 绑定的入口点,详见 EntryPoints | ep1,ep2 |
traefik.http.routers.<router_name>.middlewares | 该 Router 挂载的中间件(引用链),详见 middlewares overview | auth,prefix,cb |
traefik.http.routers.<router_name>.service | 该 Router 使用的 Service,详见 Service | myservice |
traefik.http.routers.<router_name>.tls | 是否启用 TLS(HTTPS 入口),详见 TLS overview | true |
traefik.http.routers.<router_name>.tls.certresolver | 为 Router 指定 ACME 证书解析器,详见 certResolver | myresolver |
traefik.http.routers.<router_name>.tls.domains[n].main | 主域名(SAN 主体),详见 ACME 域定义 | example.org |
traefik.http.routers.<router_name>.tls.domains[n].sans | SAN 备用域名,同上 | test.example.org,dev.example.org |
traefik.http.routers.<router_name>.tls.options | 引用的 TLS Options(由 File/K8s CRD Provider 定义),详见 TLS options | foobar |
traefik.http.routers.<router_name>.observability.accesslogs | 控制该 Router 是否产生访问日志 | true |
traefik.http.routers.<router_name>.observability.metrics | 控制该 Router 是否产生指标 | true |
traefik.http.routers.<router_name>.observability.tracing | 控制该 Router 是否产生链路追踪 | true |
traefik.http.routers.<router_name>.priority | 路由优先级,详见 优先级计算 | 42 |
4.1.1 同一容器暴露两个域名(两个 Router)
services: my-container: # ... labels: - 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=90004.2 Services(HTTP 负载均衡服务)
修改容器自动生成的 Service 时,添加traefik.http.services.<name-of-your-choice>.前缀的标签。例如想关闭passHostHeader(默认把请求 Host 原样转发给后端):
labels: - traefik.http.services.<name-of-your-choice>.loadbalancer.passhostheader=false| Label | Description | Value |
|---|---|---|
traefik.http.services.<service_name>.loadbalancer.server.port | 注册端口。容器暴露多个端口时非常有用 | 8080 |
traefik.http.services.<service_name>.loadbalancer.server.scheme | 覆盖默认转发协议(http/https/h2c等) | http |
traefik.http.services.<service_name>.loadbalancer.server.url | 直接指定完整后端 URL。不能与port/scheme同时使用 | http://foobar:8080 |
traefik.http.services.<service_name>.loadbalancer.serverstransport | 引用一个 ServersTransport(由 File 或 Kubernetes CRD Provider 定义),详见 serverstransport | foobar@file |
traefik.http.services.<service_name>.loadbalancer.passhostheader | 转发请求时是否保留原始 Host 头 | true |
traefik.http.services.<service_name>.loadbalancer.healthcheck.headers.<header_name> | 健康检查请求附加头,详见 health check | foobar |
traefik.http.services.<service_name>.loadbalancer.healthcheck.hostname | 健康检查 Host 头 | example.org |
traefik.http.services.<service_name>.loadbalancer.healthcheck.interval | 健康检查间隔 | 10s |
traefik.http.services.<service_name>.loadbalancer.healthcheck.unhealthyinterval | 后端不健康时使用的检查间隔 | 10s |
traefik.http.services.<service_name>.loadbalancer.healthcheck.path | 健康检查请求路径 | /foo |
traefik.http.services.<service_name>.loadbalancer.healthcheck.method | 健康检查请求方法 | foobar |
traefik.http.services.<service_name>.loadbalancer.healthcheck.status | 判定健康所需的状态码/范围 | 42 |
traefik.http.services.<service_name>.loadbalancer.healthcheck.port | 健康检查使用的端口 | 42 |
traefik.http.services.<service_name>.loadbalancer.healthcheck.scheme | 健康检查协议 | http |
traefik.http.services.<service_name>.loadbalancer.healthcheck.timeout | 健康检查超时 | 10s |
traefik.http.services.<service_name>.loadbalancer.healthcheck.followredirects | 健康检查是否跟随重定向 | true |
traefik.http.services.<service_name>.loadbalancer.sticky.cookie | 是否开启基于 Cookie 的会话保持 | true |
traefik.http.services.<service_name>.loadbalancer.sticky.cookie.httponly | Cookie 是否带 HttpOnly 属性 | true |
traefik.http.services.<service_name>.loadbalancer.sticky.cookie.name | Cookie 名称 | foobar |
traefik.http.services.<service_name>.loadbalancer.sticky.cookie.path | Cookie 的 Path 属性 | /foobar |
traefik.http.services.<service_name>.loadbalancer.sticky.cookie.secure | Cookie 是否带 Secure 属性 | true |
traefik.http.services.<service_name>.loadbalancer.sticky.cookie.samesite | Cookie 的 SameSite 属性 | none |
traefik.http.services.<service_name>.loadbalancer.sticky.cookie.maxage | Cookie 的 Max-Age(秒) | 42 |
traefik.http.services.<service_name>.loadbalancer.responseforwarding.flushinterval | 反向代理刷新间隔(秒),常用于流式/SSE 场景 | 10 |
4.2.1 指定自定义转发端口:避免 502 的关键
请求从http://example.com转发到http://<容器私网IP>:12345:
services: my-container: # ... labels: - traefik.http.routers.my-container.rule=Host(`example.com`) # 告诉 Traefik 使用 12345 端口连接 my-container - traefik.http.services.my-service.loadbalancer.server.port=12345!!! important "Traefik 连错端口导致HTTP/502 Gateway Error" 默认情况下,Traefik 使用容器第一个被暴露的端口来建立后端连接。设置traefik.http.services.xxx.loadbalancer.server.port可以覆盖这一行为,从而规避"连到错误端口"引发的 502。
从实现细节看,getPort(shared.go)会遍历NetworkSettings.Ports,把端口按数值升序排序后取第一个;而当显式指定port时,addServer(config.go)会把该端口与容器 IP 拼成scheme://ip:port形式的 server URL——若同时给出了url而还写了scheme/port,则直接报错拒绝(与表格中"url 不能与 port/scheme 组合"的约束一一对应)。
4.3 Middleware(HTTP 中间件)
中间件同样可以在容器标签中声明,再用middlewares=引用:
- 声明以
traefik.http.middlewares.<name-of-your-choice>.开头,后跟中间件类型与选项; - 例如声明一个名为
my-redirect的 redirectscheme 中间件:traefik.http.middlewares.my-redirect.redirectscheme.scheme=https; - 更多中间件清单见 middlewares 总览。
示例:声明并引用一个中间件
services: my-container: # ... labels: # 声明一个中间件 - traefik.http.middlewares.my-redirect.redirectscheme.scheme=https # 在 Router 上引用它 - traefik.http.routers.my-container.middlewares=my-redirect!!! warning "同名中间件冲突" 如果以同名声明了多个参数不同的中间件,声明将失败(配置被丢弃)。这与动态配置合并时的资源冲突策略有关:DynConfBuilder.build会对每个容器生成独立配置再执行provider.Merge,同名冲突会触发合并失败。完整的中间件配置解析与合法性检查可结合 config_test.go 与 builder_test.go 中的用例验证。
五、TCP 层标签详解
5.1 TCP 示例
services: my-container: # ... labels: - "traefik.tcp.routers.my-router.rule=HostSNI(`example.com`)" - "traefik.tcp.routers.my-router.tls=true" - "traefik.tcp.services.my-service.loadbalancer.server.port=4123"!!! warning "TCP 与 HTTP 的互斥" 一旦为容器声明了 TCP Router/Service,Traefik 就不会再像默认那样自动创建 HTTP Router/Service。若同一个容器两种协议都需要,必须同时手动声明 TCP 与 HTTP 两套 Router/Service。
5.2 TCP Routers 配置选项
| Label | Description | Value |
|---|---|---|
traefik.tcp.routers.<router_name>.entrypoints | 绑定入口点,详见 EntryPoints | ep1,ep2 |
traefik.tcp.routers.<router_name>.rule | TCP 匹配规则,详见 TCP rules | HostSNI(`example.com`) |
traefik.tcp.routers.<router_name>.ruleSyntax | 按 Router 指定规则解析语法。⚠️ 已弃用,请改用 v3 语法 | v3 |
traefik.tcp.routers.<router_name>.service | 使用的 TCP Service,详见 TCP service | myservice |
traefik.tcp.routers.<router_name>.tls | 是否启用 TLS,详见 TCP TLS | true |
traefik.tcp.routers.<router_name>.tls.certresolver | 为 TCP Router 指定证书解析器,详见 TCP TLS 配置选项 | myresolver |
traefik.tcp.routers.<router_name>.tls.domains[n].main | 证书主域名 | example.org |
traefik.tcp.routers.<router_name>.tls.domains[n].sans | 证书 SAN 域名 | test.example.org,dev.example.org |
traefik.tcp.routers.<router_name>.tls.options | 引用的 TLS Options | mysoptions |
traefik.tcp.routers.<router_name>.tls.passthrough | TLS 透传(不解密、直接转发加密流量),详见 Passthrough | true |
traefik.tcp.routers.<router_name>.priority | TCP 路由优先级,详见 优先级计算 | 42 |
5.3 TCP Services 配置选项
| Label | Description | Value |
|---|---|---|
traefik.tcp.services.<service_name>.loadbalancer.server.port | 注册应用端口 | 423 |
traefik.tcp.services.<service_name>.loadbalancer.server.tls | 与后端建连时是否启用 TLS | true |
traefik.tcp.services.<service_name>.loadbalancer.serverstransport | 引用 ServersTransport 资源,详见 TCP serverstransport | foobar@file |
5.4 TCP Middleware
TCP 中间件用traefik.tcp.middlewares.<name-of-your-choice>.前缀声明。例如声明一个名为test-inflightconn的 InFlightConn 限流中间件:
# ... # 声明一个中间件:限制并发连接数为 10 traefik.tcp.middlewares.test-inflightconn.inflightconn.amount=10 # 在 Router 上引用 traefik.tcp.routers.my-service.middlewares=test-inflightconn更完整的 TCP 中间件目录见 TCP middlewares overview。同名参数不同的声明同样会失败。
六、UDP 层标签详解
6.1 UDP 示例
services: my-container: # ... labels: - "traefik.udp.routers.my-router.entrypoints=udp" - "traefik.udp.services.my-service.loadbalancer.server.port=4123"!!! warning "UDP 与 HTTP 的互斥" 与 TCP 相同:声明了 UDP Router/Service 后,容器不会自动生成 HTTP Router/Service;需要 HTTP+UDP 同时暴露时请手动声明两套。
6.2 UDP Routers 配置选项
| Label | Description | Value |
|---|---|---|
traefik.udp.routers.<router_name>.entrypoints | 绑定入口点 | ep1,ep2 |
traefik.udp.routers.<router_name>.service | 使用的 UDP Service,详见 UDP service | myservice |
6.3 UDP Services 配置选项
| Label | Description | Value |
|---|---|---|
traefik.udp.services.<service_name>.loadbalancer.server.port | 注册应用端口 | 423 |
七、Docker Provider 专属标签
以下标签不改变"动态路由",而是控制Docker Provider 的服务发现行为本身,定义于 shared_labels.go 的labelConfiguration/dockerSpecificConfiguration:
| Label | Description | Value |
|---|---|---|
traefik.enable | 显式告知 Traefik 是否纳入该容器(true/false)。该标签优先于静态配置的exposedByDefault | true |
traefik.docker.allownonrunning | 默认 Traefik 只关注running状态的容器。设为true后,非 running(stopped/paused/exited)容器仍参与服务发现:保留 Router/Service 配置、Service 后端列表为空、对停机容器的请求返回503 Service Unavailable(而非404 Not Found),且会完整执行中间件链让中间件有机会拦截请求。⚠️ 注意:若有多个已停止容器暴露同一 Router 但配置相互分歧,这些 Router 会被整体丢弃 | true |
traefik.docker.network | 覆盖默认用于连接容器的 Docker 网络。容器挂了多个网络时务必指定正确网络名(可用docker inspect <container_id>查询),否则 Traefik 会随机选择(取决于 Docker 返回顺序)。用 compose 文件stack部署时,定义在 compose 中的网络会自动加上stack前缀 | mynetwork |
上述标签在代码中落实于 config.go 的keepContainer判定逻辑,其判定顺序与优先级值得注意:
- 容器标签里的
traefik.enable=false直接过滤(可覆盖静态默认值ExposedByDefault); - 通过
constraints约束表达式过滤; AllowNonRunning的优先级高于健康/状态检查——一旦命中traefik.docker.allownonrunning=true,不再关心容器 health/status,直接保留配置(对应表格中"创建空后端列表、请求返回 503"的描述);- 否则,非 running 容器、以及(默认不允许空 Service 时)不健康或 starting 的容器都会被过滤掉。
traefik.docker.network则对应getIPAddress:先精确查找指定网络;网络名不存在时告警并回退到第一个可用网络。代码对host网络模式与container:共享网络模式也分别做了处理。
八、结合源码看"容器实例 → 后端 Server"的生命周期
把标签模型与运行态对应起来,有几个值得记住的实现事实(均可在 config.go 中追溯):
- 每实例一 Server:一个容器就是 Service 里的一个 server。
addServer/addServerTCP/addServerUDP会把"空占位 server"填充为真实ip:port(HTTP 再拼接scheme://),见loadBalancer.Servers[0].URL/.Address的赋值逻辑; - 非 running / 不健康容器 → 空后端:在
build*ServiceConfiguration中,若容器状态非 running 或 health 非 healthy,会直接返回并保留一个空 server 列表的负载均衡器,而非报错——这与traefik.docker.allownonrunning描述的行为一致; - Service 名取自 compose 服务名:
getServiceName(shared.go)会优先使用 compose 的com.docker.compose.service+com.docker.compose.project组合(形如service_project)并经Normalize清洗,因此在 compose 项目内即使不同文件、不同 host 也可以形成稳定的配置命名; - 默认 Rule 模板:容器未在标签里声明 rule 时,Router 的规则来自静态配置的
defaultRule,其默认值是Host(\{{ normalize .Name }}`)(见DefaultTemplateRule`),即"用规范化后的服务名作为主机名"。
仓库中 config_test.go 与 builder_test.go 覆盖了大量标签解码、默认端口、多网络、自动绑定等场景的单元测试;集成层面可参考 integration/docker_test.go 及配套 fixture(如 integration/fixtures/docker/simple.toml),它们验证了真实 Docker 环境下的路由暴露与转发行为。
九、常见陷阱速查
- 502 Gateway Error:容器暴露多个端口、或应用监听端口并非暴露端口时,Traefik 默认取的"第一个暴露端口"会连错——用
traefik.http.services.<name>.loadbalancer.server.port显式指定; - 端口暴露了但连不上:容器挂在多个网络时,务必用
traefik.docker.network锁定 Traefik 所在的那个网络,否则可能随机选中一个网络导致 IP 不通; - 404 而非 502:想保留"已停止但注册过的容器"的路由返回 503 而非 404,需开启
traefik.docker.allownonrunning=true; - 自动生成与你预期不符:一旦写 TCP/UDP Router/Service 标签,HTTP 的自动 Router/Service 就不会再生成;
- 名称冲突:Router/Service/Middleware 名中不要用
@;同名不同参数的中间件声明会导致整个声明失败。
如需继续深入,本文相关的上游页面还包括:Docker Provider 静态配置、HTTP Service 与健康检查、规则与优先级、TLS 默认证书,以及 Swarm/File/K8s 等其它 Provider 的标签文档(位于 docs/content/reference/routing-configuration/other-providers)。
【免费下载链接】traefikThe Cloud Native Application Proxy项目地址: https://gitcode.com/GitHub_Trending/tr/traefik
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考