- 云原生
- CLI
【免费下载链接】openebs
A popular & widely deployed Open Source Container Native Storage platform for Stateful Persistent Applications on Kubernetes.
本文依据 OpenEBS 仓库中的设计文档 OEP 3796:Expose OpenEBS HelmChart's Container Images(状态为 implemented)编写。该提案为 OpenEBS 伞形 Helm Chart 引入了一份单一事实来源的容器镜像清单,并通过正则提取、Chart 注解存储与自动化脚本,使清单能够随 Chart 演进持续保持最新。读者读完本文后,将掌握离线(air-gapped)部署前批量获取镜像、用
helm template+ 正则全量枚举 Chart 镜像、以及借助仓库脚本完成清单生成/回写/集群校验的完整实战方法。
一、设计背景与动机:为什么需要一份"镜像清单"
OpenEBS 是一个以 Helm 伞形 Chart 形式发布的云原生存储平台。打开仓库根目录下的 charts/Chart.yaml 可以看到,这个伞形 Chart 通过dependencies聚合了 8 个子 Chart:
openebs-crds(CRD 安装)loki、alloy(可观测性,来自 Grafana Helm 仓库)localpv-provisioner(Local PV Hostpath 引擎)zfs-localpv、lvm-localpv、rawfile-localpv(Local PV 存储引擎)mayastor(Replicated PV 复制引擎)
每个子 Chart 内部又依赖 CSI sidecar(csi-provisioner、csi-attacher、csi-resizer等)、etcd、NATS 消息总线、MinIO 对象存储等一系列镜像。结果是:部署 OpenEBS 到底需要拉取哪些容器镜像,用户和开发者很难凭肉眼从模板树中理清。
设计文档给出的动机非常直接:提供一个清晰、完整的"每个 OpenEBS Chart 所需全部容器镜像"清单,从而减少用户查找、拉取镜像的时间与精力,尤其对需要预先下载镜像的离线(air-gapped)环境至关重要。文档还指出,另一个 OEP 可以在本提案之上构建"可一键下载的镜像 tar 包"。
1.1 目标(Goals)
- 为每个 OpenEBS Chart 提供一份详细的容器镜像清单;
- 必须建立自动化机制,确保清单随 Chart 的任何变更保持同步更新。
1.2 非目标(Non-Goals)
- 不创建所有镜像的可下载打包(留给后续 OEP 处理);
- 不负责构建或维护容器镜像;
- 不做"镜像与具体功能"的关联映射——也就是说,清单是全量的,可能包含某个用户未启用的特性所对应的镜像(这是刻意为之,见"风险"一节)。
二、总体方案:把镜像清单写进 Chart 的 annotations
提案的核心设计是:在每个 OpenEBS Chart 的Chart.yaml(或doc.yaml)中增加一个annotations字段,其中存放该 Chart 所需的全部容器镜像列表;该列表自动生成并随 Chart 变更自动更新。同时,每个 OpenEBS Chart 会复用其依赖的子 Chart 中已声明的注解,从而把"生成镜像清单"这件最可靠的工作下放到最了解自身的那个 Chart。
文档给出了一个示意示例(以 Mayastor 为例,注意其中版本号仅为示意,当前仓库 Chart.yaml 中的实际版本见后文):
project: name: OpenEBS Mayastor annotations: images: | - name: mayastor-agent-core image: docker.io/openebs/mayastor-agent-core:v2.7.1 - name: linux-utils image: docker.io/openebs/linux-utils:4.1.0 - name: promtail image: docker.io/grafana/promtail:2.8.3在仓库的当前实现中,注解键名落地为helm.sh/images,块标量(|)内是若干- name: <组件名>/image: <完整镜像地址>键值对(见 charts/Chart.yaml)。
2.1 用户故事
- Story 1:作为用户,我希望 OpenEBS Chart 有一个容器镜像的单一事实来源,以便轻松拉取部署所需镜像。
- Story 2:作为用户,我希望这个单一事实来源可以被自动化消费(例如脚本循环拉取、镜像仓库同步),而无需人工逐条翻查模板。
三、实现细节:可发现镜像、暴露镜像与不可发现镜像
文档将镜像划分为两类,处理策略截然不同。
3.1 可发现镜像(Discoverable images):正则 + helm template 枚举
凡是在 Helm 模板中以image:键出现的镜像,都可以通过对 Chart 执行模板化(helm template)后扫描输出得到。为此提案给出了一条正则表达式:
^[ \t]*image: \K(.*:.*)$逐段拆解如下:
| 片段 | 含义 |
|---|---|
^ | 行首断言,匹配必须从行首开始 |
[ \t]* | 匹配零个或多个空格或制表符(即 YAML 的缩进),贪婪匹配 |
image: | 字面匹配image:(区分大小写) |
\K | 重置匹配报告的起点:此前消耗的字符不再计入最终匹配结果(PCRE 特性) |
(.*:.*) | 捕获组:任意字符 + 冒号 + 任意字符,确保抓到的值含冒号(即<registry>/<repo>:<tag>形态) |
$ | 行尾断言 |
配合helm template与grep(-P启用 PCRE、-o只输出匹配部分)即可提取出全部镜像:
helm template . --set "$ENABLE_ALL_FEATURES" | grep -Po "^[ \t]*image: \K(.*:.*)$" | tr -d \"注意:此处必须开启全部特性(enable all features),才能保证拿到一份完整、无遗漏的镜像清单——这正是文档对"全量清单"定位的直接体现。
3.2 仓库中的等价实现
设计文档的这条命令在仓库中已经被完整落地到 scripts/helm/images.sh 的generate流程里,且分成了两条互补的扫描:
# ① 通过 install.sh 的 template 模式,打开全部本地引擎与复制引擎 $SCRIPT_DIR/install.sh --locals --replicated --template --helm "--kubeconfig $CHART_DIR/fake" \ | grep -Po "^[ \t]*image: \K(.*:.*)$" | tr -d \" | LC_ALL=C sort | uniq # ② 对伞形 Chart 本身执行 helm template,并显式开启分析/遥测相关特性 helm template "$CHART_DIR" \ --set "mayastor.eventing.enabled=true,mayastor.obs.callhome.enabled=true,mayastor.obs.callhome.sendReport=true" \ --kubeconfig "$CHART_DIR/fake" --is-upgrade \ | grep -Po "^[ \t]*image: \K(.*:.*)$" | tr -d \" | LC_ALL=C sort | uniq其中ENABLE_ALL_FEATURES在脚本中对应变量ENABLE_ANALYTICS(见 images.sh),即把 Mayastor 的 eventing 与 callhome 上报功能强制打开;而install.sh --locals --replicated会通过 scripts/helm/install.sh 中的参数拼装逻辑把engines.local.*与engines.replicated.mayastor.enabled全部置为true(对应 values.yaml 中的引擎开关)。tr -d \"去掉 YAML 中可能出现的引号,sort | uniq去重后得到干净的去重列表。
3.3 暴露镜像(Expose images):写回 annotations
拿到镜像列表后,将其作为 annotations 写入 Chart。当前 charts/Chart.yaml 中的helm.sh/images注解实际列出了约 50 个镜像条目,按用途大致可分为:
| 类别 | 代表条目(当前仓库实际值) |
|---|---|
| 可观测性 | docker.io/grafana/loki:3.4.2、docker.io/grafana/alloy:v1.8.1、docker.io/kiwigrid/k8s-sidecar:1.30.2、quay.io/prometheus-operator/prometheus-config-reloader:v0.81.0 |
| 消息总线(NATS) | docker.io/nats:2.9.17-alpine、docker.io/natsio/nats-box:0.13.8、docker.io/natsio/nats-server-config-reloader:0.10.1、docker.io/natsio/prometheus-nats-exporter:0.11.0 |
| 基础设施/工具 | docker.io/openebs/etcd:3.6.4-debian-12-r0、docker.io/openebs/kubectl:1.25.15、docker.io/openebs/linux-utils:4.6.0、docker.io/openebs/alpine-bash:4.6.0、docker.io/openebs/alpine-sh:4.6.0、docker.io/openebs/mc:*、docker.io/openebs/minio:* |
| Local PV 引擎驱动 | docker.io/openebs/provisioner-localpv:4.6.0、docker.io/openebs/provisioner-localpv:4.7.0-develop、docker.io/openebs/zfs-driver:2.12.0-develop、docker.io/openebs/lvm-driver:1.11.0-develop、docker.io/openebs/rawfile-localpv:v0.15.1 |
| Replicated PV Mayastor 系列 | docker.io/openebs/mayastor-agent-core:develop等 12 个mayastor-*组件(agent/api-rest/csi/io-engine/eventing/obs/operator-diskpool 等) |
| 升级任务 | docker.io/openebs/openebs-upgrade-job:v4.7.0-develop |
| CSI sidecar | registry.k8s.io/sig-storage/csi-attacher:v4.8.1、csi-provisioner(v5.2.0 / v6.1.0 / v6.3.0 三个版本)、csi-resizer(v1.13.2 / v2.0.0 / v2.2.1)、csi-node-driver-registrar(v2.13.0 / v2.17.0)、csi-snapshotter(v8.2.0 / v8.6.0)、snapshot-controller(v8.2.0 / v8.6.0) |
可以观察到:同一个组件名会出现多个版本条目(如provisioner-localpv同时有 4.6.0 与 4.7.0-develop,csi-provisioner有三个版本)。这是各存储引擎子 Chart 各自声明依赖的 CSI sidecar 版本不同所致——伞形 Chart 把依赖子 Chart 的注解原样汇总,恰好印证了文档"每个 Chart 使用其所依赖 Chart 的注解"的设计意图。
3.4 不可发现/运行时镜像(Non-Discoverable / Runtime images)
并非所有镜像都能从helm template中发现。凡是在运行时才被动态部署的 Pod/Job(而非 Helm 渲染的静态资源),其镜像就无法通过扫描模板获得,文档明确指出这类镜像需要人工查找并"硬编码"进清单。仓库中恰好有两个典型实例:
实例一:LocalPV 的 helper pod(初始化容器)
localpv-provisioner在为 PVC 准备文件系统时,会按需拉起一个 init pod(helper pod)。该镜像不会出现在 helm 渲染结果中,因此 images.sh 专门实现了helm_localpv_prov_helper_image():从子 Chart 的helperPod.image.registry / repository / tag取值(支持helperPod.image.registry与global.imageRegistry两级回退),拼出完整镜像地址并追加进清单(images.sh)。对应的 values 配置见 charts/values.yaml。
实例二:openebs-upgrade-job(升级任务)
OpenEBS 的升级任务由kubectl openebs upgrade插件在升级时按需创建 Job,而非随 Chart 常驻部署。从插件源码 plugin/src/cli_utils/upgrade/mod.rs 可以看到镜像地址是在运行时拼装的:{image_registry}/{namespace}/openebs-upgrade-job:{image_tag}。为此,images.sh 中的upgrade_job_image()以 callhome 镜像的 registry/namespace 为默认来源(可用--openebs-registry/--openebs-namespace覆盖),结合 Chart 的.version生成openebs-upgrade-job:vX.Y.Z并写入镜像清单(images.sh)。脚本注释也说明了当前阶段升级任务尚未集成进 Chart,故需从清单中单独提取。
此外,伞形 Chart 自身静态渲染的资源(如 charts/templates/pre-upgrade-hook.yaml 中引用的
openebs/kubectl:1.25.15升级钩子 Job)属于可发现镜像,会由helm template扫描覆盖。
四、仓库中的自动化闭环:generate / patch / verify 三命令
设计文档的"目标"章节要求"自动化机制保证清单与 Chart 同步",这一要求在 scripts/helm/images.sh 中落地为一个完整的 CI 可用闭环,脚本提供三个子命令:
4.1generate:从 Chart 生成镜像清单
三步流水线(images.sh):
- 收集依赖注解:遍历
Chart.yaml中所有带repository的依赖,用helm show chart读取各自.annotations."helm.sh/images"中的镜像(helm_dep_collect_images); - 全量扫描模板:依次执行
install.sh --locals --replicated --template与helm template --set "$ENABLE_ANALYTICS" --is-upgrade,用上文正则提取image:值; - 补充按需镜像:调用
helm_on_demand_images追加 helper pod 镜像与openebs-upgrade-job镜像。
最终sort | uniq后写入 charts/images.txt(每行一个完整镜像地址)。配合--exit-code选项,脚本会执行git diff --exit-code,一旦清单与 Chart 不同步即返回非零退出码——这正是文档"自动化测试保证清单保持最新"(见"缓解措施"一节)的具体实现,可无缝接入 CI。
4.2patch:把清单回写为 Chart 注解
patch子命令将images.txt的每一行解析出组件名(按/与:切分取倒数第二段),构造{ "name": ..., "image": ... }的 JSON 数组,通过yq写回Chart.yaml的.annotations."helm.sh/images",并确保其保持块标量(|)格式(images.sh)。这样charts/Chart.yaml注解与charts/images.txt互为可再生的同一份数据。
4.3verify:在真实集群上校验清单
verify子命令把清单与实际部署做交叉验证(images.sh):
# 从 openebs 命名空间收集所有 Pod 的容器与 initContainer 镜像 kubectl -n openebs get pods -o json \ | jq -r '.items[].spec.containers[]?.image, .items[].spec.initContainers[]?.image' \ | LC_ALL=C sort | uniq随后将 live 镜像与images.txt、Chart.yaml注解逐条比对,任何"集群在用但清单缺失"的镜像都会触发log_fatal报错;脚本还内置了一个 sanity check(live 镜像少于 5 条视为异常),防止在空命名空间上误判通过。
这与设计文档 Test Plan 中提出的思路完全对应:可以在集群上用镜像白名单(只允许清单内的镜像被拉取)或禁用镜像拉取 + 预加载的方式,验证清单的正确性与完整性。
五、测试计划与风险缓解
5.1 测试计划(Test Plan)
文档要求随项目发展持续验证镜像清单的正确性与时效性,验证可在真实集群上执行,两种典型做法:
- 镜像白名单:配置运行时只允许清单内的镜像被拉取,一旦部署请求了清单之外的镜像即失败,从而暴露清单缺漏;
- 禁用镜像拉取 + 预加载:把所有镜像预先加载进集群并禁止在线拉取,模拟离线环境,验证清单是否足以支撑完整部署。
上述两种方式在仓库中均有可用的工具基础:verify子命令提供了 live 镜像与清单的自动比对,scripts/staging/mirror-images.sh 等 staging 脚本也可用于镜像的同步与校验场景。
5.2 风险与缓解(Risks and Mitigations)
文档明确列出的风险包括:
- 可能收录非必需镜像:因为生成清单时启用了全部特性,清单会包含某些用户配置下用不到的镜像;由于镜像随用户启用的功能而异,这个问题难以从清单层面根除(这也正是 Non-Goals 中"不做镜像-功能关联映射"的原因);
- 不可发现镜像必须人工维护:运行时镜像无法自动扫描,必须手动加入清单。
对应的缓解措施是:尽可能自动化(仓库已通过 generate/patch/verify 三命令实现),并使用自动化测试保证清单持续更新(--exit-code的 CI 接入即为此设计)。
六、如何消费这份清单:离线部署与镜像预拉取
对于读者而言,消费这份清单的最直接场景是离线(air-gapped)部署:
- 查看清单:完整的镜像地址列表在 charts/images.txt(每行一个);带组件名/镜像地址的注解形式在 charts/Chart.yaml。
- 批量拉取:逐行
docker pull或接入镜像同步工具将镜像导入离线镜像仓库,即可在无外网集群上完成 OpenEBS 部署。 - 按需筛选:需要注意清单是全量的——若只部署部分引擎(例如在 values.yaml 中关闭
engines.replicated.mayastor.enabled,或在安装时加--set engines.replicated.mayastor.enabled=false,见 charts/README.md 的安装示例),清单仍会包含 Mayastor 系列镜像,请结合自己的启用项决定是否全部预拉。 - 自定义镜像仓库:清单记录的是默认 registry(
docker.io、registry.k8s.io、quay.io等)。若通过global.imageRegistry全局覆盖镜像仓库(charts/values.yaml),实际拉取地址会与清单不同,预拉取时应换算为覆盖后的地址。
七、局限性与替代方案
文档坦诚地列出了该方案的代价与备选:
- Drawback:在无法全自动生成清单的场景下(即存在不可发现镜像时),需要额外的维护精力来保持清单更新;
- Alternatives:维持现状——不提供集中清单,由用户在部署时手动逐个发现所需容器镜像。这显然更耗时且更容易出错,也正是本提案被采纳(状态为 implemented)的原因。
八、实施历史与展望
按 OEP 流程,文档经历了Summary与Motivation合并以表明 owner 接受提案的阶段;当前仓库中的helm.sh/images注解、images.txt清单与images.sh自动化脚本即为该设计的最终落地形态。正如文档开篇所展望的,这份"单一事实来源"的镜像清单还可以作为后续 OEP 的基础,用于构建"包含全部所需容器镜像、可一键下载的 tar 包",进一步降低离线交付 OpenEBS 的门槛。
延伸阅读(均为仓库内文件):
- 设计文档原文:designs/helm-charts/expose-container-images.md
- 镜像清单(纯镜像地址):charts/images.txt
- 镜像注解(名称+地址):charts/Chart.yaml
- 自动化脚本(generate/patch/verify):scripts/helm/images.sh
- 模板化安装脚本(全特性开启逻辑):scripts/helm/install.sh
- 引擎开关与镜像相关 values:charts/values.yaml
- 升级任务镜像的运行时拼装:plugin/src/cli_utils/upgrade/mod.rs
- 云原生
- CLI
【免费下载链接】openebs
A popular & widely deployed Open Source Container Native Storage platform for Stateful Persistent Applications on Kubernetes.
相关推荐
Ingress NGINX Controller v1.13.9 版本发布解析:镜像清单、变更内容与 Helm Chart 配套升级
Ingress NGINX Controller v1.13.9 版本发布解析:镜像清单、变更内容与 Helm Chart 配套升级 Ingress NGINX
后端API网关负载均衡云原生革命性交互式Go编程:lgo项目完全指南
革命性交互式Go编程:lgo项目完全指南 lgo是一个功能强大的Go语言Jupyter Notebook内核和交互式REPL工具,它彻底改变了Go语言的编程体验
虚拟化桌面应用图形学Flux Helm OCI 支持(RFC-0002):把 Helm Chart 存入容器镜像仓库的设计与落地
Flux Helm OCI 支持(RFC 0002):把 Helm Chart 存入容器镜像仓库的设计与落地 本篇基于 Flux 官方设计文档 RFC 0002
云原生CI/CD容器编排DevOps
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考