准备迁移

迁移的阶段 0。Mesh 中不会发生任何变化:您需要检查集群是否就绪,记录 Mesh v1 的配置,备份后续阶段会涉及的所有资源,并启动用于确认应用流量是否持续流动的探针。

在安装 Mesh v2 控制平面之前,完成每个部分。

先决条件

  • Alauda Container Platform 4.2 或 4.3,并且每个运行 Mesh 工作负载的节点上都使用 Kubernetes 1.32 或更高版本。Istio 1.30 要求 Kubernetes 1.32。

  • Mesh v1 运行 Istio 1.22。下面的记录 Mesh v1 配置部分会对此进行检查。

  • 集群目录中提供 Alauda Service Mesh v2 Operator、Alauda Service Mesh Console(以前称为 Alauda Build of Kiali)Operator 和 Alauda Container Platform Application Observability(以前称为 Alauda Build of OpenTelemetry v2)Operator。暂时不要安装它们。

  • 如果应用使用 OpenTelemetry Java 自动插桩,则需要在集群可以拉取的 registry 中准备一个 OpenTelemetry Java agent 2.x 镜像。Mesh v1 自带 agent 镜像,而 Mesh v2 不提供该镜像,请参阅准备 Java agent 镜像。

  • 已安装 ACP Multi-Network(以前称为 ACP Networking for Multus)插件。IstioCNI 会将其配置写入 Multus 配置目录。

  • 在运行 Mesh 的集群以及 global 集群上使用 cluster-admin,并将这两个集群的 context 配置在同一个 kubeconfig 中。跨越多个集群的 Mesh 需要为每个集群配置一个 context,请参阅一次处理一个集群。

  • 在运行迁移的机器上安装以下工具:

    工具版本用途
    kubectl与集群匹配所有步骤
    istioctl1.30.x,请参阅 Istioctl 工具检查 Mesh v2 代理
    jq1.6 或更高版本读取和重写资源
    openssl1.1.1 或更高版本阶段 6中的证书颁发机构交接
  • 与应用所有者约定维护窗口。应用流量会持续流动,但工作负载和网关会重启,并且从阶段 5到阶段 6结束期间,tracing 会中断。

istioctl 和 Mesh v1

istioctl 1.30 会与 Mesh v2 控制平面通信。不要将其指向运行 Istio 1.22 的 Mesh v1 控制平面:两个版本相差太大。本节中针对 Mesh v1 的检查会改为读取 pod 的 sidecar.istio.io/status annotation。

设置变量

本部分中的每个页面都会使用这些变量。每个 shell 设置一次即可。将 <global-cluster-context> 和 <cluster-name> 替换为你自己的值。

umask 077
export GLOBAL_CONTEXT=<global-cluster-context>
export CLUSTER_NAME=<cluster-name>
export BACKUP_DIR=${HOME}/mesh-v1-migration-backup-${CLUSTER_NAME}
mkdir -p "${BACKUP_DIR}"
chmod 700 "${BACKUP_DIR}"

export MESH_GROUP=$(kubectl --context "${GLOBAL_CONTEXT}" -n cpaas-system get servicemeshs.asm.alauda.io \
  "${CLUSTER_NAME}" -o jsonpath='{.metadata.labels.asm\.cpaas\.io/meshgroup}')
export CLUSTER_NAMES=$(kubectl --context "${GLOBAL_CONTEXT}" -n cpaas-system get servicemeshgroups.asm.alauda.io \
  "${MESH_GROUP}" -o jsonpath='{.spec.clusters[*]}')
echo "mesh ${MESH_GROUP} spans: ${CLUSTER_NAMES}"

CLUSTER_NAME 是平台中集群的名称。它也是 global 集群上代表此集群的 ServiceMesh 资源的名称。MESH_GROUP 是此集群所属的网格,CLUSTER_NAMES 是该网格的每个集群——对于单集群网格,则是一个名称。阶段 5 使用这些变量,仅删除此网格的资源:一个平台可以承载多个网格。

下面写入的备份包含网格根证书颁发机构的私钥和每个网关证书的私钥。umask 077 和目录模式确保只有你可以读取这些私钥。在迁移 shell 的其余过程中保留 umask。

对于多集群网格,请为每个集群再次设置 CLUSTER_NAME 和 BACKUP_DIR。下面记录的所有内容都因集群而异。

记录 Mesh v1 配置

Mesh v2 必须使用与 Mesh v1 相同的网格身份和网格范围设置进行创建。现在记录这些内容:保存它们的资源会在阶段 5中删除。

查找控制平面

Mesh v1 网关也是 IstioOperator 资源。没有网关标签的资源就是控制平面。

export V1_CONTROL_PLANE_IOP=$(kubectl -n istio-system get istiooperators.install.istio.io \
  -l '!asm.cpaas.io/component' -o jsonpath='{.items[0].metadata.name}')
export V1_REVISION=$(kubectl -n istio-system get istiooperators.install.istio.io "${V1_CONTROL_PLANE_IOP}" \
  -o jsonpath='{.spec.revision}')
echo "${V1_CONTROL_PLANE_IOP} ${V1_REVISION}"

示例输出

istio-122 1-22

版本必须为 1-22。如果版本较低,请先将 Mesh v1 更新到 Istio 1.22,再进行迁移。

记录网格身份和注入策略

IOP_JSON=$(kubectl -n istio-system get istiooperators.install.istio.io "${V1_CONTROL_PLANE_IOP}" -o json)
export MESH_ID=$(jq -r '.spec.values.global.meshID // ""' <<<"${IOP_JSON}")
export NETWORK=$(jq -r '.spec.values.global.network // ""' <<<"${IOP_JSON}")
export MESH_CLUSTER_NAME=$(jq -r '.spec.values.global.multiCluster.clusterName // ""' <<<"${IOP_JSON}")
export V1_AUTO_INJECT=$(jq -r '.spec.values.global.proxy.autoInject // "enabled"' <<<"${IOP_JSON}")
export TRUST_DOMAIN=$(kubectl -n istio-system get configmap "istio-${V1_REVISION}" \
  -o jsonpath='{.data.mesh}' | awk '/^trustDomain:/{print $2}')
echo "meshID=${MESH_ID} network=${NETWORK} clusterName=${MESH_CLUSTER_NAME} trustDomain=${TRUST_DOMAIN} autoInject=${V1_AUTO_INJECT}"

示例输出

meshID=mesh-1 network=mesh-1 clusterName=cluster-1 trustDomain=cluster.local autoInject=disabled
  1. 即使对于单集群网格,也会设置 network,并且每个注入的 Pod 都带有匹配的 topology.istio.io/network 标签。Mesh v2 必须使用相同的值,否则两个控制平面会对端点所属的网络产生分歧,Mesh v1 Pod 与 Mesh v2 Pod 之间的流量将失败。
  2. autoInject 是网格的注入策略。使用 disabled 时,只有带有 sidecar.istio.io/inject: "true" 的 Pod 才会被注入,这正是 Mesh v1 让网格命名空间中未注入的工作负载保持未注入状态的方式。Mesh v2 必须使用相同的策略。

记录网格范围设置

kubectl -n istio-system get istiooperators.install.istio.io "${V1_CONTROL_PLANE_IOP}" -o json \
  | jq '{meshConfig: (.spec.meshConfig // {}),
         proxy: ((.spec.values.global.proxy // {}) | {includeIPRanges, excludeIPRanges, resources}),
         traceSampling: .spec.values.pilot.traceSampling}' \
  | tee "${BACKUP_DIR}/v1-mesh-settings.json"
export V1_TRACE_SAMPLING=$(jq -r '.traceSampling // 1' "${BACKUP_DIR}/v1-mesh-settings.json")
echo "traceSampling=${V1_TRACE_SAMPLING}"

这些设置会改变网格中每个代理的行为,例如对 ServiceEntry 主机的 DNS 捕获、网关处受信任代理的数量、连接排空以及跟踪采样率。阶段 2 会将这些设置复制到 Mesh v2 的 Istio 资源中。

traceSampling 是网格跟踪的请求百分比,默认 Mesh v1 安装中的值为 100。Istio 本身的默认值为 1,因此配置跟踪的每个阶段都会使用 ${V1_TRACE_SAMPLING},而不是固定值。

列出网格命名空间和注入的工作负载

for ns in $(kubectl get ns -l "istio.io/rev=${V1_REVISION}" -o jsonpath='{.items[*].metadata.name}'); do
  kubectl -n "${ns}" get pod -o json | jq -r --arg ns "${ns}" '
    ([.items[] | select(.metadata.annotations["sidecar.istio.io/status"] != null)] | length) as $injected
    | "\($ns) injected=\($injected) pods=\(.items | length)"'
done | tee "${BACKUP_DIR}/v1-mesh-namespaces.txt"

示例输出

app-all      injected=6 pods=6
app-partial  injected=4 pods=6
java-apps    injected=0 pods=4
  • injected 等于 pods:命名空间中的每个工作负载都在网格中。
  • injected 小于前者但不为零:这是混合命名空间。请一次迁移一个工作负载,参阅迁移工作负载。
  • injected 为零:命名空间带有修订版本标签,但没有网格工作负载,通常是仅使用 OpenTelemetry 自动插桩的命名空间。不要切换其标签;阶段 5会将其移除。

${BACKUP_DIR}/v1-mesh-namespaces.txt 保存这些行。阶段 3每次从中取出一个命名空间,而 injected 计数决定迁移方式:整个命名空间迁移、逐个工作负载迁移,或完全不迁移。请保留此文件:它记录的是所有内容发生变化前的列表;而在集群中,命名空间的修订版本标签会在其迁移的瞬间发生变化,随后上面的标签选择器将不再返回该命名空间。

网关在自己的命名空间中运行,该命名空间可能没有修订版本标签。迁移网关会列出这些网关。

统计 Mesh v1 注入的每个 Pod,无论其运行在哪个命名空间中。这是阶段 2进行比较的基准:

kubectl get pod -A -o json | jq -r --arg rev "${V1_REVISION}" '[.items[]
  | select((.metadata.annotations["sidecar.istio.io/status"] // "") | test("\"revision\":\"" + $rev + "\""))] | length' \
  | tee "${BACKUP_DIR}/v1-injected-pod-count.txt"

当网关在没有修订版本标签的命名空间中运行时,该数字会高于上面命名空间列表的总和,这是通常的布局:两个网关会增加两个 Pod。

记录跟踪后端

Mesh v1 的 Jaeger 资源会在阶段 5中删除。现在记录其 Elasticsearch 索引的索引前缀和保留期限;阶段 6需要这两项信息,以便继续查询现有跟踪数据。

kubectl -n istio-system get jaegers.jaegertracing.io -o json | jq -r '.items[]
  | "name=\(.metadata.name) prefix=\(.spec.storage.options["es.index-prefix"] // "") days=\(.spec.storage.esIndexCleaner.numberOfDays // "") url=\(.spec.storage.options["es.server-urls"] // "") credentials=\(.spec.storage.options["es.asm.cnamespace"] // "istio-system")/\(.spec.storage.options["es.asm.cname"] // "")"' \
  | tee "${BACKUP_DIR}/v1-jaeger.txt"

示例输出

name=jaeger-prod prefix=asm-mesh-mesh-1 days=7 url=https://elasticsearch.example.com/es_proxy credentials=istio-system/system-elasticsearch-basic-auth

Jaeger 资源不保存 Elasticsearch 用户名和密码,而是指向保存这些信息的 secret。阶段 6需要这些凭据,以便基于同一 Elasticsearch 集群部署新的后端;该 secret 位于 istio-system 中,与阶段 5会移除的 Mesh v1 组件相邻。请将其备份:

sed -n 's|.*credentials=\([^/ ]*\)/\([^ ]*\).*|\1 \2|p' "${BACKUP_DIR}/v1-jaeger.txt" \
  | while read -r ns name; do
      [ -z "${name}" ] && continue
      kubectl -n "${ns}" get secret "${name}" -o yaml > "${BACKUP_DIR}/v1-jaeger-es-credentials.yaml"
      echo "${ns}/${name} keys: $(kubectl -n "${ns}" get secret "${name}" -o jsonpath='{.data}' | jq -r 'keys | join(",")')"
    done

密钥是 ES_USERNAME 和 ES_PASSWORD,而不是 username 和 password。

记录插桩的工作负载

记录应用当前的插桩方式,以便 Mesh v1 移除后应用的遥测数据保持不变。阶段 6会从此文件中逐个工作负载恢复这些设置:

kubectl get instrumentations.opentelemetry.io -A -o yaml > "${BACKUP_DIR}/instrumentations.yaml"
kubectl get pod -A -o json | jq -r '.items[]
  | select(.metadata.annotations["instrumentation.opentelemetry.io/inject-java"] != null)
  | . as $p
  | ($p.metadata.labels["asm.cpaas.io/msname"] // "") as $ms
  | ([$p.metadata.ownerReferences[]? | select(.kind == "ReplicaSet") | .name] | first // $p.metadata.name) as $owner
  | ($owner | sub("-" + ($p.metadata.labels["pod-template-hash"] // "-") + "$"; "")) as $workload
  | (($p.metadata.annotations["instrumentation.opentelemetry.io/java-container-names"] // "")
      | split(",")[0]) as $annotated
  | ([$p.spec.containers[] | select(any((.env // [])[]; .name == "OTEL_SERVICE_NAME")) | .name] | first // "") as $withenv
  | (if $annotated != "" then $annotated else $withenv end) as $container
  | ([$p.spec.containers[] | select(.name == $container) | (.env // [])[]
      | select(.name == "OTEL_SERVICE_NAME") | .value] | first // "") as $raw
  | ($raw
      | gsub("\\$\\(ASM_MS_NAME\\)"; $ms)
      | gsub("\\$\\(ASM_POD_NAMESPACE\\)"; $p.metadata.namespace)) as $name
  | [$p.metadata.namespace, $workload, $container, $ms, $name]
  | map(if . == "" then "-" else . end) | join(" ")' \
  | sort -u | tee "${BACKUP_DIR}/v1-instrumented-pods.txt"

示例输出

app-all frontend frontend frontend-ms frontend-ms.app-all

共有五列:命名空间、工作负载、其应用容器、Mesh v1 微服务名称,以及应用报告的服务名称。阶段 6会根据其中一行设置工作负载的变量;到那时,集群中将无法再读取这些值。

Mesh v1 应用的服务名称由 Mesh v1 webhook 添加到 Pod 的变量构成,因此 Pod 的环境中显示的是 $(ASM_MS_NAME).$(ASM_POD_NAMESPACE),而不是实际值。该命令会将其解析为应用实际报告的名称 <microservice>.<namespace>,从 asm.cpaas.io/msname 标签中获取微服务名称,并从 instrumentation.opentelemetry.io/java-container-names 注解中获取应用容器名称;这两者都由同一个 webhook 写入。每个工作负载一行,而不是每个 Pod 一行。显示为 - 的列表示无法从 Pod 中确定该值;请在阶段 6之前手动填写。

记录 Mesh v1 webhook 添加到 Pod 的变量

同一批 webhook 会将微服务身份添加到其处理的每个 Pod 的应用容器中。这些变量不在工作负载模板中,因此 webhook 在阶段 5中消失后,这些变量也会消失:

kubectl get pod -A -o json | jq -r '.items[]
  | select(.metadata.annotations["sidecar.istio.io/status"] != null
           or .metadata.annotations["instrumentation.opentelemetry.io/inject-java"] != null)
  | . as $p | .spec.containers[] | select(.name != "istio-proxy")
  | [(.env // [])[] | select(.name | startswith("ASM_")) | .name] as $asm
  | select($asm | length > 0)
  | "\($p.metadata.namespace) \($p.metadata.name) \(.name) \($asm | join(","))"' \
  | tee "${BACKUP_DIR}/v1-injected-env.txt"

示例输出

app-all  frontend-7d9c6b8f2-abcde  frontend  ASM_MS_NAME,ASM_CLUSTER_NAME,ASM_CONTAINER_NAME,ASM_POD_NAMESPACE

OTEL_SERVICE_NAME 由 ASM_MS_NAME 和 ASM_POD_NAMESPACE 构成,迁移可观测性堆栈会将其作为字面值恢复。请询问应用所有者,应用本身是否读取这些变量中的任何一个。如果应用会读取这些变量,迁移后仍可继续运行,但读取到的值为空——类似 frontend-ms.app-all 的名称会变为 null.null——并且不会报告任何错误。

保存变量

这些值的来源资源会在阶段 5期间删除,而同一阶段的后续步骤仍会使用这些值。现在将它们写下来:

cat > "${BACKUP_DIR}/vars.env" <<EOF
export GLOBAL_CONTEXT='${GLOBAL_CONTEXT}' CLUSTER_NAME='${CLUSTER_NAME}' BACKUP_DIR='${BACKUP_DIR}'
export MESH_GROUP='${MESH_GROUP}' CLUSTER_NAMES='${CLUSTER_NAMES}'
export V1_CONTROL_PLANE_IOP='${V1_CONTROL_PLANE_IOP}' V1_REVISION='${V1_REVISION}'
export MESH_ID='${MESH_ID}' NETWORK='${NETWORK}' MESH_CLUSTER_NAME='${MESH_CLUSTER_NAME}'
export TRUST_DOMAIN='${TRUST_DOMAIN}' V1_AUTO_INJECT='${V1_AUTO_INJECT}' V1_TRACE_SAMPLING='${V1_TRACE_SAMPLING}'
EOF
cat "${BACKUP_DIR}/vars.env"
在每个新 shell 中加载文件

在新 shell 中运行 source "${BACKUP_DIR}/vars.env",而不是重新推导这些值。从阶段 5中段开始,ServiceMesh 资源和 Mesh v1 控制平面都已不存在,上面的命令会返回空值。此时不会发生任何失败:空名称的 kubectl delete 会删除一个不存在的对象,带有空值的标签选择器不会匹配任何命名空间,而清理步骤会静默跳过原本应执行的操作。

检查兼容性

审计 ServiceEntry 资源

Istio 1.30 会拒绝不含端口的 ServiceEntry,以及包含超过 256 个主机的此类资源。

kubectl get serviceentries.networking.istio.io -A -o json | jq -r '.items[]
  | select((.spec.ports == null) or ((.spec.ports | length) == 0) or ((.spec.hosts | length) > 256))
  | "\(.metadata.namespace)/\(.metadata.name)"'

预期输出:无。在继续之前,修复列出的每个资源。

审计 EnvoyFilter 资源

一旦工作负载运行在 Mesh v2 上,固定代理版本的 EnvoyFilter 将不再生效,因为此时数据平面使用的是 Istio 1.30。

kubectl get envoyfilters.networking.istio.io -A -o json | jq -r '.items[]
  | select([.. | objects | .proxyVersion? // empty] | length > 0)
  | "\(.metadata.namespace)/\(.metadata.name)"'

由 Mesh v1 提供而 Mesh v2 不提供的功能生成的资源(包括 API 指标和允许列表)会显示在这里。它们会随着阶段 5中的 Mesh v1 一起消失。对于其他每个资源,请检查是否仍需要该过滤器,并针对 Istio 1.30 重新构建。

查找 Mesh v2 不提供的功能

for kind in canarydeliveries globalratelimiters apiattributes isolatepods; do
  printf '%s: ' "${kind}"
  kubectl get "${kind}.asm.alauda.io" -A --no-headers 2>/dev/null | wc -l
done

这里统计的每个资源都会在移除 Mesh v1 后停止工作。在开始阶段 2之前,完成金丝雀发布、释放隔离的 Pod,并与应用所有者确定全局速率限制和 API 指标的替代方案。

检查中间证书的有效期

Mesh v1 会自动续期网格的中间证书,并在其控制器于阶段 5中被移除后停止执行此操作。阶段 6会使用长期有效的证书替换该证书。

kubectl -n istio-system get secret cacerts -o jsonpath='{.data.ca-cert\.pem}' \
  | base64 -d | openssl x509 -noout -subject -enddate

到期日期必须晚于计划迁移结束日期,其中包括阶段 6。

收集自定义 Istio 配置

Mesh v1 支持通过 Web 控制台和平台资源更改网格,其中一些设置最终会进入 Mesh v2 不会读取的位置:IstioOperator 资源、Mesh v1 的 ServiceMesh 资源,以及 Mesh v1 控制平面的注入模板。

kubectl -n istio-system get istiooperators.install.istio.io "${V1_CONTROL_PLANE_IOP}" -o yaml \
  > "${BACKUP_DIR}/v1-control-plane-iop.yaml"
kubectl --context "${GLOBAL_CONTEXT}" -n cpaas-system get servicemeshs.asm.alauda.io "${CLUSTER_NAME}" -o yaml \
  > "${BACKUP_DIR}/v1-servicemesh.yaml"

逐一检查这两个文件,并确定每个与默认值不同的设置在 Mesh v2 中的表达方式:

Mesh v1 中的设置在 Mesh v2 中的去向
控制平面资源的 spec.meshConfigIstio 资源的 spec.values.meshConfig,请参阅安装 Mesh v2 控制平面
spec.values.global.proxy — 资源、流量捕获、注入策略Istio 资源的 spec.values.global.proxy
每个微服务的 sidecar 设置工作负载模板中的注解,请参阅迁移工作负载
spec.components — 控制平面和网关的副本数、资源、调度控制平面的 spec.values.pilot,以及自行创建的网关部署
Mesh v1 定义的注入模板不可用。使用 Pod 注解复现工作负载所需的内容
Istio 资源 — Gateway、VirtualService、DestinationRule、Telemetry 以及其他资源保持不变,Mesh v2 会按原样读取这些资源

工作负载在 Mesh v2 上重启时,任何未迁移的内容都会静默恢复为 Istio 默认值。

查看 Istio 行为变化

数据平面将从 Istio 1.22 迁移到 Istio 1.30。请查看 Alauda Service Mesh v2.2 的升级说明,以及 1.23 至 1.30 期间上游发布的公告,了解影响流量策略的变更。

检查集群容量

在 阶段 2 和 阶段 5 之间,两个控制平面会同时运行,并且每个正在迁移的工作负载在滚动更新期间都会运行第二个 Pod。Pod 只能调度到未请求容量足以覆盖该 Pod 资源请求的节点上 — 节点有多繁忙并不重要。请检查请求的资源,而不是已使用的资源:

kubectl describe node | grep -E '^Name:|^  (cpu|memory) +[0-9]'

对于每个节点,第一个值是其上 Pod 的资源请求,后面是这些请求占该节点可分配资源的比例。第二个值是限制值。

迁移会按如下方式更改请求。以下值是 Alauda Container Platform 4.3 上测得的每个副本的默认值;Mesh v1 的值取决于 Mesh v1 的安装方式:

阶段变更命名空间CPU内存
1添加 Kiali Operatorkiali-operator+10m+64Mi
1添加 Kialiistio-system+100m+64Mi
2添加 Alauda Service Mesh v2 Operatorsail-operator+10m+64Mi
2添加 Mesh v2 istiodistio-system+500m+2Gi
2添加 istio-cni-node,每个节点上各一个istio-cni每个节点 +100m每个节点 +100Mi
3工作负载在滚动更新期间运行第二个 Pod,其中包括 sidecar工作负载的命名空间Pod 的请求Pod 的请求
4Mesh v2 网关,直到 Mesh v1 网关缩容至零网关的命名空间+100m+128Mi
5移除 Mesh v1 控制平面、operator 和控制器istio-system、cpaas-system约 −1050m约 −2.6Gi
5移除 Mesh v1 跟踪:OpenTelemetry v1 Operator 和 collector、Jaeger 1.x Operator、collector 和 queryistio-system、cpaas-system约 −700m约 −2.3Gi
6添加 Alauda Build of OpenTelemetry v2 Operatoropentelemetry-operator2+100m+64Mi
6添加 OpenTelemetry collector 和 Jaeger v2jaeger-system+250m+576Mi

资源请求在 阶段 2 和 阶段 5 之间达到峰值:在 Mesh v1 的基础上增加约 620m CPU 和 2.2Gi 内存,此外每个节点增加 100m 和 100Mi。仅 Mesh v2 istiod 就请求 2Gi — 是 Mesh v1 istiod 的四倍 — 当没有节点剩余这么多未请求内存时仍会保持 Pending。如果节点已满到这种程度,请在阶段 2 中设置 Istio 资源的 spec.values.pilot.resources。

实际使用量增长少得多。一个运行小型网格的三节点集群 — 三个命名空间、七个已注入工作负载、两个网关 — 在两个控制平面同时运行期间,相比仅运行 Mesh v1 时多使用约 400m CPU 和 800Mi 内存。

备份网格

阶段 5 会删除 Mesh v1 资源。您依赖的某些资源由这些资源所有,并会随其一起被垃圾回收 — 通常包括网关服务以及每个微服务和每个网关的遥测资源。下面的记录 Mesh v1 所有的资源会生成适用于您集群的列表;在阅读该列表之前,不要假设某个资源由 Mesh v1 所有,也不要假设它不属于 Mesh v1。网关证书通常不属于 Mesh v1,但它们存储在 istio-system 中,与 Mesh v1 组件相邻,因此也会在此处备份。

如果某个阶段必须回滚,则从备份中恢复。

备份 Istio 和 Mesh v1 资源

kubectl get ns -o yaml > "${BACKUP_DIR}/namespaces.yaml"
for crd in $(kubectl get crd -o name | sed 's|.*/||' | grep -E '\.istio\.io$'); do
  kubectl get "${crd}" -A -o yaml > "${BACKUP_DIR}/istio-${crd}.yaml"
done
for crd in $(kubectl get crd -o name | sed 's|.*/||' | grep -E '\.asm\.alauda\.io$'); do
  kubectl get "${crd}" -A -o yaml > "${BACKUP_DIR}/asm-${crd}.yaml"
done
kubectl get asms.operator.alauda.io,flaggers.operator.alauda.io -A -o yaml > "${BACKUP_DIR}/asm-operator-crs.yaml"
kubectl get opentelemetrycollectors.opentelemetry.io,instrumentations.opentelemetry.io -A -o yaml > "${BACKUP_DIR}/otel.yaml"
kubectl get jaegers.jaegertracing.io -A -o yaml > "${BACKUP_DIR}/jaeger.yaml"
kubectl get subscriptions.operators.coreos.com,clusterserviceversions.operators.coreos.com -A -o yaml > "${BACKUP_DIR}/olm.yaml"
kubectl -n istio-system get secret cacerts -o yaml > "${BACKUP_DIR}/cacerts.yaml"
kubectl -n istio-system get secret -l istio/multiCluster=true -o yaml > "${BACKUP_DIR}/remote-secrets.yaml"

遍历自定义资源定义会涵盖每个 Istio API 组,包括 telemetry.istio.io。手动列出资源类型会遗漏阶段 5进行垃圾回收的遥测资源。

备份网关服务和证书

kubectl get gatewaydeploys.asm.alauda.io -A -o json | jq -r '.items[] | "\(.metadata.namespace) \(.metadata.name)"' \
  | while read -r ns name; do
      kubectl -n "${ns}" get svc "${name}" -o yaml > "${BACKUP_DIR}/gateway-svc-${ns}-${name}.yaml"
    done
kubectl get gateways.networking.istio.io -A -o json \
  | jq -r '.items[].spec.servers[]? | .tls.credentialName // empty' | sort -u \
  | while read -r secret; do
      kubectl get secret -A --field-selector "metadata.name=${secret}" -o yaml > "${BACKUP_DIR}/gateway-tls-${secret}.yaml"
    done
无法重新创建网关证书

网关证书的私钥仅存在于其 secret 中。如果 secret 丢失,则必须重新签发证书。在迁移完成并通过验证之前,请保留此备份。

记录 Mesh v1 所有的资源

for kind in $(kubectl get crd -o name | sed 's|.*/||' | grep -E '\.istio\.io$') secrets services; do
  kubectl get "${kind}" -A -o json 2>/dev/null | jq -r --arg k "${kind}" '.items[]
    | select(any(.metadata.ownerReferences[]?; .apiVersion | test("asm\\.alauda\\.io")))
    | "\($k) \(.metadata.namespace) \(.metadata.name) \(.metadata.ownerReferences[0].kind)/\(.metadata.ownerReferences[0].name)"'
done | tee "${BACKUP_DIR}/asm-owned-resources.txt"
while read -r kind ns name owner; do
  kubectl -n "${ns}" get "${kind}" "${name}" -o yaml > "${BACKUP_DIR}/owned-${kind}-${ns}-${name}.yaml"
done < "${BACKUP_DIR}/asm-owned-resources.txt"

示例输出

envoyfilters.networking.istio.io  app-all  frontend-1.22  ApiAttribute/frontend
telemetries.telemetry.istio.io    app-all  frontend       MicroService/frontend
telemetries.telemetry.istio.io    gw       ingress-1      GatewayDeploy/ingress-1
services                          gw       ingress-1      GatewayDeploy/ingress-1

阶段 5 中,Kubernetes 会在每个资源的所有者被删除时删除列表中的该资源。停用 Mesh v1 会从您保留的资源中移除所有者引用 — 且仅从这些资源中移除:此处列出的大多数内容是 Mesh v1 配置,阶段 3 和阶段 4 会用各自的等效配置替换这些内容。

Mesh v1 生成但由其他对象所有的资源 — 例如,由外部服务的 ServiceEntry 所有的 DestinationRule — 不会出现在此列表中,也不会在阶段 5中被删除。也请列出这些资源,以便您了解哪些资源会在没有所有者维护的情况下遗留:

for crd in $(kubectl get crd -o name | sed 's|.*/||' | grep -E '\.istio\.io$'); do
  kubectl get "${crd}" -A -l 'asm.cpaas.io/creator=asm-controller' -o json 2>/dev/null | jq -r --arg k "${crd}" '.items[]
    | "\($k) \(.metadata.namespace) \(.metadata.name) \(.metadata.ownerReferences[0].kind // "-")/\(.metadata.ownerReferences[0].name // "-")"'
done | tee "${BACKUP_DIR}/asm-generated-resources.txt"

备份 global 集群资源

kubectl --context "${GLOBAL_CONTEXT}" -n cpaas-system get servicemeshs.asm.alauda.io -o yaml > "${BACKUP_DIR}/global-servicemeshes.yaml"
kubectl --context "${GLOBAL_CONTEXT}" -n cpaas-system get servicemeshgroups.asm.alauda.io -o yaml > "${BACKUP_DIR}/global-servicemeshgroups.yaml"
kubectl --context "${GLOBAL_CONTEXT}" -n cpaas-system get secret asm-istio-rootca -o yaml > "${BACKUP_DIR}/global-asm-istio-rootca.yaml"
kubectl --context "${GLOBAL_CONTEXT}" get moduleplugins.cluster.alauda.io asm-global -o yaml \
  > "${BACKUP_DIR}/global-asm-moduleplugin.yaml"
kubectl --context "${GLOBAL_CONTEXT}" get moduleinfoes.cluster.alauda.io -l cpaas.io/module-name=asm-global -o yaml \
  > "${BACKUP_DIR}/global-asm-moduleinfo.yaml"
kubectl --context "${GLOBAL_CONTEXT}" get \
  mutatingwebhookconfiguration/servicemesh-v1alpha1-mutation \
  mutatingwebhookconfiguration/servicemeshgroup-v1alpha1-mutation \
  validatingwebhookconfiguration/global-asm-controller-validating-webhook-configuration \
  -o yaml > "${BACKUP_DIR}/global-webhooks.yaml"

只有当 global 集群继续为另一个 Mesh v1 网格提供服务时,才会再次需要 webhook 配置,参见停用 Mesh v1。

asm-global 是 Alauda Service Mesh Essentials 集群插件。ModulePlugin 是该插件在平台插件目录中的条目,ModuleInfo 是记录该插件已安装在 global 集群上的条目。回滚迁移会根据这两个文件重新安装插件。

此备份中包含根证书颁发机构

asm-istio-rootca 包含网格根证书颁发机构的私钥。阶段 6 会使用该私钥签发新的中间证书。请将备份存储在保存私钥的位置,而不要存储在集群上。

从备份恢复资源

上述每个文件都是集群保存该资源时的完整资源,其中包括 Mesh v1 设置的所有者引用。在所有者消失后应用此类文件会重新创建资源,而垃圾回收器会在几秒内再次删除它,因为该资源引用了一个不再存在的所有者。请改用此函数进行恢复,该函数会移除所有者引用以及属于已删除对象的字段:

restore_from_backup() {
  kubectl create -f "$1" --dry-run=client -o json \
    | jq 'def clean: del(.metadata.ownerReferences, .metadata.uid, .metadata.resourceVersion,
                         .metadata.creationTimestamp, .metadata.generation, .metadata.managedFields,
                         .metadata.annotations["kubectl.kubernetes.io/last-applied-configuration"], .status);
          if .kind == "List" then .items |= map(clean) else clean end' \
    | kubectl apply -f -
}

此函数接收一个包含单个资源的文件和一个包含资源列表的文件。如果 API server 因集群 IP 在此期间已被重新分配而拒绝恢复的 Service,请同时移除 spec.clusterIP 和 spec.clusterIPs,然后再次应用。

冻结更改

在迁移期间:

  • 不要通过 Web 控制台更改网格:不要添加新的微服务或网关,也不要更改策略。
  • 不要批准 Mesh v1 operator 的更新。
  • 告知应用所有者,工作负载会分阶段重启。

设置流量探针

每个阶段都会通过持续流量进行验证。运行覆盖网格所服务的每条路径的探针,并保留带时间戳的输出:

  • 网格内两个工作负载之间的一次调用;
  • 每个入口网关路由各发送一个请求,包括每个 TLS 主机;
  • 每个出口路由各发送一个请求;
  • 对于多集群网格,每个集群对之间进行一次调用。

探针循环可以简单到如下所示:

while true; do
  ts=$(date -u +%FT%TZ)
  for target in "${PROBE_TARGETS[@]}"; do
    code=$(curl -sk -o /dev/null -w '%{http_code}' --max-time 5 "${target}")
    case "${code}" in
      2*|3*) echo "${ts} ${target} ${code} OK" ;;
      *)     echo "${ts} ${target} ${code:-000} FAIL" ;;
    esac
  done
  sleep 1
done

将 PROBE_TARGETS 设置为你自己的 URL。替换 <ingress-gateway-host>、<ingress-gateway-tls-host> 和 <path>:

export PROBE_TARGETS=(
  "http://<ingress-gateway-host>/<path>"
  "https://<ingress-gateway-tls-host>/<path>"
)

入口路由可从你的计算机访问。其他两条路径——两个工作负载之间的调用和一条出口路由——无法从外部访问:它们只存在于网格内部,必须从 pod 中进行探测。使用最后迁移的工作负载,这样它自身的重启不会显示为中断。将 <namespace>、<workload>、<application-container>、<service>、<port> 和 <path> 替换为你自己的值:

export PROBE_NAMESPACE=<namespace> PROBE_WORKLOAD=<workload> PROBE_CONTAINER=<application-container>
export IN_MESH_TARGET=http://<service>.<namespace>:<port>/<path>
while true; do
  ts=$(date -u +%FT%TZ)
  out=$(kubectl -n "${PROBE_NAMESPACE}" exec "deploy/${PROBE_WORKLOAD}" -c "${PROBE_CONTAINER}" -- \
    curl -s -o /dev/null -w '%{http_code}' --max-time 5 "${IN_MESH_TARGET}" 2>&1)
  code=${out%%[!0-9]*}
  case "${code}" in
    2??|3??)         echo "${ts} ${IN_MESH_TARGET} ${code} OK" ;;
    [0-9][0-9][0-9]) echo "${ts} ${IN_MESH_TARGET} ${code} FAIL" ;;
    *)               echo "${ts} ${IN_MESH_TARGET} PROBE-ERROR ${out}" ;;
  esac
  sleep 1
done
从何处运行探针

从网格外部或最后迁移的命名空间运行探针。在正在重启的工作负载内运行的探针会将该工作负载自身的重启报告为中断。

必须以 HTTPS 的形式探测 HTTPS 路由。无法加载其证书的网关仍会继续响应其 HTTP 路由,只会在其 HTTPS 端口上重置 TLS 握手,因此仅使用 HTTP 的探针无法检测到此故障。

必须针对网关检查出口探针,而不能只检查其状态码。使用默认的 outboundTrafficPolicy: ALLOW_ANY 时,向解析到集群外部的主机发送请求,如果出口路由发生故障,请求会通过直通集群离开,并且仍会返回 200。在迁移前以及每个涉及网关的阶段之后各确认一次,确保请求确实经过出口网关——网关 pod 的访问日志会指出所使用的路由,而 istioctl proxy-config 会显示代理选择了哪个集群。使用 curl --resolve <host>:<port>:<address> 将主机名强制指向其 ServiceEntry 的地址,可使探针保持在预期路径上。

验证

  • echo "${MESH_ID} ${NETWORK} ${MESH_CLUSTER_NAME} ${TRUST_DOMAIN} ${V1_AUTO_INJECT}" 输出网格身份和注入策略。
  • cat "${BACKUP_DIR}/vars.env" 显示后续阶段所需的每个变量,且没有空值。
  • cat "${BACKUP_DIR}/v1-mesh-namespaces.txt" 列出网格中的每个命名空间及其注入 pod 数量。
  • ls "${BACKUP_DIR}" 列出 Istio 资源、Mesh v1 资源、网关服务和证书、cacerts secret 以及 global 集群资源的备份。
  • cat "${BACKUP_DIR}/v1-jaeger.txt" 显示跟踪后端的索引前缀、保留期限和凭据 secret,${BACKUP_DIR}/v1-jaeger-es-credentials.yaml 保存该 secret。
  • 对每个目标,探针均报告 OK,且没有 PROBE-ERROR 行。
  • 兼容性审计不会返回任何意外资源。

继续执行迁移单集群网格;如果网格跨越多个集群,则继续执行迁移多集群网格。