迁移网关
迁移的阶段 4。对于每个 Mesh v1 网关,在其旁边部署一个 Mesh v2 网关,将流量切换过去,然后将 Mesh v1 网关缩容为零。Gateway 和 VirtualService 资源不会更改:新部署会被与旧部署相同的标签选中。
请先迁移网关后面的工作负载,参见迁移工作负载。
一次迁移一个网关
此页面中的所有内容均限定于 GATEWAY_NAMESPACE 和 GATEWAY_NAME。针对一个网关运行完整个页面的步骤——收集其配置、在其旁边部署 Mesh v2 网关、切换流量并进行验证——然后再将这两个变量设置为下一个网关并重新开始。
收集网关配置
列出 Mesh v1 网关
将 <gateway-namespace> 和 <gateway-name> 替换为第一个命令列出的某个网关。
kubectl get gatewaydeploys.asm.alauda.io -A -o json | jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name)"'
export GATEWAY_NAMESPACE=<gateway-namespace>
export GATEWAY_NAME=<gateway-name>
检查控制平面自身的网关
上述列表涵盖平台管理的网关。Mesh v1 控制平面会在 istio-system 中部署自己的 istio-ingressgateway,而多网络网格还会在其旁边添加 istio-eastwestgateway。二者都不是 GatewayDeploy 资源,因此不会显示在上述列表中,本页面的任何步骤也不会涉及它们。阶段 5 会将它们与控制平面一同删除。
export CP_GATEWAYS=$(kubectl -n istio-system get deploy -l "istio.io/rev=${V1_REVISION}" -o json | jq -c '[.items[]
| select((.metadata.labels["operator.istio.io/component"] // "") | test("Gateways$"))
| {name: .metadata.name, labels: (.spec.template.metadata.labels // {})}]')
jq -r '.[] | "\(.name) \(.labels | tojson)"' <<<"${CP_GATEWAYS}"
echo "--- routed through them:"
kubectl get gateways.networking.istio.io -A -o json | jq -r --argjson d "${CP_GATEWAYS}" '
.items[] as $gw
| ($gw.spec.selector // {}) as $sel
| select(($sel | length) > 0)
| $d[] as $dep
| select($sel | to_entries | all(.value == $dep.labels[.key]))
| "\($dep.name) <- \($gw.metadata.namespace)/\($gw.metadata.name)"'
示例输出
istio-ingressgateway {"app":"istio-ingressgateway","istio":"ingressgateway","istio.io/rev":"1-22",...}
--- routed through them:
routed through them 下没有任何内容意味着这些网关不承载任何路由,在阶段 5中删除它们不会造成任何影响。如果其中列出了 Gateway,则表示它承载的流量尚未由本页面中的任何步骤迁移:请按照以下步骤为其部署 Mesh v2 网关,并使用其部署所打印的标签作为选择器标签。
在多网络网格中,istio-eastwestgateway 会显示在此处,并通过路由访问。它不会在本页面中迁移:迁移多网络网格 会在阶段 2中部署其 Mesh v2 替代项,然后再移动任何工作负载。
读取选择器、服务和证书
Mesh v2 网关 Pod 必须携带当前用于选择 Mesh v1 网关的所有标签:网关 Service 的选择器,以及此网关所服务的每个 Gateway 资源的选择器。
echo "service selector:"
kubectl -n "${GATEWAY_NAMESPACE}" get svc "${GATEWAY_NAME}" -o jsonpath='{.spec.selector}{"\n"}'
echo "service owner:"
kubectl -n "${GATEWAY_NAMESPACE}" get svc "${GATEWAY_NAME}" -o jsonpath='{.metadata.ownerReferences}{"\n"}'
echo "gateway selectors and credentials:"
kubectl get gateways.networking.istio.io -A -o json | jq -r '.items[]
| "\(.metadata.namespace)/\(.metadata.name) selector=\(.spec.selector | tojson) credentials=\([.spec.servers[]?.tls.credentialName // empty] | join(","))"'
echo "namespace revision label:"
kubectl get ns "${GATEWAY_NAMESPACE}" -o jsonpath='{.metadata.labels.istio\.io/rev}{"\n"}'
示例输出
service selector:
{"app":"ingress-1"}
service owner:
[{"apiVersion":"asm.alauda.io/v1alpha1","kind":"GatewayDeploy","name":"ingress-1",...}]
gateway selectors and credentials:
gw/http-gw selector={"app":"ingress-1","istio":"gw-ingress-1"} credentials=
gw/https-gw selector={"app":"ingress-1","istio":"gw-ingress-1"} credentials=example-com-tls
namespace revision label:
记录以下三项:
- 选择器标签的并集——示例中的
app: ingress-1 和 istio: gw-ingress-1。仅携带 Service 选择器的 Mesh v2 网关能够接收流量,但没有监听器,所有请求都会返回 404。
- 网关
Service 是否由 GatewayDeploy 资源拥有。通常是,这意味着除非移除所有者引用,否则 Kubernetes 会在阶段 5中随 Mesh v1 资源一同删除该服务。
- 网关命名空间是否带有
istio.io/rev 标签。后续步骤会有所不同。
选择 Mesh v2 网关的注入方式
kubectl label ns "${GATEWAY_NAMESPACE}" istio.io/rev="${V2_REVISION}" --overwrite
切换命名空间标签是单向操作
仅当网关命名空间已经带有版本标签时才运行此命令。Mesh v1 使用 Mesh v2 中不存在的模板(asm-gateway)注入其网关,因此一旦命名空间指向 Mesh v2 版本,就无法再创建 Mesh v1 网关 Pod。现有 Pod 会继续运行,但此时回滚意味着切换标签,而不是将旧网关扩容回来。切换标签后,立即部署并验证 Mesh v2 网关。当网关与 mesh 工作负载共用命名空间时,阶段 3已经切换了标签,网关正处于此状态:请在迁移这些工作负载后立即迁移网关。
部署 Mesh v2 网关
新网关就绪后会立即接收流量
Mesh v2 网关携带 Mesh v1 网关的选择器标签,因此现有的 Service 会在新 Pod 就绪后立即开始在两个网关之间进行负载均衡。在创建部署之前完成以下证书和权限步骤,并在此之后立即运行验证。若要随时将所有流量发回 Mesh v1 网关,请将 Mesh v2 部署缩容到零。
将 TLS 证书复制到网关命名空间
Mesh v1 首先在网关命名空间中解析 credentialName,然后回退到 istio-system。Mesh v2 运行上游 Istio,只会在网关工作负载所在的命名空间中查找。通过 Web 控制台创建的证书存储在 istio-system 中,因此请复制此网关的 Gateway 所引用的每个证书:
for secret in $(kubectl get gateways.networking.istio.io -A -o json \
| jq -r --arg sel "${GATEWAY_NAME}" '.items[] | select(.spec.selector | tostring | contains($sel))
| .spec.servers[]?.tls.credentialName // empty' | sort -u); do
src_ns=$(kubectl get secret -A --field-selector "metadata.name=${secret}" \
-o jsonpath='{range .items[*]}{.metadata.namespace}{"\n"}{end}' \
| awk -v gw="${GATEWAY_NAMESPACE}" '$0==gw{print; found=1; exit}
$0=="istio-system"{is=1}
END{if (!found && is) print "istio-system"}')
echo "${secret}: ${src_ns:-NOT FOUND} -> ${GATEWAY_NAMESPACE}"
[ -z "${src_ns}" ] && continue
[ "${src_ns}" = "${GATEWAY_NAMESPACE}" ] && continue
kubectl -n "${src_ns}" get secret "${secret}" -o json \
| jq 'del(.metadata.namespace, .metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp,
.metadata.ownerReferences, .metadata.managedFields, .metadata.annotations["kubectl.kubernetes.io/last-applied-configuration"])' \
| kubectl -n "${GATEWAY_NAMESPACE}" apply -f -
done
查找遵循 Mesh v1 的解析顺序:首先查找网关命名空间,其次查找 istio-system。第三个命名空间中同名的证书是不同的证书,不会被复制——网关会向客户端提供该证书,且不会有任何检查报告问题,因为错误证书与正确证书一样是 ACTIVE。
保留原始 secret:Mesh v1 网关在阶段 5之前仍会使用它。
创建服务账户和 SDS 权限
istiod 只会向服务账户可以读取其命名空间中 secret 的网关发送证书。没有此角色时,证书会保持在 WARMING 状态:网关会继续响应其 HTTP 路由,但其 HTTPS 端口上的每次 TLS 握手都会被重置。
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${GATEWAY_NAME}-v2
namespace: ${GATEWAY_NAMESPACE}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ${GATEWAY_NAME}-v2-sds
namespace: ${GATEWAY_NAMESPACE}
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ${GATEWAY_NAME}-v2-sds
namespace: ${GATEWAY_NAMESPACE}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ${GATEWAY_NAME}-v2-sds
subjects:
- kind: ServiceAccount
name: ${GATEWAY_NAME}-v2
namespace: ${GATEWAY_NAMESPACE}
EOF
部署网关
下面的部署是标准的网关注入部署。它携带 Mesh v1 网关的选择器标签,使现有的 Service 和 Gateway 资源能够选择它;此外还带有一个专属标签,以便分别对两个版本进行扩缩容。
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${GATEWAY_NAME}-v2
namespace: ${GATEWAY_NAMESPACE}
spec:
replicas: 1
selector:
matchLabels:
gateway.migration: ${GATEWAY_NAME}-v2
template:
metadata:
annotations:
inject.istio.io/templates: gateway
labels:
gateway.migration: ${GATEWAY_NAME}-v2
sidecar.istio.io/inject: "true"
istio.io/rev: ${V2_REVISION}
app: ${GATEWAY_NAME}
istio: ${GATEWAY_NAMESPACE}-${GATEWAY_NAME}
spec:
serviceAccountName: ${GATEWAY_NAME}-v2
containers:
- name: istio-proxy
image: auto
securityContext:
capabilities:
drop:
- ALL
allowPrivilegeEscalation: false
privileged: false
readOnlyRootFilesystem: true
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
EOF
kubectl -n "${GATEWAY_NAMESPACE}" rollout status "deploy/${GATEWAY_NAME}-v2" --timeout=300s
inject.istio.io/templates: gateway 将注入的 proxy 设置为网关而不是 sidecar,而 image: auto 允许控制平面填充 proxy 镜像。
sidecar.istio.io/inject: "true" 是必需的,因为 mesh 保留了 Mesh v1 的注入策略:只有 Pod 主动选择加入时,才会为其注入。
istio.io/rev 选择 Mesh v2 控制平面。当命名空间已经带有版本标签时,请移除此标签。
- 将最后两个标签替换为所收集选择器的并集:网关
Service 使用的每个标签,以及 Gateway 资源使用的每个标签。此示例中的值遵循 Mesh v1 的命名方式,即 app: <gateway> 和 istio: <namespace>-<gateway>。
securityContext 满足 Restricted Pod Security Admission 配置文件的要求。通过注入安装的网关是你自己的部署,因此这里设置此项,而不是在 Istio 资源上设置。请参阅 Pod Security Admission。
重新创建访问日志配置
为 Mesh v1 网关启用访问日志的 Telemetry 资源会根据只有 Mesh v1 设置的标签选择 Pod,并会在阶段 5中随其所有者一同删除。请创建一个与 Mesh v2 网关匹配的等效资源:
cat <<EOF | kubectl apply -f -
apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata:
name: ${GATEWAY_NAME}-access-logging
namespace: ${GATEWAY_NAMESPACE}
spec:
selector:
matchLabels:
app: ${GATEWAY_NAME}
accessLogging:
- providers:
- name: envoy
EOF
验证 Mesh v2 网关
POD=$(kubectl -n "${GATEWAY_NAMESPACE}" get pod -l "gateway.migration=${GATEWAY_NAME}-v2" -o jsonpath='{.items[0].metadata.name}')
istioctl proxy-config listener "${POD}" -n "${GATEWAY_NAMESPACE}"
istioctl proxy-config secret "${POD}" -n "${GATEWAY_NAMESPACE}"
proxy-config 从 Pod 的 proxy 中读取配置,而不是从控制平面读取,因此与 proxy-status 不同,它不接受 --revision。
- 监听器应与
Gateway 资源的端口匹配。没有监听器意味着 Pod 标签与 Gateway 选择器不匹配。
- 如果网关提供 HTTPS,则每个
kubernetes://<credential> secret 都应为 ACTIVE。处于 WARMING 状态的 secret 表示此命名空间中缺少证书,或服务账户无权读取该证书——在此状态持续期间,一半的 HTTPS 请求会失败。仅提供 HTTP 的网关——收集配置时其 credentials 为空——不会列出任何 kubernetes:// secret,此检查不适用。
如果任一检查失败,请将部署缩容到零,修复原因,然后将其扩容回来:
kubectl -n "${GATEWAY_NAMESPACE}" scale deploy "${GATEWAY_NAME}-v2" --replicas=0
切换流量
两个部署由同一个 Service 选中,因此流量比例取决于副本数。
将 Mesh v2 网关扩容到 Mesh v1 网关的规模
export GATEWAY_REPLICAS=$(kubectl -n "${GATEWAY_NAMESPACE}" get deploy "${GATEWAY_NAME}" -o jsonpath='{.spec.replicas}')
echo "${GATEWAY_NAME} runs ${GATEWAY_REPLICAS} replicas" | tee -a "${BACKUP_DIR}/gateway-replicas.txt"
kubectl -n "${GATEWAY_NAMESPACE}" scale deploy "${GATEWAY_NAME}-v2" --replicas="${GATEWAY_REPLICAS}"
kubectl -n "${GATEWAY_NAMESPACE}" rollout status "deploy/${GATEWAY_NAME}-v2" --timeout=300s
现在有一半的请求会到达 Mesh v2 网关。在继续之前,请检查此网关的每条路由,包括 HTTPS 路由。
GATEWAY_REPLICAS 是 Mesh v1 网关回滚时必须恢复到的规模。下一步会将其副本数设置为零,因此之后无法再从集群中读取该值——这就是为什么还要将其写入备份。
将 Mesh v1 网关缩容到零
kubectl -n "${GATEWAY_NAMESPACE}" scale deploy "${GATEWAY_NAME}" --replicas=0
保留该部署:在 阶段 5 之前,如果新网关运行异常,这是最快的恢复方式。停用 Mesh v1 时会删除它。
保护为外部服务生成的资源
将外部服务绑定到出口网关会使 Mesh v1 生成 Gateway、VirtualService 和 DestinationRule 资源。为它们添加注释,使 Mesh v1 控制器停止管理这些资源,并在平台资源删除时保留它们:
kubectl get gateways.networking.istio.io,virtualservices.networking.istio.io,destinationrules.networking.istio.io -A -o json \
| jq -r --arg gw "${GATEWAY_NAME}" '.items[]
| select(.metadata.name | contains("--" + $gw + "--"))
| "\(.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=true --overwrite
done
生成资源的名称包含外部服务、网关命名空间和网关名称,这正是过滤器匹配的内容。Mesh v1 还会生成名称不符合该模式的资源——每个外部服务的 ServiceEntry 旁边有一个 DestinationRule,以及每个具有负载均衡策略的服务对应一个资源。这里不会选中这些资源,也不需要选中:它们的所有者是服务的 ServiceEntry 或 Endpoints,而不是 Mesh v1 资源,因此 阶段 5 不会删除它们,Mesh v2 会继续按原样读取它们。阶段 0 中的 ${BACKUP_DIR}/asm-generated-resources.txt 会列出这些资源及其所有者:
destinationrules.networking.istio.io istio-system httpbingo-org-se ServiceEntry/httpbingo-org-se
destinationrules.networking.istio.io app-partial asm-backend Endpoints/backend
在这里浏览一次该列表。无需为任何资源添加注释,但迁移完成后,将没有控制器维护这些资源;从现在起,你可以自行编辑或删除它们。
验证
- 网关的每个路由都像之前一样响应 HTTP;如果网关提供 HTTPS,则也应通过 HTTPS 响应。
- 流量切换期间,探针未报告任何故障。
kubectl -n "${GATEWAY_NAMESPACE}" get deploy 显示 Mesh v1 网关的副本数为 0,Mesh v2 网关的副本数已达到完整副本数。
kubectl get gateways.networking.istio.io,virtualservices.networking.istio.io -A 返回与迁移前相同的资源。迁移不会更改这些资源。
- 新网关 Pod 仍会生成访问日志。
对每个网关(入口和出口)重复执行上述步骤,然后继续阅读停用 Mesh v1。