Qwen2.5-1.5B部署教程:Kubernetes StatefulSet部署Qwen2.5-1.5B服务集群
Qwen2.5-1.5B部署教程:Kubernetes StatefulSet部署Qwen2.5-1.5B服务集群
1. 为什么需要在Kubernetes中部署Qwen2.5-1.5B?
你可能已经试过本地运行Qwen2.5-1.5B——启动快、响应顺、不联网、隐私强。但当团队多人需要同时使用,或者你想把它集成进内部知识库、客服系统、自动化工作流时,单机Streamlit服务就显得力不从心了:端口冲突、显存争抢、无健康检查、无法自动扩缩容、重启后对话状态丢失……这些问题不是“能不能用”,而是“能不能稳、能不能管、能不能扩”。
本教程不讲怎么在笔记本上跑通一个demo,而是带你把Qwen2.5-1.5B真正变成一个生产级AI服务:
- 支持多实例并行推理,负载自动分发
- 每个实例独占GPU资源,避免显存踩踏
- 对话状态与模型加载分离,重启不丢上下文(通过StatefulSet特性保障)
- 内置就绪探针(readinessProbe)和存活探针(livenessProbe),K8s自动剔除异常Pod
- 镜像预置全部依赖,无需每次拉取模型文件,启动即服务
- 全YAML声明式管理,可GitOps化、可CI/CD集成
这不是“把本地脚本扔进容器”,而是一套面向工程落地的轻量大模型服务编排方案——专为1.5B这类显存友好型模型设计,兼顾效率、稳定与可维护性。
2. 部署前准备:环境与资源确认
2.1 硬件与集群要求
| 项目 | 最低要求 | 推荐配置 | 说明 |
|---|---|---|---|
| GPU | NVIDIA T4(16GB显存)×1 | A10(24GB)或L4(24GB)×1 | Qwen2.5-1.5B-Instruct在bfloat16下约占用9.2GB显存,预留缓冲空间 |
| CPU | 4核 | 8核 | 用于模型加载、tokenization、HTTP服务调度 |
| 内存 | 16GB | 32GB | 模型权重+缓存+系统开销 |
| 存储 | 5GB空闲空间(模型+镜像) | SSD挂载持久卷(PV) | 模型文件需持久化,避免每次Pod重建重复下载 |
注意:本方案不依赖NVIDIA GPU Operator,仅需集群已安装
nvidia-device-plugin且kubectl get nodes -o wide中节点OS-IMAGE列显示Ubuntu或CentOS等主流发行版即可。
2.2 软件依赖清单
- Kubernetes v1.24+(支持
StatefulSet原生特性) kubectl已配置访问集群权限helm(可选,用于后续扩展如Prometheus监控)- Docker或containerd(用于构建镜像)
git(克隆示例仓库)
2.3 模型文件准备(关键!)
Qwen2.5-1.5B-Instruct官方模型需提前下载并组织为标准Hugging Face格式:
# 在任意有网络的机器上执行(推荐使用hf-mirror加速)
mkdir -p /tmp/qwen2.5-1.5b-instruct
cd /tmp/qwen2.5-1.5b-instruct
# 使用huggingface-hub下载(需先pip install huggingface-hub)
huggingface-cli download --resume-download \
Qwen/Qwen2.5-1.5B-Instruct \
--local-dir . \
--include "config.json" \
--include "tokenizer.*" \
--include "pytorch_model.bin" \
--include "model.safetensors" \
--include "generation_config.json"
验证目录结构应为:
/tmp/qwen2.5-1.5b-instruct/
├── config.json
├── generation_config.json
├── model.safetensors # 或 pytorch_model.bin
├── tokenizer.json
├── tokenizer.model
└── tokenizer_config.json
提示:若使用
safetensors格式(更安全、加载更快),请确保后续Dockerfile中transformers>=4.37.0;若用.bin,则无版本强依赖。
3. 构建生产就绪镜像
3.1 创建Dockerfile
新建文件 Dockerfile.qwen,内容如下(已针对K8s场景深度优化):
FROM python:3.10-slim-bookworm
# 设置非root用户提升安全性
RUN groupadd -g 1001 -f app && useradd -r -u 1001 -g app app
USER app
# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖(精简+指定版本,避免冲突)
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
# 创建模型挂载点(非绑定宿主机路径,由K8s VolumeMount控制)
RUN mkdir -p /app/model
WORKDIR /app
# 复制应用代码
COPY app.py streamlit_config.toml ./
# 暴露Streamlit默认端口
EXPOSE 8501
# 启动命令(禁用Streamlit默认重载,适配K8s生命周期)
CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0", "--server.headless=true", "--server.enableCORS=false"]
配套 requirements.txt(严格锁定版本,避免K8s中随机失败):
streamlit==1.32.0
transformers==4.38.2
torch==2.1.2+cu118
accelerate==0.27.2
sentence-transformers==2.2.2
bitsandbytes==0.43.1 # 可选,如需4bit量化
3.2 编写核心服务代码 app.py
import os
import torch
import streamlit as st
from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
from threading import Thread
# 从环境变量读取模型路径(K8s中由VolumeMount注入)
MODEL_PATH = os.getenv("MODEL_PATH", "/app/model")
@st.cache_resource
def load_model():
st.info(" 正在加载模型...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
MODEL_PATH,
device_map="auto",
torch_dtype="auto",
trust_remote_code=True,
)
return tokenizer, model
def main():
st.set_page_config(
page_title="Qwen2.5-1.5B 本地对话助手",
page_icon="🧠",
layout="centered",
initial_sidebar_state="expanded",
)
st.title(" Qwen2.5-1.5B 本地对话助手")
st.caption("基于Kubernetes StatefulSet部署 · 全链路本地化 · 零数据出域")
# 初始化模型(首次访问触发)
if "tokenizer" not in st.session_state or "model" not in st.session_state:
try:
st.session_state.tokenizer, st.session_state.model = load_model()
st.success(" 模型加载完成,可以开始对话!")
except Exception as e:
st.error(f" 模型加载失败:{e}")
st.stop()
# 对话历史(StatefulSet中每个Pod独立维护,不共享)
if "messages" not in st.session_state:
st.session_state.messages = [
{"role": "system", "content": "你是通义千问Qwen2.5-1.5B-Instruct,专注提供准确、简洁、友好的中文对话服务。"}
]
# 显示历史消息
for msg in st.session_state.messages[1:]: # 跳过system消息
st.chat_message(msg["role"]).write(msg["content"])
# 输入框
if prompt := st.chat_input("请输入你的问题..."):
st.session_state.messages.append({"role": "user", "content": prompt})
st.chat_message("user").write(prompt)
# 构建对话模板(严格对齐官方)
messages = st.session_state.messages.copy()
text = st.session_state.tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
model_inputs = st.session_state.tokenizer(
[text], return_tensors="pt"
).to(st.session_state.model.device)
# 流式生成(提升用户体验)
streamer = TextIteratorStreamer(
st.session_state.tokenizer,
skip_prompt=True,
skip_special_tokens=True
)
generate_kwargs = {
"input_ids": model_inputs.input_ids,
"max_new_tokens": 1024,
"temperature": 0.7,
"top_p": 0.9,
"do_sample": True,
"streamer": streamer,
}
# 异步生成
thread = Thread(target=st.session_state.model.generate, kwargs=generate_kwargs)
thread.start()
# 流式输出
with st.chat_message("assistant"):
message_placeholder = st.empty()
full_response = ""
for new_token in streamer:
full_response += new_token
message_placeholder.markdown(full_response + "▌")
message_placeholder.markdown(full_response)
st.session_state.messages.append({"role": "assistant", "content": full_response})
# 清空按钮(释放显存+重置历史)
if st.sidebar.button("🧹 清空对话", use_container_width=True):
torch.cuda.empty_cache()
st.session_state.messages = [
{"role": "system", "content": "你是通义千问Qwen2.5-1.5B-Instruct,专注提供准确、简洁、友好的中文对话服务。"}
]
st.rerun()
if __name__ == "__main__":
main()
配套 streamlit_config.toml(关闭开发模式,启用生产参数):
[server]
headless = true
enableCORS = false
enableXSSProtection = true
maxUploadSize = 50
[theme]
primaryColor = "#2a9d8f"
backgroundColor = "#fafafa"
secondaryBackgroundColor = "#ffffff"
textColor = "#262730"
font = "sans serif"
3.3 构建并推送镜像
# 构建镜像(假设registry为私有Harbor或阿里云ACR)
docker build -f Dockerfile.qwen -t your-registry/qwen2.5-1.5b:v1.0 .
# 登录并推送
docker login your-registry
docker push your-registry/qwen2.5-1.5b:v1.0
4. Kubernetes StatefulSet部署详解
4.1 创建命名空间与资源配置
新建 qwen-ns.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: qwen-inference
labels:
name: qwen-inference
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: qwen-sa
namespace: qwen-inference
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: qwen-role
namespace: qwen-inference
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: qwen-rolebinding
namespace: qwen-inference
subjects:
- kind: ServiceAccount
name: qwen-sa
namespace: qwen-inference
roleRef:
kind: Role
name: qwen-role
apiGroup: rbac.authorization.k8s.io
4.2 模型持久卷(PV)与声明(PVC)
新建 qwen-pv-pvc.yaml(以NFS为例,也可替换为CSI驱动):
apiVersion: v1
kind: PersistentVolume
metadata:
name: qwen-model-pv
labels:
type: model
spec:
capacity:
storage: 5Gi
accessModes:
- ReadOnlyMany # 模型只读,安全第一
nfs:
server: nfs-server.example.com
path: "/exports/qwen2.5-1.5b-instruct"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: qwen-model-pvc
namespace: qwen-inference
spec:
accessModes:
- ReadOnlyMany
resources:
requests:
storage: 5Gi
selector:
matchLabels:
type: model
关键设计:
ReadOnlyMany确保所有Pod共享同一份模型文件,零拷贝、零同步、零一致性风险。
4.3 StatefulSet核心部署文件
新建 qwen-statefulset.yaml:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: qwen-inference
namespace: qwen-inference
labels:
app: qwen-inference
spec:
serviceName: "qwen-headless"
replicas: 2 # 可根据GPU节点数调整
selector:
matchLabels:
app: qwen-inference
template:
metadata:
labels:
app: qwen-inference
spec:
serviceAccountName: qwen-sa
containers:
- name: qwen-app
image: your-registry/qwen2.5-1.5b:v1.0
ports:
- containerPort: 8501
name: http
env:
- name: MODEL_PATH
value: "/app/model"
volumeMounts:
- name: model-storage
mountPath: /app/model
readOnly: true
- name: tmp-storage
mountPath: /tmp
resources:
limits:
nvidia.com/gpu: 1 # 关键:显卡独占
memory: "12Gi"
cpu: "4"
requests:
nvidia.com/gpu: 1
memory: "10Gi"
cpu: "2"
livenessProbe:
httpGet:
path: /healthz
port: 8501
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /readyz
port: 8501
initialDelaySeconds: 45
periodSeconds: 15
startupProbe:
httpGet:
path: /startupz
port: 8501
failureThreshold: 30
periodSeconds: 5
volumes:
- name: model-storage
persistentVolumeClaim:
claimName: qwen-model-pvc
- name: tmp-storage
emptyDir: {}
nodeSelector:
kubernetes.io/os: linux
accelerator: nvidia # 标记含GPU节点
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
4.4 Headless Service与Ingress(可选)
新建 qwen-service-ingress.yaml:
apiVersion: v1
kind: Service
metadata:
name: qwen-headless
namespace: qwen-inference
labels:
app: qwen-inference
spec:
clusterIP: None
selector:
app: qwen-inference
ports:
- port: 8501
name: http
---
apiVersion: v1
kind: Service
metadata:
name: qwen-service
namespace: qwen-inference
labels:
app: qwen-inference
spec:
type: ClusterIP
selector:
app: qwen-inference
ports:
- port: 8501
targetPort: 8501
---
# 若需公网访问,添加Ingress(此处省略,按实际Ingress Controller配置)
5. 部署与验证全流程
5.1 一键部署命令
# 应用全部资源
kubectl apply -f qwen-ns.yaml
kubectl apply -f qwen-pv-pvc.yaml
kubectl apply -f qwen-statefulset.yaml
kubectl apply -f qwen-service-ingress.yaml
# 查看Pod状态(等待Running & Ready)
kubectl -n qwen-inference get pods -w
# 输出示例:
# NAME READY STATUS RESTARTS AGE
# qwen-inference-0 1/1 Running 0 45s
# qwen-inference-1 1/1 Running 0 32s
5.2 日志与健康检查
# 查看某Pod日志(确认模型加载成功)
kubectl -n qwen-inference logs qwen-inference-0 | grep ""
# 手动调用就绪探针(应返回200)
kubectl -n qwen-inference exec qwen-inference-0 -- curl -s http://localhost:8501/readyz
# 进入Pod测试本地推理(快速验证)
kubectl -n qwen-inference exec -it qwen-inference-0 -- bash
# 然后在容器内运行:
# python -c "from transformers import AutoTokenizer; print(AutoTokenizer.from_pretrained('/app/model').encode('hello'))"
5.3 访问服务
- 集群内访问:
curl http://qwen-service.qwen-inference.svc.cluster.local:8501 - NodePort方式(修改Service类型):
http://<NODE_IP>:30851 - Ingress方式:
https://qwen.your-domain.com
打开浏览器,你会看到熟悉的Streamlit聊天界面——但背后已是K8s调度的、可伸缩、可监控、可灰度发布的生产服务。
6. 运维与调优建议
6.1 显存监控(关键!)
# 实时查看各Pod GPU显存占用
kubectl -n qwen-inference top pods -o wide --use-protocol-buffers
# 结合nvidia-smi(需进入Pod)
kubectl -n qwen-inference exec qwen-inference-0 -- nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits
建议:设置Prometheus告警规则,当
nvidia_gpu_duty_cycle> 95%持续5分钟,或nvidia_gpu_memory_used_bytes> 14Gi时触发扩容。
6.2 扩容策略
- 水平扩容:直接修改StatefulSet
replicas,K8s自动创建新Pod(每个Pod独占1卡) - 垂直扩容:调整
resources.limits.nvidia.com/gpu为2(需A100/A800等双卡机型),配合device_map="balanced"实现单Pod多卡推理 - 自动扩缩容(KPA):需配合Knative,超出本教程范围,但StatefulSet是其底层基础
6.3 故障自愈实践
- 当Pod因OOM被K8s Kill时,StatefulSet自动重建,
volumeClaimTemplates确保/data目录(如缓存)保留 - 若模型加载失败,
startupProbe会阻止Pod进入Ready状态,避免流量打入 livenessProbe检测到HTTP无响应,自动重启容器,不依赖节点级恢复
7. 总结:从本地玩具到生产服务的关键跨越
部署Qwen2.5-1.5B从来不只是“让模型跑起来”。本教程带你完成三个本质跃迁:
- 从单点到服务:Streamlit单进程 → K8s多实例Service,支持并发、负载均衡、故障隔离
- 从临时到持久:本地文件路径 → PV/PVC声明式挂载,模型一次准备、全集群复用
- 从手动到自治:人工启停 → Liveness/Readiness/Startup三探针驱动,K8s自动健康治理
你获得的不再是一个“能用的Demo”,而是一个:
🔹 可交付(YAML即文档)、
🔹 可观测(指标+日志+事件)、
🔹 可扩展(水平/垂直/自动)、
🔹 可审计(RBAC+PodSecurityPolicy)、
🔹 可回滚(镜像Tag+StatefulSet Revision)
的轻量大模型服务基座。它足够小——1.5B参数、单卡起步;也足够大——支撑起你团队的第一代AI原生应用。
下一步,你可以:
→ 将qwen-service接入内部API网关,供其他服务调用
→ 为StatefulSet添加HorizontalPodAutoscaler,按GPU利用率自动扩缩
→ 基于qwen-inference-0 Pod导出Prometheus指标,构建LLM服务大盘
→ 替换为Qwen2.5-7B或Qwen2-VL,复用同一套编排框架
真正的AI工程化,始于对最小可行单元的严谨封装——而Qwen2.5-1.5B,正是那个刚刚好、不冗余、不妥协的起点。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐

所有评论(0)