迁移多网络网格

多网络网格中的每个集群都有自己的网络和东西向网关,跨集群流量会经过这些网关。本文档列出了迁移单集群网格的每个阶段新增的内容;此处未提及的所有内容都相同。

请先确认拓扑,参见确定网络拓扑。

下面的大多数代码块都作用于一个集群,并将其指定为 ${CTX}。请在第一个代码块之前设置它,并且每次切换到下一个集群时再次设置,参见一次指定一个集群:

export CTX="${CTX_CLUSTER1}"

每个阶段新增的内容

阶段新增内容
阶段 0记录每个集群的网格标识和跨集群端点,备份每个集群,并探测跨集群路径
阶段 1为多集群网格安装 Kiali
阶段 2为每个集群创建 Istio 资源,每个资源都有自己的 network 和集群名称
阶段 2(之后)在移动任何工作负载之前,为每个集群部署一个 Mesh v2 东西向网关
阶段 3逐个迁移集群,并在每个命名空间迁移后检查跨集群端点
阶段 5在删除 Mesh v1 控制平面之前分离东西向网关服务;global 中的步骤一次涵盖整个网格
阶段 6每个集群一个中间证书,以及整个网格一个 tracing 索引系列

阶段 0:记录并探测每个集群

针对每个集群运行为迁移做准备,并将 --context 设置为该集群;为每个集群保留一组变量。每个集群的网格标识都不同,必须分别记录:

for ctx in "${CTX_CLUSTER1}" "${CTX_CLUSTER2}"; do
  kubectl --context "${ctx}" -n istio-system get istiooperators.install.istio.io -l '!asm.cpaas.io/component' -o json \
    | jq -r --arg ctx "${ctx}" '.items[0] | "\($ctx) meshID=\(.spec.values.global.meshID) network=\(.spec.values.global.network) clusterName=\(.spec.values.global.multiCluster.clusterName)"'
  kubectl --context "${ctx}" -n istio-system get secret -l istio/multiCluster=true --no-headers | awk -v c="${ctx}" '{print c, "remote secret:", $1}'
done

每个集群上的 meshID 都相同;network 和 clusterName 不同。Mesh v2 必须为每个集群复现这三个值。

扩展设置流量探测中的流量探测,以覆盖跨集群路径:为每个集群提供一个网格内探测,该探测调用一个由两个集群中的工作负载共同支持的服务,即跨集群探测中指定的服务。两个方向都要进行探测,因为一个集群上的迁移步骤会改变另一个集群能够访问的内容。

在任何内容发生变化之前,使用针对每个集群运行的阶段 3 和阶段 4 的端点检查记录该路径解析到的内容:另一个集群的端点,以及这些端点上的 rq_success,构成后续阶段进行比较的基线。将这些记录与迁移笔记放在一起,而不要放在 BACKUP_DIR 中,因为每个集群上的该目录都不同。

请与记录的内容进行比较,而不是与平均分配进行比较:网格的流量策略决定分配方式,加权本地性分配、本地性故障转移,以及两者都没有的网格,其表现都不同。端点是始终能够提供信息的部分——无论流量是否使用它们,它们都存在,因此,即使本地性策略让每次调用都留在自己的集群中,端点仍能显示跨集群路径是否已编程。

阶段 1:面向多集群网格的 Kiali

Kiali 从一个存储中读取每个集群的指标,并需要对每个集群具有读取权限。请遵循在多集群网格中安装 Kiali,而不是单集群说明:Kiali server 在网格中的一个集群上运行,其他每个集群都会获得一个 Kiali 资源,该资源只生成远程集群资源。

它的先决条件正是迁移后的网格通常需要添加的内容:

  • 各集群向同一个监控存储报告。kubectl --context "${CTX}" get feature monitoring -o jsonpath='{.spec.accessInfo.database.address}' 在网格的每个集群上都返回相同的地址。
  • 每个集群都安装 Kiali Operator,包括不运行 Kiali server 的集群。
  • 每个集群上都存在 PodMonitor,并按照创建指标资源中的说明进行限制;ServiceMonitor 会在阶段 5中跟进。
  • 网格范围的 Telemetry 资源在每个运行控制平面的集群上都携带 mesh_id 标签覆盖。Kiali 根据该标签过滤网格的指标,因此报告 mesh_id="unknown" 的代理对它不可见。迁移中的网格无需在此处添加任何内容:每个集群的 Mesh v1 资源都携带该标签,并配置两代代理;阶段 5会在每个集群上将其替换为同样携带该标签的 asm-default。现在创建 asm-default 不会改变任何内容,参见创建指标资源。

Kiali 会在其日志中报告发现的内容:每个集群对应一行 Discovered cluster,每行都包含 Accessible=true。

阶段 2:每个集群一个控制平面

按照安装 Mesh v2 控制平面中的说明,在每个集群上创建 IstioCNI 和 Istio 资源,并使用为该集群记录的值:

spec:
  values:
    global:
      meshID: ${MESH_ID}
      network: ${NETWORK}
      multiCluster:
        clusterName: ${MESH_CLUSTER_NAME}

其他内容均不变:每个控制平面都会加载其所在集群的 cacerts secret,并且两个中间证书都由同一个根证书签名,因此工作负载在跨集群和跨代际的情况下仍会相互信任。

远程 secret 会被重复使用。验证每个 Mesh v2 控制平面都能看到另一个集群:

istioctl --context "${CTX_CLUSTER1}" remote-clusters --revision "${V2_REVISION}"
istioctl --context "${CTX_CLUSTER2}" remote-clusters --revision "${V2_REVISION}"

每个控制平面都会列出另一个集群,其状态为 synced。

部署 Mesh v2 东西向网关

跨集群流量会通过其所在集群的东西向网关到达工作负载。Mesh v2 代理会将流量发送到控制平面提供的网关地址,因此每个集群都需要一个 Mesh v2 东西向网关,然后才能迁移工作负载。

Mesh v1 东西向网关会继续并行运行:两代网关都会终止相同的基于 SNI 的透传,并且每个代理都会使用其自身控制平面播发的网关。

读取当前网关

kubectl --context "${CTX}" -n istio-system get svc istio-eastwestgateway -o json \
  | jq -c '{labels: .metadata.labels, selector: .spec.selector, type: .spec.type, ports: [.spec.ports[] | {name, port}]}'
kubectl --context "${CTX}" -n istio-system get gateways.networking.istio.io istio-eastwestgateway -o json \
  | jq -c '{selector: .spec.selector, servers: .spec.servers}'

记录 service 和 Gateway 资源的选择器,以及网络标签。Mesh v2 部署会携带这些选择器的并集,与南北向网关完全相同。

部署 Mesh v2 东西向网关

cat <<EOF | kubectl --context "${CTX}" apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: istio-eastwestgateway-v2
  namespace: istio-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: istio-eastwestgateway-v2
  namespace: istio-system
spec:
  replicas: 1
  selector:
    matchLabels:
      gateway.migration: istio-eastwestgateway-v2
  template:
    metadata:
      annotations:
        inject.istio.io/templates: gateway
      labels:
        gateway.migration: istio-eastwestgateway-v2
        sidecar.istio.io/inject: "true"
        istio.io/rev: ${V2_REVISION}
        app: istio-eastwestgateway
        istio: eastwestgateway
        topology.istio.io/network: ${NETWORK}
    spec:
      serviceAccountName: istio-eastwestgateway-v2
      containers:
        - name: istio-proxy
          image: auto
          env:
            - name: ISTIO_META_REQUESTED_NETWORK_VIEW
              value: ${NETWORK}
          securityContext:
            capabilities:
              drop:
                - ALL
            allowPrivilegeEscalation: false
            privileged: false
            readOnlyRootFilesystem: true
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
EOF
kubectl --context "${CTX}" -n istio-system rollout status deploy/istio-eastwestgateway-v2 --timeout=300s
  1. topology.istio.io/network 使此 pod 成为其网络的东西向网关。没有该标签,控制平面就不会向另一个集群播发此网关。
  2. ISTIO_META_REQUESTED_NETWORK_VIEW 将网关获得的端点限制为其自身网络中的端点,这与 Mesh v1 网关配置的值相同。没有该配置,网关还会获知另一个集群的端点,并将从那里到达的流量直接发回,参见部分跨集群请求失败并返回 503。
  3. 现有的 Service 会通过 app 和 istio 选择新的 pod,因此向另一个集群发布的网关地址不会发生变化。
  4. 由于网格保留 Mesh v1 的注入策略,因此需要 sidecar.istio.io/inject: "true"。

检查网关是否已编程

POD=$(kubectl --context "${CTX}" -n istio-system get pod -l gateway.migration=istio-eastwestgateway-v2 -o jsonpath='{.items[0].metadata.name}')
istioctl --context "${CTX}" proxy-config listener "${POD}" -n istio-system --port 15443 | tail -n +2 | wc -l
kubectl --context "${CTX}" -n istio-system get endpoints istio-eastwestgateway -o jsonpath='{.subsets[*].addresses[*].ip}{"\n"}'
kubectl --context "${CTX}" -n istio-system exec "${POD}" -c istio-proxy -- \
  pilot-agent request GET clusters | grep "${TARGET_SERVICE}" | grep cx_total

第一个数字统计该 pod 在 15443 端口上终止的 SNI 匹配项数量,每个网格跨集群发布的 service 和端口对应一个匹配项——在小型网格中通常有几十个;如果网关未按此方式编程,则为 0。随后,service 端点中会同时包含 Mesh v1 和 Mesh v2 网关 pod。

最后一个命令会列出网关将网格 service 解析到的端点。这些端点必须全部是其所在集群的 pod 地址。其中如果有一个地址的端口为 15443,则表示该地址是另一个集群的东西向网关,这意味着缺少网络视图,请求可能会被反弹回来。

阶段 3 和阶段 4:逐个迁移集群

按照单集群网格中的说明,迁移一个集群的工作负载和网关,然后转到下一个集群。探针会报告跨集群路径是否持续提供流量。每个命名空间迁移后,在两个集群上检查它是否仍已编程——一个集群上的迁移步骤会改变另一个集群所能到达的内容:

istioctl --context "${CTX}" proxy-config endpoint "deploy/${CLIENT}.${MESH_NAMESPACE}" \
  --cluster "outbound|${APP_PORT}||${TARGET_SERVICE}" -o json \
  | jq -r '.[0].hostStatuses[] | "\(.address.socketAddress.address):\(.address.socketAddress.portValue) \(.healthStatus.edsHealthStatus) rq_success=\(.stats[]? | select(.name=="rq_success") | .value // 0)"'

另一个集群的端点是其东西向网关在 15443 端口上的地址,并且处于正常状态。探针运行期间,其 rq_success 会持续增长,增长比例与阶段 0中记录的比例相同;如果本地计数器持续增长而另一个计数器停止,则表示调用不再离开该集群。如果端点是另一个集群的原始 pod 地址,则表示该控制平面的 network 值不正确。除标头外完全没有输出,则表示 TARGET_SERVICE 不是 service 的完全限定名称,请参见跨集群探针。

阶段 5:逐个停用集群

在 global 集群上执行一次以下步骤:删除网格中每个集群的 ServiceMesh 资源,以及将这些资源连接在一起的 ServiceMeshGroup。在准备迁移中设置的 CLUSTER_NAMES 和 MESH_GROUP 会将此次删除限制在此网格内。只有当平台上不再有其他 Mesh v1 网格时,才会卸载该插件。

每个网格集群上的步骤与单集群网格中的步骤相同,但有一项额外操作。东西向网关 service 属于 Mesh v1 控制平面资源——并非通过 owner reference,而是通过 operator 标签关联——因此会随该资源一起删除。请先在每个集群上将其解除关联:

kubectl --context "${CTX}" -n istio-system label svc istio-eastwestgateway \
  install.operator.istio.io/owning-resource- \
  install.operator.istio.io/owning-resource-namespace- \
  operator.istio.io/managed-
kubectl --context "${CTX}" -n istio-system get svc istio-eastwestgateway -o jsonpath='{.metadata.labels}{"\n"}'

其余标签仍必须包含 app、istio 和 topology.istio.io/network:Mesh v2 网关 pod 就是通过这些标签选择的。删除 Mesh v1 控制平面资源后,会移除 Mesh v1 网关 pod,同时保留 service、其地址以及后端的 Mesh v2 pod。

阶段 6:完成每个集群

  • 证书。对每个集群重复移交证书颁发机构,使用同一个根证书为每个中间证书签名,并在其 subject 中使用集群名称。当集群持有来自不同中间证书的证书时,跨集群 mutual TLS 仍可正常工作,因为根证书未发生变化。
  • 修订标签和宽限期。对于每个集群,按照单集群网格中的方式执行。
  • 追踪。整个网格使用一个索引系列:在每个集群上使用相同的索引前缀运行安装,以便在一个位置组装跨集群的追踪。作用于 Elasticsearch 的步骤——生命周期策略、索引初始化以及附加现有 Mesh v1 索引——针对整个网格运行一次,而不是每个集群运行一次。随后,每个集群保留自己的 collector 和自己的 Jaeger 实例,并且每个实例都提供整个网格的追踪。

将追踪报告保留在每个集群内

每个集群运行自己的 collector,且二者都使用相同的 service 名称 otel-collector.jaeger-system.svc.cluster.local 对外发布。若不做处理,控制平面会将它们合并为一组端点,每个 proxy 都会在两个端点之间对其 span 导出进行负载均衡。collector 没有 sidecar,因此没有网络身份,其端点不会被重写为东西向网关地址:proxy 会收到远程 collector 的原始 pod 地址,而该地址属于另一个网络,无法访问。两个端点都被报告为 HEALTHY——没有任何组件检查它们——因此,发送到远程端点的每次导出都会无声无息地丢失。追踪消失的程度取决于导出的分布方式:追踪看起来不完整,有些追踪会完全丢失。

将网格指向新的 collector会将 collector service 声明为仅限集群本地,从而丢弃另一个集群的端点。在每个运行控制平面的集群上执行此操作,然后检查是否只剩下一个端点:

istioctl --context "${CTX}" proxy-config endpoint "deploy/${WORKLOAD}.${MESH_NAMESPACE}" \
  --cluster "outbound|4317||otel-collector.jaeger-system.svc.cluster.local"

一个端点,位于本地集群中。这样,跨集群的追踪就会包含两侧的 span。

此设置会通过控制平面传播——Operator 会重新渲染网格配置,istiod 会重新加载该配置并将其推送到每个 proxy——因此,与修补操作同时运行的检查仍会列出两个 collector。等待几秒后再次运行。

此条目仅影响该主机。应用 service 的跨集群负载均衡以及它们使用的东西向网关路径均不变。

验证

  • istioctl --context "${CTX}" remote-clusters --revision "${V2_REVISION}" 在每个集群上报告 synced。
  • 每个集群的跨集群端点都是另一个集群的东西向网关,运行正常,并且其 rq_success 按照阶段 0 中记录的比例增长。
  • 依赖拓扑的流量策略——例如本地性负载均衡和异常点检测——其行为与迁移前一致。它们是 Istio 资源,不会因迁移而改变。
  • Kiali 显示每个集群的工作负载,跨集群的追踪包含两侧的 span。span 会将其生成所在的集群作为 istio.cluster_id 携带,这正是读取跨集群调用追踪时所依据的信息。
  • 每个集群上的东西向网关 service 保留其地址,其端点是 Mesh v2 网关 pod。

迁移后

Kiali 的流量图是整个已迁移网格的视图,而上述检查只涵盖探针调用的 service。打开 Traffic Graph,选择网格的命名空间,并启用 Display > Cluster Boxes:工作负载会按集群分组,每个跨集群调用都是框之间的一条边。确认边会在两个方向上绘制,并且没有任何 service 丢失其流量中跨集群的那一半,参见在多集群网格中安装 Kiali 的验证。

如果端点检查通过,但图中没有绘制连接两个集群的边,通常问题出在 Kiali,而不是网格:它从一个存储中读取两个集群的指标,并按 mesh_id 进行筛选;指标缺失或标记为 mesh_id="unknown" 的集群对它不可见,参见阶段 1:用于多集群网格的 Kiali。