Kubernetes 基于 Container-Optimized OS(COS/GCI)的镜像选型与 GCE E2E 测试实践指南
导读
在 Google Cloud(GCP)上运行 Kubernetes,无论是通过 kube-up 脚本自建集群,还是驱动端到端(E2E)测试矩阵,节点操作系统镜像的选择都直接影响测试稳定性、回滚成本与安全补丁的获取速度。本文以 cluster/gce/gci/README.md 为核心脉络,系统讲解 Container-Optimized OS(曾用名 Google Container-VM image,简称 GCI)的发行模型与命名规律,逐一拆解 image、image_family、image_regex 三种镜像指定方式的适用场景,并结合仓库中 GCE runner 源码与 kube-up 启动脚本,说明这些配置在真实测试与集群创建流程中如何被解析和执行。读完本文,你将能为不同测试类型选择正确的 COS 镜像,并掌握安全升级、回滚与镜像迁移的完整方法论。
COS 是什么:GCP 上为容器负载而生的操作系统
Container-Optimized OS 是面向 Google Cloud Platform 的容器优化镜像,主要用于在 GCP 上运行各类服务。它基于开源的 ChromiumOS 项目构建,这种底座使 Kubernetes 团队能够对构建管理、安全合规以及针对 GCP 的定制获得更强的掌控力(见 cluster/gce/gci/README.md)。在历史命名上它曾被称为 Google Container-VM image(GCI),仓库中 cluster/gce/gci 目录也因此得名,而 cluster/gce/cos、cluster/gce/custom、cluster/gce/ubuntu 在文件系统中都是指向 gci 的符号链接,说明同一套启动与配置逻辑被复用于多种发行版变体。
在 Kubernetes 的 GCE 集群自建体系中,COS/GCI 是默认首选发行版:cluster/gce/config-default.sh 中 MASTER_OS_DISTRIBUTION 与 NODE_OS_DISTRIBUTION 的默认值均为 gci(当用户显式传入 cos 时也会被归一化为 gci,见第 65-75 行),并有注释明确说明"默认情况下 master 与节点都运行在 COS(曾用名 gci)之上"。
发行模型:Milestone、Channel 与 LTS
COS 采用里程碑(milestone)方式对外发布,例如 milestone 81、85。每个 milestone 会依次经历三个发行渠道(release channel):
- dev——最先体验新功能的渠道;
- beta——功能趋于稳定;
- stable——稳定渠道。
channel 之间的晋级周期约为六周。自 milestone 69 起,每 4 个 milestone 中的最后一个,在进入 stable 之后会进一步被提升为 LTS(长期支持) 镜像。这一机制意味着:
- 69 是第一个 LTS 里程碑,在此之前 COS 只有 dev、beta、stable 三种镜像;
- 正因为如此,stable 镜像在当前的测试代码中被相当频繁地使用;
- 官方建议测试镜像应逐步从 stable 向 LTS 迁移(在条件允许的前提下)。
理解 COS 镜像的命名与 Family 规律
COS 镜像的命名与"家族"(family)之间存在清晰的对应关系,这直接决定了你在配置文件中该写什么:
- 在进入 LTS 阶段之前,镜像以其家族作为名字前缀,例如
cos-dev、cos-beta、cos-stable。这些家族内的 milestone 编号会随 channel 晋级而发生变化(例如某个镜像从 beta 晋级到 stable 时,milestone 编号可能整体改变)。 - 只有当某个 milestone 成为 LTS 时,镜像才会获得全新的 family(形如
cos-<milestone>-lts),并且 镜像名中的 milestone 编号从此保持不变——即使该 milestone 日后被废弃,镜像也依然长期存在,这正是 LTS 镜像便于引用、便于回滚的根本原因。
这一"名称中 milestone 固定"的特性,也是官方建议"优先用 LTS、其次用 stable、尽量在同一个 channel 内升级镜像"的技术依据。
测试配置文件中指定镜像的三种方式
在 Kubernetes 各测试套件的镜像配置文件(image config file)中,每个测试镜像可以通过 image、image_regex 或 image_family 三种字段之一来指定,同时必须提供 project 与 metadata 等配套信息。
方式一:image——固定精确镜像(推荐)
image 指定一个确切存在的镜像名,可复现性最好,因此被官方标注为首选(preferred)。缺点是每当 COS 发布新镜像后,需要人工更新 yaml 配置,才能让测试套件摆脱对已废弃镜像的依赖;这也意味着 COS 每次发布新镜像都会触发一次配置文件改动(未来可能的改进方向是引入"autobumper"机器人来自动化这一过程)。
cos-stable:
image: cos-77-12371-274-0
project: cos-cloud
metadata: "user-data</go/src/github.com/containerd/cri/test/e2e_node/init.yaml,containerd-configure-sh</go/src/github.com/containerd/cri/cluster/gce/configure.sh,containerd-extra-init-sh</go/src/github.com/containerd/cri/test/e2e_node/gci-init.sh,containerd-env</workspace/test-infra/jobs/e2e_node/containerd/cri-master/env,gci-update-strategy=update_disabled"
方式二:image_family——始终跟随家族内最新镜像
image_family 适用于"只要该 family 的最新镜像"的场景:COS 一发布新镜像,测试便会自动开始使用它,无需人工改配置。但代价是不可预测——由于无法预知新镜像何时出现,测试存在被意外打断的可能。
- 对 LTS 或 stable 镜像而言,OS 自身导致测试挂掉(breakage)的概率较低;
- 对 dev 或 beta 镜像而言,该概率明显偏高;
- 更关键的是,一旦出了问题,使用
image_regex/image_family的方式很难回滚到之前的镜像。
cos-stable:
image_family: cos-77-lts
project: cos-cloud
metadata: "user-data</workspace/test-infra/jobs/e2e_node/containerd/init.yaml,cni-template</workspace/test-infra/jobs/e2e_node/containerd/cni.template,containerd-config</workspace/test-infra/jobs/e2e_node/containerd/config.toml"
方式三:image_regex——按命名模式匹配
image_regex 用于筛选"符合同一命名模式"的镜像:当多个镜像同时匹配正则时,最新者胜出。不过该方式在测试代码中很少被使用,其随机性与回滚困难程度与 image_family 相当。
提示:上述示例中的
metadata使用逗号分隔的key=value或key<path语法——前者表示键值对,后者表示"键的取值从本地路径path读取"(该语义与 GCE runner 的--instance-metadata命令行参数一致,见下文源码分析)。样例中的键如user-data、containerd-configure-sh、cni-template、containerd-config等,是把容器运行时(containerd)与 CNI 的启动配置注入 COS 实例实例元数据的典型做法,而末尾的gci-update-strategy=update_disabled则是显式关闭 COS 的自动更新策略,保证测试期间系统镜像不被 COS 自动升级所干扰。
三种方式的对比速查表
| 字段 | 语义 | 更新方式 | 可复现性 | 回滚难度 | 典型场景 |
|---|---|---|---|---|---|
image |
固定精确镜像名 | 需人工/机器人更新配置 | 高 | 低 | 发布阻断、presubmit/postsubmit/periodic 等关键测试 |
image_family |
取 family 内最新镜像 | 自动跟随 COS 新发布 | 低 | 高 | 与运行时持续集成的测试 |
image_regex |
匹配命名模式取最新 | 自动跟随匹配结果 | 低 | 高 | 测试代码中很少使用 |
源码视角:三种配置在 GCE runner 中如何解析
要真正理解三种方式的取舍,值得深入 test/e2e_node/remote/gce/gce_runner.go 这一 Kubernetes Node E2E 在 GCE 上的 runner 实现。
配置文件的格式定义在该文件的 GCEImageConfig / GCEImage 结构体中(gce_runner.go),结构大致为:
images:
<short-name>:
image: gce-image-name # 可选
image_regex: <regex> # 可选
image_family: cos-81-lts # 可选,family 中的最新镜像将被选中
image_description: <desc> # 可选,为空时复用 image 字段值
kernel_arguments: [...] # 可选
project: gce-image-project # 必填
metadata: <逗号分隔键值> # 必填
machine: <机器类型> # benchmark 专用
resources:
accelerators: [...] # 可选
解析与回退逻辑位于 prepareGceImages,其核心规则是:
- 当
image_regex或image_family非空且image为空时,调用getGCEImage向 GCE 发起gcloud compute images list --format=json --project=...查询,然后从中挑选出符合条件的最新镜像; - 否则直接采用
image字段的值(即固定镜像); - 解析时使用
yaml.Unmarshal读入配置文件,并支持通过--image-config-file/--image-config-dir两个命令行参数指定;另外还可用--images+--image-project命令行参数直接追加额外镜像(用于本地临时测试,此时--zone、--project必须提供,参见第 307-336 行的校验逻辑)。
pickNewestImage(gce_runner.go)实现了"最新"的具体判定:
- 使用
regexp.Compile(而非MustCompile)编译用户提供的image_regex——因为该正则是用户配置,编译失败时不能直接 panic,而是返回可读的错误; - 遍历候选镜像,分别按
image_regex匹配镜像名、按image_family精确匹配家族(注释特别指出 gcloud 的--filter=family并不保证精确,因此需要在客户端再校验一次); - 解析每个镜像的
creationTimestamp(RFC3339),按创建时间排序,选取最新创建的镜像作为结果。
一个值得注意的实现细节在 gceImageListArgs(gce_runner.go):当指定了 image_family 时,会附加 --filter=family=<family> 把查询范围缩小到该家族——否则 gcloud 会列出整个项目下的全部镜像,对大型项目而言既慢又占内存(注释中记录了某次 Ubuntu 相关任务因此不得不提高内存配额的真实教训)。
在**集群创建(kube-up)**一侧,同等的选镜像逻辑以环境变量的形式体现于 cluster/gce/config-default.sh:
GCI_VERSION=${KUBE_GCI_VERSION:-} # 显式版本,默认空
IMAGE_FAMILY=${KUBE_GCE_IMAGE_FAMILY:-cos-129-lts} # 默认镜像家族
IMAGE_PROJECT=${KUBE_GCE_IMAGE_PROJECT:-cos-cloud} # 默认镜像项目
export MASTER_IMAGE=${KUBE_GCE_MASTER_IMAGE:-}
export MASTER_IMAGE_FAMILY=${KUBE_GCE_MASTER_IMAGE_FAMILY:-${IMAGE_FAMILY}}
export NODE_IMAGE=${KUBE_GCE_NODE_IMAGE:-${GCI_VERSION}}
export NODE_IMAGE_FAMILY=${KUBE_GCE_NODE_IMAGE_FAMILY:-${IMAGE_FAMILY}}
export NODE_IMAGE_PROJECT=${KUBE_GCE_NODE_PROJECT:-${IMAGE_PROJECT}}
脚本注释明确写到:"默认情况下,除非显式设置了镜像,否则将使用镜像家族中的最新镜像。"也就是说,在默认配置下,自建集群的 master 与节点均从 cos-cloud 项目下的 cos-129-lts 家族解析最新 COS 镜像,也可以通过 KUBE_GCE_MASTER_IMAGE、KUBE_GCE_NODE_IMAGE、KUBE_GCE_IMAGE_FAMILY 等环境变量覆盖,其优先级模型与测试配置文件的 image vs image_family 完全同构。
你的测试应该选什么镜像
原文档根据测试重要性(importance)与 COS 稳定性,给出了镜像选型的四条指导原则:
| 测试类型 | 推荐渠道 | 指定方式 | 理由 |
|---|---|---|---|
| 发布阻断(release blocking)测试 | 最新的 LTS 镜像 | image |
需要最高的稳定性与可复现性 |
| presubmit / postsubmit / periodic 测试 | 最新 LTS;需要两个镜像时用最近两个 LTS | image |
LTS 稳定且通常携带最新的 bug 与安全修复 |
| 与 runc、containerd、docker、kubernetes 等容器技术持续集成 | 最新 LTS 或 stable | image_family |
需要自动跟随生态内新镜像 |
| 尝鲜 COS 最新特性 | 最新 dev / beta / stable | image 或 image_family |
追求新功能,容忍不稳定 |
可以这样解读这四条准则背后的权衡:
- 关键测试优先选 LTS +
image。LTS 镜像的 milestone 编号固定不变、镜像长期存在,用固定image引用后,测试结果可精确复现;即便出问题,也能通过改回旧镜像名实现确定性的回滚。 - 集成测试优先选
image_family。这类测试的本质是"持续跟随上游容器技术的最新状态",让镜像跟随家族自动更新,正好契合其不停机验证兼容性的目标。 - 尝鲜测试才使用 dev / beta。它们是 COS 内部发布流程的中间产物,不应被用于任何需要稳定结论的测试;如果某套既有测试正在用 dev 或 beta,升级时应延续原有的渠道,除非你完全理解当初选用该渠道的底层原因。
如何查询每个 Channel 当前的 COS 镜像
无论是编写配置还是核对现有镜像,都可以用 gcloud 命令列出 cos-cloud 项目下的公开 COS 镜像。原文档给出了可直接执行的示例:
$ gcloud compute images list --project=cos-cloud | grep cos-cloud
cos-69-10895-385-0 cos-cloud cos-69-lts READY
cos-73-11647-534-0 cos-cloud cos-73-lts READY
cos-77-12371-274-0 cos-cloud cos-77-lts READY
cos-81-12871-119-0 cos-cloud cos-81-lts READY
cos-beta-81-12871-117-0 cos-cloud cos-beta READY
cos-dev-84-13078-0-0 cos-cloud cos-dev READY
cos-stable-81-12871-119-0 cos-cloud cos-stable READY
输出中可以直观看到两条规律:
- 处于 dev / beta / stable 阶段的镜像,名字以
cos-dev、cos-beta、cos-stable为前缀,其 milestone 编号会在 channel 晋级时改变; - 而
cos-69-lts、cos-73-lts、cos-77-lts、cos-81-lts等 LTS 家族的镜像,milestone 编号固化在镜像名中且长期 READY,可以放心作为image的取值写入测试配置。
在 Kubernetes 仓库的实际运行中,Node E2E 测试正是通过 getGCEImage 直接调用 gcloud compute images list --format=json 并将输出按 JSON 解析(见 gce_runner.go),所以上表中的 image 名即对应 --format=json 输出中的 name 字段。
升级与迁移镜像时务必遵守的规则
原文档对"何时、如何升级镜像"给出了三条明确纪律,建议在维护任何测试配置或集群配置前先过一遍:
- 升级镜像时,优先在同一 channel 内升级(例如
cos-77-lts升级到同家族更新构建,或 stable 家族内升级)。跨渠道跳跃会同时引入操作系统层面与 Kubernetes 支持层面的不确定变量。 - 镜像应从 stable 逐步向 LTS 迁移(能迁则迁)。再次强调 69 之前的 COS 不存在 LTS,仅 dev / beta / stable 三种,这也是历史遗留的 stable 测试镜像较多的原因;而现在有了稳定的 LTS 供应,长期停留在 stable 已经没有必要。
- 对正在使用 dev / beta 的测试,保留其在原渠道的选择。不要想当然地把 dev/beta 测试"顺手"改成 stable/LTS——除非你已经理解了当初选用非稳定渠道测试的意图(例如专门验证新内核或新特性),否则修改会破坏测试原本的覆盖目标。
在集群自建侧,仓库还在系统层面兜底了 COS 的自动更新行为:COS 实例支持 gci-update-strategy 实例元数据键。Kubernetes 的集群脚本会默认将其值写为 update_disabled(即禁用系统自动更新)——helper.sh 中的 ensure-gci-metadata-files 函数在 gci-update.txt 不存在时会写入 update_disabled,随后 node-helper.sh 与 master-helper.sh 在创建节点/主控实例时都会把 gci-update-strategy 拼进实例元数据。这样保证集群节点不会在测试或运行中途被 COS 后台静默升级,与测试配置中显式写 gci-update-strategy=update_disabled 的意图完全一致。
落点一览:COS 镜像启动 Kubernetes 实例后发生了什么
了解选镜像与传元数据之后,值得再看一眼这些元数据最终驱动了什么。cluster/gce/gci 目录(即符号链接 cluster/gce/cos 所指向的目录)下的 master.yaml 与 node.yaml 是写入 COS 实例的 cloud-config:
- 拉取启动脚本:通过 curl 从 GCE 元数据服务器
http://metadata.google.internal/computeMetadata/v1/instance/attributes/configure-sh下载configure.sh(对应配置中注入的configure-sh元数据),下载失败会按--retry 5 --retry-delay 3(node)或--retry 600(master)等策略重试; - systemd 编排:cloud-config 定义了
kube-node-installation.service/kube-node-configuration.service(master 侧为kube-master-installation.service、kube-master-configuration.service)以及kube-logrotate.timer、kubernetes.target等单元,将"下载二进制→配置组件→定期日志轮转"编排为一次性(oneshot)启动链;master 还额外包含kube-bootstrap-logs-forwarder.service(把引导日志转发到串口)与kube-master-internal-route.service(执行 kube-master-internal-route.sh 配置内部路由); - 运行时细节:node 侧写入
/etc/modprobe.d/sunrpc.conf,把 sunrpc 保留端口上限限制到 986 以下(注释说明 GKE 元数据服务器占用 987-989 端口),并通过runcmd屏蔽 Ubuntu 的snapd、unattended-upgrades等与容器 OS 定位冲突的服务。
这些由元数据驱动的 configure.sh、configure-helper.sh、configure-kubeapiserver.sh 脚本正是 COS 节点完成 kubelet、容器运行时与 kube-proxy 等组件安装配置的执行主体,仓库中还配套有 configure_helper_test.go、apiserver_etcd_test.go、audit_policy_test.go 等单元测试对关键逻辑进行验证。从整体上看,一套完整的链路是:选择 COS 镜像(image/image_family/image_regex 或环境变量)→ 注入实例元数据 → cloud-config 编排 systemd → configure 系列脚本拉起 Kubernetes 组件。
结语:COS 镜像选型的决策清单
最后把本文要点浓缩为一份可直接照做的清单:
- 查可用镜像用
gcloud compute images list --project=cos-cloud,优先记录 LTS 家族镜像名; - 关键、可复现的测试(release blocking、presubmit、periodic)用最新 LTS +
image; - 持续集成类测试用最新 LTS/stable +
image_family,接受其不可预测性并做好失败预案; - 尝鲜新特性才用 dev/beta,且仅在理解原意图的前提下沿用;
- 升级时在同一 channel 内进行,尽量从 stable 迁往 LTS;
- 无论哪条路径,都记得通过
gci-update-strategy=update_disabled(或集群脚本的默认行为)关闭 COS 自动更新,把镜像版本的决定权牢牢握在自己手中。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00