北京时间 10 月 13 日 23:00 到 10 月 14 日 02:00,Istio 会把 gcr.io/istio-release、registry.istio.io/release 和 istio-release.storage.googleapis.com 再关一次,12 月这几个地址就正式退役。镜像改拉 docker.io/istio 或自建拉取缓存,Helm 仓库换成 https://blob.istio.io/istio-release/charts,OCI chart 换成 oci://ghcr.io/istio/release/charts。这周把地址换完,13 日深夜那三个小时正好拿来验收。

为什么要搬

Istio 官方博客 8 月 21 日发文(作者 Steven Jin 与 Keith Mattix),原话是今年要把全部基础设施从 Google Cloud Platform 迁到 Amazon Web Services,原因是 "changes in our funding model",也就是资金模式变了。

registry.istio.io 为什么也要一起下线,7 月 23 日那篇博客交代过。这个地址原本是 Cloudflare Worker,可以把请求代理到任意 OCI 仓库,背后一直指向 gcr.io/istio-release。项目 2027 年基础设施预算有限,打算改用免费的镜像托管平台,可所有流量经 Cloudflare 汇到少数几个出口 IP,会触发这些平台的限流,于是只能放弃。

GitHub 上的跟踪 issue #61467 里有人问为什么镜像不放 GHCR,维护者回复 GHCR 有未公开的限制,按 Istio 现在的拉取量很容易超。所以镜像的正式去处是 Docker Hub。

哪些地址要换

用途 旧地址(12 月退役) 新地址
容器镜像 gcr.io/istio-release、registry.istio.io/release docker.io/istio,或指向它的拉取缓存
Helm 仓库 https://istio-release.storage.googleapis.com/charts https://blob.istio.io/istio-release/charts
OCI Helm chart gcr.io/istio-release/charts ghcr.io/istio/release/charts
RPM、DEB、源码、SPDX、istioctl、licenses https://istio-release.storage.googleapis.com/releases https://blob.istio.io/istio-release/releases

版本边界很清楚。1.30 是最后一个往旧地址发布镜像和 chart 的小版本,1.31 起只发新地址。官方说新地址已经放了所有历史版本,所以老版本集群也可以直接切,不用先升级。10 月 6 日我核对了一遍:旧 Helm 索引里最高只到 1.30.5,gcr.io/istio-release/pilot:1.31.1 不存在;blob.istio.io 和 ghcr.io 上 1.30.x、1.31.1 都能取到,Docker Hub 上 istio/pilot:1.30.5 与 1.31.1 也都在。

最容易中招的是 1.30。7 月的博客写明,1.30 默认就用 registry.istio.io/release 作为镜像仓库,其他版本默认是 docker.io/istio。翻 istiod chart 的默认值也能看到,1.30.0 的 global.hub 是 registry.istio.io/release,1.29.0 和 1.31.1 是 docker.io/istio。如果你用 1.30 且没改过 hub,大概率要动手。

1.31 本身还有不少功能变化,这篇不展开,它的升级说明里第一条就是 GCP 下线。

四次断服测试,换成北京时间

官方把这种演练叫 scream test,测试期间会临时关闭 gcr.io/istio-release、registry.istio.io/release 和整个 https://istio-release.storage.googleapis.com/。博客、1.31 升级说明和 issue #61467 三处给的时间一致:

次序 官方时间(UTC) 北京时间(UTC+8) 时长
第一次 9 月 15 日 15:00 至 16:00 9 月 15 日 23:00 至 9 月 16 日 00:00 1 小时,已结束
第二次 10 月 13 日 15:00 至 18:00 10 月 13 日 23:00 至 10 月 14 日 02:00 3 小时
第三次 11 月 17 日 15:00 至 21:00 11 月 17 日 23:00 至 11 月 18 日 05:00 6 小时
第四次 12 月 8 日 15:00 至 12 月 9 日 15:00 12 月 8 日 23:00 至 12 月 9 日 23:00 24 小时

窗口一次比一次长,第四次整整一天。官方只说旧地址 2026 年 12 月退役,没有给出具体哪一天,按最后一次测试之后随时可能关来准备更稳妥。

窗口里可能出什么问题

已经在节点上跑着的 Pod 不会因为仓库断开而停掉。带版本号的镜像默认拉取策略是 IfNotPresent,节点上有缓存就不会去拉。出问题的是那些需要真正去拉镜像的时刻:节点扩容、Pod 被调度到没缓存的新节点、新建 Pod 注入 sidecar、Istio 组件滚动升级。这时 Pod 会卡在 ErrImagePull 或 ImagePullBackOff。

Helm 这边,仍指向旧仓库的 helm repo update 取不到 index.yaml,helm upgrade 或 helm pull 旧 OCI 地址也会失败。CI 里如果有脚本直接从 istio-release.storage.googleapis.com/releases 下载 istioctl 或安装包,同样会断。

13 日深夜可以这样用这个窗口:挑一个不重要、已注入 sidecar 的 Deployment 做一次 rollout restart,最好让它落到新节点上;在 CI 里手动跑一遍会拉 Istio 镜像或 chart 的流水线;本地执行一次 helm repo update。窗口内或窗口后看一下事件里有没有旧地址:

kubectl get events -A --field-selector reason=Failed | grep -E 'gcr\.io/istio-release|registry\.istio\.io'

排查清单

下面的命令都只读不写,可以直接复制。正则同时匹配两个旧镜像仓库。

正在运行的 Pod,包括 initContainers

kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{"\t"}{range .spec.initContainers[*]}{.image}{" "}{end}{range .spec.containers[*]}{.image}{" "}{end}{"\n"}{end}' \
  | grep -E 'gcr\.io/istio-release|registry\.istio\.io'

sidecar 模式下 istio-proxy 容器和 istio-init 初始化容器都会被列出来。只查 containers 会漏掉 init 容器,上面的命令两者都覆盖了。

Deployment、DaemonSet、StatefulSet 的模板

kubectl get deploy,ds,sts -A \
  -o custom-columns='KIND:.kind,NAMESPACE:.metadata.namespace,NAME:.metadata.name,IMAGES:.spec.template.spec.containers[*].image,INIT:.spec.template.spec.initContainers[*].image' \
  | grep -E 'gcr\.io/istio-release|registry\.istio\.io'

有些团队在 Pod 模板上用注解 sidecar.istio.io/proxyImage 固定了 sidecar 镜像,这个也要查:

kubectl get deploy,ds,sts -A \
  -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{"\t"}{.spec.template.metadata.annotations.sidecar\.istio\.io/proxyImage}{"\n"}{end}' \
  | grep -E 'gcr\.io/istio-release|registry\.istio\.io'

sidecar 注入模板和 Istio 的 ConfigMap

注入用的 hub 存在 istio-sidecar-injector(多 revision 时带后缀)这类 ConfigMap 的 values 里。下面这条把所有内容里含旧地址的 ConfigMap 都找出来,需要装 jq:

kubectl get configmap -A -o json \
  | jq -r '.items[] | select((.data // {}) | tostring | test("gcr\\.io/istio-release|registry\\.istio\\.io")) | "\(.metadata.namespace)/\(.metadata.name)"'

如果集群里还有 IstioOperator 资源,顺手看一眼 hub:

kubectl get istiooperators.install.istio.io -A -o yaml | grep -nE 'hub:'

Helm release

查 Helm 装的 release 时,建议看渲染后的清单。helm get values --all 会带上 chart 内置的 _internal_defaults_do_not_set 块,1.30 的 chart 在这里写着 registry.istio.io/release,即使你已经改了 global.hub 也会被误报。

helm list -A -o json | jq -r '.[] | "\(.name) \(.namespace) \(.chart)"' \
  | while read -r name ns chart; do
      helm get manifest "$name" -n "$ns" | grep -qE 'gcr\.io/istio-release|registry\.istio\.io' && echo "$ns/$name ($chart)"
    done

想看自己显式设置过哪些值,用不带 --all 的 helm get values <release> -n <namespace>。

本地、CI 和代码仓库

先看 Helm 客户端里还有没有旧仓库:

helm repo list -o json | jq -r '.[] | select(.url | test("istio-release\\.storage\\.googleapis\\.com")) | "\(.name) \(.url)"'

有的话用同名覆盖,原来的 istio/istiod 这类引用不用改:

helm repo add istio https://blob.istio.io/istio-release/charts --force-update
helm repo update istio

用 OCI 方式的,把地址换成 GHCR:

helm install istiod oci://ghcr.io/istio/release/charts/istiod --version 1.31.1 -n istio-system

CI 镜像、Terraform、Argo CD 或 Flux 的 values、Kustomize 补丁里也可能写死了旧地址,在代码仓库根目录搜一遍:

rg -n 'gcr\.io/istio-release|registry\.istio\.io|istio-release\.storage\.googleapis\.com'

把 hub 改过来

用 istioctl 安装的,在 IstioOperator 里改 hub,或者命令行直接指定:

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  hub: docker.io/istio  # 或你的拉取缓存地址
istioctl install -f istiooperator.yaml
# 或者
istioctl install --set hub=docker.io/istio

用 Helm 安装的,官方建议在 values 里同时设置顶层 hub 和 global.hub:

hub: docker.io/istio         # 或你的拉取缓存地址
global:
  hub: docker.io/istio       # 或你的拉取缓存地址

升级时把 --version 写成 helm list 里看到的当前版本,免得顺手把小版本也升了:

helm upgrade istiod istio/istiod -n istio-system --version <当前版本> -f my-values.yaml

base、cni、ztunnel、gateway 等其他 chart 也按同样方式处理。sidecar 镜像是在 Pod 创建时由注入模板写进去的,改完 hub 之后已有的 Pod 不会自动换,需要把相关命名空间的工作负载重启一遍,例如 kubectl rollout restart deployment -n <namespace>,然后再跑一次前面的排查命令确认清零。

拉取缓存与 Docker Hub 限流

镜像全部回到 Docker Hub 之后,要留意限流。按 Docker 官方文档,未登录用户每个 IPv4 地址(或 IPv6 /64 网段)每 6 小时 100 次拉取,Personal 账号登录后 200 次,Pro、Team、Business 不限。多架构镜像每个架构各算一次。大集群节点往往共用 NAT 出口,一次大规模扩容就可能撞线,超限时 Docker Hub 返回 429。官方 7 月的博客也建议,能配拉取缓存最好配上。

常见的三种做法:

Harbor 代理缓存项目。 在 Harbor 里建一个指向 Docker Hub 的 registry endpoint,再建 proxy cache 项目,hub 改成 harbor.example.com/<项目名>/istio。Harbor 文档提到 2.1.1 起检查缓存更新用 HEAD 请求,不计入 Docker Hub 限流,建议用这个版本以上。

containerd 镜像源。 在 containerd 配置里把 config_path 指向 /etc/containerd/certs.d,再给 docker.io 写一个 hosts.toml,hub 保持 docker.io/istio 不变,节点拉取时先走内部镜像:

# /etc/containerd/certs.d/docker.io/hosts.toml
server = "https://registry-1.docker.io"

[host."https://docker-mirror.internal"]
  capabilities = ["pull", "resolve"]

这个办法只对引用 docker.io 的镜像生效,救不了仍然写着 gcr.io/istio-release 的配置,hub 还是得先改。托管 Kubernetes 的节点配置方式因云厂商而异,以厂商文档为准。

Amazon ECR pull through cache。 ECR 支持把 Docker Hub 作为上游,但 Docker Hub 属于需要认证的上游,凭据要先存进 AWS Secrets Manager,secret 名必须以 ecr-pullthroughcache/ 开头,并与规则在同一账号和区域:

aws ecr create-pull-through-cache-rule \
  --ecr-repository-prefix docker-hub \
  --upstream-registry-url registry-1.docker.io \
  --credential-arn arn:aws:secretsmanager:<region>:<account-id>:secret:ecr-pullthroughcache/<name> \
  --region <region>

之后 hub 写成 <account-id>.dkr.ecr.<region>.amazonaws.com/docker-hub/istio。ECR 对已缓存的标签大约每 24 小时回源检查一次,上游不可达时仍返回最后一次缓存的镜像。

验签的公钥要按版本选

如果你在流水线或准入控制里用 cosign 校验 Istio 镜像签名,这次迁移还换了签名密钥。官方博客和镜像签名文档给出的对应关系是:

Istio 版本 公钥
1.18.x 至 1.30.x https://istio.io/misc/istio-key.pub
1.31.0 https://istio.io/misc/istio-key.pub
1.31.1 及以后 https://istio.io/misc/istio-key-v2.pub

旧公钥会长期保留。校验命令按官方文档写法:

cosign verify --key "https://istio.io/misc/istio-key-v2.pub" docker.io/istio/pilot:1.31.1

注意一个细节:10 月 6 日中午(北京时间)我实测,https://istio.io/misc/istio-key.pub 能正常下载,istio-key-v2.pub 会跳转到 /latest/misc/istio-key-v2.pub 并返回 404。打算切到 v2 公钥的团队,先确认地址能取到再改流水线,取不到时盯一下官方页面的更新。

参考来源

— 感谢阅读 —

一起交流

分享你的思考,让讨论更进一步。