1. 从 NodePort 到 Ingress:为什么要把入口交给 traefik-ingress-controller
K8S Ingress 是集群七层流量的统一入口抽象,traefik-ingress-controller 则是把这个抽象真正落地成反向代理进程的那块拼图。它能做什么?简单说,你写一条 Ingress 规则,声明「哪个域名、哪个路径,转发到哪个 Service」,traefik 就会监听 80/443,把外部请求按规则送到后端 Pod。适合谁?适合已经用 NodePort 暴露过服务、被 30000~32767 端口范围和证书配置折腾过的人。
我最早接触这块时,服务是用 NodePort 固定 30001 对外暴露的。能跑,但问题很快堆起来:端口范围受限,想用 80/443 得改 apiserver 参数,不推荐;服务一多,每个都要占一个 NodePort,还得人工维护端口表避免冲突;对外开的端口越多,安全面越大;最要命的是 HTTPS 证书,还得单独再起一个 nginx 或 traefik 来配。这几个需求叠在一起,结论就很清楚了——需要一个统一的反向代理层,而 K8S 原生给的方案就是 Ingress + Ingress Controller。
组件关系得先理清,不然后面配置容易懵。Service 是后端真实服务的抽象,一个 Service 对应多个相同 Pod;Ingress 是转发规则的抽象,按 Host 和 path 决定请求落到哪个 Service;Ingress Controller 是真正干活的 Pod,它解析 Ingress 规则,规则一变就热更新自己的转发配置,收到请求后按规则转发。K8S 本身不自带 Controller,它只是标准,实现有 Nginx、Traefik 等多种。本文用 traefik-ingress-controller,把入口从 NodePort 改到 Ingress,并给出可复制的 YAML、证书生成、secret 创建和 curl 验证链路。
这里有个容易忽略的点:Ingress、Service、Pod、Secret 必须在同一个 namespace,而 Ingress Controller 自己的 namespace 没有这个要求。这个结论我在实测里踩过,后面排障章节会展开。
2. TaoToken 前置准备:拿到 Base URL、Key 和 Model ID
在把入口改到 traefik 之前,得先把后端要对接的模型服务准备好。TaoToken 在这里扮演的是统一 API 网关的角色,你拿到一组凭证,就能在集群里以标准 OpenAI 兼容接口的方式调用模型。这一步不复杂,但三件套必须齐全:Base URL、API Key、Model ID。
Base URL 是https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为请求前缀。API Key 在控制台的 API Keys 页面生成,生成后只显示一次,务必当场复制保存。Model ID 取决于你要调用的具体模型,在模型列表里能看到对应的标识串。
如果你只是想在集群里验证一下模型能不能通,可以先用模型对话页面手动发一条请求,确认 Key 有效、模型可用,再去配 K8S 里的 Service 和 Ingress。这个顺序能帮你把「凭证问题」和「网络问题」分开排查,省很多时间。
对于长期跑编码任务或 Agent 的场景,Coding Plan 更合适,它按订阅方式提供额度,不用每次请求都算 token。而如果你只是偶尔验证,用 API Keys 按量调用就够了。两条路径的凭证是同一套体系,切换成本很低。
把这三件套记下来,后面写 Service 的 ExternalName 或者 ConfigMap 时会直接用到。我建议先在本地用 curl 打一次,确认返回正常,再进集群配置,这样出问题时能快速定位是凭证错还是网络不通。
3. 可复制配置:traefik 部署、Ingress 规则与后端 Service
这一节是全文的核心,所有片段都可以直接复制。先部署 traefik-ingress-controller,版本用 traefik:1.7.20,包含 Namespace、ConfigMap、RBAC、Deployment 四部分。ConfigMap 里的 traefik.toml 定义了 http/https 两个 entryPoint,开启 compress,并配置了 ping 健康检查入口。
apiVersion: v1 kind: Namespace metadata: name: traefik-ingress-controller --- apiVersion: v1 kind: ConfigMap metadata: name: traefik-ingress labels: app: traefik namespace: traefik-ingress-controller data: traefik.toml: | logLevel = "info" defaultEntryPoints = ["http", "https"] [entryPoints] [entryPoints.http] address = ":80" compress = true [entryPoints.http.redirect] entryPoint = "https" [entryPoints.https] address = ":443" compress = true [entryPoints.https.tls] [ping] entryPoint = "http" [kubernetes] [traefikLog] format = "json" --- kind: ServiceAccount apiVersion: v1 metadata: name: traefik-ingress namespace: traefik-ingress-controller --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: traefik-ingress rules: - apiGroups: [""] resources: ["pods", "services", "endpoints", "secrets"] verbs: ["get", "list", "watch"] - apiGroups: ["extensions"] resources: ["ingresses"] verbs: ["get", "list", "watch"] - apiGroups: ["extensions"] resources: ["ingresses/status"] verbs: ["update"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: traefik-ingress roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: traefik-ingress subjects: - kind: ServiceAccount name: traefik-ingress namespace: traefik-ingress-controller --- apiVersion: apps/v1 kind: Deployment metadata: name: traefik-ingress labels: app: traefik namespace: traefik-ingress-controller spec: replicas: 1 selector: matchLabels: app: traefik template: metadata: labels: app: traefik spec: hostNetwork: true serviceAccountName: traefik-ingress terminationGracePeriodSeconds: 60 containers: - image: traefik:1.7.20 name: traefik-ingress readinessProbe: httpGet: path: /ping port: "http" failureThreshold: 1 initialDelaySeconds: 10 periodSeconds: 10 livenessProbe: httpGet: path: /ping port: "http" failureThreshold: 3 initialDelaySeconds: 10 periodSeconds: 10 volumeMounts: - mountPath: /config name: config ports: - name: http containerPort: 80 protocol: TCP - name: https containerPort: 443 protocol: TCP args: - --configfile=/config/traefik.toml volumes: - name: config configMap: name: traefik-ingress注意hostNetwork: true这一行,它让 traefik 直接占用宿主机 80/443,省去再套一层 Service 的麻烦。生产环境如果要多副本,得改成 LoadBalancer 或 NodePort 方式,这里为了演示链路清晰就用 hostNetwork。
接着是后端 Service。假设你的模型调用服务叫taotoken-backend,监听 8080,Service 定义如下:
apiVersion: v1 kind: Service metadata: name: taotoken-backend namespace: default spec: selector: app: taotoken-backend ports: - name: http port: 8080 targetPort: 8080 protocol: TCP type: ClusterIP然后是 Ingress 规则,把域名api.taotoken-demo.com的/v1路径转发到上面的 Service:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: taotoken-ingress namespace: default annotations: kubernetes.io/ingress.class: traefik spec: tls: - hosts: - api.taotoken-demo.com secretName: taotoken-tls rules: - host: api.taotoken-demo.com http: paths: - path: /v1 backend: serviceName: taotoken-backend servicePort: 8080这里kubernetes.io/ingress.class: traefik是关键注解,告诉集群这条规则归 traefik 管。tls 段引用了 secrettaotoken-tls,下一步生成。
证书用 openssl 自签,命令如下:
openssl genrsa -out taotoken-key.pem 2048 openssl req -new -x509 -key taotoken-key.pem -out taotoken.pem \ -subj /C=CN/ST=BeiJing/L=BeiJing/O=TaoToken/OU=Demo/CN=api.taotoken-demo.com \ -days 3650 openssl x509 -in taotoken.pem -text -noout生成后创建 secret:
kubectl create secret tls taotoken-tls \ --key taotoken-key.pem \ --cert taotoken.pem \ -n default最后一次性 apply:
kubectl apply -f traefik.yml kubectl apply -f taotoken-service.yaml kubectl apply -f taotoken-ingress.yaml如果你用的是 Cline MCP 或 Codex 的 auth.json 方式对接,记得把 Base URL 填https://taotoken.net/api,Key 填控制台生成的串,Model ID 填你选定的模型标识,三者缺一不可。CC Switch 场景同理,切换配置时这三项要同步改,否则会出现 401 或模型找不到的报错。
4. 验证请求链路:curl 看状态码与返回体
配置 apply 完,先确认资源状态。执行kubectl get pod -A,应该看到 traefik-ingress-controller 命名空间下有一个 Running 的 Pod,default 下有你后端的 Pod。再kubectl get ingress -n default,HOSTS 列显示api.taotoken-demo.com,PORTS 显示 80, 443。kubectl get secret -n default能看到taotoken-tls,TYPE 是 kubernetes.io/tls。
然后在本地 hosts 文件加一条解析,把域名指向 traefik 所在节点 IP。Linux/macOS 是/etc/hosts,Windows 是C:\Windows\System32\drivers\etc\hosts,追加:
192.168.1.65 api.taotoken-demo.com接着用 curl 验证。先测 HTTP 是否被重定向到 HTTPS:
curl -I http://api.taotoken-demo.com/v1/models预期返回 301 或 308,Location 指向 https 地址。再测 HTTPS 直连,自签证书加-k跳过校验:
curl -k -i https://api.taotoken-demo.com/v1/models \ -H "Authorization: Bearer $TAOTOKEN_API_KEY"正常情况返回 200,body 里是模型列表 JSON。如果后端服务需要 POST,可以这样打一条对话请求:
curl -k -X POST https://api.taotoken-demo.com/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-id", "messages": [{"role": "user", "content": "ping"}] }'返回体里出现choices数组,说明整条链路通了:外部请求 → traefik 80/443 → Ingress 规则匹配 → Service → 后端 Pod → TaoToken API → 模型返回。状态码 200 加上 choices 字段,就是链路健康的标志。
如果只想快速确认 traefik 本身活着,可以打它的 ping 端点:
curl -k https://api.taotoken-demo.com/ping返回OK说明 controller 正常。这一步能把「controller 挂了」和「后端不通」区分开,排障时很有用。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
排障这块我按真实遇到过的报错来写,每条都给定位思路。
401 Unauthorized:最常见。先确认 curl 里的Authorization: Bearer后面那串 Key 是不是控制台生成的完整串,有没有多空格或换行。再确认后端服务读取 Key 的环境变量有没有正确注入。如果是 Cline MCP 或 Codex auth.json 场景,检查文件里的 Base URL 是不是https://taotoken.net/api,Key 和 Model ID 是否三件套齐全。缺任何一项都会 401。
local proxy failed:这个报错通常出现在客户端侧,说明请求根本没到 traefik。先kubectl get pod -n traefik-ingress-controller看 controller 是否 Running,再看kubectl logs有没有监听 80/443 失败。如果 hostNetwork 模式下 80 被占用,controller 会起不来。用ss -lntp | grep :80查占用进程。另外确认 hosts 解析的 IP 是不是 traefik 所在节点,解析错了请求会打到别处。
reading choices 相关报错:一般是返回体解析失败,说明请求到了后端但响应格式不对。检查后端服务返回的是不是标准 JSON,有没有被中间件改写。如果 Ingress 上加了 rewrite 或 redirect 中间件,路径可能被改掉,导致后端收到错误路径返回非预期内容。把中间件临时去掉再测一次,能快速定位。
OAuth 报错:如果你在 Ingress 上挂了 OAuth 中间件做鉴权,报错多半是 token 过期或回调地址不匹配。检查中间件配置里的 client ID、secret、callback URL 是否和实际域名一致。自签证书场景下,OAuth 回调可能因为证书不受信而失败,测试阶段可以先绕过鉴权中间件,确认链路通了再逐步加回。
还有一个隐蔽的坑:Ingress、Service、Pod、Secret 不在同一个 namespace。我实测时把 Ingress 放在 default,Service 放在别的 namespace,规则匹配到了但后端找不到,返回 503。记住那条结论——这四个资源必须同 namespace,controller 自己的 namespace 无所谓。
排查顺序建议:先kubectl get看资源状态,再kubectl logs看 controller 日志,然后 curl 打 ping 确认 controller 活着,最后带 Key 打业务接口。一层层往下,别一上来就怀疑模型服务。
6. 把入口固定下来:后续接入与凭证管理
链路验证通过后,日常维护其实就两件事:管好凭证,管好规则。凭证方面,API Key 建议用 K8S Secret 注入到后端 Pod,不要硬编码在镜像或 YAML 里。轮换 Key 时只改 Secret,重启 Pod 即可,Ingress 规则不用动。
规则方面,新增服务时复制一份 Ingress YAML,改 host、path、serviceName、servicePort 四个字段就行。如果多个服务共用一个域名,用不同 path 区分,比如/v1/chat和/v1/embeddings分别指向不同 Service。traefik 会按最长路径优先匹配,不用担心冲突。
证书续期别忘了。自签证书虽然设了 3650 天,但生产环境建议用 cert-manager 自动续期,或者至少记个日历提醒。证书过期后浏览器和 curl 都会报错,但 traefik 本身不会主动告警,容易漏。
如果你后面要跑长期编码任务或 Agent,把凭证从按量 API Key 换成 Coding Plan 的订阅凭证,接入方式不变,还是 Base URL + Key + Model ID 三件套,只是额度模型不同。切换时记得同步更新 K8S Secret 和客户端配置,避免一半旧一半新导致 401。
整套配置跑下来,最大的收益是入口统一了:80/443 对外,证书集中管,新增服务只改一条 Ingress。NodePort 那套端口维护表可以彻底扔掉。