容器集群迁移的分阶段切换

📅 2026/8/22 21:16:29
容器集群迁移的分阶段切换
容器集群迁移的分阶段切换将运行多年的单体服务或物理机/VM 架构直接重构为 Docker 镜像后一次性部署到 Kubernetes容易忽略网络、DNS 与有状态依赖的差异。跨可用区延迟、CoreDNS 负载和数据库连接池都应在切流前验证并准备回滚方案。存量业务迁移到 Kubernetes 涉及网络、存储、域名解析和流量切分。适合采用绞杀者模式Strangler Fig Pattern等渐进迁移方案而非一次性切换。1. 存量系统迁移的三大“死亡陷阱”在规划物理机/虚拟机向 K8s 迁移的路径时如果不提前解决基础设施的差异绝大多数故障会在切换流量的瞬间爆发跨界网络通达与 DNS 幽灵虚拟机环境采用固定 IP 通信而 K8s Pod IP 随重启动态变化。若未搭建 Calico/Flannel BGP 模式或 AWS VPC-CNI容器与物理机便处于网络孤岛。更致命的是Pod 内高频解析外部物理机 MySQL 的域名极易将 CoreDNS 打挂。有状态存储的会话与锁机制存量系统常依赖本地磁盘文件存储如/data/uploads或基于本地内存的 Session。简单打成容器后Pod 重启会导致本地文件丢失、用户登录态断开。缺乏细粒度回滚能力若在入口处直接把全部流量切到新集群而后续发现连接、线程或依赖配置异常没有按比例、按请求特征切流的手段就只能做范围更大的回退。2. 四阶段分阶段平滑切换架构为了实现全过程无感知迁移我们必须采用“网络打通 - 无状态灰度 - 有状态双写 - 旧架构绞杀”的四阶段策略1. 阶段 1基础设施与 DNS 平滑对接在不迁移任何业务代码的前提下首先完成 Calico 的 BGP 路由与物理交换机对等Peering使物理机可以直通 Pod IP。同时在 CoreDNS 中配置stubDomains让容器内部解析*.internal.legacy时自动转向自建的 Bind DNS避免 DNS 解析穿透。2. 阶段 2无状态层基于 Request Header 的精准灰度利用网关层的动态权重Weight与特定 Header 匹配如X-Canary-User: True优先将内部员工或测试流量切入 K8s Pod。在此阶段Pods 与物理机应用共同使用后端的物理机数据库。3. 阶段 3有状态数据同步与读写分离对于依赖共享文件的系统必须提前接入 NFS/Ceph CSI 挂载或者重构为 S3 兼容的敏捷对象存储。数据库层则通过 DTS 或 Canal 实现跨 K8s 内外的实时双向同步确保随时具备一秒回滚到物理机的能力。4. 生产级灰度切流与网关平滑控制代码下面的 Go 语言示例展示了一个具备“按权重分流”与“故障自动回退”功能的确定性流量切分控制代理package main import ( fmt math/rand net/http net/http/httputil net/url sync/atomic time ) // TrafficSplitter 流量切分器 type TrafficSplitter struct { LegacyURL *url.URL K8sURL *url.URL K8sWeight int32 // 0 - 100 灰度百分比 K8sErrors uint64 // 记录 K8s 侧 5xx 错误数 } func NewTrafficSplitter(legacyTarget, k8sTarget string, initialK8sWeight int32) (*TrafficSplitter, error) { uLegacy, err : url.Parse(legacyTarget) if err ! nil { return nil, err } uK8s, err : url.Parse(k8sTarget) if err ! nil { return nil, err } return TrafficSplitter{ LegacyURL: uLegacy, K8sURL: uK8s, K8sWeight: initialK8sWeight, }, nil } func (ts *TrafficSplitter) ServeHTTP(w http.ResponseWriter, r *http.Request) { // 规则 1带着特权 Header 的请求强制切入 K8s if r.Header.Get(X-Env-Target) k8s-canary { ts.forwardTo(ts.K8sURL, w, r, true) return } // 规则 2根据可控权重进行随机切流 currentWeight : atomic.LoadInt32(ts.K8sWeight) rand.Seed(time.Now().UnixNano()) if rand.Intn(100) int(currentWeight) { ts.forwardTo(ts.K8sURL, w, r, true) } else { ts.forwardTo(ts.LegacyURL, w, r, false) } } func (ts *TrafficSplitter) forwardTo(target *url.URL, w http.ResponseWriter, r *http.Request, isK8s bool) { proxy : httputil.NewSingleHostReverseProxy(target) // 自定义 ModifyResponse 用于监控故障率触发熔断回滚 proxy.ModifyResponse func(resp *http.Response) error { if isK8s resp.StatusCode 500 { errCount : atomic.AddUint64(ts.K8sErrors, 1) // 如果 1 分钟内触发 10 次 5xx自动触发权重归零熔断 if errCount 10 { atomic.StoreInt32(ts.K8sWeight, 0) fmt.Println([熔断警告] K8s 故障数超限灰度权重已紧急降为 0%全量回滚至物理机) } } return nil } proxy.ServeHTTP(w, r) } func main() { // 初始化代理旧系统物理机 IP新系统 K8s Ingress Cluster IP splitter, _ : NewTrafficSplitter(http://192.168.10.50:8080, http://10.96.100.200:80, 10) server : http.Server{ Addr: :80, Handler: splitter, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } fmt.Println(灰度切流代理服务已启动初始 K8s 流量权重: 10%...) server.ListenAndServe() }5. 现场诊断工具与跨界网络排障迁移过程中最常见的问题是物理机网段与 Pod 网段路由不通或者 CoreDNS 出现解析延迟。步骤 1排查物理机与 Pod 跨界 BGP 路由通达性在物理机宿主机上检查内核路由表与 Calico 路由条目# 在物理机上查看到达 K8s Pod 网段 (如 10.244.0.0/16) 的下一跳 ip route show | grep 10.244 # 使用 ping 与 traceroute 验证到达容器虚拟网卡的延迟 traceroute -n 10.244.3.15步骤 2使用 tcpdump 抓包排查 CoreDNS 解析死锁登入 K8s Worker 节点抓取 53 端口的 DNS 查询响应确认是否存在 upstream 响应超时# 抓取指定 Pod 交互的 UDP 53 端口流量 tcpdump -i any port 53 -nn -v # 验证 Pod 内部解析外部物理机域名的耗时 kubectl exec -it order-service-v1-6d9b4c-92kx \ -- time dig 10.96.0.10 legacy-db.internal.company.com short步骤 3动态调整 Nginx Ingress 流量权重在使用 Nginx Ingress Controller 时利用 Annotations 动态修改 Canary 切流比例# 部署 Canary Ingress 配置并赋予 20% 流量权重 kubectl apply -f - EOF apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: order-service-canary annotations: nginx.ingress.kubernetes.io/canary: true nginx.ingress.kubernetes.io/canary-weight: 20 spec: ingressClassName: nginx rules: - host: order.company.com http: paths: - path: / pathType: Prefix backend: service: name: order-service-k8s port: number: 80 EOF6. 存量迁移工程的安全防线坚持日志与 Metric 的“双重归拢”在流量切入 K8s 前必须保证 K8s 的 Promtail/Vector 探针已经将 Pod 日志同步到了与物理机相同的 ELK/Loki 实例中否则切流后会导致运维排障上下文断裂。连接池硬隔离迁移初期K8s Pod 与物理机应用共用同一个外部 MySQL 时必须调小 Pod 侧的maxOpenConns。防止 Pod 在 Autoscale 扩容时突然建立上千个 TCP 连接一举压垮物理机数据库。按业务周期保留旧环境即便主要流量已切入新集群也不宜立即清理旧环境。保留可恢复的镜像、配置和数据并结合业务结算周期决定观察时长不同系统的周期并不相同。