SSD Nodes Learn
指南 Matt Connor作者: Matt Connor · 更新于 2026-07-19

Traefik v2 到 v3 迁移分步指南

Traefik v3 把 ipWhiteList 改名为 ipAllowList,并调整了规则语法。先用兼容模式从 v2 升级,再逐个服务迁移标签。

Traefik v2 与 v3 之间有哪些变化

Traefik v2 到 v3 的迁移大多是改名的工作,最有名的改名就是 ipWhiteList 中间件变成了 ipAllowList。除此之外,v3 收紧了路由器规则语法(PathPrefix 失去了正则功能,若干匹配器被改名或移除),彻底移除了几个 provider 和选项,其余一切照常工作:入口点(entrypoint)、ACME 证书设置、Docker 标签工作流,以及您的 acme.json 全部沿用。v3 还带有一个兼容模式,让 v2 规则语法继续可用,因此您可以先升级二进制,再逐个服务地重写规则,而不必在一个提心吊胆的晚上一次性完成。

本指南假设您使用的是 Traefik 反向代理指南中基于标签的 Docker Compose 配置。那篇文章是原生 v3 的;本文则面向仍在运行 traefik:v2 标签的机器。

改名与移除

  • ipWhiteList 现在叫 ipAllowList,HTTP 和 TCP 两种中间件都是如此。内部选项不变,因此 sourcerange 的含义完全一样。当前的 v3 版本(包括 v3.5)仍然接受旧名称作为已弃用的别名,并继续按列表执行,所以这一处改名在切换时不会导致任何服务中断。但还是要改:这个别名已计划移除,而且它会悄无声息地从弃用列表中消失,不会大声提醒。
  • providers.docker.swarmMode=true 已移除。Swarm 有了自己的 provider,配置为 providers.swarm.endpoint
  • pilot 部分被彻底移除。
  • experimental.http3 已移除。HTTP/3 现在直接在入口点上启用。
  • tls.caOptional 已从各 provider 和 forwardAuth 中间件中移除。
  • InfluxDB v1 指标 provider、Rancher provider 和 Marathon provider 都已移除。
  • 追踪(tracing)转向了 OpenTelemetry。原有的专用追踪后端(其中包括 Jaeger 和 Zipkin 集成)已移除,v3 改为导出 OTLP(OpenTelemetry 协议)。
  • headers 中间件中已弃用的 ssl* 选项(sslRedirectsslHost 等)已移除。取而代之的是入口点重定向和 redirectScheme 中间件。

这些移除比看上去更要紧,因为当静态配置中包含 Traefik 不认识的选项时,它会拒绝启动。残留的 pilotswarmMode 行会让容器在启动时停下,并给出一条点名残留项的 incompatible deprecated static option found 消息;而一个 Traefik 从未听说过的选项(拼写错误,或 tls.caOptional)则会以 field not found 让它停下。请在动镜像标签之前先清理好静态配置。

一个 Traefik 确实不认识的中间件名称(拼写错误,或被移除而非改为别名的名称)失败方式不同:引用它的路由器会加载出一个错误而不是一条路由,仪表盘会标记它,API 会报告 middleware "offce@docker" does not exist。指向该主机名的请求会得到 404,因为路由器根本没有起来。请注意,在当前的 v3 上 ipwhitelist 不属于这一类:它作为已弃用的别名存续,所以一个没改名的标签会安静地继续工作。

规则语法的变化

规则才是真正可能需要重写的地方。v3 的变化:

  • 匹配器内的值必须用反引号包裹。v2 也接受双引号;v3 不接受,所以 Host("app.example.com") 必须改成 Host(app.example.com)。
  • PathPrefix 不再理解正则表达式或 {id} 式的占位符。像 PathPrefix(/api/{version:v[0-9]+}) 这样的 v2 规则必须改成用 Go 正则语法书写的 PathRegexp 匹配器。
  • 匹配器现在只接受单个值。v2 允许 Host(app.example.com,www.example.com);v3 需要 Host(app.example.com) || Host(www.example.com)。例外的是 HeaderHeaderRegexpQueryQueryRegexp,它们仍然接受一个名称加一个值。
  • HeadersHeadersRegexp 改名为 HeaderHeaderRegexp
  • HostHeader 已移除。改用 Host,它在 v3 中匹配的是同一个东西。
  • 新增两个匹配器:QueryRegexp,以及用于在规则内匹配客户端地址的 ClientIP

好消息是:一条用反引号书写的普通 Host(app.example.com) 规则已经是有效的 v3 语法。大多数小型 Compose 配置用的正是这种写法,也就是说大多数标签迁移时无需任何规则改动。

开始之前先审计您的标签

您可以用一次搜索来衡量迁移的规模,因为每一处破坏性的标签变化都会留下 grep 能找到的模式:

grep -rnE 'ipwhitelist|HostHeader|Headers\(|PathPrefix\(`[^`]*\{|Host\(`[^`]*`,' docker-compose*.yml

每一处命中就是需要编辑的一行。ipwhitelist 变成 ipallowlistHostHeader 变成 HostHeaders 变成 HeaderPathPrefix 内的 {...} 占位符变成 PathRegexp 匹配器。Host() 内的逗号变成两个用 || 连接的 Host() 匹配器。零命中意味着您的标签已经是有效的 v3 语法,迁移就缩减为静态配置加镜像标签两件事。

保持不变的部分

入口点及其 HTTP 到 HTTPS 的重定向、带两种质询类型的 ACME 解析器、exposedByDefault、路由器和服务标签、loadbalancer.server.port,以及仪表盘,在 v3 中都和 v2 一样工作。您的证书也会沿用,因为 v3 会继续读取 v2 写下的 acme.json。开始前仍然要备份这个文件,因为一次弄丢了它的回滚会直接撞上 Let's Encrypt 的重复证书速率限制:

cp ./letsencrypt/acme.json ./letsencrypt/acme.json.v2-backup

迁移路径

第 1 步:固定您当前运行的版本。 把任何 traefik:latesttraefik:v2 标签改成您正在运行的确切发行版,例如 traefik:v2.11,并把整个 compose 目录提交到 git。这样后面的每一步都能用一次 checkout 撤销。如果用 docker compose up -d <service> 重建单个服务对您来说还不是习以为常的操作,Docker Compose 基础指南覆盖了本次迁移所依赖的这些操作。

第 2 步:清理静态配置并打开兼容模式。 移除 v3 删掉的每个选项(pilotswarmModetls.caOptionalexperimental.http3),然后让 v3 默认把规则当作 v2 语法处理。在 traefik.yml 中:

core:
  defaultRuleSyntax: v2

或者作为 compose 的 command: 列表中的一个标志:--core.defaultRuleSyntax=v2。兼容模式只覆盖规则语法。它不会让被移除的选项复活,也不会替您为中间件改名。

第 3 步:准备中间件的改名。 在 compose 文件中搜索旧名称:grep -rn ipwhitelist docker-compose*.yml。把每个 ipwhitelist 标签改成 ipallowlist,但先不要应用这个改动,因为新名称在 v2 中并不存在。这些编辑要和下一步的切换一起上线。(如果漏了一个,当前的 v3 仍会把旧名称当作已弃用的别名予以兑现,列表照常执行;在下一轮里修它,而不是在凌晨两点。)

第 4 步:切换镜像标签。 把 Traefik 镜像设为当前的 v3 发行版,撰写本文时是 traefik:v3.5,然后:

docker compose up -d
docker compose logs -f traefik

因为兼容模式已打开,您的 v2 规则会继续匹配;又因为 up -d 同时重建了那些改了中间件标签的服务,那些路由器会干净地起来。健康的日志里既没有 field not found 行,也没有 does not exist 行。

对这一步打开的时间窗口要对自己诚实。一个引用了 v3 确实不认识的中间件名称(拼写错误,或被移除的选项)的路由器,从新 Traefik 启动的那一刻起,一直到它的应用容器被重建之前都是宕的,而在单台机器上这段时间就是 docker compose up -d 逐个处理完列表所需的那几秒。如果某条路由真的一秒都不能眨眼,就在切换前把改名后的中间件从那个路由器的 middlewares 标签中移除,切换后再加回去,并事先决定这条路由能否在中间那一分钟里没有它的 IP 允许列表也照常运行。

第 5 步:逐个服务迁移规则。 一次处理一个应用:把它的规则重写为 v3 语法,只用 docker compose up -d app 重建那个服务,测试通过后再往下走。如果某个服务有一条您还无法重写的规则,就给那一个路由器加上应急标签 traefik.http.routers.app.ruleSyntax=v2,继续前进。

第 6 步:关闭兼容模式。 当每条规则都是 v3 语法后,删除 defaultRuleSyntax 和所有 ruleSyntax 标签,重启 Traefik,确认每个路由器在仪表盘里仍然显示绿色。不要一直开着兼容模式将就下去:Traefik 在 v3.4 中弃用了这两个选项,并将在下一个大版本中移除它们,所以它们是一座桥,不是终点。

前后对比:一个服务的标签

这里有一个一次性带上全部著名变化的应用:一个多值 Host、一个 PathPrefix 占位符,以及一个 ipWhiteList 中间件。v2 的代码块:

  app:
    image: app:1.4
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.app.rule=Host(`app.example.com`,`www.example.com`) && PathPrefix(`/api/{version:v[0-9]+}`)
      - traefik.http.routers.app.entrypoints=websecure
      - traefik.http.routers.app.tls.certresolver=le
      - traefik.http.routers.app.middlewares=office
      - traefik.http.middlewares.office.ipwhitelist.sourcerange=10.0.0.0/24
      - traefik.http.services.app.loadbalancer.server.port=8080

同一个服务迁移到 v3 后:

  app:
    image: app:1.4
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.app.rule=(Host(`app.example.com`) || Host(`www.example.com`)) && PathRegexp(`^/api/v[0-9]+`)
      - traefik.http.routers.app.entrypoints=websecure
      - traefik.http.routers.app.tls.certresolver=le
      - traefik.http.routers.app.middlewares=office
      - traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.0/24
      - traefik.http.services.app.loadbalancer.server.port=8080

有两个标签变了。规则把它的多值 Host 拆成了两个用 || 连接的匹配器,并把占位符换成了 PathRegexp;中间件标签则把 ipwhitelist 换成了 ipallowlist。入口点、证书解析器、路由器到中间件的接线,以及服务端口都没有变动。

用仪表盘测试每个服务

每次切换之后,打开仪表盘的 HTTP 路由器页面。每个路由器都应该是绿色的。带错误徽标的路由器会点名它的确切问题,通常是某个中间件在新名称下不存在,或是某条 v3 无法解析的规则。然后从外部逐个主机名确认:

curl -sI https://app.example.com/api/v1/status

返回 200 或您应用正常的重定向,意味着路由和 TLS 都存活了下来。来自 Traefik 的 404 意味着路由器没有起来;回到仪表盘去读它的错误。工作时在第二个终端里一直开着 docker compose logs -f traefik,因为每一次解析失败都会在容器重启的那一刻落到那里。

关于回滚的诚实话

把 v2 的 compose 文件、它的静态配置,以及 acme.json 备份都保留着,直到每个服务都在 v3 上路由并经过真实使用为止。回滚意味着 checkout 迁移前的那次提交并运行 docker compose up -d,而且必须是整个文件,不能只回镜像标签,因为 v3 专有的标签在 v2 下是错的,其错法正如 v2 标签在 v3 下是错的一样:ipallowlist 在 v2 中不存在,PathRegexp 匹配器在那里也解析不了。如果 acme.json 在途中丢失或损坏了,请在启动 v2 之前先恢复备份副本,这样回滚就不会把您的 Let's Encrypt 速率限制花在一次性重新签发五张证书上。

FAQ

迁移到 Traefik v3 是否必须重写每条路由器规则?

不必。一条用反引号书写的普通 Host(app.example.com) 规则在两个版本中都有效,而这覆盖了大多数 Compose 配置。只有当规则用了 v2 专有特性时才需要重写:PathPathPrefix 里的正则或占位符、一个 Host() 里的多个主机名、用引号而非反引号,或者已被移除的 HeadersHeadersRegexpHostHeader 匹配器。

Traefik v3 里的 ipWhiteList 怎么了?

它被改名为 ipAllowList,内部配置不变,所以像 traefik.http.middlewares.office.ipwhitelist.sourcerange=10.0.0.0/24 这样的 v2 标签就变成同一行、里面写 ipallowlist。当前的 v3 版本(包括 v3.5)仍然接受旧名称作为已弃用的别名,所以一个没改名的标签会安静地继续执行允许列表。把这当作借来的时间,而不是跳过改名的理由:这个别名已计划移除,而一个 Traefik 确实不认识的中间件名称会大声地失败,带着路由器错误和 404。仪表盘会显示这个错误,指向该主机名的请求会返回 404。

Traefik v3 还能读 v2 规则语法吗?

能。在静态配置里设置 core.defaultRuleSyntax: v2,在迁移期间把 v2 语法保留为默认;把默认切回来之后,再对个别掉队的路由器用逐路由器的 ruleSyntax=v2 标签。把两者都当作临时手段:Traefik 在 v3.4 中弃用了它们,并将在下一个大版本中移除。

我的 Let's Encrypt 证书能在升级后存活吗?

能。Traefik v3 会继续读取 v2 写下的 acme.json 文件,所以不会仅仅因为二进制变了就重新签发证书。开始前仍然要把这个文件复制到安全的地方,因为一次回滚,或一个弄丢了 acme.json 的被删卷,会逼着一次性重新签发每一张证书,而 Let's Encrypt 对同一组主机名每周只允许五张重复证书。

为什么 Traefik v3 升级后启动失败?

几乎总是因为静态配置里还留着一个 v3 移除的选项,而 Traefik 拒绝在它不认识的选项上启动。对于那些众所周知的残留项(pilotproviders.docker.swarmModeexperimental.http3),日志会说 incompatible deprecated static option found 并点名祸首;对于任何 v3 从未听说过的东西,例如 tls.caOptional,它会说 field not found 并给出所在节点。删除或替换每一个,然后再次启动容器。