找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4209

积分

0

好友

549

主题
发表于 1 小时前 | 查看: 4| 回复: 0

Docker Swarm 迁移到 Kubernetes,最危险的做法是把 docker-compose.yml 逐字段“翻译”成 Deployment YAML,然后直接切流。两者都能运行容器,但关键网络、存储、健康检查、滚动和权限模型不一一对应。迁移失败通常不是 YAML 写错,而是原来被 Swarm 默认行为掩盖的依赖没有被显式建模。

本文以一个无状态 Web 服务为主线:源端由 Docker Swarm stack 部署,目标端由 Kubernetes 的 Deployment、Service 和 Ingress 运行,数据库仍作为外部受管依赖,不在本次迁移中搬库。持久化数据单独使用 StatefulSet 与 PVC,并先完成数据迁移方案。

文中的 <源Stack名><源服务名><命名空间><应用名><镜像地址><业务域名><健康检查路径><存储类名> 等尖括号内容均为占位符。<命名空间> 需要在迁移前由平台或 GitOps 流程创建并完成 RBAC、配额和网络基线。文中所有 kubectl 命令都显式携带 -n <命名空间>;请替换后执行。

迁移前的原则:先把现网事实做成清单

不要把 compose 文件当作唯一真相。一个 Swarm 服务的实际行为还受 stack 变量、secret、config、overlay 网络、节点标签、外置反向代理、卷驱动和历史手工操作影响。迁移前应形成可审计的“服务契约”,至少包括:

  • 容器镜像、启动命令、环境变量来源、监听端口和进程用户。
  • 副本数、可调度节点、CPU/内存限制、重启策略、发布策略。
  • 对外入口、内部服务名、访问路径、TLS 终止位置和回源头。
  • 需要持久化的数据路径、卷驱动、读写方式、备份和恢复演练记录。
  • 健康检查命令、启动时间、真正可接流量的条件,以及依赖失效时的行为。
  • 监控、日志、告警、权限、回滚和切流责任人。

先获取 Swarm 的只读事实。下面命令不会修改 stack;敏感环境变量、secret 名和内部地址不应直接复制到公开工单。

# 代码 1:确认源端 Swarm 状态、stack 服务与任务分布
docker version
docker info --format '{{.Swarm.LocalNodeState}} {{.Swarm.ControlAvailable}}'
docker stack services "<源Stack名>"
docker stack ps "<源Stack名>" --no-trunc

docker stack services 展示的是期望副本与当前副本的摘要;任务频繁重建、被调度到不同节点、或长期有 REJECTED 状态时,必须先解释原因。把一个本来就不稳定的服务迁走,不能证明 Kubernetes 有问题。

# 代码 2:导出源服务的配置、任务和网络引用
set -euo pipefail

stack_name="<源Stack名>"
service_name="<源服务名>"
evidence_dir="<证据目录>/swarm-$service_name-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$evidence_dir"

docker service inspect "$stack_name"_"$service_name" > "$evidence_dir/service-inspect.json"
docker service ps "$stack_name"_"$service_name" --no-trunc > "$evidence_dir/service-tasks.txt"
docker network ls > "$evidence_dir/network-list.txt"
docker volume ls > "$evidence_dir/volume-list.txt"
printf '%s\n' "$evidence_dir"

Stack 部署后的 service 名通常是 <源Stack名>_<源服务名> ,但不要假设所有环境都遵循此规则;应以 docker stack services 的输出为准。导出的 JSON 可能包含镜像仓库、环境变量或挂载路径,证据目录需要访问控制。

下面的脚本把源服务中最容易漏掉的字段抽出来,作为目标 YAML 审核清单。它依赖本机有 docker CLI 与 jq;若 jq 不存在,先安装经批准的软件包或把完整 JSON 提交给迁移评审,不要用脆弱的 grep 解析 JSON。

#!/usr/bin/env bash
# 代码 3:生成 Swarm 服务迁移核对清单
set -euo pipefail

service_ref="<源Stack名>_<源服务名>"
docker service inspect "$service_ref" \
  | jq '.[0].Spec.TaskTemplate.ContainerSpec as $c
    | {
        image: $c.Image,
        command: $c.Command,
        args: $c.Args,
        env: $c.Env,
        mounts: $c.Mounts,
        secrets: $c.Secrets,
        configs: $c.Configs,
        labels: $c.Labels,
        service_labels: .[0].Spec.Labels,
        networks: .[0].Spec.TaskTemplate.Networks,
        endpoint: .[0].Endpoint.Spec,
        placement: .[0].Spec.TaskTemplate.Placement,
        resources: .[0].Spec.TaskTemplate.Resources,
        restart_policy: .[0].Spec.TaskTemplate.RestartPolicy
      }'

不要逐字映射字段;每项意图都要明确在 Kubernetes 中由什么对象实现。placement.constraints 可能变为 nodeSelectornodeAffinity 或不再保留;发布端口则可能由 Service、Ingress 或负载均衡承担。

坑一:把 Swarm 的服务模型当成 Deployment 的逐行翻译

Swarm 的 service、task、replicas 与 Kubernetes 的 Deployment、ReplicaSet、Pod 看似相似,但调度和标签语义不同。Service 只路由到 selector 匹配、通常已经 Ready 的 Pod;Deployment selector 创建后不可随意修改。

不要在迁移时沿用无意义的临时标签。至少固定 app.kubernetes.io/nameapp.kubernetes.io/instanceapp.kubernetes.io/component,并让 Deployment template labels 与 selector 完全一致。

# 代码 4:目标应用的 Deployment 基础结构
apiVersion: apps/v1
kind: Deployment
metadata:
  name: <应用名>
  namespace: <命名空间>
  labels:
    app.kubernetes.io/name: <应用名>
    app.kubernetes.io/instance: <环境标识>
    app.kubernetes.io/component: web
spec:
  replicas: <初始副本数>
  selector:
    matchLabels:
      app.kubernetes.io/name: <应用名>
      app.kubernetes.io/instance: <环境标识>
  template:
    metadata:
      labels:
        app.kubernetes.io/name: <应用名>
        app.kubernetes.io/instance: <环境标识>
        app.kubernetes.io/component: web
    spec:
      containers:
      - name: web
        image: <镜像地址>
        imagePullPolicy: IfNotPresent

selectortemplate labels 不匹配会被 API 拒绝;更隐蔽的错误是 Service selector 少了环境标签,导致测试环境流量误打到同 namespace 的其他 Pod。不要在多个环境共享同一个 namespace 时只用 app=<应用名> 作为路由选择器。

资源请求决定调度时的保留量,limit 决定容器可用上界。Swarm 的 reservation/limit 不能机械照抄,因为 Kubernetes 节点可分配资源、sidecar、init container 与命名空间配额都可能不同。先根据源端真实峰值和容量预算设定,再用灰度数据修正。

# 代码 5:为同一容器补全端口、资源与安全上下文
resources:
  requests:
    cpu: "<CPU请求>"
    memory: "<内存请求>"
  limits:
    cpu: "<CPU上限>"
    memory: "<内存上限>"
ports:
  - name: http
    containerPort: <容器端口>
    protocol: TCP
securityContext:
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop:
      - ALL

readOnlyRootFilesystem 只有在应用不再往镜像层写临时文件、PID、缓存或日志时才能启用。迁移前应让日志输出到 stdout/stderr,并为确有需要的临时目录挂载受控 emptyDir;否则 Pod 会启动但在首个写入点失败。

服务端 dry-run 只验证 API 与准入,不代表真实调度、镜像拉取或业务可用。

# 代码 6:校验目标清单并查看 Deployment 实际状态
kubectl apply --dry-run=server -n <命名空间> -f "<Deployment清单路径>"
kubectl apply -n <命名空间> -f "<Deployment清单路径>"
kubectl rollout status -n <命名空间> deployment/<应用名> --timeout=<等待时长>
kubectl get deployment,pod -n <命名空间> \
  -l app.kubernetes.io/name=<应用名> -o wide

若 rollout status 超时,不要立刻 rollback 再重试。先查看 Pod 的 Pending、ImagePullBackOff、CrashLoopBackOff 或探针失败事件。每一种状态代表不同责任边界,反复 rollout 只会覆盖第一次失败的证据。

# 代码 7:通过 Pod 状态、事件与日志解释首次部署失败
kubectl get pod -n <命名空间> \
  -l app.kubernetes.io/name=<应用名> -o wide
kubectl describe pod -n <命名空间> <Pod名称>
kubectl logs -n <命名空间> <Pod名称> --all-containers --tail=200
kubectl get event -n <命名空间> \
  --sort-by=.lastTimestamp | tail -n 100

Pod 名称是临时对象,替换 <Pod名称> 前应通过前一条命令取得当前实例。若容器包含 sidecar,--all-containers 便于首轮排查;后续根因结论仍要明确是哪一个 container 的日志和退出码。

坑二:把 overlay 网络和发布端口误当成 Kubernetes Service 与 Ingress

Swarm overlay 网络天然给服务名提供内部可达路径,published port 又可能采用 routing mesh 或 host 发布模式。Kubernetes 中,Service 负责稳定虚拟 IP 和 Pod 选择,Ingress 或 Gateway 负责 HTTP 路由,负载均衡器、CNI、NetworkPolicy 负责各自层次的能力。把 Swarm 的 ports 直接填进 containerPort,外部流量并不会自动到达。

先建立 ClusterIP Service,让同 namespace 内服务通过稳定 DNS 访问。targetPort 应明确引用命名端口,避免容器端口变更后 Service 静默指向错误端口。

# 代码 8:仅提供集群内访问的 ClusterIP Service
apiVersion: v1
kind: Service
metadata:
  name: <应用名>
  namespace: <命名空间>
  labels:
    app.kubernetes.io/name: <应用名>
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: <应用名>
    app.kubernetes.io/instance: <环境标识>
  ports:
    - name: http
      port: 80
      targetPort: http
      protocol: TCP

Service 的 selector 与 Deployment labels 必须一字不差;否则 Service 仍会创建,但 EndpointSlice 没有后端,入口层看到的可能是 503 或连接失败。创建后立即检查 EndpointSlice,不要只看 Service 有 ClusterIP。

# 代码 9:验证 Service 是否真的选择到了 Ready 后端
kubectl get service -n <命名空间> <应用名> -o wide
kubectl get endpointslice -n <命名空间> \
  -l kubernetes.io/service-name=<应用名> -o yaml
kubectl get pod -n <命名空间> \
  -l app.kubernetes.io/name=<应用名> \
  -o custom-columns=NAME:.metadata.name,READY:.status.containerStatuses
  • .ready,IP:.status.podIP
  • EndpointSlice 中 ready 条件为 true 才说明该 IP 可作为正常流量后端。若为空,按 selector、Pod readiness、命名空间与控制器事件逐项排查,不要通过手工创建 Endpoints 绕过问题。

    外部 HTTP 路由由已部署并受平台运维的 Ingress Controller 或 Gateway 实现。下面使用标准 Ingress API;ingressClassName 必须是目标集群真实存在的 class。证书、WAF、限流与 source IP 保留策略不应在迁移窗口临时改变。

    # 代码 10:将业务域名路由到 ClusterIP Service 的 Ingress
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: <应用名>
      namespace: <命名空间>
    spec:
      ingressClassName: <IngressClass名称>
      rules:
      - host: <业务域名>
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: <应用名>
                port:
                  name: http

    不同 Ingress Controller 对 annotation 的支持不一致。迁移开始前确认 IngressClass、TLS、负载均衡地址、健康检查路径与访问日志位置。

    网络默认允许还是默认拒绝,取决于 CNI 和 NetworkPolicy 基线。若目标 namespace 采用默认拒绝入站,应用必须显式允许来自入口控制器 namespace 或具有相应标签的 Pod;用 0.0.0.0/0 放通只是把网络模型退回裸奔。

    # 代码 11:只允许指定入口组件访问 Web Pod 的 NetworkPolicy
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-ingress-to-<应用名>
      namespace: <命名空间>
    spec:
      podSelector:
        matchLabels:
          app.kubernetes.io/name: <应用名>
          app.kubernetes.io/instance: <环境标识>
      policyTypes:
        - Ingress
      ingress:
        - from:
            - namespaceSelector:
                matchLabels:
                  kubernetes.io/metadata.name: <入口控制器命名空间>
          ports:
            - protocol: TCP
              port: <容器端口>

    该策略只在 CNI 实现 NetworkPolicy 时生效,且 namespaceSelector 标签必须真实存在。发布前核对入口 namespace、Pod 标签和 CNI 基线。

    # 代码 12:从集群内临时调试容器验证 Service DNS 与健康端点
    kubectl run -n <命名空间> network-check \
      --image=curlimages/curl:<已批准版本> \
      --restart=Never \
      --rm -i -- \
      curl --fail --silent --show-error \
      "http://<应用名>.<命名空间>.svc.cluster.local<健康检查路径>"

    该命令会创建并删除临时 Pod;先确认镜像、权限与审计要求。生产不允许临时 Pod 时,改用平台诊断工作负载或灰度 namespace。

    坑三:把 Swarm local volume 当成 Kubernetes 持久化存储

    Docker local volume 常与某台 Swarm 节点的磁盘绑定。服务在 Swarm 中恰好稳定落在该节点时看似正常,一旦迁移到 Kubernetes 或发生重调度,数据可能根本不在目标节点。把 hostPath 或临时 emptyDir 当成持久卷,只会把风险换一种形式保留。

    先对每个源端 mount 明确分类:配置只读、可重建缓存、日志、用户上传、数据库数据、消息队列数据。数据库和其他有状态组件不应通过“复制一个卷目录”直接切换;需要停写、备份、校验、恢复、双写或逻辑复制等独立数据迁移方案。

    # 代码 13:检查源端服务挂载与卷的实际驱动、范围
    docker service inspect "<源Stack名>_<源服务名>" \
      --format '{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}' | jq .
    
    docker volume inspect "<源卷名>"
    docker volume ls --filter "name=<源卷名>"

    volume inspect 中的 Driver、Mountpoint、Labels 和 Scope 是证据。Scope 为 local 时,卷只能在特定节点可见;即使 Scope 看起来是 global,也要确认外部存储驱动、权限、快照、备份和恢复流程是否满足目标集群要求。

    若该服务只有可重建缓存,可使用 emptyDir 并明确它会在 Pod 重建后丢失。若是业务持久数据,选择受平台支持的 StorageClass,并在一次独立的数据演练中验证 PVC 绑定、挂载、备份恢复和故障域。

    # 代码 14:为需要独占读写的应用数据申请 PVC
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: <应用名>-data
      namespace: <命名空间>
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: <存储类名>
      resources:
        requests:
          storage: <容量>

    ReadWriteOnce 的语义由存储插件实现,不能简单理解成“任何时候只允许一个 Pod”。部署多副本前必须确认是否允许多节点挂载、是否需要 ReadWriteMany,或改为让应用无状态并将数据交给外部存储。

    # 代码 15:在 Deployment 中明确挂载 PVC 与临时目录
    spec:
      template:
        spec:
          containers:
          - name: web
            volumeMounts:
            - name: app-data
              mountPath: <应用数据路径>
            - name: tmp
              mountPath: /tmp
          volumes:
          - name: app-data
            persistentVolumeClaim:
              claimName: <应用名>-data
          - name: tmp
            emptyDir: {}

    这个片段应嵌入已有 Deployment 的正确层级,不能作为独立 YAML 直接 apply。应用挂载前,确认 UID/GID、fsGroup、init container 和存储权限模型一致;把宿主机目录 chmod 777 并不能修复 CSI 卷的权限问题。

    # 代码 16:验证 PVC 绑定、Pod 挂载和容量事件
    kubectl get pvc -n <命名空间> <应用名>-data -o wide
    kubectl describe pvc -n <命名空间> <应用名>-data
    kubectl get pod -n <命名空间> \
      -l app.kubernetes.io/name=<应用名> -o wide
    kubectl get event -n <命名空间> \
      --sort-by=.lastTimestamp | rg -i 'volume|mount|attach|claim|provision' || true

    PVC Bound 只证明控制平面完成绑定,不能证明数据与权限正确。应在隔离环境恢复备份并运行一致性检查。

    对于确实需要稳定身份、稳定网络名与逐副本卷的组件,使用 StatefulSet,而不是把 replicas>1 的 Deployment 直接挂同一个 RWO PVC。下面只展示基本绑定结构,数据库参数、备份、反亲和、初始化和恢复策略必须由对应组件的官方运维方案负责。

    # 代码 17:StatefulSet 的稳定身份与逐副本卷模板结构
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: <有状态应用名>
      namespace: <命名空间>
    spec:
      serviceName: <无头Service名>
      replicas: <副本数>
      selector:
        matchLabels:
          app.kubernetes.io/name: <有状态应用名>
      template:
        metadata:
          labels:
            app.kubernetes.io/name: <有状态应用名>
        spec:
          containers:
          - name: app
            image: <镜像地址>
            volumeMounts:
            - name: data
              mountPath: <应用数据路径>
      volumeClaimTemplates:
      - metadata:
          name: data
        spec:
          accessModes: [ "ReadWriteOnce" ]
          storageClassName: <存储类名>
          resources:
            requests:
              storage: <容量>

    坑四:把 Swarm HEALTHCHECK 等同于 Kubernetes readiness 和 liveness

    Swarm HEALTHCHECK 主要影响容器健康状态和调度观察;Kubernetes 则把“可以接流量”“进程失活需要重启”“启动较慢需要保护”拆成 readinessProbe、livenessProbe、startupProbe。只配置 liveness 而没有 readiness,Pod 可能在依赖未就绪时就被 Service 加入后端;把依赖数据库的深度检查设为 liveness,又可能让短暂下游故障引发全量重启。

    先区分三个问题:

    • startupProbe:应用是否还在正常启动窗口内,避免启动慢时被过早 liveness 杀死。
    • readinessProbe:此副本现在是否适合接收业务流量。
    • livenessProbe:进程是否进入无法自行恢复的状态,才需要 kubelet 重启。
    # 代码 18:HTTP 应用的 startup、readiness、liveness 探针
    startupProbe:
      httpGet:
        path: <启动检查路径>
        port: http
      periodSeconds: 5
      failureThreshold: <最大启动检查次数>
    readinessProbe:
      httpGet:
        path: <健康检查路径>
        port: http
      periodSeconds: 5
      timeoutSeconds: 2
      failureThreshold: 3
    livenessProbe:
      httpGet:
        path: <存活检查路径>
        port: http
      periodSeconds: 10
      timeoutSeconds: 2
      failureThreshold: 3

    三条路径可以相同,但语义必须经过应用团队确认。若健康检查依赖数据库,数据库短暂波动会让所有 Pod 同时 NotReady;这对保护流量可能合理,但不应再让 liveness 把每个 Pod 重启。检查端点必须无副作用,不得写数据、消耗大量资源或绕过认证后暴露敏感信息。

    # 代码 19:查看探针事件、重启次数与当前 Ready 条件
    kubectl get pod -n <命名空间> \
      -l app.kubernetes.io/name=<应用名> \
      -o custom-columns=NAME:.metadata.name,READY:.status.containerStatuses
  • .ready,RESTARTS:.status.containerStatuses
  • .restartCount,PHASE:.status.phase kubectl describe pod -n <命名空间> <Pod名称> kubectl logs -n <命名空间> <Pod名称> --previous --tail=200
  • --previous 只在容器发生过重启且日志仍可获取时有效。探针失败的结论应引用 describe 中的具体事件,例如连接拒绝、超时或 HTTP 状态码,而不是仅写“健康检查异常”。

    坑五:把 Config、Secret、环境变量和镜像内配置混在一起

    Swarm config 与 secret 的挂载方式、权限和更新行为不等于 Kubernetes ConfigMap 与 Secret。Kubernetes Secret 默认只是编码数据,不自动等于端到端加密;是否启用静态加密、外部密钥管理、工作负载身份和审计,取决于集群平台。绝不能把生产密码写进 Git 明文 YAML、镜像层、命令行历史或日志。

    非敏感、可版本化配置可放 ConfigMap。下面示例展示应用配置文件挂载,不代表所有配置都应热更新;多数应用读取配置只发生在进程启动,需要通过受控发布让新配置生效。

    # 代码 20:以文件形式挂载非敏感应用配置
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: <应用名>-config
      namespace: <命名空间>
    data:
      app.yaml: |
        server:
          port: <容器端口>
        logging:
          level: <日志级别>
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: <应用名>
      namespace: <命名空间>
    spec:
      template:
        spec:
          containers:
          - name: web
            volumeMounts:
            - name: config
              mountPath: /etc/<应用名>
              readOnly: true
          volumes:
          - name: config
            configMap:
              name: <应用名>-config

    这段 Deployment 片段同样需要合并到完整对象;对已存在的 Deployment,宜用受控 Helm、Kustomize 或 GitOps 变更,而不是手工多次 apply 造成字段漂移。ConfigMap 名变更并滚动发布,是比依赖卷更新时机更可预测的做法。

    敏感值应从现有密钥管理或平台 Secret 同步机制导入。若必须使用 Kubernetes Secret 清单,stringData 方便生成但文件本身仍是敏感材料,必须从代码库、CI 日志和工单附件中隔离。

    # 代码 21:Secret 作为环境变量来源的结构示例
    apiVersion: v1
    kind: Secret
    metadata:
      name: <应用名>-runtime
      namespace: <命名空间>
    type: Opaque
    stringData:
      DATABASE_URL: <由受控密钥系统注入的连接串>
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: <应用名>
      namespace: <命名空间>
    spec:
      template:
        spec:
          containers:
          - name: web
            envFrom:
            - secretRef:
                name: <应用名>-runtime

    环境变量可能被诊断输出、崩溃报告或进程工具暴露。高价值凭据宜采用平台支持的工作负载身份、外部 Secret 挂载或短期令牌;无论哪种方式,都需要轮换、双密钥切换和回滚预案。

    # 代码 22:不展开 Secret 值地核对引用、ServiceAccount 与配置版本
    kubectl get deployment -n <命名空间> <应用名> -o yaml \
      | rg -n 'serviceAccountName|secretRef|configMap|image:|envFrom:' -C 2
    kubectl get configmap -n <命名空间> <应用名>-config -o yaml
    kubectl get secret -n <命名空间> <应用名>-runtime \
      -o custom-columns=NAME:.metadata.name,TYPE:.type,CREATED:.metadata.creationTimestamp

    命令故意不读取 secret.data。若需要验证凭据本身,只能在受控应用日志、数据库审计或经过授权的密钥系统中完成,不能通过把 Secret 解码贴到终端或工单里完成。

    坑六:忽略切流、滚动、容量与回滚的组合效应

    Swarm 的 update_config、restart_policy 和副本调度会在迁移时失去默认保障。Kubernetes 的 Deployment strategy、maxUnavailable、maxSurge、PodDisruptionBudget、HPA、反亲和和节点容量需要显式设计。没有 readiness、没有最小可用副本、没有入口灰度和没有快速回滚的“全量 DNS 切换”,不是迁移计划。

    先把滚动策略写入 Deployment。maxUnavailable=0 只在有足够可调度容量、探针可信、存储不阻塞和资源配额允许时才可能保持可用;如果资源不足,rollout 会一直卡住,不能当作零风险配置。

    # 代码 23:明确 Deployment 滚动发布策略
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: <应用名>
      namespace: <命名空间>
    spec:
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxUnavailable: 0
          maxSurge: 1
        minReadySeconds: <最小就绪稳定秒数>
        progressDeadlineSeconds: <发布超时秒数>

    PodDisruptionBudget 只约束自愿中断,不能阻止崩溃、宕机、探针失败或资源不足。minAvailable 必须不高于稳定副本数,否则会阻断维护。

    # 代码 24:为多副本 Web 服务保留最少可用 Pod
    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
      name: <应用名>
      namespace: <命名空间>
    spec:
      minAvailable: <最少可用副本数>
      selector:
        matchLabels:
          app.kubernetes.io/name: <应用名>
          app.kubernetes.io/instance: <环境标识>

    HPA 不是迁移当天的兜底开关。它需要可靠 metrics API、合理 requests 与业务指标,且不会修复下游容量不足。

    # 代码 25:基于 CPU 利用率的 HPA 结构示例
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: <应用名>
      namespace: <命名空间>
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: <应用名>
      minReplicas: <最小副本数>
      maxReplicas: <最大副本数>
      metrics:
      - type: Resource
        resource:
          name: cpu
          target:
            type: Utilization
            averageUtilization: <目标CPU利用率百分比>

    迁移切流前,先把所有准备动作在灰度 namespace 或灰度域名完成。以下脚本只做 API 校验、部署与滚动等待,不修改 DNS、不删除 Swarm stack;它是切流前的一个检查点,不是完整迁移自动化。镜像仓库凭据、namespace、RBAC、存储类、IngressClass 和监控接入都应已就绪。

    #!/usr/bin/env bash
    # 代码 26:Kubernetes 灰度发布前置核验
    set -euo pipefail
    
    namespace="<命名空间>"
    manifest_dir="<清单目录>"
    app_name="<应用名>"
    
    kubectl apply --dry-run=server -n "$namespace" -f "$manifest_dir"
    kubectl apply -n "$namespace" -f "$manifest_dir"
    kubectl rollout status -n "$namespace" "deployment/$app_name" --timeout=<等待时长>
    kubectl get deployment,service,pod -n "$namespace" \
      -l "app.kubernetes.io/name=$app_name" -o wide
    kubectl get endpointslice -n "$namespace" \
      -l "kubernetes.io/service-name=$app_name" -o yaml

    如果灰度发布失败,停在当前阶段检查事件、日志、镜像、探针、网络策略和存储,不要切 DNS。真正切流应采用现有负载均衡或 DNS 的低风险机制,例如权重灰度、短 TTL 前置验证、可观测的分批流量,并保留 Swarm 原服务健康、未缩容、可立即回切。

    发生业务回归时,Kubernetes 侧的回滚与入口回切要分开处理:先停止新增流量,再确认回滚目标版本可用,最后让入口恢复到已知健康的一侧。不要先删除 PVC、Deployment 或 namespace;这些动作可能造成数据丢失或让排障证据消失。

    # 代码 27:只回滚 Deployment 版本并核验,不删除任何资源
    namespace="<命名空间>"
    app_name="<应用名>"
    
    kubectl rollout history -n "$namespace" "deployment/$app_name"
    kubectl rollout undo -n "$namespace" "deployment/$app_name" --to-revision=<已验证修订号>
    kubectl rollout status -n "$namespace" "deployment/$app_name" --timeout=<等待时长>
    kubectl get pod -n "$namespace" \
      -l "app.kubernetes.io/name=$app_name" -o wide

    rollout undo 只能回到 Kubernetes 仍保留的 Deployment revision,且不会回滚 ConfigMap、Secret、外部数据库、Ingress 控制器设置或 DNS。迁移计划应单独记录这些对象的版本和恢复方式;回滚成功的证据包括入口真实请求成功、后端 EndpointSlice Ready、错误率恢复以及 Swarm 或 K8s 中被选中的服务与预期一致。

    成功标准不是“Pod Running”,而是路由、数据、配置、资源与回滚都已被可观测地迁移。把六个坑在切换前逐项关闭,迁移才不会变成线上补设计。更多生产环境迁移与运维实践,欢迎来 云栈社区 一起交流。




    上一篇:C语言使用cJSON读写配置文件并实现交互式配置工具
    下一篇:Kimi K3 架构深度解析:从 GPT-2 到万亿参数的七年技术演进
    您需要登录后才可以回帖 登录 | 立即注册

    手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

    GMT+8, 2026-8-3 05:37 , Processed in 0.968646 second(s), 41 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2026 云栈社区.

    快速回复 返回顶部 返回列表