回滚迁移

在 Mesh v1 停用之前,每个阶段都可以撤销。本文档列出了每个阶段要执行的操作,以及超过不可逆点后的恢复方式。

何时回滚

满足以下任一条件,且无法在变更窗口内解释清楚时,请执行回滚:

  • 探针报告的故障不会自行停止。
  • 工作负载之间的调用因 TLS 错误而失败。
  • 当前阶段的某个验证步骤未通过。

按阶段回滚

阶段回滚方式条件
阶段 1 — 可观测性删除 Kiali 资源以及创建的监控资源,请参阅下文无
阶段 2 — 控制平面删除 Istio 和 IstioCNI 资源,以及多网络 mesh 的 Mesh v2 东西向网关,请参阅下文尚无命名空间指向 Mesh v2 修订版本
阶段 3 — 工作负载将命名空间标签改回原值并重启工作负载Mesh v1 仍在运行
阶段 4 — 网关重新扩容 Mesh v1 网关,并缩容 Mesh v2 网关Mesh v1 网关 deployment 仍然存在,请参阅下方的说明
阶段 5 — 停用从备份重新安装 Mesh v1灾难恢复,请参阅下文
阶段 6 — 最终化从备份恢复之前的 cacerts;删除 default 修订版本标签,请参阅下文Mesh v2 继续运行,且没有命名空间使用 istio-injection=enabled

阶段 6 的可观测性部分没有自己的回滚路径。Mesh v1 的 tracing 组件已在阶段 5 中删除,因此要恢复到这些组件,就意味着执行下面的恢复操作;截至该阶段之前的所有内容——新的 collector、Telemetry provider、Instrumentation 资源以及 Kiali tracing 配置——都应向前重新配置,而不是回滚。

跨越多个集群的 mesh 采用相同的方式回滚,每个集群执行一次,并加上多集群 mesh中所述的补充操作。

阶段 1

删除 Kiali 资源会移除 operator 为其创建的内容:deployment、service、ingress、service account 和配置。

Kiali Operator、kiali 和 kiali-monitoring-basic-auth secrets(位于 istio-system 中)以及该命名空间上的 cpaas.io/project 标签会保留。它们不被任何内容依赖,只会占用一个小型 deployment。重新执行阶段 1时会复用它们:此时两个 kubectl create secret 命令会报告 already exists,这是预期结果,无需修正。

在多集群 mesh 中,Kiali server 还会为每个其他集群保留一个 kiali-remote-cluster-secret-<cluster> secret,而该 secret 不得复用。删除远程集群的 Kiali 资源会删除该 secret 用于进行身份验证的 service account,重新执行阶段 1 时会创建一个带有新令牌的新 service account。此时 Kiali server 将完全无法启动:它会报告 failed to get server groups: the server has asked for the client to provide credentials,并且什么都不显示,甚至不会显示它自身运行所在的集群。请重新生成远程集群 secrets,参阅创建长期有效的 service account 令牌和创建远程集群 secret。如果要从多集群 mesh 中移除 Kiali,而不是重新执行相关操作,请按照清理 Kiali中的步骤操作,该步骤会在删除 server 之前注销远程集群。

阶段 2

kubectl delete istio default
kubectl delete istiocni default

Mesh v1 控制平面不会受到影响,也不会重启任何工作负载。如果希望继续监视 mesh,可以保留 Kiali。

多网络 mesh 还需要为每个集群移除一个资源。部署 Mesh v2 东西向网关在 Mesh v1 网关旁边添加了一个 istio-eastwestgateway-v2 deployment,而删除控制平面不会停止它。两代网关由同一个 service 选择,因此 Mesh v2 pod 会继续作为一个 mesh 的就绪 endpoint 运行,而该 mesh 已不再有 Mesh v2 控制平面;它会提供自己最后收到的配置:到达该 pod 的跨集群流量会被发送到它当时知道的 pod 地址,而连接到此后已被替换的地址时都会失败。系统不会报告此问题——具有 locality 故障转移功能的 mesh 会改为从本地集群响应,并显示 200——因此请在每个集群上将其移除:

kubectl -n istio-system delete deploy istio-eastwestgateway-v2
kubectl -n istio-system delete sa istio-eastwestgateway-v2
kubectl -n istio-system get endpoints istio-eastwestgateway

该 service 的 endpoints 会再次变为 Mesh v1 网关 pod。

如果稍后重新执行阶段 2,而不是继续使用 Mesh v1,则无需移除并重新创建网关:新的控制平面会以相同名称发布,pod 会自行重新连接到该控制平面,其 endpoints 也会再次正确。

阶段 3

kubectl label ns "${MESH_NAMESPACE}" istio.io/rev="${V1_REVISION}" --overwrite
kubectl -n "${MESH_NAMESPACE}" rollout restart "deploy/${WORKLOAD}"
kubectl -n "${MESH_NAMESPACE}" rollout status "deploy/${WORKLOAD}" --timeout=300s

迁移期间写入工作负载模板的 pod 设置同样适用于 Mesh v1——这些值正是 Mesh v1 自身使用的值——因此无需撤销其他内容。

命名空间标签适用于整个命名空间。回滚一个工作负载也意味着该命名空间中的所有其他工作负载在下次重启时都会返回 Mesh v1。

阶段 4

kubectl -n "${GATEWAY_NAMESPACE}" scale deploy "${GATEWAY_NAME}" --replicas="${GATEWAY_REPLICAS}"
kubectl -n "${GATEWAY_NAMESPACE}" rollout status "deploy/${GATEWAY_NAME}" --timeout=300s
kubectl -n "${GATEWAY_NAMESPACE}" scale deploy "${GATEWAY_NAME}-v2" --replicas=0

请等待 Mesh v1 网关就绪后,再缩容 Mesh v2 网关。两个 deployment 位于同一个 service 后面,如果同时进行扩容和缩容,该 service 将在 Mesh v1 pod 启动所需的整个时间内没有就绪 endpoint——在测试集群中约为九秒;每隔几秒运行一次的探针无法发现这一情况,但客户端可以发现。

GATEWAY_REPLICAS 是 Mesh v1 网关在 Mesh v2 网关扩容时记录的副本数,请参阅迁移网关。此后 Mesh v1 deployment 一直保持为零,因此无法从集群中读取该值;如果 shell 已退出,请从 ${BACKUP_DIR}/gateway-replicas.txt 中获取该值。

带有修订版本标签的命名空间中的网关

如果网关命名空间带有 istio.io/rev 标签,并且你已将其切换到 Mesh v2 修订版本,则无法再创建 Mesh v1 网关 pod:Mesh v2 中不存在其注入模板。此时扩容旧 deployment 会生成被拒绝的 pod。请先将命名空间标签切换回原值,然后再执行扩容。没有修订版本标签的命名空间中的网关不受影响。

阶段 6

使用准备迁移中的函数恢复 certificate authority,该函数会就地应用 secret:

restore_from_backup "${BACKUP_DIR}/cacerts.yaml"

不要删除 secret 后重新创建。istiod 会在短时间内处于未配置 certificate authority 的状态,这正是移交 certificate authority中警告的情况。约一分钟后,istiod 会报告它获取的证书;不会重启任何 pod,工作负载证书会在其正常有效期内使用恢复的中间证书重新签发。两个证书由同一个根证书签名,因此仍持有由另一个证书签发的证书的 pod 仍会被信任。

恢复的证书是 Mesh v1 签发的证书,其有效期也是 Mesh v1 设置的有效期——约几个月,而移交 certificate authority中替换它的证书有效期为十年。没有任何组件会续期该证书:负责此操作的是 Mesh v1 controller,请参阅检查中间证书的有效期。读取其过期日期,并决定在到期前采取的措施。

只有在没有命名空间使用该修订版本标签时,删除 default 才是安全的:

kubectl get ns -l istio-injection=enabled

预期输出:无。在多集群 mesh 中,请对每个集群运行此命令,并使用 --context 为每个集群命名:如果 No resources found 恰好针对 kubeconfig 当前选中的集群执行,其输出会与针对目标集群时的预期输出完全相同。通过采用默认修订版本标签从 istio.io/rev 移动到 istio-injection=enabled 的命名空间,在该标签消失后将不再被任何 webhook 认领。其 pod 会继续运行,也不会报告错误;下次重启时会静默生成不带 sidecar 的 pod。请先将此类命名空间重新添加回原标签:

kubectl label ns "${MESH_NAMESPACE}" istio.io/rev="${V2_REVISION}" istio-injection- --overwrite
kubectl delete istiorevisiontag default

阶段 5 及更晚阶段

Mesh v1 已不存在。恢复意味着重新安装它。使用准备迁移中的函数恢复每个资源:备份保存的是集群中该对象原本的状态,而在不做任何修改的情况下应用它,会重新创建指向已不存在资源的所有者引用,从而导致垃圾回收器删除你刚刚恢复的内容。

  1. 仅当阶段 5卸载了 Service Mesh Essentials plugin 时,才从 global 集群的 Marketplace → Cluster Plugins 安装该 plugin。如果平台仍提供另一个 Mesh v1 mesh,则该 plugin 会保持安装状态,global-asm-controller 和 Mesh v1 webhooks 也不会消失,因此整个步骤会成为空操作。检查你属于以下哪种情况:

    kubectl --context "${GLOBAL_CONTEXT}" get moduleinfoes.cluster.alauda.io -l cpaas.io/module-name=asm-global
    kubectl --context "${GLOBAL_CONTEXT}" -n cpaas-system get deploy global-asm-controller

    存在一个 ModuleInfo 且 controller 正在运行,表示该 plugin 仍已安装,可以继续执行步骤 2。否则请安装它:阶段 5 会卸载 plugin,但不会触及其 catalog entry,因此它通常仍会列在 Marketplace 中。如果未列出,请先应用 ${BACKUP_DIR}/global-asm-moduleplugin.yaml:该资源就是 catalog entry,随后安装操作会重新创建 ${BACKUP_DIR}/global-asm-moduleinfo.yaml。

  2. 恢复此 mesh 的 ServiceMesh 和 ServiceMeshGroup 资源,并等待 Mesh v1 控制平面运行。

    ${BACKUP_DIR}/global-servicemeshes.yaml 和 ${BACKUP_DIR}/global-servicemeshgroups.yaml 保存平台中的每个 mesh,而不仅是你的 mesh。完整应用它们还会写入其他 mesh 的资源,这些资源来自迁移开始前创建的快照,并静默撤销其中自那以后发生的任何更改。使用阶段 5删除 mesh 时所用的变量来选择你自己的 mesh:

    kubectl config use-context "${GLOBAL_CONTEXT}"
    kubectl create -f "${BACKUP_DIR}/global-servicemeshes.yaml" --dry-run=client -o json \
      | jq --arg names "${CLUSTER_NAMES}" 'select(.metadata.name as $n | ($names | split(" ")) | index($n))' \
      > "${BACKUP_DIR}/mesh-servicemeshes.json"
    kubectl create -f "${BACKUP_DIR}/global-servicemeshgroups.yaml" --dry-run=client -o json \
      | jq --arg group "${MESH_GROUP}" 'select(.metadata.name == $group)' \
      > "${BACKUP_DIR}/mesh-servicemeshgroups.json"
    restore_from_backup "${BACKUP_DIR}/mesh-servicemeshes.json"
    restore_from_backup "${BACKUP_DIR}/mesh-servicemeshgroups.json"

    restore_from_backup 应用于选定的上下文,而这两个资源位于 global 集群中,因此代码块会选中该集群。继续执行步骤 3 之前,请再次选择 mesh 集群。

    global-asm-controller 会根据这些资源重新创建 mesh 集群的 plugin 记录——即 asm 和 istio ModuleInfo 资源,这些资源已由阶段 5清除,且不属于备份内容——并安装其中记录的 Mesh v1 operators。如果某个 operator 未以此方式恢复,请从 ${BACKUP_DIR}/olm.yaml 恢复其 subscription,并使用其中记录的版本。不要完整应用该文件:它包含集群的每个 subscription 和每个 cluster service version,包括与 mesh 无关的那些内容。选择 Mesh v1 packages,即阶段 5删除的同四个 package:

    kubectl create -f "${BACKUP_DIR}/olm.yaml" --dry-run=client -o json \
      | jq 'select(.kind == "Subscription"
            and (.spec.name | test("^(asm-operator|flagger-operator|jaeger-operator|opentelemetry-operator)$")))' \
      > "${BACKUP_DIR}/v1-operator-subscriptions.json"
    restore_from_backup "${BACKUP_DIR}/v1-operator-subscriptions.json"

    带有 installPlanApproval: Manual 的 subscription 会等待其 install plan 获得批准后,才会安装任何内容:

    kubectl -n istio-system get installplans.operators.coreos.com -o json \
      | jq -r '.items[] | select(.spec.approved | not) | .metadata.name' \
      | while read -r plan; do
          kubectl -n istio-system patch installplans.operators.coreos.com "${plan}" \
            --type=merge -p '{"spec":{"approved":true}}'
        done
  3. 从 ${BACKUP_DIR}/asm-*.yaml 文件恢复平台资源——microservices、gateways、policies。省略 Mesh v1 为自身生成的内容;这些内容会由其 controllers 重新创建。

    gateway services 会阻碍 GatewayDeploy 资源。阶段 5保留了这些 service,因为删除它们会中断流量,而 Mesh v1 webhook 拒绝创建已有 service 的 gateway:admission webhook "vgatewaydeploys.kb.io" denied the request: Service "<gateway>" already exists。备份会将集群中的所有 gateway 保存到一个文件中,因此请先删除每个 gateway 的 service——如果恢复文件时仍有一个 service 被保留,就会创建该 gateway,而其余 gateway 会被拒绝:

    kubectl create -f "${BACKUP_DIR}/asm-gatewaydeploys.asm.alauda.io.yaml" --dry-run=client -o json \
      | jq -r '"\(.metadata.namespace) \(.metadata.name)"' \
      | while read -r ns name; do
          kubectl -n "${ns}" delete svc "${name}" --ignore-not-found
        done
    restore_from_backup "${BACKUP_DIR}/asm-gatewaydeploys.asm.alauda.io.yaml"

    Mesh v1 会同时重新创建每个 service 及其 deployment。读取它们:

    kubectl create -f "${BACKUP_DIR}/asm-gatewaydeploys.asm.alauda.io.yaml" --dry-run=client -o json \
      | jq -r '"\(.metadata.namespace) \(.metadata.name)"' \
      | while read -r ns name; do
          kubectl -n "${ns}" get svc "${name}" -o wide
        done

    node ports 属于 GatewayDeploy,因此访问 gateway 的地址不会改变。但 cluster IP 会改变:这是一个新的 service。从删除 service 的那一刻起,直到 Mesh v1 重新创建它之前,Ingress 都会被拒绝——如果两个命令连续执行,间隔为一秒半;如果中途停止,则会持续你停留的时间。在此期间 node ports 不属于任何对象,因此其他 service 可能会占用其中一个。继续之前,请将每个重新创建的 service 与其 ${BACKUP_DIR}/gateway-svc-<namespace>-<gateway>.yaml 进行比较。

  4. 从 ${BACKUP_DIR}/namespaces.yaml 恢复 namespace labels,并重启 workloads。

  5. 删除 Mesh v2 资源。首先检查 Mesh v1 gateways 是否已就绪:两个版本共用一个 service,如果 Mesh v1 gateway 尚未运行就删除 Mesh v2 gateway,会中断流量。

    要删除的内容创建于
    Istio、IstioCNI 和 istio-cni namespace阶段 2
    在多网络 mesh 中,每个集群的 istio-eastwestgateway-v2 deployment 及其 service account阶段 2
    为 Mesh v2 创建的 PodMonitor 和 ServiceMonitor阶段 1 和 阶段 5
    Kiali 及其 secrets;在多集群 mesh 中,按照清理 Kiali中的顺序执行阶段 1
    每个已迁移 namespace 的 access-logging Telemetry阶段 3
    每个 gateway 的 <gateway>-v2 deployment、其 service account,以及 <gateway>-v2-sds role 和 role binding;<gateway>-access-logging Telemetry阶段 4
    istio-system 中的 asm-default Telemetry阶段 5
    名为 default 的 IstioRevisionTag阶段 6
    Mesh v2 operator(如果之后不再重新安装 Mesh v2)阶段 2

    按此顺序逐项处理表格。operator 必须最后删除,因为 Istio、IstioCNI 和 IstioRevisionTag 带有只有 operator 才能清除的 finalizer:在它们之前删除 operator,会使它们永久停留在 Terminating 中,并带有永远不会返回的 kubectl delete;此时只能手动修改其 finalizers,才能释放该对象。

    重新安装的 Mesh v1 会带回自己的 monitors 和 mesh-wide Telemetry。保留两组 monitors 会使每个 proxy 被抓取两次。在两个 mesh-wide Telemetry 资源中,实际上只有一个生效——istio-system 中最早且没有 selector 的那个——因此 asm-default 会始终优先于 Mesh v1 重新创建的资源,并使 mesh 继续指向已不存在的 providers。

    遗留的 Mesh v2 gateway 是这些问题中最难察觉的一个。它的 pod 仍会被恢复的 gateway service 选中,并继续提供它上次收到的配置;即使其控制平面已经消失很久,仍会响应 200。从那一刻起,它不再跟随任何更改。步骤 7 会揭示这一问题。多网络 mesh 的 east-west gateway 最容易被遗忘,也最不易察觉地失效,参阅阶段 2。

  6. 如果阶段 6已经替换了 cacerts,请从 ${BACKUP_DIR}/cacerts.yaml 恢复它,如上文阶段 6所述。

  7. 使用探针进行验证,并检查每个 pod 是由哪个控制平面注入的:

    kubectl get pod -A -o json | jq -r '[.items[]
      | (.metadata.annotations["sidecar.istio.io/status"] // "") | fromjson? | .revision // empty]
      | group_by(.) | map({revision: .[0], pods: length})'

    只能保留 Mesh v1 revision。仍报告 Mesh v2 revision 的 pod,要么是在步骤 4 中未重启的 workload,要么是步骤 5 中的某个 Mesh v2 gateway——包括多网络 mesh 的 east-west gateway。

  8. 从为 external services 生成的资源中移除 asm.cpaas.io/user-managed annotation。阶段 4设置它是为了阻止 Mesh v1 controllers 管理这些资源;在该 annotation 存在期间,恢复的 Mesh v1 也不会管理它们,并且在 web console 中对 external service 所做的更改永远不会应用到 mesh。

    kubectl get gateways.networking.istio.io,virtualservices.networking.istio.io,destinationrules.networking.istio.io \
      -A -o json | jq -r '.items[]
        | select(.metadata.annotations["asm.cpaas.io/user-managed"] == "true")
        | "\(.kind) \(.metadata.namespace) \(.metadata.name)"' \
      | while read -r kind ns name; do
          kubectl -n "${ns}" annotate "$(echo "${kind}" | tr '[:upper:]' '[:lower:]').networking.istio.io" "${name}" \
            asm.cpaas.io/user-managed-
        done

Mesh v2 收集的 traces 和 metrics 会保留在其各自的 indices 中,不会由此操作恢复。

由于此路径很长,而且依赖迁移之前创建的备份,请先在测试环境中演练整个迁移流程,如迁移概览所要求。

多集群 mesh

上文的每个步骤都作用于一个集群,并且会在 mesh 的每个集群上重复执行。例外是恢复操作步骤中的 global 步骤——步骤 1 和步骤 2——它们一次涵盖整个 mesh,正如阶段 5中的对应步骤一样。

可以一次回滚一个集群,而 mesh 可以保持中间状态:第一个集群已恢复到 Mesh v1、第二个集群仍提供 Mesh v2 的 mesh,仍会保留其跨集群调用分布、远程端点和 mutual TLS。一个集群的两代版本都使用该集群的 cacerts,所有中间证书都由同一个根证书签名。

按照一次处理一个集群的要求,使用 --context 明确指定每个集群。阶段 6的前置条件最容易遗漏这一点:对错误集群执行 No resources found 得到的结果看起来与预期输出相同,而据此删除 revision tag 会导致你原本要处理的集群上某个 namespace 的 sidecar 被删除。

操作步骤中还增加了三项内容:

  • 多网络 mesh 的 Mesh v2 east-west gateway,请参阅阶段 2。在回滚阶段 2 时,以及恢复操作步骤的步骤 5 中,都要按集群将其移除。

  • Kiali 的远程集群 secrets,请参阅阶段 1。删除 Kiali 资源不会删除这些 secrets;重新执行阶段 1 前,必须再次生成它们。

  • mesh 的远程 secrets。每个控制平面都通过这些 secrets 读取其他集群的信息,并且两代版本都会通过 label 发现它们。删除 Mesh v1 控制平面不会删除这些 secrets,因此在停用后它们通常仍然存在——请确认,而不要假定它们存在:

    kubectl -n istio-system get secret -l istio/multiCluster=true

    每个其他集群对应一个 secret。使用 restore_from_backup "${BACKUP_DIR}/remote-secrets.yaml" 恢复缺失的 secret,并检查恢复的 Mesh v1 控制平面是否再次读取其他集群:

    istioctl remote-clusters --revision "${V1_REVISION}"

    每个集群都会以 synced 状态列出另一个集群。