
1. 为什么 Docker 环境下的反向代理总在重复造轮子如果你手上有三五个容器在跑一开始可能觉得端口映射就够了8080:80、3000:3000、9000:9000浏览器里记几个端口号也能凑合。但容器一多问题就来了——端口号记不住、HTTPS 证书要一个个配、某个服务换了端口还得翻 compose 文件改映射。更麻烦的是当你开始接入各种 AI 服务的 API 时每个容器里都塞一份 Key改一次密钥要挨个重启散落得让人头皮发麻。Traefik 解决的就是这个层面的问题。它是一个云原生反向代理核心能力和传统 Nginx 最大的区别在于「服务发现」你不需要手写 upstream 配置Traefik 会监听 Docker 的事件流容器一启动它读取容器上的 labels自动生成路由规则。容器销毁路由自动回收。整个过程你只维护一份docker-compose.yml路由信息跟着服务走而不是跟着代理走。这篇指南面向的是已经在用 Docker Compose 跑服务、想把手动端口映射升级成域名路由的开发者。我会从零给出一份可直接复制的docker-compose.yml覆盖 labels 路由、entryPoints 定义、Lets Encrypt 证书自动签发然后用 curl 验证路由和证书链是否正常。最后一部分会讲一个实际痛点多个容器都要调用上游 AI 服务时怎么把 endpoint 和 Key 统一收敛到 TaoToken避免密钥在十几个容器里各存一份。Traefik 的版本我选 v2.10这是 v2 系列里比较稳定的一个版本v3 的配置语法有变动新手直接上 v3 容易在文档里迷路。下面所有配置都基于 v2.10 实测通过。2. 前置准备TaoToken 统一 Key 接入与 Docker 网络规划在写 Traefik 配置之前先把两件事定下来一是 Docker 网络怎么划二是上游 AI 服务的 Key 怎么管。先说网络。Traefik 需要和它代理的服务在同一个 Docker 网络里才能通信。我习惯单独建一个traefik-net所有需要被代理的容器都接进来。这样做的原因是隔离数据库、缓存这类不需要暴露的服务就不接入减少攻击面。网络用 bridge 驱动即可Compose 里声明一次多个 service 引用同一个网络名。再说 Key 管理。假设你有五个容器都要调用大模型 API传统做法是在每个容器的环境变量里写OPENAI_API_KEYsk-xxx。问题很明显密钥轮换时要改五处某个容器日志打印了环境变量就泄露一份不同服务用的模型和额度也没法统一看。更合理的做法是让所有容器把请求发到同一个入口由这个入口统一持有 Key 并转发。TaoToken 在这里扮演的就是这个统一入口的角色。它提供兼容 OpenAI 格式的 API endpoint你只需要在 TaoToken 控制台生成一个 Key然后在各个容器里把base_url指向https://taotoken.net/apiKey 填同一个。这样密钥只有一份轮换时改一处所有容器下次请求自动生效。对于 Traefik 来说TaoToken 就是一个普通的上游 HTTPS 服务你甚至可以用 Traefik 给它加一层内部路由和访问控制。具体操作上先去 TaoToken 控制台创建一个 API Key路径是 console 页面下的 api-keys 管理。创建完复制出来格式类似sk-开头的一串字符。这个 Key 后面会用在容器的环境变量里。如果你还没决定用哪个模型可以先用模型对话页面测一下连通性确认 Key 有效再往下走。网络和 Key 都准备好之后目录结构建议这样组织traefik-demo/ ├── docker-compose.yml ├── acme.json # 证书存储权限必须 600 └── dynamic/ └── conf.yml # 动态配置可选acme.json这个文件必须先创建好并改权限否则 Traefik 启动时会因为无法写入证书而报错。命令是touch acme.json chmod 600 acme.json。这一步很多人会漏导致后面证书一直签不下来。3. 可复制配置docker-compose.yml 与动态配置片段这一节给出完整的 Compose 文件你可以直接复制到本地改域名。我把它拆成 Traefik 本体和一个示例服务 whoami方便你启动后立刻验证。先看 Traefik 本体的配置version: 3.8 services: traefik: image: traefik:v2.10 container_name: traefik restart: unless-stopped command: - --providers.dockertrue - --providers.docker.exposedbydefaultfalse - --entrypoints.web.address:80 - --entrypoints.websecure.address:443 - --entrypoints.web.http.redirections.entrypoint.towebsecure - --entrypoints.web.http.redirections.entrypoint.schemehttps - --certificatesresolvers.le.acme.emailyouexample.com - --certificatesresolvers.le.acme.storage/acme.json - --certificatesresolvers.le.acme.httpchallenge.entrypointweb - --log.levelINFO - --accesslogtrue ports: - 80:80 - 443:443 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./acme.json:/acme.json networks: - traefik-net networks: traefik-net: driver: bridge几个关键点解释一下。exposedbydefaultfalse很重要它意味着容器默认不被 Traefik 代理只有显式打了traefik.enabletrue标签的容器才会生成路由。这是安全实践避免你随手起的临时容器被暴露到公网。entrypoints.web.http.redirections那两行实现了 HTTP 到 HTTPS 的全局跳转访问 80 端口的请求会自动 301 到 443不用在每个服务里单独配。然后是示例服务用官方 whoami 镜像它会返回请求头信息方便验证路由是否生效whoami: image: traefik/whoami container_name: whoami restart: unless-stopped labels: - traefik.enabletrue - traefik.http.routers.whoami.ruleHost(whoami.example.com) - traefik.http.routers.whoami.entrypointswebsecure - traefik.http.routers.whoami.tls.certresolverle - traefik.http.services.whoami.loadbalancer.server.port80 networks: - traefik-net注意loadbalancer.server.port80这一行。whoami 容器内部监听 80Traefik 需要知道往哪个端口转发。如果你的服务内部监听 3000这里就写 3000。这个标签经常被忘记导致 502。如果你需要给服务加 Basic Auth在 labels 里追加- traefik.http.routers.whoami.middlewaresauth - traefik.http.middlewares.auth.basicauth.usersadmin:$$apr1$$9C...htpasswd生成的值里有$符号在 Compose 里要写成$$转义这是 YAML 的坑很多人卡在这里。路径重写的配置片段如下适合把/api/*转发到后端根路径- traefik.http.middlewares.stripprefix.stripprefix.prefixes/api - traefik.http.routers.myapi.ruleHost(api.example.com) PathPrefix(/api) - traefik.http.routers.myapi.middlewaresstripprefix启动命令就一句docker-compose up -d启动后docker-compose logs -f traefik看日志如果看到Configuration loaded和证书申请相关的信息说明基本正常。4. 验证请求curl 检查路由生效与证书链完整配置写完不代表生效必须验证。我习惯分三步先验证 HTTP 跳转再验证 HTTPS 路由最后验证证书链。第一步验证 80 端口跳转。用 curl 不带-L看返回码是不是 301curl -I http://whoami.example.com期望输出里有HTTP/1.1 301 Moved Permanently和Location: https://whoami.example.com/。如果返回 404说明 Traefik 没匹配到路由检查 labels 里的 Host 规则和 DNS 解析。第二步验证 HTTPS 路由。用-k跳过证书校验先确认服务通不通curl -k https://whoami.example.com正常会返回类似这样的内容Hostname: whoami IP: 172.20.0.3 RemoteAddr: 172.20.0.2:xxxxx GET / HTTP/1.1 Host: whoami.example.com X-Forwarded-For: ... X-Forwarded-Proto: https看到X-Forwarded-Proto: https说明 Traefik 正确识别了 TLS 终止并把原始协议头传给了后端。这一步通了路由就没问题。第三步验证证书链。去掉-k用-v看握手细节curl -v https://whoami.example.com 21 | grep -A5 SSL certificate或者用 openssl 更直观echo | openssl s_client -connect whoami.example.com:443 -servername whoami.example.com 2/dev/null | openssl x509 -noout -issuer -subject -dates期望看到 issuer 是 Lets Encrypt 的中间证书subject 是你的域名dates 里的有效期是 90 天。如果 issuer 显示Fake LE Intermediate说明你用的是 staging 环境正式环境要去掉--certificatesresolvers.le.acme.caserver那行默认就是生产。证书申请失败最常见的原因是 HTTP challenge 走不通。Lets Encrypt 会从外部访问http://你的域名/.well-known/acme-challenge/xxx如果你的 80 端口没放行或者 DNS 没解析到这台机器就会失败。日志里会看到acme: error: 403或timeout。排查方法是先用curl http://你的域名/.well-known/acme-challenge/test确认外部能访问到 80 端口。5. 常见报错排查401、local proxy failed 与证书问题这一节列几个我实际踩过的坑对照报错找原因。报错一401 Unauthorized来自上游 AI 服务。这个不是 Traefik 的问题而是容器里调用 TaoToken 时 Key 没配对。检查容器环境变量里的OPENAI_API_KEY是否和 TaoToken 控制台生成的一致。如果你用的是https://taotoken.net/api作为 base_url注意结尾不要多加/v1具体路径以接入文档为准。401 的响应体里通常有invalid_api_key字样看到这个就说明 Key 错了或者被撤销了。报错二local proxy failed或dial tcp: connection refused。这是 Traefik 转发不到后端容器。三个检查点第一后端容器和 Traefik 是否在同一个网络里docker network inspect traefik-net看两边是否都在第二loadbalancer.server.port标签写的端口和容器实际监听的是否一致用docker exec 容器名 netstat -tlnp确认第三容器是否真的起来了docker-compose ps看状态。报错三reading choices或流式响应中断。这个出现在代理 AI 服务的流式接口时。Traefik 默认会缓冲响应导致 SSE 流被截断。解决办法是在服务标签里关闭缓冲- traefik.http.services.myservice.loadbalancer.server.port8000 - traefik.http.middlewares.no-buffering.buffering.maxRequestBodyBytes0或者在 Traefik 的 command 里加--serversTransport.forwardingTimeouts.responseHeaderTimeout0s。实测下来流式接口必须关掉缓冲否则前端会一直转圈。报错四OAuth 回调地址不对。如果你代理的是带 OAuth 登录的服务回调 URL 里的域名要和 Traefik 路由的 Host 一致。比如你在 labels 里写的是auth.example.com但 OAuth 应用里配的是example.com回调就会 404。改法是把两边对齐或者用 Traefik 的 redirect 中间件做域名跳转。报错五证书一直 pending。除了前面说的 80 端口问题还有一种情况是acme.json权限不对。Traefik 容器内以非 root 用户运行如果宿主机上acme.json是 644容器写不进去。确认权限是 600并且文件属主和容器运行用户匹配。如果之前申请失败过acme.json里可能残留了失败记录删掉重建再试。排查时善用docker-compose logs traefik --tail100Traefik 的日志会明确告诉你哪条路由匹配了、哪个证书申请失败了。开--log.levelDEBUG能看到更细的匹配过程但生产环境别开日志量很大。6. 把上游 AI 服务收敛到统一入口的实践回到开头说的 Key 散落问题。当你用 Traefik 管起十几个容器后会发现每个调用 AI 服务的容器都在环境变量里存了一份 Key。这时候有两种收敛思路。第一种是让每个容器直接指向 TaoToken 的 endpointKey 用同一个。这是最简单的做法改一处 Key 全局生效。配置上就是在容器的环境变量里写environment: - OPENAI_BASE_URLhttps://taotoken.net/api - OPENAI_API_KEYsk-你的统一Key不同框架的环境变量名可能不同比如有的用OPENAI_API_BASE有的用base_url参数具体看接入文档。关键是 endpoint 和 Key 都指向同一个来源不再各存各的。第二种是在 Traefik 层面再加一层内部路由把 AI 请求也纳入统一管理。比如你有一个内部网关服务所有 AI 调用先经过它由它持有 Key 并做限流、日志。Traefik 给这个网关配一个内部域名其他容器通过这个域名调用。这样 Key 只存在于网关容器里其他容器完全不知道 Key 是什么。配置上就是给网关容器打 labels路由规则用内部域名不暴露到公网。两种方式各有适用场景。小规模用第一种改起来快规模大了、需要审计和限流就用第二种。不管哪种核心思路都是「Key 只存一份endpoint 只指一处」。如果你还在用多个不同的 AI 服务商TaoToken 的好处是它兼容 OpenAI 格式你不需要为每个服务商改代码只改 base_url 和 model 名就行。模型名可以在模型对话页面查确认哪个模型可用再写进配置。长期跑编码类任务的话Coding Plan 的额度模式比按量计费更划算适合容器里跑 Agent 的场景。最后提醒一点Traefik 的 Docker provider 会读取容器 labels而 labels 在docker inspect里是明文可见的。所以不要把 Key 直接写在 labels 里用环境变量或者 Docker secret。labels 只放路由规则不放敏感信息。这是安全底线。