大数跨境

Hermes EKS 实测:镜像拉取快 85%,无需重建镜像

Hermes EKS 实测:镜像拉取快 85%,无需重建镜像 云妙算
2026-06-22
1
导读:在 EKS 上对 Hermes 做的实测:OpenSearch、Solr、Spark 三个工作负载,镜像拉取快 71%–85%,首个 HTTP 200 最多快 34%,全程使用原始 OCI 镜像、无需

点击上方卡片,关注「CloudPilot AI」

回复关键词【案例】

查看多邻国、Canva等名企的云端降本实践


01 背景

对于使用大镜像的 Kubernetes 工作负载,冷启动分为两段:先是节点准备镜像,然后应用才开始启动。在服务能够加载代码、插件、配置、模型或索引之前,containerd 必须先让镜像的文件系统就绪。

在传统的 containerd overlayfs 路径下,节点必须先下载并解压整个镜像,容器才能启动。懒加载(lazy loading)改变了这个顺序:它不再要求整个文件系统在进程启动前全部落到本地,而是通过索引挂载镜像,文件在被访问时才按需拉取。

Hermes 让这套流程变成策略驱动(policy-driven)。应用团队继续使用原始的 OCI 镜像,无需重建镜像、无需发布转换后的镜像标签、无需修改 Dockerfile,也无需改动 Pod 的镜像引用。平台团队只需定义一个 HermesPolicy,Hermes 便会在集群中准备好懒加载所需的制品(artifacts)。

上一篇 Hermes 基准测试主要关注 Pod Ready 和镜像拉取的表现。这次我们更进一步:测量从 Pod 被调度到一台全新 EKS 节点,直到服务返回第一个成功的 HTTP 200 响应的整条路径。

这样的口径让测量贴近 Hermes 真正能影响的环节:镜像拉取、挂载、容器启动,以及随后的应用启动。需要说明的是,首个 HTTP 200 仍然包含运行时初始化、库与配置读取、插件加载、模型或索引加载以及服务自举(bootstrap),因此它的百分比提升会小于纯镜像拉取的提升。

02 / 测试对象

我们测试了三个使用大体积公共镜像的 HTTP 工作负载。它们恰好都是基于 Java 的,但 Hermes 工作在应用运行时之下:它改变的是镜像文件系统的准备和读取方式,而不是 Java 的启动方式。

🔹 Solr:docker.io/library/solr:10.0.0

🔹 OpenSearch:docker.io/opensearchproject/opensearch:2.19.1

🔹 Spark master:docker.io/apache/spark:python3-java17

这些 Pod 使用上游公共镜像,并以 digest 固定版本。没有引入任何 Hermes 专用的转换镜像标签。

我们对比了两条路径:

🔹 overlay:标准的 containerd overlayfs 镜像拉取与解压。

🔹 Hermes:相同的工作负载镜像,在目标节点上走 Hermes 懒加载路径。

每个服务、每种路径各运行三次,共计 18 次目标测试。

03 / 测试环境

基准测试运行在 EKS 上:

🔹 Kubernetes:v1.34.9-eks-93b80c6

🔹 节点操作系统:Amazon Linux 2023

🔹 运行时:containerd 2.2.4

🔹 目标实例类型:m6i.large

🔹 平台:linux/amd64

🔹 节点隔离:每次目标测试都使用一台全新的目标节点

每次测试开始前,基准测试都会先移除上一次的目标节点,以避免测到上一个 Pod 遗留的本地镜像缓存。

04 / 实验步骤

步骤一:安装 Hermes Controller 与 CRD

Controller 会监听 HermesPolicy 资源和 Pod。当它发现一个匹配的镜像时,就会构建并缓存懒加载制品。

kubectl apply -f deploy/hermespolicy-crd.yaml
kubectl apply -f deploy/hermes-controller-eks.yaml
kubectl -n hermes-system rollout status deploy/hermes-controller

步骤二:配置两个目标 NodePool

测试使用了两组 NodePool/NodeClass:

🔹 一个 overlay NodePool:标准的 AL2023/containerd overlay 路径。

🔹 一个 Hermes NodePool:AL2023/containerd,并通过 EC2NodeClass 的 userData 安装 Hermes 守护进程。

启用 Hermes 的 NodeClass 会在节点引导(bootstrap)阶段安装该守护进程:

export HERMES_INSTALLER_URL="..."
export HERMES_DAEMON_URL="..."
export HERMES_DAEMON_SHA256="..."

curl -fsSL "${HERMES_INSTALLER_URL}" | \
  HERMES_DAEMON_URL="${HERMES_DAEMON_URL}" \
  HERMES_DAEMON_SHA256="${HERMES_DAEMON_SHA256}" \
  bash -s --

两个 NodePool 使用相同的实例类型和容量类型。

步骤三:创建 HermesPolicy

该策略选中了三个基准测试的工作负载镜像:

apiVersion: hermes.cloudpilot.ai/v1alpha1
kind:HermesPolicy
metadata:
name:benchmark-images
spec:
imageSelectors:
    -imageRegex:'.*(solr:10\.0\.0|opensearchproject/opensearch:2\.19\.1|apache/spark:python3-java17).*'
platforms:
    - linux/amd64

步骤四:等待制品就绪

Hermes 的制品准备发生在目标 Pod 启动计时窗口之外,它是 controller 侧针对匹配镜像的一次性准备步骤。

本次运行中,准备耗时为:

🔹 Solr:58.918s

🔹 OpenSearch:2m44.999s

🔹 Spark:1m4.493s

目标工作负载的基准测试在制品达到 Ready 后才开始。

步骤五:在全新节点上运行目标 Pod

对每个服务和每种路径,基准测试会:

1. 删除上一次的目标 Pod 和 Service。

2. 移除上一次的目标节点。

3. 等待目标节点完全消失。

4. 在一台稳定节点上创建一个 watcher Pod。

5. 在一台全新的目标节点上创建目标工作负载 Pod。

6. 记录镜像拉取、容器启动、Pod Ready 以及首个 HTTP 200 的时间。

首个 HTTP 200 的时间戳是在 watcher 收到成功的 HTTP 响应之后记录的,而不是在发出请求之前。

步骤六:工作负载 YAML 与 HTTP 200 探测

两种路径使用完全相同的应用 YAML。唯一的调度差异是目标节点池:overlay 运行使用 hermes-overlay,Hermes 运行使用 hermes-startuplocal

每个目标 Pod 由一个 headless Service 暴露。该 Service 通过 appvariant 和 e2e-run 标签选中某一次运行特定的 Pod:

apiVersion: v1
kind:Service
metadata:
name:opensearch-overlay-r1-20260622025902-ope
namespace:hermes-e2e
spec:
clusterIP:None
publishNotReadyAddresses:true
selector:
    app:opensearch-e2e
    variant:overlay
    e2e-run:20260622025902-opensearch-overlay-r1
ports:
    -name:http
      port:9200
      targetPort: http

publishNotReadyAddresses: true 让 watcher 能在 Pod 被标记为 Ready 之前,就通过 Service DNS 解析并调用该 Pod。否则 Service 通常会在就绪成功之前隐藏该端点,使得测量首个成功的 HTTP 200 变得更困难。

下面是基准测试中使用的 OpenSearch 目标 Pod。Hermes 运行使用相同的容器规格,仅改动了运行标签/名称以及 karpenter.sh/nodepool 选择器:

apiVersion: v1
kind:Pod
metadata:
name:opensearch-overlay-r1-20260622025902-ope
namespace:hermes-e2e
labels:
    app:opensearch-e2e
    variant:overlay
    e2e-run:20260622025902-opensearch-overlay-r1
spec:
restartPolicy:Never
terminationGracePeriodSeconds:1
nodeSelector:
    kubernetes.io/os:linux
    kubernetes.io/arch:amd64
    karpenter.sh/nodepool:hermes-overlay
containers:
    -name:opensearch
      image:docker.io/opensearchproject/opensearch:2.19.1@sha256:7ad3c515e43fb1642ddf2181dfd03402e42e85a16030e098ed1f3fc1404d7e89
      imagePullPolicy:IfNotPresent
      args:
        -opensearch
        --Ediscovery.type=single-node
        --Ehttp.host=0.0.0.0
        --Etransport.host=127.0.0.1
      env:
        -name:OPENSEARCH_JAVA_OPTS
          value:"-Xms512m -Xmx512m"
        -name:DISABLE_SECURITY_PLUGIN
          value:"true"
      ports:
        -name:http
          containerPort:9200
      readinessProbe:
        httpGet:
          path:/_cluster/health?local=true
          port:http
        periodSeconds:1
        timeoutSeconds:1
        failureThreshold:900
      resources:
        requests:
          cpu:"1"
          memory:"2Gi"
          ephemeral-storage:"8Gi"
        limits:
          memory: "3Gi"

Solr 和 Spark 使用相同的 Pod 结构,它们的工作负载容器配置如下:

# Solr
-name:solr
image:docker.io/library/solr:10.0.0@sha256:c5d3e51740f81612dac200c91908c253bf8302dca330874d6dcef23dacafc723
imagePullPolicy:IfNotPresent
env:
    -name:SOLR_HEAP
      value:"512m"
ports:
    -name:http
      containerPort:8983
readinessProbe:
    httpGet:
      path:/solr/admin/info/system
      port:http
    periodSeconds:1
    timeoutSeconds:1
    failureThreshold:900
resources:
    requests:
      cpu:"500m"
      memory:"1Gi"
      ephemeral-storage:"4Gi"
    limits:
      memory:"3Gi"

# Spark master
-name:spark-master
image:docker.io/apache/spark:python3-java17@sha256:6fb854a580e552290a21d7b9c6214f2d8840733e63a90ff687a2ffce80f45ef9
imagePullPolicy:IfNotPresent
command:
    -/opt/spark/bin/spark-class
args:
    -org.apache.spark.deploy.master.Master
    ---host
    -0.0.0.0
    ---port
    -"7077"
    ---webui-port
    -"8080"
ports:
    -name:web
      containerPort:8080
    -name:master
      containerPort:7077
readinessProbe:
    httpGet:
      path:/
      port:web
    periodSeconds:1
    timeoutSeconds:1
    failureThreshold:900
resources:
    requests:
      cpu:"500m"
      memory:"1Gi"
      ephemeral-storage:"4Gi"
    limits:
      memory: "4Gi"

HTTP 200 的时间戳来自一个独立的 watcher Pod,它运行在一台稳定的托管节点上,反复调用 Service 的 DNS 名称,并且只有在收到 HTTP 200 状态码后才打印 first_http_200_ns:

apiVersion: v1
kind:Pod
metadata:
name:watch-opensearch-overlay-r1-20260622025902-ope
namespace:hermes-e2e
labels:
    app:opensearch-first-200
    variant:overlay
    e2e-run:20260622025902-opensearch-overlay-r1
spec:
restartPolicy:Never
nodeSelector:
    eks.amazonaws.com/nodegroup:eks-spot-20260620152041180300000015
containers:
    -name:watcher
      image:python:3.12-alpine
      imagePullPolicy:IfNotPresent
      env:
        -name:TARGET_URL
          value:http://opensearch-overlay-r1-20260622025902-ope.hermes-e2e.svc.cluster.local:9200/_cluster/health?local=true
      command:
        -sh
        --lc
        -|
          python - <<&#x27;PY&#x27;
          import os
          import time
          import urllib.request

          url=os.environ["TARGET_URL"]
          print(f"watcher_start_ns={time.time_ns()}",flush=True)
          while True:
              try:
                  withurllib.request.urlopen(url,timeout=0.5)as response:
                      ifresponse.status==200:
                          print(f"first_http_200_ns={time.time_ns()}",flush=True)
                          break
              except Exception:
                  pass
              time.sleep(0.25)
          PY

05 / 测试结果

服务
Overlay 拉取
Hermes 拉取
拉取降幅
拉取差值
Overlay 调度→HTTP 200
Hermes 调度→HTTP 200
HTTP 200 降幅
HTTP 200 差值
OpenSearch
20.371s
2.998s
85.28%
-17.373s
37.943s
30.375s
19.95%
-7.568s
Solr
9.053s
2.584s
71.46%
-6.469s
18.572s
14.919s
19.67%
-3.653s
Spark
15.763s
3.811s
75.82%
-11.952s
20.191s
13.304s
34.11%
-6.887s

OpenSearch 的拉取时间变化最大,从 20.371s 降到 2.998s。Spark 的首个 HTTP 200 变化最大,从 20.191s 降到 13.304s。

06 / 这些数字意味着什么

Hermes 对镜像拉取的影响最为明显。在三个工作负载上,镜像拉取时间下降了 71% 到 85%。

首个 HTTP 200 提升了 20% 到 34%。这个数字低于拉取时间的提升,因为首个 HTTP 200 包含的不只是镜像拉取:

🔹 容器创建

🔹 进程启动

🔹 运行时初始化

🔹 代码、库、配置或模型的读取

🔹 插件加载

🔹 服务就绪检查

以 OpenSearch 为例,overlay 基线从 Pod 被调度到首个 HTTP 200 约为 38 秒。Hermes 把拉取时间从约 20 秒降到约 3 秒,服务到达首个 HTTP 200 的时间提前了约 7.6 秒。剩下的时间是容器启动之后 OpenSearch 自身的启动工作。

这正是表格同时给出镜像拉取和首个 HTTP 200 的原因:拉取时间反映镜像路径的直接收益,首个 HTTP 200 则反映最终到达服务边界的效果。

07 / 结论

这次基准测试表明,在不改动应用镜像引用、也不重建镜像的前提下,镜像拉取时间得到了明显下降。

在这次 EKS 测试中,OpenSearch 的拉取时间从 20.371s 降到 2.998s,Spark 的首个 HTTP 200 从 20.191s 降到 13.304s。

这一结果并不局限于 Java。Hermes 工作在镜像文件系统层,因此同样的机制适用于任何 OCI 镜像。这些基于 Java 的工作负载只是一组具有真实 HTTP 就绪行为的具体测试样本。

在运维层面,整个流程依然简单:平台准备好懒加载制品,而 Pod 保持原有的镜像和规格不变。下一步的优化空间在容器启动之后的路径——让应用关键文件的表现更接近本地 overlay 文件系统。

关注项目:https://github.com/cloudpilot-ai/hermes


推荐阅读


全球抢 GPU,Kubernetes 却闲置?看 DRA 如何让算力按需飞

别了,EC2 Auto Scaling!AWS 2025 变革信号背后的行业真相

公司 GPU 还在 “摸鱼” 吗?这项Kubernetes 技术或许能帮你节省百万算力成本


公司介绍

CloudPilot AI 是一家总部位于旧金山硅谷的科技公司。致力于为全世界最严苛的团队提供弹性扩缩容解决方案。无需任何手动调优即可消除云浪费、提升应用性能并降低运维风险。已为数百家全球顶尖科技公司提供服务,累计为客户节省超过5亿美金,平均节省67%。


免费试用,2步5分钟,降低50%云成本:

cloudpilot.ai


【声明】内容源于网络
0
0
云妙算
让您在云中花费的每一分钱都物超所值
内容 0
粉丝 0
云妙算 让您在云中花费的每一分钱都物超所值
总阅读0
粉丝0
内容0