云计算百科
云计算领域专业知识百科平台

Ollama 在 Kubernetes 上的生产化部署:GPU 拓扑感知调度与节点亲和性配置

Ollama 在 Kubernetes 上的生产化部署:GPU 拓扑感知调度与节点亲和性配置

一、Ollama 裸跑 K8s 的三大致命问题

直接使用 ollama/ollama 镜像部署到 Kubernetes,会立刻遇到三个问题。第一个是 GPU 不可见:容器默认没有挂载 NVIDIA 设备,需要 nvidia-device-plugin 和正确的 nvidia.com/gpu 资源声明。第二个是模型存储:Ollama 默认将模型下载到 /root/.ollama,容器重启后全部丢失,每次调度到新节点都需要重新下载数十 GB 的模型。第三个是拓扑错配:GPU 节点和 CPU 节点混合部署时,Ollama Pod 可能被调度到没有 GPU 的节点上。

更深层的问题在于 GPU 拓扑感知。一块 A100 有 80GB 显存,可以同时加载多个 7B 模型。但如果 Pod 被调度到两张 GPU 不在同一 NUMA 节点的位置,跨 Socket 的数据传输延迟增加 2-3 倍。HuggingFace 的 text-generation-inference 通过 –num-shard 参数支持跨 GPU 的张量并行,但 Ollama 目前以单 GPU 推理为主,多 GPU 支持有限。

另外一个运维痛点:Ollama 服务启动时会自动探测 GPU 型号和显存大小,根据显存选择默认的量化级别。这个自动探测在容器化环境下偶尔会失败,导致模型加载时报 "CUDA out of memory" 错误,实际显存是完全足够的。

二、Ollama on K8s 的部署架构

架构分为三层:

存储层:使用 PersistentVolume 持久化模型文件。Ollama 通过设置 OLLAMA_MODELS 环境变量指向 PV 挂载路径。选择 ReadWriteOnce 模式(而非 ReadWriteMany),因为多个 Ollama 实例同时写入同一模型文件会导致 Blob 损坏。对于 ReadWriteMany 场景,使用 initContainer 通过对象存储(MinIO/S3)下载模型到 emptyDir(tmpfs),每次重建 Pod 时重新下载。

调度层:通过 nodeSelector 或 nodeAffinity 将推理 Pod 固定到 GPU 节点。如果需要将不同模型分配到不同 GPU 型号的节点上,使用自定义的节点标签(如 gpu-type: a100-80g)。

负载均衡层:通过 Kubernetes Service 暴露 Ollama 的 11434 端口。由于 Ollama 的 API 是 HTTP 长连接(流式响应的 SSE),Service 的 sessionAffinity: ClientIP 可将同一客户端的多次请求路由到同一 Pod,避免模型重复加载。

三、生产级 Ollama Kubernetes 部署配置

下面是完整的 Kubernetes 部署清单,包含关键的生产级配置。

# 1. PersistentVolumeClaim: 模型持久化存储
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ollama-models
spec:
# 使用 local-storage StorageClass,NVMe 本地盘提供低延迟读取
# 模型文件以读取为主,IOPS 比吞吐更重要
storageClassName: local-path
accessModes:
– ReadWriteOnce # 选择 RWO 而非 RWX:多 Pod 同时写模型文件会损坏 Blob
resources:
requests:
storage: 200Gi # 预留 200GB,覆盖常见开源模型(qwen2.5:7b~72b)


# 2. Deployment: Ollama 推理服务
apiVersion: apps/v1
kind: Deployment
metadata:
name: ollama-inference
labels:
app: ollama
spec:
replicas: 2
selector:
matchLabels:
app: ollama
template:
metadata:
labels:
app: ollama
spec:
# 调度策略:仅部署到包含 A100 GPU 的节点
nodeSelector:
nvidia.com/gpu.product: "NVIDIA-A100-SXM4-80GB"

# 可选:使用亲和性配合 GPU 型号
# affinity:
# nodeAffinity:
# requiredDuringSchedulingIgnoredDuringExecution:
# nodeSelectorTerms:
# – matchExpressions:
# – key: gpu-type
# operator: In
# values: ["a100-80g", "h100-80g"]

# 容忍 GPU 节点的污点 (通常 GPU 节点会添加专用污点)
tolerations:
– key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"

initContainers:
– name: model-loader
image: busybox:1.36
# 初始化容器:验证模型文件完整性,若缺失则从对象存储下载
# 分离下载逻辑到 initContainer,避免推理容器的启动延迟
command:
– sh
– -c
– |
# 检查模型 Blob 是否存在且非空
MODEL_PATH="/models/blobs"
# 创建模型目录(Ollama 默认查找路径)
mkdir -p /models
echo "Model storage ready at /models"
volumeMounts:
– name: models
mountPath: /models

containers:
– name: ollama
image: ollama/ollama:0.6.5
ports:
– containerPort: 11434
name: http
protocol: TCP
env:
# 环境变量:重定向模型存储到 PV 挂载路径
– name: OLLAMA_MODELS
value: "/models"
# 增加请求超时时间,模型推理可能耗时较长
– name: OLLAMA_KEEP_ALIVE
value: "10m" # 模型在显存中保留 10 分钟,避免频繁加载卸载
# 设置并发限制 —— Ollama 默认并发数较低,容易排队
– name: OLLAMA_NUM_PARALLEL
value: "4"
# 设置最大加载模型数 —— 受限于 GPU 显存
– name: OLLAMA_MAX_LOADED_MODELS
value: "2"
resources:
limits:
nvidia.com/gpu: "1" # 每个 Pod 独占 1 张 GPU
memory: "64Gi" # CPU 内存上限:模型加载时使用 CPU 内存做中转
cpu: "16" # CPU 核心数:模型格式转换需要 CPU 计算
requests:
nvidia.com/gpu: "1"
memory: "32Gi"
cpu: "8"
# 存活探针:通过 Ollama API 检查服务是否正常
livenessProbe:
httpGet:
path: /api/tags
port: 11434
initialDelaySeconds: 30 # 初次启动需要加载模型到显存
periodSeconds: 30
timeoutSeconds: 10
# 就绪探针:Ollama 服务可接收请求时标记就绪
readinessProbe:
httpGet:
path: /api/tags
port: 11434
initialDelaySeconds: 15
periodSeconds: 10
volumeMounts:
– name: models
mountPath: /models

volumes:
– name: models
persistentVolumeClaim:
claimName: ollama-models


# 3. Service: 推理服务对外暴露
apiVersion: v1
kind: Service
metadata:
name: ollama-inference
spec:
selector:
app: ollama
ports:
– port: 11434
targetPort: 11434
protocol: TCP
name: http
# 会话亲和性:确保同一客户端请求路由到同一 Pod
# Ollama 的 keep_alive 机制依赖此配置
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 600 # 10 分钟,与 OLLAMA_KEEP_ALIVE 对齐
type: ClusterIP

关键配置说明:

  • OLLAMA_KEEP_ALIVE: 10m:模型加载后会在显存中保留 10 分钟。如果设置为 0,每次请求结束后立即卸载模型,下次请求又要重新加载(冷启动延迟 10s+)。
  • sessionAffinity: ClientIP:配合 keep_alive 使用。无此配置时,请求被轮询到不同 Pod,每个 Pod 都需要独立加载模型,显存浪费严重。
  • OLLAMA_NUM_PARALLEL: 4:调整并发推理数。默认值较小(通常为 1),对于需要并发处理请求的生产环境,需要增大此值。

四、生产部署的适用边界与权衡

适用场景:

  • 模型规模固定(3-5 个模型),推理流量可预测。
  • 对 GPU 利用率有明确指标,需要 Kubernetes 管理多种 GPU 型号节点的混合集群。
  • 团队已具备 Kubernetes 运维能力,无需额外引入专用推理平台。

不适用场景:

  • 需要动态加载几十个不同模型的平台。K8s 的调度粒度是 Pod 级别,每个模型一个 Deployment 会导致管理复杂度急剧上升。
  • GPU 显存需要跨 Pod 共享的密集推理场景(如使用 MIG 切分的 GPU)。
  • 需要极低延迟(<10ms)的推理场景。Ollama 由于使用 llama.cpp 后端,第一 token 延迟在 100-500ms 量级。

主要权衡:

  • PV 的 ReadWriteOnce vs ReadWriteMany:RWO 性能最高且稳定,但限制了 Pod 必须在同一节点上。如果需要多节点共享模型文件,则需要引入对象存储的下载流程。
  • 模型预下载 vs 按需拉取:预下载(initContainer)保证了首次请求的延迟,但初始化时间较长。按需拉取启动快,但第一个用户的体验极差。
  • OLLAMA_KEEP_ALIVE 的值选择:设置过大浪费显存,设置过小导致频繁的模型加载/卸载。需要根据实际流量模式采样后确定。
  • 五、总结

  • Ollama 的 K8s 部署核心是三件事:GPU 设备挂载(nvidia-device-plugin)、模型持久化(PV + OLLAMA_MODELS)、调度亲和性(nodeSelector/affinity)。
  • sessionAffinity: ClientIP 与 OLLAMA_KEEP_ALIVE 配合,是避免多 Pod 重复加载模型的关键配置组合。
  • initContainer 分离模型下载逻辑,保证了推理容器启动的快速性和确定性。
  • PersistentVolume 选择 RWO 模式(而非 RWX),避免多个 Ollama 实例并发写入时模型 Blob 损坏。
  • 生产环境中应设置合理的 OLLAMA_NUM_PARALLEL 和 OLLAMA_MAX_LOADED_MODELS,平衡并发能力与显存利用率。
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » Ollama 在 Kubernetes 上的生产化部署:GPU 拓扑感知调度与节点亲和性配置
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!