1. 从 SpringCloud 到 K8S7 天迁移的真实战场SpringCloud 微服务迁移 K8S 这件事听起来像是一次普通的架构升级但真正动手之后你会发现它更像是在高速行驶中换轮胎。我接到的任务是 7 天内把一套跑了两年多的 SpringCloud 项目迁到 K8S 集群组件包括 Spring Gateway、Nacos、OpenFeign、MinIO原来通过 Docker 部署在虚拟机上现在要全部搬到 K8S并用 Traefik2 替代 Gateway 的入口职责。这套项目大概有 5 个业务服务加 4 个公共服务改造重点集中在 gateway 和 auth 两个模块。迁移的核心目标有三个第一去掉 SpringCloud 全家桶依赖让每个微服务变成独立的 SpringBoot 应用第二用 K8S 的 ConfigMap 和 Secret 替代 Nacos 的配置中心功能第三用 Traefik2 的 IngressRoute 和 ForwardAuth 中间件替代 Spring Gateway 的鉴权过滤器。在这个过程中我还需要解决一个容易被忽略的问题多个服务在迁移期间需要调用大模型能力做代码辅助审查和接口文档生成如果每个服务各自管理一套 API Key不仅泄露风险高排查问题也极其痛苦。所以我引入了 TaoToken 作为统一的大模型 API 通道用一个 Key 打通所有服务的调用入口。这篇文章会完整还原这 7 天的改造过程包括 Helm Chart 的写法、Traefik2 的配置、TaoToken 的接入参数以及我踩过的那些坑。2. TaoToken 统一 Key 接入让迁移期的大模型调用不再混乱迁移期间最怕什么不是代码改不动而是改到一半发现某个服务调不通你分不清是 K8S 网络问题、鉴权问题还是大模型 API Key 过期了。我试过在每个微服务里单独配置大模型 Key结果第三天就出现了 Key 泄露告警因为某个测试环境的配置文件被误提交到了仓库。TaoToken 解决的就是这个问题。它提供一个统一的 API 入口你只需要申请一个 Key就可以在多个服务、多个环境中复用。对于 SpringCloud 迁移 K8S 这个场景来说它的价值体现在三个方面第一统一入口降低配置复杂度。原来每个微服务的application.yml里都要写不同的 API Key 和 Base URL现在只需要在 K8S 的 Secret 里存一份通过环境变量注入到所有 Pod 中。第二便于审计和限流。迁移期间调用量波动大统一 Key 可以在一个地方看到所有服务的调用情况避免某个服务异常刷量导致整体被限。第三切换模型方便。迁移过程中可能需要从 GPT 系列切到 Claude 系列做代码审查只需要改一个 Model ID不用动业务代码。接入方式很简单你需要在 TaoToken 控制台创建一个 API Key然后拿到两个核心参数Base URL 是https://taotoken.net/apiModel ID 根据你用的模型填写比如claude-sonnet-4-20250514或gpt-4o。如果你用的是 Claude Code 做代码迁移辅助还需要配置 Anthropic 兼容的接入点具体路径可以在文档里找到。这里有一个关键点TaoToken 的 Key 不要硬编码在代码或 Helm values 里而是通过 K8S Secret 注入。我在 Helm Chart 的secret.yaml模板中增加了env_taotoken_key和env_taotoken_base两个变量部署时从 CI 的变量组读取这样镜像本身不包含任何敏感信息符合安全合规要求。另外如果你需要长期做代码迁移和 Agent 辅助可以考虑 Coding Plan它比按量计费更适合高频调用场景。控制台里可以创建和管理多个 Key建议按环境拆分比如 dev 一个、prod 一个方便出问题时快速定位。3. 可复制配置Helm values.yaml 与 Traefik2 IngressRoute 完整片段这一节是整篇文章的核心我会给出可以直接复制使用的配置文件。先看 Helm 的目录结构我采用的是多环境 values 文件加 config/secret 覆盖的方式think-manifesto/ ├── Chart.yaml ├── values.yaml ├── values-dev.yaml ├── values-qa.yaml ├── values-prod.yaml ├── config/ │ ├── dev/ │ │ ├── config.yaml │ │ └── secret.yaml │ ├── qa/ │ └── prod/ └── templates/ ├── configmap.yaml ├── secret.yaml ├── deployment.yaml ├── service.yaml └── ingressroute.yamlvalues.yaml中定义基础镜像和副本数values-dev.yaml覆盖环境相关配置。关键字段包括configOverrides和secretOverrides它们指向 config 目录下的文件# values-dev.yaml nameSpace: think-dev replicaCount: 2 image: repository: registry.example.com/think-manifesto tag: 20250601-abc123 pullPolicy: IfNotPresent configOverrides: - config/dev/config.yaml secretOverrides: - config/dev/secret.yaml service: port: 8080 portName: http resources: limits: cpu: 500m memory: 512Mi requests: cpu: 200m memory: 256Miconfig/dev/config.yaml里放非敏感变量比如 Redis 地址和 TaoToken 的 Base URLenv_redis_host: redis-svc.think-dev env_redis_port: 6379 env_redis_database: 0 env_taotoken_base: https://taotoken.net/api env_taotoken_model: claude-sonnet-4-20250514config/dev/secret.yaml里放敏感信息Helm 模板会自动做 base64 编码env_redis_password: your-redis-password env_taotoken_key: sk-xxxxxxxxxxxxxxxxtemplates/configmap.yaml的写法如下它会把 config 文件里的键值对转成 ConfigMap 的 dataapiVersion: v1 kind: ConfigMap metadata: name: {{ include think-manifesto.fullname . }}-configmap namespace: {{ .Values.nameSpace }} data: {{- $files : .Files }} {{- range .Values.configOverrides }} {{- range $key, $value : ($files.Get (printf %s .) | fromYaml) }} {{ $key | indent 2 }}: {{ $value | quote }} {{- end }} {{- end }}templates/secret.yaml类似只是多了b64encapiVersion: v1 kind: Secret metadata: name: {{ include think-manifesto.fullname . }}-secret namespace: {{ .Values.nameSpace }} type: Opaque data: {{- $files : .Files }} {{- range .Values.secretOverrides }} {{- range $key, $value : ($files.Get (printf %s .) | fromYaml) }} {{ $key | indent 2 }}: {{ $value | b64enc }} {{- end }} {{- end }}deployment.yaml中通过envFrom把 ConfigMap 和 Secret 注入为环境变量SpringBoot 会自动读取env_redis_host这类变量并映射到spring.redis.hostapiVersion: apps/v1 kind: Deployment metadata: name: {{ include think-manifesto.fullname . }} namespace: {{ .Values.nameSpace }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ include think-manifesto.name . }} template: metadata: labels: app: {{ include think-manifesto.name . }} annotations: checksum/config: {{ include (print $.Template.BasePath /configmap.yaml) . | sha256sum }} checksum/secret: {{ include (print $.Template.BasePath /secret.yaml) . | sha256sum }} spec: containers: - name: {{ .Chart.Name }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }} imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: {{ .Values.service.portName }} containerPort: {{ .Values.service.port }} envFrom: - configMapRef: name: {{ include think-manifesto.fullname . }}-configmap - secretRef: name: {{ include think-manifesto.fullname . }}-secret resources: {{- toYaml .Values.resources | nindent 12 }}Traefik2 的 IngressRoute 配置如下它替代了原来 Spring Gateway 的路由和鉴权功能。ForwardAuth中间件指向 auth 服务的/auth/verify接口每个请求都会先经过鉴权apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: think-manifesto-route namespace: think-dev spec: entryPoints: - web routes: - match: Host(api.think-dev.example.com) PathPrefix(/api) kind: Rule services: - name: think-manifesto port: 8080 middlewares: - name: auth-forward --- apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: auth-forward namespace: think-dev spec: forwardAuth: address: http://auth-svc.think-dev:8081/auth/verify trustForwardHeader: true authResponseHeaders: - X-User-Id - X-User-Role安装 Traefik2 时我用的命令是helm repo add traefik https://traefik.github.io/charts helm repo update helm install traefik traefik/traefik \ --namespace traefik \ --set deployment.kindDaemonSet \ --set service.enabledfalse注意service.enabledfalse是因为我用 DaemonSet 加 hostNetwork 的方式暴露入口如果你的环境用 LoadBalancer可以去掉这个参数。4. 验证请求curl 测试网关路由与 TaoToken 鉴权链路配置写完之后最关键的一步是验证。我分三层来测第一层测 Traefik2 的路由是否生效第二层测 ForwardAuth 鉴权是否拦截未授权请求第三层测 TaoToken 的 API 调用是否正常。先测路由。在 K8S 集群外部执行curl -v http://api.think-dev.example.com/api/health如果返回 200 和健康检查信息说明 Traefik2 的 IngressRoute 匹配成功请求已经转发到后端 Pod。如果返回 404检查match规则里的 Host 和 PathPrefix 是否和实际请求一致。再测鉴权。不带 Token 访问受保护接口curl -v http://api.think-dev.example.com/api/user/profile预期返回 401并且响应头里会有WWW-Authenticate信息。这说明 ForwardAuth 中间件生效了auth 服务正确拦截了未授权请求。如果返回 200说明中间件没有绑定到路由上检查 IngressRoute 的middlewares字段。带上有效 Token 再测curl -v -H Authorization: Bearer your-jwt-token \ http://api.think-dev.example.com/api/user/profile预期返回 200 和用户信息同时响应头里会带上X-User-Id和X-User-Role这是 auth 服务通过authResponseHeaders透传的。最后测 TaoToken 的调用链路。在 Pod 内部执行kubectl exec -it deploy/think-manifesto -n think-dev -- sh curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $env_taotoken_key \ -H Content-Type: application/json \ -d { model: $env_taotoken_model, messages: [{role: user, content: ping}], max_tokens: 10 }如果返回正常的 JSON 响应说明环境变量注入成功TaoToken 的 Key 和 Base URL 都正确。如果返回 401检查 Secret 里的env_taotoken_key是否被正确 base64 编码如果返回 404检查 Base URL 是否多了或少了/v1路径。我还写了一个简单的 SpringBoot 测试接口用来验证业务代码里读取环境变量的逻辑RestController public class ConfigCheckController { Value(${env_taotoken_base:not-set}) private String taotokenBase; Value(${env_taotoken_model:not-set}) private String taotokenModel; GetMapping(/api/config/check) public MapString, String check() { MapString, String result new HashMap(); result.put(taotokenBase, taotokenBase); result.put(taotokenModel, taotokenModel); result.put(redisHost, System.getenv(env_redis_host)); return result; } }访问/api/config/check如果返回的taotokenBase是https://taotoken.net/api说明 ConfigMap 注入成功。这个接口在迁移期间非常有用可以快速确认每个环境的配置是否正确。5. 常见报错排查401、local proxy failed 与 OAuth 问题迁移过程中我遇到的报错主要集中在三类鉴权失败、网络代理问题和 OAuth 回调异常。下面逐个说明排查思路。401 Unauthorized 且响应体为空这是最常见的报错。首先确认 Traefik2 的 ForwardAuth 中间件是否真的调到了 auth 服务。执行kubectl logs -n think-dev deploy/traefik --tail100 | grep auth如果看到forwardAuth相关的错误日志说明 Traefik 无法连接到 auth 服务。检查 auth 服务的 Service 名称和端口是否和address字段一致。如果 Traefik 日志正常但 auth 服务没有收到请求检查 NetworkPolicy 是否阻止了跨命名空间访问。另一个常见原因是 auth 服务返回了 401 但 Traefik 没有正确透传。检查authResponseHeaders配置确保X-User-Id和X-User-Role在 auth 服务的响应头里存在。local proxy failed 或 connection refused这个报错通常出现在 Pod 内部调用 TaoToken API 时。首先确认 Pod 是否能解析外部域名kubectl exec -it deploy/think-manifesto -n think-dev -- nslookup taotoken.net如果解析失败检查 CoreDNS 配置和 Pod 的dnsPolicy。如果解析正常但连接被拒绝检查集群的出口网络策略确保允许 HTTPS 出站。有些企业环境会限制出站流量需要把taotoken.net加入白名单。还有一个容易忽略的点如果 Pod 的env_taotoken_base被错误地设置成了内网地址也会导致连接失败。用第 4 节的/api/config/check接口确认实际生效的值。OAuth 回调失败或 reading choices 报错如果你在迁移期间同时改造了登录流程可能会遇到 OAuth 回调地址不匹配的问题。Traefik2 作为入口时X-Forwarded-Proto和X-Forwarded-Host头需要正确传递否则 auth 服务生成的回调地址会是http://而不是https://。在 Traefik2 的 entryPoints 配置中增加entryPoints: web: address: :80 forwardedHeaders: trustedIPs: - 10.0.0.0/8 - 172.16.0.0/12同时确保 auth 服务的server.forward-headers-strategy设置为framework或native。至于reading choices这类报错通常出现在调用大模型 API 时响应格式不符合预期。检查 TaoToken 返回的 JSON 结构确认choices字段存在。如果返回的是错误信息先看error.message字段常见原因是 Model ID 写错或 Key 额度不足。Helm 模板渲染报错执行helm template时如果报nil pointer evaluating interface检查 values 文件里是否缺少模板中引用的字段。我习惯在values.yaml里给所有字段设默认值避免环境文件遗漏。helm template think-manifesto ./think-manifesto \ --values ./think-manifesto/values-dev.yaml \ --dry-run --debug这个命令可以在本地验证模板是否正确不需要连接 K8S 集群。6. 迁移完成后的接入建议与长期维护7 天迁移完成后我最大的感受是Helm 和 Traefik2 的组合确实比原来 SpringCloud 全家桶轻量很多但前提是你要把配置管理做扎实。迁移期间我踩过的坑大部分都和配置注入有关而不是代码逻辑本身。如果你也在做类似的迁移我的建议是先把 TaoToken 的 Key 管理规范定下来。不要等到出问题了才去查是哪个服务的 Key 过期了。在控制台里按环境创建 Key配合 K8S Secret 注入这样每个环境的调用量、错误率都能独立观察。对于长期编码和 Agent 场景Coding Plan 比按量计费更划算尤其是迁移期间需要频繁做代码审查和文档生成的时候。你可以先通过模型对话快速验证接口是否通确认没问题后再接入到 CI 流程里。最后提醒一点Traefik2 的 ForwardAuth 中间件在高并发场景下会成为瓶颈因为每个请求都要同步调用 auth 服务。如果 QPS 较高建议在 auth 服务前面加一层本地缓存或者把鉴权逻辑下沉到 Traefik 的插件里。这个优化我在迁移完成后第二周才做效果很明显P99 延迟从 120ms 降到了 35ms。接入文档和 API Keys 管理页面建议收藏迁移过程中随时可能需要查参数。如果你也在做 SpringCloud 到 K8S 的改造希望这篇实战记录能帮你少走一些弯路。