Ollama: 生产部署方案

生产部署是本地 AI 从实验室到战场的最后一跃——高可用、可扩展、能容灾。

💡 提示:Nginx 反向代理 + 负载均衡是生产部署的标准架构——Nginx 负责 SSL 终止、API Key 认证、速率限制和流量分发,Ollama 节点只需绑定 127.0.0.1。多节点集群建议用 least_conn(最少连接)策略,因为推理请求耗时差异大。

📋 前置知识:需要先掌握以下内容

1. 你将学到


2. 一个 SaaS 创业者的真实故事

💡 提示: 生产环境建议 Nginx 负载均衡使用 least_conn(最少连接)策略而非默认的 round_robin。Ollama 推理请求耗时差异大(短问答 1 秒,长文本 30 秒),least_conn 能更均匀地分配负载。

ℹ️ 信息: Ollama 目前不原生支持集群模式(无主从同步、无分布式推理)。多节点集群的模型一致性、会话亲和性等需要在上层(Nginx/FastAPI)自行实现。Kubernetes 部署时也需注意这一点。

(1) 痛点:单点故障导致全服务中断

Alice 的 SupportBot 只运行在一台 Ollama 服务器上。服务器维护或 GPU OOM 导致服务中断,客服系统停摆 2 小时,影响 500+ 客户。

(2) 解法:多节点高可用集群

部署 3 节点 Ollama 集群 + Nginx 负载均衡,任一节点故障自动切换:

100%
flowchart TD
    A[Nginx Load Balancer] --> B[Ollama Node 1<br/>GPU 1]
    A --> C[Ollama Node 2<br/>GPU 2]
    A --> D[Ollama Node 3<br/>GPU 3]

3. 生产架构设计

⚠️ 警告: 多节点集群中每个 Ollama 节点独立管理模型和状态,不像数据库有主从复制。节点 A 上拉取的模型,节点 B 不会自动同步。必须通过统一初始化脚本或共享存储确保所有节点模型一致。

(1) 单节点 vs 多节点

维度 单节点 多节点集群
可用性 单点故障 N-1 容错
吞吐 受限 线性扩展
成本 高(3x+)
运维复杂度
适用 开发/小规模 生产环境

(2) 生产架构全图

100%
flowchart TD
    A[Internet] --> B[WAF / CDN]
    B --> C[Nginx<br/>SSL + Auth + LB]
    C --> D[Ollama Node 1<br/>GPU + Model]
    C --> E[Ollama Node 2<br/>GPU + Model]
    C --> F[Ollama Node 3<br/>GPU + Model]
    D --> G[NFS / S3<br/>Shared Model Storage]
    E --> G
    F --> G
    D --> H[Chroma Cluster]
    E --> H
    F --> H
    I[Prometheus] --> D
    I --> E
    I --> F
    I --> J[Grafana Dashboard]

(3) 组件清单

组件 数量 规格 用途
Nginx LB 1 2 vCPU, 4GB RAM 负载均衡 + SSL
Ollama Node 3 8 vCPU, 16GB RAM, 1x RTX 4090 模型推理
Chroma 1 4 vCPU, 8GB RAM, SSD 向量存储
NFS/S3 1 500GB+ 模型共享存储
Prometheus 1 2 vCPU, 4GB RAM 监控

4. 高可用方案

⚠️ 注意:SSL 证书配置是生产部署的必选项——HTTPS 不仅加密传输,还是 API Key 认证的前提(HTTP 下 API Key 明文传输等于没有安全)。推荐用 certbot 获取 Let's Encrypt 免费证书,或用 Caddy 自动 HTTPS。

(1) 健康检查与故障转移

机制 检查方式 间隔 超时
Nginx 被动检查 请求失败时标记不可用 每次请求 5s
主动健康检查 定期 GET /api/tags 10s 3s
应用层检查 自定义推理测试 30s 10s

▶ 示例 1: Nginx 负载均衡配置

NGINX
# /etc/nginx/conf.d/ollama-lb.conf

upstream ollama_cluster {
    least_conn;  # Route to least busy node

    server ollama-node1:11434 max_fails=3 fail_timeout=30s;
    server ollama-node2:11434 max_fails=3 fail_timeout=30s;
    server ollama-node3:11434 max_fails=3 fail_timeout=30s;
}

server {
    listen 443 ssl;
    server_name ai.example.com;

    ssl_certificate     /etc/ssl/certs/ai.example.com.crt;
    ssl_certificate_key /etc/ssl/private/ai.example.com.key;

    location /v1/ {
        # API Key authentication
        if ($http_x_api_key != "your-secret-key") {
            return 401 '{"error": "Unauthorized"}';
        }

        proxy_pass http://ollama_cluster;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_connect_timeout 5s;
        proxy_read_timeout 120s;

        # Rate limiting
        limit_req zone=api burst=20 nodelay;
    }

    # Health check endpoint
    location /health {
        proxy_pass http://ollama_cluster/api/tags;
    }
}

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例 2: 自动故障转移脚本

BASH
#!/bin/bash
# Ollama cluster health monitor

NODES=("ollama-node1:11434" "ollama-node2:11434" "ollama-node3:11434")
HEALTH_URL="/api/tags"
ALERT_EMAIL="ops@example.com"

check_node() {
    local node=$1
    if curl -sf --connect-timeout 3 "http://$node$HEALTH_URL" > /dev/null; then
        echo "UP"
    else
        echo "DOWN"
    fi
}

while true; do
    for node in "${NODES[@]}"; do
        status=$(check_node "$node")
        if [ "$status" = "DOWN" ]; then
            echo "ALERT: $node is DOWN at $(date)" >> /var/log/ollama_health.log
            # Send alert
            echo "Ollama node $node is DOWN" | mail -s "OLLAMA ALERT" "$ALERT_EMAIL" 2>/dev/null
        fi
    done
    sleep 10
done

输出:

TEXT 📖 仅展示
NAME                    ID              SIZE    
llama3.2:latest        a80...          2.0 GB  
mistral:latest         61...           4.1 GB

5. 扩缩容策略

(1) 扩缩容触发条件

指标 扩容阈值 缩容阈值 等待时间
请求延迟 P95 > 5s < 2s 5 分钟
并发连接数 > 80% 容量 < 30% 容量 5 分钟
GPU 利用率 > 85% < 40% 10 分钟
队列长度 > 10 = 0 3 分钟

(2) 扩缩容方式对比

方式 速度 成本 复杂度
手动扩缩 慢(小时级) 可控
自动脚本 中(分钟级) 可控
K8s HPA 快(秒级) 自动

▶ 示例 3: 基于延迟的自动扩容

PYTHON
import subprocess
import time
from typing import Optional

class AutoScaler:
    def __init__(self, scale_up_threshold: float = 5.0,
                 scale_down_threshold: float = 2.0,
                 cooldown: int = 300):
        self.scale_up_threshold = scale_up_threshold
        self.scale_down_threshold = scale_down_threshold
        self.cooldown = cooldown
        self.last_scale_time = 0

    def get_avg_latency(self) -> Optional[float]:
        try:
            result = subprocess.run(
                ["curl", "-sf", "http://localhost:11434/api/chat", "-d",
                 '{"model":"qwen2.5","messages":[{"role":"user","content":"hi"}],"stream":false}'],
                capture_output=True, text=True, timeout=10
            )
            if result.returncode == 0:
                import json
                data = json.loads(result.stdout)
                duration_ns = data.get("total_duration", 0)
                return duration_ns / 1e9
        except Exception:
            pass
        return None

    def check_and_scale(self):
        latency = self.get_avg_latency()
        if latency is None:
            return

        now = time.time()
        if now - self.last_scale_time < self.cooldown:
            return

        if latency > self.scale_up_threshold:
            print(f"High latency ({latency:.1f}s), scaling up...")
            self._scale_up()
            self.last_scale_time = now
        elif latency < self.scale_down_threshold:
            print(f"Low latency ({latency:.1f}s), scaling down...")
            self._scale_down()
            self.last_scale_time = now

    def _scale_up(self):
        # Add new Ollama node (e.g., via Docker or cloud API)
        print("Adding Ollama node...")

    def _scale_down(self):
        # Remove idle Ollama node
        print("Removing idle Ollama node...")

# Usage
# scaler = AutoScaler()
# while True:
#     scaler.check_and_scale()
#     time.sleep(60)

输出:

TEXT 📖 仅展示
Adding Ollama node...
Removing idle Ollama node...

6. Kubernetes 部署

(1) K8s 部署架构

100%
flowchart TD
    A[Ingress<br/>nginx-ingress] --> B[Service<br/>ollama-svc]
    B --> C[Deployment<br/>ollama-deploy<br/>3 replicas]
    C --> D[Pod 1<br/>GPU + Model]
    C --> E[Pod 2<br/>GPU + Model]
    C --> F[Pod 3<br/>GPU + Model]
    G[PVC<br/>model-storage] --> D
    G --> E
    G --> F

(2) GPU 调度关键配置

资源 配置 说明
nvidia.com/gpu (requests) 资源请求 每个Pod 1 GPU
nvidia.com/gpu (limits) 资源限制 最多 1 GPU
nodeSelector GPU 节点选择 指定 GPU 节点
tolerations GPU 污点容忍 允许调度到 GPU 节点

▶ 示例 4: Ollama K8s Deployment

YAML
# ollama-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ollama
  labels:
    app: ollama
spec:
  replicas: 3
  selector:
    matchLabels:
      app: ollama
  template:
    metadata:
      labels:
        app: ollama
    spec:
      nodeSelector:
        gpu: "true"
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
      containers:
        - name: ollama
          image: ollama/ollama
          ports:
            - containerPort: 11434
          resources:
            requests:
              nvidia.com/gpu: 1
              memory: "8Gi"
            limits:
              nvidia.com/gpu: 1
              memory: "16Gi"
          env:
            - name: OLLAMA_HOST
              value: "0.0.0.0:11434"
            - name: OLLAMA_NUM_PARALLEL
              value: "4"
            - name: OLLAMA_KEEP_ALIVE
              value: "30m"
          volumeMounts:
            - name: model-storage
              mountPath: /root/.ollama
          livenessProbe:
            httpGet:
              path: /api/tags
              port: 11434
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /api/tags
              port: 11434
            initialDelaySeconds: 10
            periodSeconds: 5
      volumes:
        - name: model-storage
          persistentVolumeClaim:
            claimName: ollama-models-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: ollama-svc
spec:
  selector:
    app: ollama
  ports:
    - port: 11434
      targetPort: 11434
  type: ClusterIP

输出:

TEXT 📖 仅展示
K8s Deployment 创建成功,Pod 运行正常

输出:

TEXT 📖 仅展示
HPA 自动扩缩配置成功,集群资源监控已启用

▶ 示例 5: K8s HPA 自动扩缩

YAML
# ollama-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: ollama-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: ollama
  minReplicas: 2
  maxReplicas: 6
  metrics:
    - type: Resource
      resource:
        name: nvidia.com/gpu
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 1
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 1
          periodSeconds: 300

输出:

TEXT 📖 仅展示
# HPA 创建成功
kubectl get hpa ollama-hpa
# NAME          REFERENCE            TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
# ollama-hpa   Deployment/ollama    45%/80%         2         6         3          5m

7. 灾备与回滚

(1) 灾备策略

策略 RTO RPO 成本
主备 5-15 分钟 0 2x 硬件
多活 0(自动切换) 0 3x 硬件
冷备 1-4 小时 有损 1x+存储

(2) 配置即代码

资源 版本管理方式
Modelfile Git 仓库
Docker Compose Git 仓库
K8s Manifests Git + ArgoCD
Nginx 配置 Git 仓库
模型版本 Ollama 标签 + 文档记录

8. 综合示例:生产部署 Checklist

PYTHON
# ============================================
# Comprehensive: Production deployment checklist
# Full verification before going live
# ============================================

PRODUCTION_CHECKLIST = {
    "infrastructure": [
        ("Ollama nodes: 3+ replicas running", "CRITICAL"),
        ("GPU drivers: installed and verified", "CRITICAL"),
        ("Model storage: shared NFS/S3 mounted", "CRITICAL"),
        ("Network: internal VLAN for Ollama traffic", "HIGH"),
        ("DNS: ai.example.com resolves to LB", "HIGH"),
    ],
    "security": [
        ("Ollama bound to 127.0.0.1 on each node", "CRITICAL"),
        ("Nginx SSL certificate valid", "CRITICAL"),
        ("API Key authentication enabled", "CRITICAL"),
        ("Rate limiting configured (60 req/min)", "HIGH"),
        ("WAF rules in place", "MEDIUM"),
    ],
    "high_availability": [
        ("Health checks configured (Nginx + app layer)", "CRITICAL"),
        ("Failover tested: kill 1 node, service continues", "CRITICAL"),
        ("Load balancer: least_conn algorithm", "HIGH"),
        ("Session affinity: disabled (stateless)", "HIGH"),
    ],
    "monitoring": [
        ("Prometheus scraping all nodes", "HIGH"),
        ("Grafana dashboards created", "HIGH"),
        ("Alert rules: GPU OOM, high latency, service down", "CRITICAL"),
        ("Log aggregation: Loki or ELK", "MEDIUM"),
    ],
    "disaster_recovery": [
        ("Config in Git (Modelfile, Compose, K8s)", "HIGH"),
        ("Model backup: GGUF files on S3/NFS", "HIGH"),
        ("Chroma data backup: daily snapshot", "HIGH"),
        ("Recovery drill: tested within last 30 days", "MEDIUM"),
    ],
    "performance": [
        ("Baseline benchmark recorded", "HIGH"),
        ("OLLAMA_NUM_PARALLEL configured", "HIGH"),
        ("OLLAMA_KEEP_ALIVE=30m set", "MEDIUM"),
        ("num_ctx per endpoint optimized", "MEDIUM"),
    ]
}

def run_checklist() -> str:
    report = ["# Production Deployment Checklist\n"]
    total = 0
    checked = 0

    for category, items in PRODUCTION_CHECKLIST.items():
        report.append(f"\n## {category.replace('_', ' ').title()}")
        for item, priority in items:
            total += 1
            report.append(f"- [ ] [{priority}] {item}")

    report.append(f"\n---\n**Total items: {total}**")
    return "\n".join(report)

print(run_checklist())

❓ 常见问题

Q 3 节点集群最少需要几块 GPU?
A 最少 3 块(每节点 1 块)。如果预算有限,可用 2 节点(1 主 1 备),但故障切换时吞吐减半。
Q 模型文件需要每台机器都拉取吗?
A 如果用共享存储(NFS),只需拉取一次。如果本地存储,每台需拉取。推荐 NFS 共享 + 本地缓存方案。
Q Kubernetes 和 Docker Compose 该选哪个?
A < 5 节点用 Docker Compose(更简单)。> 5 节点或需要自动扩缩用 Kubernetes。大多数场景 Docker Compose 足够。
Q 如何测试故障转移?
A 手动停止一个 Ollama 节点(docker stop 或 systemctl stop),验证 Nginx 自动将流量转到其他节点,用户无感知。
Q Ollama 有内置集群模式吗?
A 没有。Ollama 是单实例服务,集群需外部负载均衡器组合。每个 Ollama 实例独立运行,Nginx 负责分发请求。
Q 模型版本如何管理?
A 用 Modelfile + Git 管理配置。模型文件用标签(qwen2.5:v1.0)标识版本。生产环境用固定标签,不用 :latest。

📖 小节


📝 作业

  1. 基础题(难度⭐):设计单机生产部署方案——Nginx + 单 Ollama + Chroma,画出架构图并编写 Docker Compose 配置。
  2. 进阶题(难度⭐⭐):搭建 2 节点 Ollama 集群 + Nginx 负载均衡,测试手动故障切换。
  3. 挑战题(难度⭐⭐⭐):编写完整生产部署文档——架构图、Docker Compose/K8s 配置、安全加固、监控告警、灾备方案,并完成故障切换演练。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏