零停机迁移:把关键 Kubernetes 工作负载移出 default 命名空间

2026-09-03 42 预计阅读时间: 1 分钟
来源: cncf.io AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

很多集群里都存在这样的历史遗留:一个关键 Deployment 长期运行在 default 命名空间。直接修改 YAML 中的 metadata.namespace 并不能完成迁移,因为命名空间属于 Kubernetes 对象身份的一部分;所谓“迁移”,本质上是在目标命名空间重建资源、切换流量,再删除旧资源。

要做到业务无中断,核心不是某条神奇的 kubectl 命令,而是让新旧两套实例并行运行,并把部署、依赖和流量切换拆成可以验证、可以回滚的步骤。

命名空间不是可以原地修改的字段

下面两个 Deployment 是不同的对象:

default/api
production/api

即使名称、标签和容器镜像完全相同,Kubernetes 也不会把它们视为同一次 Deployment rollout。因此,迁移通常要经历四个阶段:

  1. 在目标命名空间准备 ConfigMap、Secret、ServiceAccount、NetworkPolicy 等依赖。
  2. 创建新的 Deployment 和 Service,同时保留旧工作负载。
  3. 验证新实例,再逐步或一次性切换入口流量。
  4. 观察一段时间后缩容并删除旧资源。

需要特别注意的是,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 重命名。只要旧路径在验证完成前始终可用,迁移就既可观察,也可逆。


相关推荐