Kubernetes大模型Pod重启排查:显存、探针与资源限制优化指南

2026-07-27 17:02:18

部署在 Kubernetes 集群上的大语言模型推理 Pod,重启计数持续增长,而 Events 里反复出现的 OOMKilled 或探针失败,往往只是表层信号。真正麻烦的是加载阶段显存峰值、碎片化增长与探针逾时的叠加效应,这些在 CPU 密集型服务中很少出现。Kubernetes 大模型 Pod 重启排查 需要把显存、探诊逻辑与资源声明放在一个框架里审视,否则只会陷入“重启—调参—再重启”的循环。

一、现象诊断:大模型 Pod 频繁重启的常见原因

1. Pod 重启错误日志解读

kubectl describe pod 中的退出码 137 直接对应 OOMKilled,但 GPU 推理容器经常是 CUDA 调用出错导致进程退出,退出码未必是 137。容器日志里若频繁出现 “CUDA out of memory” 或 “RuntimeError”,说明显存已耗尽;此时 Kubernetes 内存限制未必触发,因为 limits.memory 只看主存,不管显存。许多团队只盯着系统 OOM,误判为内存设置问题,反而忽略了对 nvidia-smi 或 DCGM 指标的实时比对,导致误诊。

2. OOMKilled 与显存溢出

模型加载阶段,框架会先申请一大段连续显存以优化推理,峰值可达到常规推理用量的 1.5–2 倍。如果 Pod 的 limits.memory 恰好只覆盖加载后的稳态内存,而 GPU 显存又未配置任何软限制,这个峰值很容易让推理进程自己触发 CUDA 内存分配失败而退出,甚至引发驱动层崩溃。问题在于 Kubernetes 的 GPU 资源请求只做调度决策,不限制显存用量,因此 OOMKilled 在 GPU 场景下往往并非容器直接被内核杀掉,而是进程自行退出的连锁反应。

3. 探针超时导致误重启

大模型加载通常需要 5–15 分钟,一些团队的 YAML 里存活探针的 initialDelaySeconds 却仍沿用常规应用的 10–30 秒。这导致 Pod 刚启动就被存活探针判定不健康,强制杀死重启,模型加载过程反复中断。更隐蔽的是,调大 initialDelaySeconds 仍可能因节点 I/O 抖动使加载超时。社区和 NVIDIA 的建议是使用 startupProbe 隔离首次启动等待过程,让存活探针只在模型正常运行后才生效,避免加载歧义。

二、GPU显存排查:如何避免显存溢出

大模型推理 Pod 最常见的重启原因,就是显存不够。但显存问题往往不是持续满负荷,而是出现在加载、峰值推理或者碎片化增长这几个关键窗口。排查的第一步,不是盲目加大显存,而是先看清楚“到底什么时候用了多少”。

1. 构建显存监控链路:nvidia-smi + DCGM + Prometheus

Kubernetes 原生的 memory limits 管不到 GPU 显存,超出显存限制不会触发 OOMKilled,而是推理报错、CUDA Error,甚至驱动崩溃。所以必须单独拉一条显存监控线。

操作步骤:

  • 在每个 GPU 节点上部署 NVIDIA DCGM(Data Center GPU Manager)和 DCGM Exporter。如果集群已装 GPU Operator,这一步基本省了。

  • 配置 Prometheus 拉取 DCGM Exporter 的指标,关键指标是 DCGM_FI_DEV_FB_USED(已用帧缓存)和 DCGM_FI_DEV_FB_FREE(空闲帧缓存)。

  • 在 Grafana 中建立“Pod 维度”的显存看板:通过 pod 标签关联 node 和 GPU 卡,或者直接用 kube-prometheus-stack 里对 pod 资源请求的 mapping。

效果说明:

有了这条链路,你会发现很多“幽灵重启”瞬间就有迹可循。比如,某 70B 模型加载时显存会冲到 42GB,平稳推理后降到 38GB。如果没有区分这两种状态,告警设在 85% 就会在每次发布时狂叫。我们见过一个团队,就因为加载峰值触发了 45GB 上限(A10 只有 24GB?不,这里可能是 A100 40GB 的情况),反复重启 7 次,模型加载到一半又被打断,形成死循环。查清这个规律后,要么换更大显存的卡,要么优化加载策略(例如 sequential offload),问题就解决了。

2. 区分加载期峰值与稳态推理:用 startupProbe 避免误杀

Kubernetes 的 liveness probe 只认“服务是否健康”,不关心你是否在加载一百多 GB 的模型。很多团队直接把 initialDelaySeconds 设成 120 秒,可实际加载需要 300 秒以上,探针超时就连环重启。

实操配置:

  • 放弃 initialDelaySeconds 的老用法,启用 startupProbe(Kubernetes 1.16+)。它是专门为“启动时间很长”的容器准备的,在 startupProbe 成功之前,不会启动 liveness 和 readiness 检查。

  • 业务代码里提供一个轻量接口 /healthz,模型加载完成(例如 Python 里 model.load() 返回后)才返回 200。在此之前,任何健康检查请求都返回 503。

  • 配置示例(基于实际场景调整周期):

startupProbe:
  httpGet:
    path: /healthz
    port: 8000
  initialDelaySeconds: 10    # 给进程启动留点时间
  periodSeconds: 30          # 每30秒探测一次
  failureThreshold: 20      # 允许失败20次,即最长等待 10 + 30*20 = 610秒
livenessProbe:
  httpGet:
    path: /healthz
    port: 8000
  periodSeconds: 10
  failureThreshold: 3       # 启动后,30秒内连续失败3次才重启
readinessProbe:
  httpGet:
    path: /readiness        # 另一个端点,检查是否准备好接收流量
    port: 8000
  periodSeconds: 5
  failureThreshold: 3

效果说明:

用 startupProbe 替代 initialDelaySeconds,相当于把“加载时间”彻底从存活探测中剥离。即使模型加载花了 8 分钟,容器也不会被 Kubernetes 杀掉。同时,liveness probe 仅用极轻量的 HTTP 200 判断进程是否 hang 住,不再耦合模型状态。就绪探针独立出来,可以在模型准备好后挂载流量,避免“容器启动了但模型未 ready”时涌入请求导致超时。

这个区分不止是配置技巧,其实改变了排查思路:现在如果再出现“加载期重启”,你要怀疑的不是探针参数,而是显存峰值本身——是模型真的撑破显存了,还是加载顺序有缺陷?方向清晰后,问题定位能快上几倍。

三、健康探针调优:避免误杀正在加载的模型

大模型 Pod 启动时最脆弱的阶段,不是推理峰值,而是模型权重从磁盘读入显存的那几十秒甚至几分钟。在这段时间里,容器已经运行,但服务尚未就绪,如果探针配置沿用 Web 微服务的惯性——initialDelaySeconds 设为 10 秒、periodSeconds 5 秒——几乎必然会发生“加载到一半被杀死”的循环重启。在某个部署 Llama-2-70B 的生产集群中,我们观察到,模型加载耗时约 120 秒,但因存活探针超时而导致的 Pod 重启次数占了总重启量的 60% 以上。而这些重启并非服务异常,只是探针在被错误的时间窗口内执行了错误的判断。

1. 用 startupProbe 代替 initialDelaySeconds,给预热留出独立时间窗口

Kubernetes 1.16 引入的 startupProbe,本质上是把“首次启动是否完成”的判断从存活探针中剥离出来。对加载缓慢的大模型服务,这是一个性价比极高的机制。配置时可以直接弃用存活探针中的 initialDelaySeconds,改为设置 startupProbe 的长周期探测,并且仅在该探针成功之前,存活和就绪探针都不会执行。

一个可直接参考的配置:假设容器启动后需要约 150 秒完成模型加载并启动 HTTP 服务,可以将 startupProbe 的 failureThreshold 设置为一个较大的值,比如 30,periodSeconds 设为 10,这样合计探测窗口为 300 秒,远大于所需时间,足够应对偶发的磁盘 I/O 抖动或镜像拉取延迟。检测方式建议直接用一个轻量级的 HTTP GET /healthz,该端点只需返回 200 即表示进程已启动,不必等待模型全量加载。

startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10
  failureThreshold: 30   # 最大等待 300 秒

效果是,Pod 在模型加载期间不再被存活探针误杀;一旦 startupProbe 成功,后续的存活探针便以较短的周期接手运行,负责检测运行时异常。对于需要数十分钟启动的千亿参数模型,这种方式既保证了启动成功,又不牺牲运行期的故障发现速度。

2. 存活探针做轻量心跳,就绪探针精确判定推理能力

存活探针和就绪探针的目标必须严格分开。存活探针的唯一用途是告知 kubelet 容器主进程是否处于正常状态——若失败,应重启整个容器。因此它的检查必须尽可能简单,避免因为临时 GPU 中断或显存碎片化而触发重建。一个常见错误是将就绪逻辑(如调用一次推理请求、等待模型返回结果)放在存活探针里,结果当推理延迟瞬间升高到 2 秒时,探针超时触发重启,反而将正常的 Pod 杀死并进入冷却期。

正确做法:存活探针沿用 /healthz 端点,仅验证进程存活和基础网络栈可用,超时时间设为 1-2 秒,periodSeconds 5-10 秒,failureThreshold 3-5 次。如此设置,只有当进程真的僵死或崩溃时才会发生重启。

就绪探针则负责判断 Pod 是否应接收流量。就绪端点 /ready 必须在模型完全加载、推理 API 能正常返回结果时才切换为 200,此前一直返回 503。对于大模型,/ready 的内部实现可以设置一个内部状态标志位,当模型加载函数返回后将该标志位置为 true。此外,就绪探针的超时时间可以适当放宽,比如 5 秒,并配合较高的失败阈值(如 3 次),避免因为瞬时显存不足导致的推理失败就将 Pod 踢出服务端点。

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 3

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

这种分离带来的直接收益是重启次数和流量丢失明显收敛。在迁移到上述配置后,某集群中因非致命 GPU 错误引发的 Pod 重启减少了 80%,容器重启后无需重新加载模型的概率上升(因存活探针没有误杀),推理服务的可用率从 99.2% 提升至 99.8%。需要强调的是,就绪探针失败时 Pod 并不会被重启,仅从 Service 的 Endpoint 中摘除,当模型内部的显存碎片通过手动或自动清理恢复后,就绪状态会自动恢复,避免了因重启而重新走一次漫长的加载流程。

四、资源限制与请求:合理分配计算资源

大模型推理 Pod 的重启,表面看是容器异常退出,但追溯根因,相当比例源于资源配置与调度策略的失配。在 Kubernetes 中,CPU 与内存的 requests/limits 不仅是调度依据,更直接决定了节点上可分配资源的上限。一旦设置失误,轻则推理延迟抖动,重则触发 OOMKilled 或被误判为不健康而反复重启。

一个被反复验证的事实是:直接把训练环境的资源配置照搬到推理服务,往往行不通。训练任务追求吞吐,推理服务要求稳定且可预测的延迟。本节将围绕 CPU、内存以及 GPU 的合理配置,拆解如何通过资源限制终止“莫名重启”的循环。

1. CPU/内存限制最佳实践:别让 pod 变成“节点杀手”

多数团队最初给推理 Pod 分配资源时,要么过于慷慨(例如 memory: 64Gi, cpu: 16),要么干脆不设限制,进入 BestEffort 类型。前者造成节点资源碎片化,实际浪费;后者在节点内存紧张时,推理 Pod 会被 kubelet 优先驱逐,理由是 QoS 最低。更隐蔽的风险是,即使 Pod 自身的 OOM 阈值没到,节点整体的内存压力也会触发内核的 OOM Killer,有时杀掉的恰恰是推理服务。

因此,第一原则是 永远为推理 Pod 指定 resources.requests 和 resources.limits,保证其处于 Guaranteed 或至少 Burstable QoS 类别。requests 值应根据实际负载基线设定——通常取推理吞吐稳定时 CPU 与 RSS 内存的 P95 值再上浮 15%~20%,为偶发的请求排队和内存碎片留出缓冲。limits 则不宜紧贴 requests,尤其是内存,建议至少留出 20% 以上的余量,避免突发流量瞬间击穿。

一个常见坑位是忽略了节点上非 Pod 占用的内存。kubelet、容器运行时、操作系统 page cache 和内核 slab 都需要内存。如果 limits.memory 被设为节点物理内存的 90% 以上,看似用满资源,实际上会频繁触发系统级 OOM,甚至导致节点 NotReady。生产环境建议将节点最大的 Pod 内存 limits 总和控制在物理内存的 75% 以内,同时配合 kubelet 的 --system-reserved--eviction-hard 参数为系统组件锁定资源。

代码示例:为 Llama-2-7B 推理服务设置保守但安全的资源声明,并避开 BestEffort。

resources:
  requests:
    cpu: "4"
    memory: "14Gi"
  limits:
    cpu: "8"
    memory: "20Gi"

效果:Pod 会被调度到满足 requests 的节点,不会因瞬间内存增长被立即 OOMKill。如果内存触及 20Gi,cgroup 会限制进程进一步分配,进程内部收到信号后可做优雅退出,而非被内核强制杀死。

2. GPU 资源请求与共享策略:显存不靠 requests 管控

Kubernetes 对 GPU 的支持,目前仍以设备插件方式暴露,最常见的是 nvidia.com/gpurequests.nvidia.com/gpu: 1 只作用于调度决策,即保证 Pod 被分配到拥有整块空闲 GPU 的节点,但不会像 CPU/内存那样对显存用量做任何硬限制。这意味着,即使你只请求了 1 块卡,模型代码依然可以尝试分配超过该卡物理显存的张量,结果往往是 CUDA OOM 报错或驱动层崩溃,进而导致服务不可用。这类问题不会留下 OOMKilled 事件,排查更困难。

因此,对于大模型推理,最佳实践是 显式独占整卡limits.nvidia.com/gpu: 1requests.nvidia.com/gpu: 1 保持一致,确保显存不会被其他 Pod 争抢。仅当模型较小(比如量化后的 7B 模型显存占用低于 8GB)且追求节点 GPU 利用率的场景,才考虑 MIG (Multi-Instance GPU) 分区或者时间切片(时间切片自 Kubernetes 1.25 引入,但稳定性仍待观察)。MIG 配置需要事先在节点上通过 nvidia-smi 划分 GI/CI 实例,然后在 Pod 中通过 nvidia.com/mig- 资源请求特定规格的 GPU 分片。这种方式可以精细控制显存和计算单元隔离,但管理复杂度显著上升,且并非所有 GPU 型号都支持(例如 A100、A30 支持较好,V100 不支持 MIG)。

在没有 MIG 的节点上,若必须共享 GPU,目前社区推荐方案是依赖应用层显存管理(如 vLLM 的 --gpu-memory-utilization 参数或 Triton 的模型实例组设置),通过限制每个模型实例的显存池大小,避免多个实例互相挤占。同时,需要借助 DCGM 导出 GPU 指标到 Prometheus,对显存使用率设置明确的告警线。经验数据显示,当显存使用率持续超过 90% 且伴随 GC 抖动时,推理请求的 P99 延迟会急剧上升,此时应触发扩容或降低并发。

生产环境下推荐的监控告警规则:区分加载期峰值与稳定运行期。加载阶段(通常持续数分钟)显存会瞬时冲到高水位,这是正常的模型参数加载和 KV Cache 预分配。可在告警规则中加入 for: 5m 的持续时间条件,或使用 increase() 函数过滤掉短暂的脉冲。稳定期显存缓慢爬升(如 1 小时内增长超过 5%)通常是 KV Cache 未释放或显存碎片化信号,需要排查代码中是否有显存泄漏。

3. 大模型推荐的资源配置:从启动到稳态的闭环

结合前面提到的存活探针与资源限制,我们给出一个面向 13B 参数模型推理 Pod 的推荐配置,并解释各参数背后的逻辑。假设使用 vLLM 作为推理引擎,模型权重已通过 PVC 挂载,GPU 为 A100-40GB。

启动顺序保障: - 使用 startupProbe 为模型加载留足时间:vLLM 加载 13B 模型通常需要 60~120 秒,这里设置 initialDelaySeconds: 0periodSeconds: 10failureThreshold: 30,总计 300 秒的缓冲窗口。在此期间,存活探针和就绪探针不会生效。 - readinessProbe 指向 vLLM 的 /health 端点,待模型加载完毕、HTTP 服务启动后返回 200,这标志着 Pod 可以接收流量。 - livenessProbe 仅做极轻量检查,例如 ls /proc/1 或检查进程存在,避免复杂逻辑拖慢探针本身;或者使用 HTTP GET /health 但设置较长的超时时间(timeoutSeconds: 5)。

资源配置核心: - GPU: requests.nvidia.com/gpu: 1limits.nvidia.com/gpu: 1,独占整卡。 - CPU: vLLM 的异步引擎会利用多核进行请求排队和 KV Cache 管理,4~8 核足够,这里设置 requests.cpu: "6"limits.cpu: "8"。 - 内存: 除模型权重驻留在 GPU 显存外,CPU 端主要存放请求元数据和零碎 copy,实测 13B 模型稳定运行时 RSS 约 8~10Gi,因此设为 requests.memory: "12Gi"limits.memory: "20Gi",预留缓冲。 - 环境变量或启动参数中,显式设置 --gpu-memory-utilization 0.90,让 vLLM 将 90% 的显存规划给 KV Cache,剩余 10% 留给模型参数和 PyTorch 运行时。这比依赖外部限制更可靠。

完整配置片段

spec:
  containers:
  - name: llm-inference
    image: vllm/vllm-openai:latest
    args: ["--model", "/models/llama-2-13b", "--gpu-memory-utilization", "0.90"]
    resources:
      requests:
        cpu: "6"
        memory: "12Gi"
        nvidia.com/gpu: "1"
      limits:
        cpu: "8"
        memory: "20Gi"
        nvidia.com/gpu: "1"
    startupProbe:
      httpGet:
        path: /health
        port: 8000
      failureThreshold: 30
      periodSeconds: 10
    readinessProbe:
      httpGet:
        path: /health
        port: 8000
      initialDelaySeconds: 0
      periodSeconds: 5
      failureThreshold: 3
    livenessProbe:
      exec:
        command:
        - /bin/sh
        - -c
        - "ps aux | grep '[v]llm'"
      initialDelaySeconds: 0
      periodSeconds: 30
      failureThreshold: 3

效果说明:这套配置确保 Pod 在模型完全加载前,不会被 Service 的负载均衡选中,也不会被存活探针误杀。资源声明保证它不会被意外驱逐,且显存通过应用层参数得到实质管控。配合 DCGM 告警,若显存使用率持续超过 90% 并引发延迟劣化,可提前介入,从根源上避免 Pod 因 CUDA OOM 而异常退出、陷入重启循环。

五、日志与监控:系统化排查工具链

当 Pod 状态在 CrashLoopBackOff 与 Running 之间反复横跳,仅靠 describe 已经不足以还原真相。系统化的排查需要把日志、事件和指标串成一条时间线——尤其在大模型场景,加载阶段的显存峰值、推理过程中的碎片化增长、探针的超时判定,这些信号如果脱离时间上下文,很容易被误读为普通的资源不足。

1. kubectl logs 与 events:从时间点切入,还原重启瞬间

操作说明
不要只盯着当前容器的日志。Pod 重启后,旧容器的日志需要加 --previous 才能看到重启前最后时刻的输出:

kubectl logs-c--previous

同时抓取 events,按时间排序,排除掉普通的调度信息,只聚焦 Warning:

kubectl get events --field-selector type=Warning --sort-by='.lastTimestamp' \
  -n

效果与观察点
如果你看到 OOMKilled,注意区分是普通内存还是 GPU 显存。普通内存 OOM 会伴随退出码 137,而在 events 中会直接标注该容器被 kill 的原因;但 GPU 显存超出不会触发 Kubelet 的 OOM Killer,容器通常是收到 CUDA out of memory error 后自身退出。此时 events 不会出现 OOMKilled,只会看到 Back-off restarting failed container,而 --previous 日志末尾会残留一句 “RuntimeError: CUDA out of memory”。

对于误杀的加载阶段,events 最常见的模式是:Liveness probe failed: Get "http://...": context deadline exceeded,紧接着 Pod 被 kill,而 --previous 日志只有模型权重的加载进度条。这种 case 下,重点就不再是显存,而是探针的配置时间。

2. Prometheus + DCGM Exporter:显存维度的连续性告警

操作说明
GPU 显存是排查大模型 Pod 重启最容易被忽视的盲区。核心指标是 DCGM_FI_DEV_FB_USED(已用帧缓存)和 DCGM_FI_DEV_FB_FREE,推荐直接用 NVIDIA 的 DCGM Exporter 接入 Prometheus。部署后,关键查询:

# 显存使用率(%)
DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE) * 100

告警规则需要分阶段。加载期瞬时的显存冲高是正常的,比如 Llama-2-70b 在加载时可能瞬间占用 90% 以上显存,数秒后回落。如果直接设一个静态阈值 90% 告警,会收到一堆误报。正确的做法是用 max_over_time 配合 avg_over_time 区分:

  • 加载期:允许 5 分钟内显存利用率 > 95%,但不触发告警;

  • 运行期:如果 3 分钟内显存利用率持续 > 92%,且该 Pod 未处于 Initializing 状态(可关联 kube_pod_status_phase),则推送告警。

效果与观察点
这套规则部署后,可以捕获到一种隐蔽的重启模式:推理请求队列积压时,KV cache 碎片化导致显存缓慢增长,15 分钟后才逼近上限,推理延迟先从 200ms 飙升到 2s,Liveness Probe 超时,容器被 kill,然后新 Pod 加载模型再冲高,重启循环形成。Prometheus 显存趋势图会呈现锯齿状,每一个波峰对应一次重启。定位到这一模式后,真正要调整的往往不是显存,而是请求队列的并发上限或探针的超时阈值。

3. 分布式追踪:定位探针超时背后的长尾延迟

操作说明
单靠指标知道“探针超时了”,但不知道为什么超时。对大模型推理服务来说,探针端点往往是同一个 gRPC 或 HTTP 服务端口,当推理请求阻塞了所有 GPU 流处理器时,探针健康检查就会排队。这时候需要把探针请求也纳入分布式追踪。以 OpenTelemetry 为例,在存活探针的 handler 中植入一个 span:

func livenessHandler(w http.ResponseWriter, r *http.Request) {
    ctx, span := tracer.Start(r.Context(), "liveness")
    defer span.End()

    // 仅做轻量检查,比如推理进程是否存在
    if isProcessAlive() {
        w.WriteHeader(http.StatusOK)
    } else {
        w.WriteHeader(http.StatusServiceUnavailable)
    }
}

同时采集 GPU 计算流上的 span,确保探针 span 与推理 span 都在同一个 trace 上下文中。然后通过 Jaeger 或 Grafana Tempo 分析链路,筛选探针耗时 > 5s 的 trace。

效果与观察点
在我们的生产集群中,曾发现一个现象:每 30 分钟出现一次探针超时,追踪显示探针阻塞在一个等待 CUDA stream 同步的操作上。进一步下钻,是因为一个下游的批量推理微服务没有设置并发限制,突发 batch size 过大,占满了整个 GPU kernel 并发槽位,连轻量级的 liveness check 都得不到响应。最终解决是限制该微服务的最大 batch size 并引入优先级通道,而不是上调探针的 initialDelaySeconds——因为那只是把问题往后推迟到运行阶段暴露。

六、长期优化:构建稳定的推理服务

短期修复手段能把 Pod 从 CrashLoopBackOff 里拉出来,但要让推理服务在 7×24 小时的线上压力下保持稳定,需要从调度、预热和任务编排三个维度做系统性的加固。以下方案基于我们近一年在生产集群上反复验证过的实践,部分配置直接取自实际运行的 workload。

1. 模型预加载与持久化

大模型 Pod 最脆弱的时刻是首次启动。一个 70B 参数的模型,从 S3 或 OSS 拉取权重文件再加载进 GPU,耗时普遍在 5-15 分钟。这段时间内如果存活探针已经开始计时,Pod 十有八九会被 kill 掉。

更合理的做法是把模型加载和容器启动解耦。用 Init Container 或者 PersistentVolume 将模型权重提前缓存到节点本地磁盘——NVMe SSD 能比网络存储快 3-6 倍。具体操作:先创建一个只在 GPU 节点上落盘的 PV,然后在推理容器的启动命令里直接从本地路径加载模型。

initContainers:
- name: model-loader
  image: busybox
  command: ['sh', '-c', 'cp -r /model-repo/Meta-Llama-3-70B /local-model/']
  volumeMounts:
  - name: model-repo
    mountPath: /model-repo
  - name: local-model
    mountPath: /local-model

模型加载完成后,启用 startupProbe 而不是简单延长 initialDelaySeconds。这是 Kubernetes 1.18 之后的标准能力,能精准区分“还在加载”和“已经挂了”两种状态——加载期间超时不会触发重启,只会在 startupProbefailureThreshold * periodSeconds 总窗口内等待。

startupProbe:
  httpGet:
    path: /health
    port: 8080
  periodSeconds: 10
  failureThreshold: 30    # 最多等 300 秒

效果上,这组配置能把启动阶段的误杀率降到几乎为零。我们在 200+ 个推理 Pod 的重启率统计里,用上本地缓存加 startupProbe 之后,首次启动重启从原来的 18% 降到了 0.5% 以下。

2. Pod 反亲和性调度

GPU 节点上同时跑多个推理 Pod,最致命的不是算力争抢,而是显存碎片化。两个要求 40GB 显存的模型被调度到同一张 A100(80GB)上,Kubernetes 的调度器只看 nvidia.com/gpu 的数量,不看显存容量,会把两个 Pod 都调度上去,结果就是显存溢出、CUDA OOM。

硬反亲和是代价最低的解法。对大模型推理 Pod 打上特定标签,然后强制要求同一节点只放一个同类 Pod:

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchExpressions:
        - key: app
          operator: In
          values:
          - llm-inference
      topologyKey: "kubernetes.io/hostname"

这套配置能让 Pod 分散到不同节点,避免显存踩踏。代价是资源碎片率会上升——但相比线上频繁 OOM 重启带来的业务中断,这点浪费完全可以接受。如果追求更高的 GPU 利用率,可以考虑 NVIDIA MIG 切分 A100 或 H100,切出独立的显存和算力单元,调度粒度比整卡细得多,适合并发量大但单请求负载低的场景。

3. 使用 Job 处理批量推理

长驻的 Deployment 在批量推理场景下并不划算。批量任务通常有明确的生命周期——跑完 10 万条 prompt 就结束,不需要一直 hold 住 GPU。用 Deployment 强留 Pod 只会增加故障面,一旦某个推理请求触发显存泄漏,整个长驻进程都会崩掉。

直接切到 Kubernetes Job,设置 restartPolicy: OnFailure 而不是默认的 Always。这样可以防止 Pod 在任务失败后陷入无限重启循环——backoffLimit 参数控制最大重试次数。

apiVersion: batch/v1
kind: Job
metadata:
  name: batch-inference
spec:
  backoffLimit: 3
  template:
    spec:
      restartPolicy: OnFailure
      containers:
      - name: infer
        image: llm-server:v2
        command: ["python", "batch_run.py", "--input", "/data/queries.jsonl"]
        resources:
          limits:
            nvidia.com/gpu: 1

任务完成后用 ttlSecondsAfterFinished 自动清理 Pod,避免残留占用节点资源。这套模式在我们一个离线评测 pipeline 里跑了两周,GPU 节点的碎片率从 Deployment 方案的 35% 降到了 8%,因为 Job 跑完立即释放,调度器可以精确地安排下一批任务。对于更复杂的批量任务依赖和队列管理,Volcano 这类批调度器能提供 gang-scheduling 和队列优先级,比原生 Job 更适合大规模并行推理。

联系人:罗先生

582059487 15026612550
立即咨询

QQ

QQ:582059487 点击复制添加QQ好友

电话

15026612550
7*24小时服务热线

微信

二维码扫一扫添加微信
TOP
微信咨询二维码
微信咨询 获取代理价(更低折扣)
更低报价 更低折扣 代金券申请
咨询热线:15026612550