很多集群里都存在这样的历史遗留:一个关键 Deployment 长期运行在 default 命名空间。直接修改 YAML 中的 metadata.namespace 并不能完成迁移,因为命名空间属于 Kubernetes 对象身份的一部分;所谓“迁移”,本质上是在目标命名空间重建资源、切换流量,再删除旧资源。
要做到业务无中断,核心不是某条神奇的 kubectl 命令,而是让新旧两套实例并行运行,并把部署、依赖和流量切换拆成可以验证、可以回滚的步骤。
命名空间不是可以原地修改的字段
下面两个 Deployment 是不同的对象:
default/api
production/api
即使名称、标签和容器镜像完全相同,Kubernetes 也不会把它们视为同一次 Deployment rollout。因此,迁移通常要经历四个阶段:
- 在目标命名空间准备 ConfigMap、Secret、ServiceAccount、NetworkPolicy 等依赖。
- 创建新的 Deployment 和 Service,同时保留旧工作负载。
- 验证新实例,再逐步或一次性切换入口流量。
- 观察一段时间后缩容并删除旧资源。
需要特别注意的是,Service 的标签选择器只能选择同一命名空间内的 Pod。位于 default 的 Service 不能通过修改 selector 直接选中 production 中的 Pod。这意味着流量入口也必须纳入迁移方案。
先盘点资源,而不是直接导出再导入
可以先列出旧工作负载及其常见依赖:
kubectl -n default get deploy,rs,pod,svc,ingress,hpa,pdb,cm,secret,sa,role,rolebinding \
-l app=payments-api
kubectl -n default get deployment payments-api -o yaml
kubectl -n default get service payments-api -o yaml
不要把导出的 YAML 不加处理地重新提交。至少应移除这些由集群维护的字段:
metadata.uid
metadata.resourceVersion
metadata.creationTimestamp
metadata.managedFields
status
还要逐项检查:
- Secret 和 ConfigMap 是否确实存在于目标命名空间。
- ServiceAccount、RoleBinding 和镜像拉取 Secret 是否完整。
- PVC 使用的存储是否支持多实例挂载,或是否需要数据层迁移。
- NetworkPolicy 是否允许来自入口控制器、监控系统和调用方的流量。
- HPA、PDB、探针、拓扑分布和反亲和规则是否一并迁移。
- CronJob 或消息消费者能否同时运行两份,避免重复执行和重复消费。
数据库通常可以由新旧两套应用共享,但前提是版本兼容。若新版本包含数据库变更,应采用向后兼容的 expand-and-contract 方式,而不是在切流时执行破坏性 schema 修改。
可以这样实践:并行部署并验证新服务
下面示例假设应用无状态,镜像已经存在,HTTP 健康检查位于 /readyz。运行前请替换镜像地址、端口和资源规格。
apiVersion: v1
kind: Namespace
metadata:
name: production
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
namespace: production
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: payments-api
template:
metadata:
labels:
app: payments-api
spec:
terminationGracePeriodSeconds: 30
containers:
- name: api
image: registry.example.com/payments-api:1.8.4
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /readyz
port: http
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 20
periodSeconds: 10
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: payments-api
namespace: production
spec:
selector:
app: payments-api
ports:
- name: http
port: 80
targetPort: http
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payments-api
namespace: production
spec:
minAvailable: 2
selector:
matchLabels:
app: payments-api
保存为 payments-api-production.yaml 后,可以执行:
kubectl apply -f payments-api-production.yaml
kubectl -n production rollout status deployment/payments-api --timeout=5m
kubectl -n production get pods -l app=payments-api -o wide
kubectl -n production get endpointslices -l kubernetes.io/service-name=payments-api
在切换真实流量前,先从集群内部直接验证新 Service:
kubectl -n production run smoke-test \
--image=curlimages/curl:8.7.1 \
--restart=Never --rm -i \
-- curl --fail --show-error \
http://payments-api.production.svc.cluster.local/readyz
这里的 readiness probe 很关键:只有通过就绪检查的 Pod 才会进入 Service 端点。maxUnavailable: 0 则避免后续滚动发布主动降低可用副本数,但它不能替代足够的集群容量和正确的探针。
流量切换取决于调用路径
外部流量经 Ingress、Gateway 或云负载均衡器进入时,可以先为新 Service 建立并行路由,再利用入口控制器支持的权重、金丝雀或后端切换功能迁移流量。具体注解和配置因 NGINX Ingress、Traefik、Gateway API 或云厂商实现而异,不应把某一种控制器的语法当成 Kubernetes 通用能力。
集群内部调用方如果使用完整 DNS 名称,例如:
payments-api.default.svc.cluster.local
就必须更新为:
payments-api.production.svc.cluster.local
更稳妥的做法是分批更新调用方,同时让旧、新 Service 并存。每迁移一批消费者,就观察请求量、错误率和延迟;确认旧 Service 不再收到流量后,再进入清理阶段。
短 DNS 名称 http://payments-api 会解析到调用方所在命名空间中的 Service,因此调用方是否也在迁移会影响结果。不要仅凭 Service 名称相同就假设解析目标不变。
对于无法快速修改的遗留调用方,可以保留一个兼容入口,例如代理 Service 或经过验证的 ExternalName。但 ExternalName 会改变 DNS 响应语义,并可能影响 HTTP Host、TLS SNI 和不遵循 CNAME 的客户端,不能未经测试就用于关键链路。
回滚和清理要同样明确
切流前应记录旧 Deployment 的副本数和入口配置。出现错误率上升、依赖超时或权限问题时,把路由恢复到旧 Service 即可,因为旧 Pod 尚未删除。这正是并行运行的价值。
确认新环境稳定后,再执行缩容,而不是立即删除:
kubectl -n default scale deployment/payments-api --replicas=0
kubectl -n default get pods -l app=payments-api -w
保留一段与业务风险相匹配的观察期,然后删除旧对象:
kubectl -n default delete deployment payments-api
kubectl -n default delete service payments-api
删除前还应确认监控、告警、日志采集、备份任务和运维脚本已经改用新命名空间。RBAC、仪表盘查询和告警规则中硬编码的 namespace="default" 很容易被遗漏。
上线检查表
- 新旧工作负载能够并行运行,不会重复执行有副作用的任务。
- 目标命名空间中的 Secret、ConfigMap、RBAC、PVC 和网络策略完整。
- 新 Service 已通过集群内冒烟测试和真实依赖检查。
- 入口层支持可控切换,并且回滚操作已经演练。
- 监控能按命名空间分别显示新旧实例的流量、错误率和延迟。
- 旧 Deployment 先缩容观察,再删除资源。
- 后续通过策略工具或准入控制阻止应用再次部署到
default。
零停机迁移的关键,是把命名空间变更视为一次生产环境替换,而不是一次 YAML 重命名。只要旧路径在验证完成前始终可用,迁移就既可观察,也可逆。