Ollama: 生产部署方案
生产部署是本地 AI 从实验室到战场的最后一跃——高可用、可扩展、能容灾。
💡 提示:Nginx 反向代理 + 负载均衡是生产部署的标准架构——Nginx 负责 SSL 终止、API Key 认证、速率限制和流量分发,Ollama 节点只需绑定 127.0.0.1。多节点集群建议用
least_conn(最少连接)策略,因为推理请求耗时差异大。
📋 前置知识:需要先掌握以下内容
- 第15课:Docker容器化部署
- 第20课:安全加固
1. 你将学到
- 生产架构设计:负载均衡 + 多节点集群
- 高可用方案:健康检查 + 自动故障转移
- 扩缩容策略:基于队列的水平扩展
- Kubernetes 部署:Helm Chart 与 GPU 调度
- 灾备与回滚:模型版本管理与配置即代码
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 负载均衡,任一节点故障自动切换:
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) 生产架构全图
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 部署架构
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。
📖 小节
- 生产架构:Nginx LB + 多 Ollama 节点 + 共享存储 + Chroma
- 高可用:Nginx least_conn + 健康检查 + 自动故障转移
- 扩缩容:基于延迟/GPU 利用率触发,Docker Compose 手动或 K8s HPA 自动
- Kubernetes:GPU 调度 + PVC 持久化 + HPA 自动扩缩
- 灾备:配置即代码(Git)+ 模型备份(NFS/S3)+ 定期演练
- 生产 Checklist 覆盖基础设施、安全、高可用、监控、灾备、性能 6 大领域
📝 作业
- 基础题(难度⭐):设计单机生产部署方案——Nginx + 单 Ollama + Chroma,画出架构图并编写 Docker Compose 配置。
- 进阶题(难度⭐⭐):搭建 2 节点 Ollama 集群 + Nginx 负载均衡,测试手动故障切换。
- 挑战题(难度⭐⭐⭐):编写完整生产部署文档——架构图、Docker Compose/K8s 配置、安全加固、监控告警、灾备方案,并完成故障切换演练。