首页
/ Kubernetes 基于 Container-Optimized OS(COS/GCI)的镜像选型与 GCE E2E 测试实践指南

Kubernetes 基于 Container-Optimized OS(COS/GCI)的镜像选型与 GCE E2E 测试实践指南

2026-09-06 18:49:44作者:滑思眉Philip

导读

在 Google Cloud(GCP)上运行 Kubernetes,无论是通过 kube-up 脚本自建集群,还是驱动端到端(E2E)测试矩阵,节点操作系统镜像的选择都直接影响测试稳定性、回滚成本与安全补丁的获取速度。本文以 cluster/gce/gci/README.md 为核心脉络,系统讲解 Container-Optimized OS(曾用名 Google Container-VM image,简称 GCI)的发行模型与命名规律,逐一拆解 imageimage_familyimage_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/coscluster/gce/customcluster/gce/ubuntu 在文件系统中都是指向 gci 的符号链接,说明同一套启动与配置逻辑被复用于多种发行版变体。

在 Kubernetes 的 GCE 集群自建体系中,COS/GCI 是默认首选发行版:cluster/gce/config-default.shMASTER_OS_DISTRIBUTIONNODE_OS_DISTRIBUTION 的默认值均为 gci(当用户显式传入 cos 时也会被归一化为 gci,见第 65-75 行),并有注释明确说明"默认情况下 master 与节点都运行在 COS(曾用名 gci)之上"。

发行模型:Milestone、Channel 与 LTS

COS 采用里程碑(milestone)方式对外发布,例如 milestone 81、85。每个 milestone 会依次经历三个发行渠道(release channel):

  1. dev——最先体验新功能的渠道;
  2. beta——功能趋于稳定;
  3. stable——稳定渠道。

channel 之间的晋级周期约为六周。自 milestone 69 起,每 4 个 milestone 中的最后一个,在进入 stable 之后会进一步被提升为 LTS(长期支持) 镜像。这一机制意味着:

  • 69 是第一个 LTS 里程碑,在此之前 COS 只有 dev、beta、stable 三种镜像;
  • 正因为如此,stable 镜像在当前的测试代码中被相当频繁地使用;
  • 官方建议测试镜像应逐步从 stable 向 LTS 迁移(在条件允许的前提下)。

理解 COS 镜像的命名与 Family 规律

COS 镜像的命名与"家族"(family)之间存在清晰的对应关系,这直接决定了你在配置文件中该写什么:

  • 在进入 LTS 阶段之前,镜像以其家族作为名字前缀,例如 cos-devcos-betacos-stable。这些家族内的 milestone 编号会随 channel 晋级而发生变化(例如某个镜像从 beta 晋级到 stable 时,milestone 编号可能整体改变)。
  • 只有当某个 milestone 成为 LTS 时,镜像才会获得全新的 family(形如 cos-<milestone>-lts),并且 镜像名中的 milestone 编号从此保持不变——即使该 milestone 日后被废弃,镜像也依然长期存在,这正是 LTS 镜像便于引用、便于回滚的根本原因。

这一"名称中 milestone 固定"的特性,也是官方建议"优先用 LTS、其次用 stable、尽量在同一个 channel 内升级镜像"的技术依据。

测试配置文件中指定镜像的三种方式

在 Kubernetes 各测试套件的镜像配置文件(image config file)中,每个测试镜像可以通过 imageimage_regeximage_family 三种字段之一来指定,同时必须提供 projectmetadata 等配套信息。

方式一: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=valuekey<path 语法——前者表示键值对,后者表示"键的取值从本地路径 path 读取"(该语义与 GCE runner 的 --instance-metadata 命令行参数一致,见下文源码分析)。样例中的键如 user-datacontainerd-configure-shcni-templatecontainerd-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_regeximage_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 行的校验逻辑)。

pickNewestImagegce_runner.go)实现了"最新"的具体判定:

  1. 使用 regexp.Compile(而非 MustCompile)编译用户提供的 image_regex——因为该正则是用户配置,编译失败时不能直接 panic,而是返回可读的错误;
  2. 遍历候选镜像,分别按 image_regex 匹配镜像名、按 image_family 精确匹配家族(注释特别指出 gcloud 的 --filter=family 并不保证精确,因此需要在客户端再校验一次);
  3. 解析每个镜像的 creationTimestamp(RFC3339),按创建时间排序,选取最新创建的镜像作为结果。

一个值得注意的实现细节在 gceImageListArgsgce_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_IMAGEKUBE_GCE_NODE_IMAGEKUBE_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 imageimage_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

输出中可以直观看到两条规律:

  1. 处于 dev / beta / stable 阶段的镜像,名字以 cos-devcos-betacos-stable 为前缀,其 milestone 编号会在 channel 晋级时改变;
  2. cos-69-ltscos-73-ltscos-77-ltscos-81-lts 等 LTS 家族的镜像,milestone 编号固化在镜像名中且长期 READY,可以放心作为 image 的取值写入测试配置。

在 Kubernetes 仓库的实际运行中,Node E2E 测试正是通过 getGCEImage 直接调用 gcloud compute images list --format=json 并将输出按 JSON 解析(见 gce_runner.go),所以上表中的 image 名即对应 --format=json 输出中的 name 字段。

升级与迁移镜像时务必遵守的规则

原文档对"何时、如何升级镜像"给出了三条明确纪律,建议在维护任何测试配置或集群配置前先过一遍:

  1. 升级镜像时,优先在同一 channel 内升级(例如 cos-77-lts 升级到同家族更新构建,或 stable 家族内升级)。跨渠道跳跃会同时引入操作系统层面与 Kubernetes 支持层面的不确定变量。
  2. 镜像应从 stable 逐步向 LTS 迁移(能迁则迁)。再次强调 69 之前的 COS 不存在 LTS,仅 dev / beta / stable 三种,这也是历史遗留的 stable 测试镜像较多的原因;而现在有了稳定的 LTS 供应,长期停留在 stable 已经没有必要。
  3. 对正在使用 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.shmaster-helper.sh 在创建节点/主控实例时都会把 gci-update-strategy 拼进实例元数据。这样保证集群节点不会在测试或运行中途被 COS 后台静默升级,与测试配置中显式写 gci-update-strategy=update_disabled 的意图完全一致。

落点一览:COS 镜像启动 Kubernetes 实例后发生了什么

了解选镜像与传元数据之后,值得再看一眼这些元数据最终驱动了什么。cluster/gce/gci 目录(即符号链接 cluster/gce/cos 所指向的目录)下的 master.yamlnode.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.servicekube-master-configuration.service)以及 kube-logrotate.timerkubernetes.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 的 snapdunattended-upgrades 等与容器 OS 定位冲突的服务。

这些由元数据驱动的 configure.shconfigure-helper.shconfigure-kubeapiserver.sh 脚本正是 COS 节点完成 kubelet、容器运行时与 kube-proxy 等组件安装配置的执行主体,仓库中还配套有 configure_helper_test.goapiserver_etcd_test.goaudit_policy_test.go 等单元测试对关键逻辑进行验证。从整体上看,一套完整的链路是:选择 COS 镜像(image/image_family/image_regex 或环境变量)→ 注入实例元数据 → cloud-config 编排 systemd → configure 系列脚本拉起 Kubernetes 组件

结语:COS 镜像选型的决策清单

最后把本文要点浓缩为一份可直接照做的清单:

  1. 查可用镜像用 gcloud compute images list --project=cos-cloud,优先记录 LTS 家族镜像名;
  2. 关键、可复现的测试(release blocking、presubmit、periodic)用最新 LTS + image
  3. 持续集成类测试用最新 LTS/stable + image_family,接受其不可预测性并做好失败预案;
  4. 尝鲜新特性才用 dev/beta,且仅在理解原意图的前提下沿用;
  5. 升级时在同一 channel 内进行,尽量从 stable 迁往 LTS;
  6. 无论哪条路径,都记得通过 gci-update-strategy=update_disabled(或集群脚本的默认行为)关闭 COS 自动更新,把镜像版本的决定权牢牢握在自己手中。
登录后查看全文
热门项目推荐
相关项目推荐