DeepSeek-R1-Distill-Qwen-1.5B部署教程:Kubernetes集群中部署高可用Streamlit对话服务
1. 为什么要在Kubernetes里跑一个1.5B的Streamlit对话服务?
你可能第一反应是:“1.5B模型?还要上K8s?是不是小题大做了?”
其实恰恰相反——这正是轻量模型在真实生产环境中落地的关键一步。
本地单机跑Streamlit确实简单,但一旦涉及团队共享、7×24小时可用、故障自动恢复、资源弹性伸缩、日志统一收集、版本灰度发布……单机模式就立刻捉襟见肘。而Kubernetes不是“为了上云而上云”,它是让轻量模型真正具备工程可用性的基础设施底座。
DeepSeek-R1-Distill-Qwen-1.5B这个模型很特别:它不是参数动辄几十B的“巨无霸”,而是经过深度蒸馏、保留DeepSeek强推理骨架+Qwen稳定架构的“精悍型选手”。1.5B参数意味着它能在RTX 3090(24G显存)、A10(24G)、甚至L4(24G)这类主流入门级AI卡上稳稳运行,显存占用仅约11–13GB(FP16),推理延迟低于2秒(中等长度问题)。但它又足够聪明——能一步步拆解数学题、写出带注释的Python函数、解释逻辑悖论,甚至模拟多步代码调试过程。
所以,本教程不讲“怎么在笔记本上跑起来”,而是带你完成一次面向生产环境的闭环部署:
模型文件离线预置(不依赖Hugging Face实时下载)
Streamlit服务容器化封装(含健康检查、优雅退出)
Kubernetes Deployment + Service + Ingress三件套编排
GPU资源精准调度与显存隔离(避免多租户干扰)
对话状态无感持久化(重启不丢历史,非必须但推荐)
日志结构化输出(适配ELK或Loki)
零配置自动适配CPU/GPU环境(兼容无GPU测试集群)
这不是炫技,而是把“能跑”变成“敢用”。
2. 部署前准备:环境、镜像与模型路径约定
2.1 硬件与平台要求
| 组件 | 最低要求 | 推荐配置 | 说明 |
|---|---|---|---|
| Kubernetes集群 | v1.24+ | v1.26–v1.28 | 需启用DevicePlugin支持GPU |
| GPU节点 | NVIDIA L4 / A10 / RTX 3090(24G显存) | 同上,建议预留2GB显存余量 | nvidia.com/gpu: 1资源请求 |
| CPU节点(备用) | 4核8G内存 | 8核16G | 无GPU时自动降级为CPU推理(速度慢但可用) |
| 存储 | 本地PV或NFS(≥5GB) | 同上,建议SSD | 存放模型文件/data/models/ds_1.5b |
注意:本方案不依赖Hugging Face Hub在线拉取模型。所有模型权重、分词器、配置文件需提前下载并挂载至Pod内固定路径(如
/data/models/ds_1.5b),确保离线、可审计、可复现。
2.2 构建自定义Docker镜像
我们不使用官方Streamlit基础镜像,而是基于nvidia/cuda:12.1.1-runtime-ubuntu22.04构建轻量定制镜像,理由很实在:
- 官方镜像默认装了太多无关Python包,体积超800MB,启动慢、拉取耗时;
- CUDA 12.1.1 兼容A10/L4/3090/4090全系显卡,且PyTorch 2.1+已原生支持;
- Ubuntu 22.04 LTS 提供长期安全更新,比18.04更稳妥。
以下是精简版Dockerfile(实测镜像体积仅623MB,含全部依赖):
# syntax=docker/dockerfile:1 FROM nvidia/cuda:12.1.1-runtime-ubuntu22.04 # 设置时区与语言 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \ apt-get update && apt-get install -y locales && \ locale-gen en_US.UTF-8 && \ update-locale LANG=en_US.UTF-8 # 安装Python与核心依赖 RUN apt-get install -y python3.10 python3-pip python3-venv && \ rm -rf /var/lib/apt/lists/* # 升级pip并安装必要系统工具 RUN pip3 install --upgrade pip && \ pip3 install setuptools wheel # 复制应用代码(假设项目结构:/app/app.py, /app/requirements.txt) WORKDIR /app COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt # 安装Streamlit(指定版本防兼容问题) RUN pip3 install streamlit==1.32.0 # 复制主程序 COPY app.py . # 创建模型挂载点(只声明,不写入内容) VOLUME ["/data/models"] # 健康检查:检测Streamlit是否监听端口 HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD curl -f http://localhost:8501/_stcore/health || exit 1 # 启动命令(--server.port绑定到8501,--server.address=0.0.0.0允许外部访问) CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0", "--server.enableCORS=false", "--server.enableXsrfProtection=false"]requirements.txt内容精简如下(仅保留必需项,不含jupyter/tensorboard等冗余包):
torch==2.1.2+cu121 transformers==4.37.2 accelerate==0.27.2 sentencepiece==0.2.0 scipy==1.11.4构建命令:
docker build -t ds15b-streamlit:v1 .
推送至私有仓库(如Harbor)后,K8s即可拉取。
2.3 模型文件本地化准备(关键!)
请务必提前完成以下三步,否则Pod将因找不到模型卡在CrashLoopBackOff:
从魔塔平台下载完整模型包(非Git LFS链接,要
.safetensors权重文件)
地址示例(以实际魔塔页面为准):https://modelscope.cn/models/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B/files
下载后解压得到:config.json,model.safetensors,tokenizer.model,tokenizer_config.json,special_tokens_map.json按约定路径组织文件
/data/models/ds_1.5b/ ├── config.json ├── model.safetensors ├── tokenizer.model ├── tokenizer_config.json └── special_tokens_map.json挂载方式选择(二选一)
- 推荐:HostPath PV(适合单节点或测试集群)
将上述/data/models目录映射为PersistentVolume,Pod通过PVC挂载; - 生产级:NFS或对象存储网关(如MinIO+S3FS)
实现多节点共享模型,避免重复拷贝。
- 推荐:HostPath PV(适合单节点或测试集群)
3. Kubernetes核心编排:Deployment + Service + Ingress
3.1 Deployment:带GPU感知与资源隔离的Pod模板
以下YAML定义了一个生产就绪的Deployment,重点特性包括:
nvidia.com/gpu: 1显式申请1张GPU,避免被调度到无GPU节点;resources.limits精确限制GPU显存(nvidia.com/gpu-memory: 22Gi),防止OOM;livenessProbe和readinessProbe使用HTTP健康检查,而非进程PID;terminationGracePeriodSeconds: 60确保Streamlit优雅关闭(清空缓存、释放显存);env中注入MODEL_PATH=/data/models/ds_1.5b,供Python代码读取。
# ds15b-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: ds15b-streamlit labels: app: ds15b-streamlit spec: replicas: 1 selector: matchLabels: app: ds15b-streamlit template: metadata: labels: app: ds15b-streamlit spec: # 节点亲和性:只调度到有NVIDIA GPU的节点 nodeSelector: kubernetes.io/os: linux nvidia.com/gpu.present: "true" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule containers: - name: streamlit image: harbor.example.com/ai/ds15b-streamlit:v1 imagePullPolicy: IfNotPresent ports: - containerPort: 8501 name: http env: - name: MODEL_PATH value: "/data/models/ds_1.5b" resources: limits: nvidia.com/gpu: 1 nvidia.com/gpu-memory: 22Gi # 关键:显存硬限,防爆显存 memory: 4Gi cpu: "2" requests: nvidia.com/gpu: 1 memory: 3Gi cpu: "1" volumeMounts: - name: models-volume mountPath: /data/models livenessProbe: httpGet: path: /_stcore/health port: 8501 initialDelaySeconds: 60 periodSeconds: 30 timeoutSeconds: 5 readinessProbe: httpGet: path: /_stcore/health port: 8501 initialDelaySeconds: 45 periodSeconds: 15 timeoutSeconds: 3 terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumes: - name: models-volume persistentVolumeClaim: claimName: ds15b-models-pvc restartPolicy: Always terminationGracePeriodSeconds: 60提示:
nvidia.com/gpu-memory是NVIDIA Device Plugin 1.0+支持的扩展资源,需确认集群已启用(kubectl get nodes -o wide查看nvidia.com/gpu-memory列)。
3.2 Service与Ingress:让对话服务真正可访问
仅靠Deployment,服务还只是集群内部IP可达。我们需要:
- Service:提供稳定的ClusterIP,供Ingress控制器转发;
- Ingress:暴露HTTP/HTTPS入口,支持域名访问(如
chat.example.com); - 可选:TLS证书(Let’s Encrypt自动签发)。
# ds15b-service-ingress.yaml apiVersion: v1 kind: Service metadata: name: ds15b-service spec: selector: app: ds15b-streamlit ports: - port: 80 targetPort: 8501 protocol: TCP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ds15b-ingress annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/proxy-body-size: "50m" nginx.ingress.kubernetes.io/proxy-read-timeout: "300" spec: ingressClassName: nginx tls: - hosts: - chat.example.com secretName: ds15b-tls rules: - host: chat.example.com http: paths: - path: / pathType: Prefix backend: service: name: ds15b-service port: number: 80部署后验证:
curl -k https://chat.example.com/_stcore/health应返回{"status":"ok"}
4. Streamlit应用层适配:让代码真正“懂K8s”
光有K8s编排还不够,app.py本身也得配合容器化环境做微调。以下是关键改造点(对比原始单机版):
4.1 模型加载逻辑:支持挂载路径 + 自动降级
import os import torch from transformers import AutoTokenizer, AutoModelForCausalLM # 1. 从环境变量读取模型路径(K8s注入) MODEL_PATH = os.getenv("MODEL_PATH", "/data/models/ds_1.5b") if not os.path.exists(MODEL_PATH): st.error(f"❌ 模型路径不存在:{MODEL_PATH}。请检查PVC挂载是否成功。") st.stop() # 2. 自动选择设备:优先GPU,无GPU则用CPU(不报错) device = "cuda" if torch.cuda.is_available() else "cpu" st.info(f" 检测到运行环境:{device.upper()}") # 3. 加载模型(关键:device_map="auto" + torch_dtype自动推断) @st.cache_resource def load_model(): tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH) model = AutoModelForCausalLM.from_pretrained( MODEL_PATH, device_map="auto", # 自动分配GPU层/CPU层 torch_dtype="auto", # 自动选float16/bfloat16/float32 trust_remote_code=True, ) return tokenizer, model tokenizer, model = load_model()4.2 对话状态管理:兼容Pod重启
原始Streamlit每次刷新页面都会重置st.session_state。但在K8s中,我们希望:
- 用户刷新页面不丢失当前对话(短期);
- Pod重启后,若配置了Redis/PVC,可恢复最近10轮(长期);
本教程采用轻量级PVC挂载JSON文件方案(无需引入Redis):
# 在app.py顶部添加 import json import time from pathlib import Path STATE_FILE = Path("/data/chat_history.json") def load_history(): if STATE_FILE.exists(): try: return json.loads(STATE_FILE.read_text(encoding="utf-8")) except: return [] return [] def save_history(history): STATE_FILE.parent.mkdir(exist_ok=True) STATE_FILE.write_text(json.dumps(history, ensure_ascii=False, indent=2), encoding="utf-8") # 初始化session_state if "messages" not in st.session_state: st.session_state.messages = load_history() # 页面底部「清空」按钮逻辑增强 if st.sidebar.button("🧹 清空对话 & 释放显存", type="secondary"): st.session_state.messages = [] save_history([]) # 立即落盘 torch.cuda.empty_cache() # 主动释放GPU缓存 st.rerun()PVC需额外挂载
/data路径(与模型分离),保障状态独立。
5. 效果验证与日常运维要点
5.1 三步快速验证部署是否成功
| 步骤 | 操作 | 预期结果 | 说明 |
|---|---|---|---|
| ① Pod状态 | kubectl get pods -l app=ds15b-streamlit | STATUS=Running,READY=1/1 | 若为Init:0/1,检查PVC是否绑定;若为CrashLoopBackOff,kubectl logs -f <pod>查模型路径错误 |
| ② 服务连通性 | kubectl port-forward svc/ds15b-service 8080:80→ 浏览器访问http://localhost:8080 | 显示Streamlit聊天界面,底部提示“考考 DeepSeek R1...” | 确认Service与Pod网络通 |
| ③ 端到端推理 | 输入“1+1等于几?”,回车 | 气泡显示思考过程(如<think>这是基础算术运算...</think>)+ 回答 | 验证模型加载、tokenizer、推理链全通 |
5.2 生产环境必须关注的5个运维点
显存泄漏监控
kubectl top pod -l app=ds15b-streamlit每5分钟检查,若MEMORY持续上涨,检查torch.no_grad()是否生效、st.cache_resource是否误用st.cache_data。日志结构化采集
Streamlit默认日志无结构。在app.py开头添加:import logging logging.basicConfig( level=logging.INFO, format='{"time":"%(asctime)s","level":"%(levelname)s","msg":"%(message)s"}', handlers=[logging.StreamHandler()] )配合Fluentd/Loki,可实现按
user_id、query_length、latency_ms字段检索。模型热更新(零停机)
不重建Pod,仅替换PVC中模型文件 → 删除旧/data/models/ds_1.5b→ 拷贝新模型 →kubectl rollout restart deploy/ds15b-streamlit。并发连接数控制
Streamlit默认不限流。在app.py中加:import threading _lock = threading.Lock() # 在生成响应前加锁 with _lock: outputs = model.generate(...)安全加固(必做)
- Ingress启用
nginx.ingress.kubernetes.io/whitelist-source-range限制IP; - 禁用
--server.headless=false(防止本地调试端口暴露); app.py中移除所有st.secrets硬编码密钥。
- Ingress启用
6. 总结:轻量模型的高可用,从来不是“能不能”,而是“要不要”
部署DeepSeek-R1-Distill-Qwen-1.5B到Kubernetes,表面看是技术动作,背后是一次认知升级:
- 它打破了“小模型=玩具”的偏见——1.5B也能支撑严肃场景的逻辑推理;
- 它验证了“轻量服务同样需要工业级编排”——可用性、可观测性、可维护性缺一不可;
- 它提供了“私有化AI落地”的最小可行范式——不堆硬件、不追参数、不碰公有云,专注把一件事做稳。
你不需要立刻上马整套K8s生态。完全可以从单节点K3s集群起步(k3s server --disable traefik --disable servicelb),用本文YAML一键部署,再逐步接入监控、日志、CI/CD。真正的技术价值,永远藏在“开箱即用”和“长期可靠”的交界处。
现在,打开你的终端,执行kubectl apply -f ds15b-deployment.yaml——
那个会思考、能解题、懂格式、守隐私的DeepSeek R1,正等着和你开始第一场本地对话。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。