企业内部大模型 GPU 推理服务平台建设部署笔记
0. 部署线路图
GPU 节点初始化 → NVIDIA 驱动与 CUDA → containerd GPU 运行时 → 加入既有 Kubernetes → NVIDIA Device Plugin → 模型存储与镜像凭据 → vLLM → Open WebUI → HTTPS 入口 → DCGM Exporter、Prometheus、Grafana → 网页验收 → 回滚、备份与恢复。



1. 固定参数
| 项目 | 固定值 |
|---|---|
| 运维入口 | ops-bastion=10.30.10.5,账号 opsadmin |
| GPU 节点 | gpu01=10.30.20.11、gpu02=10.30.20.12、gpu03=10.30.20.13、gpu04=10.30.20.14;共 10 张 NVIDIA GPU |
| 集群 | 已有 k8s-prod;API=https://k8s-api.ops.internal:6443 |
| 版本 | Rocky Linux 9、NVIDIA Driver 550.90.07、CUDA 12.4、containerd 1.7.22、NVIDIA Container Toolkit 1.17.4、Device Plugin v0.17.1 |
| 项目对象 | namespace=llm-prod;模型=Qwen2.5-14B-Instruct;vLLM Service=vllm-qwen:8000;Open WebUI=open-webui:8080 |
| 镜像与入口 | Harbor=harbor.ops.internal;访问域名=llm.ops.internal;TLS Secret=llm-ops-tls |
| 存储与监控 | 模型 PVC=qwen-model-rwx/cephfs-rwx;WebUI PVC=open-webui-rwo/fast-rwo;DCGM Exporter 由 prometheus-monitor 采集 |
课程中的明文密码、NodePort 直暴和 privileged 容器均不进入本生产手册。
2. GPU 节点盘点
执行机器:gpu01、gpu02、gpu03、gpu04,root。四台全部执行。
# 查看 GPU 设备与型号。
lspci -nn | grep -i nvidia
# 确认默认 nouveau 驱动未加载。
lsmod | grep nouveau || true
# 查看系统、内核、时间和数据盘。
cat /etc/os-release; uname -r; timedatectl status; lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT
# 记录尚未安装驱动时的状态。
nvidia-smi || true
预期结果:每台显示 NVIDIA 控制器;模型数据盘无业务文件;未安装驱动时 nvidia-smi 不可用属于正常。
通过条件:四台主机名、IP 与 GPU 总数符合第 1 节;发现旧业务、已加入其他集群或磁盘已有数据时停止。
失败排查:lspci 无 GPU 时检查 BIOS/云平台直通;时间不同步检查 chronyd 和 NTP;磁盘已挂载须由资产负责人确认。
3. 安装驱动、CUDA 与 GPU 容器运行时
执行机器:四台 GPU 节点,root;先 gpu01 验证成功后再推广。
# 安装仓库管理和证书工具。
dnf install -y dnf-plugins-core ca-certificates
# 添加固定 CUDA 12 的 RHEL 9 签名仓库。
dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel9/x86_64/cuda-rhel9.repo
# 安装固定驱动、CUDA 与 NVIDIA Container Toolkit。
dnf install -y nvidia-driver:550.90.07 cuda-toolkit-12-4 nvidia-container-toolkit-1.17.4
# 生成启动镜像后重启以加载新内核模块。
dracut -f && reboot
重连后执行:
# 验证宿主机驱动、CUDA 和 GPU 数量。
nvidia-smi
nvcc --version
# 写入 containerd 的 NVIDIA GPU runtime 配置。
nvidia-ctk runtime configure --runtime=containerd
# 重启并确认 containerd 正常。
systemctl restart containerd
systemctl is-active containerd
# 验证容器内能访问同一张 GPU。
ctr -n k8s.io images pull harbor.ops.internal/base/cuda:12.4.1-base-ubuntu22.04
ctr -n k8s.io run --rm --gpus 0 harbor.ops.internal/base/cuda:12.4.1-base-ubuntu22.04 gpu-smoke nvidia-smi
预期结果:nvidia-smi 显示 Driver 550.90.07、CUDA 12.4;容器内显示相同 GPU UUID;containerd 为 active。
通过条件:四台都完成容器内 GPU 验证后才接入集群。
失败排查:NVIDIA-SMI 失败时执行 journalctl -k -b | grep -i nvidia;容器无 GPU 时重新执行 nvidia-ctk runtime configure --runtime=containerd,重启 containerd;禁止以 privileged 绕过问题。
4. Kubernetes GPU 调度
执行机器:ops-bastion 的 opsadmin。
# 验证当前 kubeconfig 指向生产集群且 API 就绪。
kubectl cluster-info
kubectl get --raw='/readyz'
# 确认四台 GPU 节点已按受控流程加入且没有旧业务。
kubectl get nodes -o wide -l node-role.kubernetes.io/gpu=true
# 标记 GPU 推理用途并阻止普通业务进入。
kubectl label node gpu01 gpu.ops.internal/role=inference --overwrite
kubectl label node gpu02 gpu.ops.internal/role=inference --overwrite
kubectl label node gpu03 gpu.ops.internal/role=inference --overwrite
kubectl label node gpu04 gpu.ops.internal/role=inference --overwrite
kubectl taint node gpu01 gpu.ops.internal/dedicated=inference:NoSchedule --overwrite
kubectl taint node gpu02 gpu.ops.internal/dedicated=inference:NoSchedule --overwrite
kubectl taint node gpu03 gpu.ops.internal/dedicated=inference:NoSchedule --overwrite
kubectl taint node gpu04 gpu.ops.internal/dedicated=inference:NoSchedule --overwrite
然后应用公司审计并缓存的 NVIDIA Device Plugin v0.17.1 清单:
# 创建设备插件并等待四台 GPU 节点上的 DaemonSet 就绪。
kubectl apply -f /home/opsadmin/nvidia-device-plugin-v0.17.1.yaml
kubectl -n kube-system rollout status daemonset/nvidia-device-plugin --timeout=180s
# 确认 GPU 已作为可调度资源上报。
kubectl get nodes -l gpu.ops.internal/role=inference -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu
预期结果:DaemonSet 为 Desired: 4, Ready: 4;GPU 总数为 10。
通过条件:每个 GPU 节点均上报 nvidia.com/gpu;普通 Pod 没有 toleration 时不能进入 GPU 节点。
失败排查:Device Plugin CrashLoopBackOff 时执行 kubectl -n kube-system logs ds/nvidia-device-plugin,随后回查第 3 节容器内 GPU 验证。
5. 模型存储、镜像凭据与 vLLM
执行机器:ops-bastion 的 opsadmin。模型权重由受控任务预置到 CephFS;vLLM 只读挂载模型,不从公网下载。
# 创建命名空间和密管下发的镜像、TLS 凭据。
kubectl create namespace llm-prod --dry-run=client -o yaml | kubectl apply -f -
kubectl -n llm-prod create secret docker-registry harbor-llm-pull --from-file=.dockerconfigjson=/etc/ops-secrets/llm/harbor-pull.json --type=kubernetes.io/dockerconfigjson --dry-run=client -o yaml | kubectl apply -f -
kubectl -n llm-prod create secret tls llm-ops-tls --cert=/etc/ops-pki/llm/fullchain.pem --key=/etc/ops-pki/llm/privkey.pem --dry-run=client -o yaml | kubectl apply -f -
# 创建 80Gi 共享模型卷和 20Gi WebUI 数据卷。
kubectl -n llm-prod apply -f /home/opsadmin/llm-storage.yaml
kubectl -n llm-prod get pvc
# 发布固定 vLLM 镜像和模型服务。
kubectl apply --dry-run=server -f /home/opsadmin/vllm-qwen.yaml
kubectl apply -f /home/opsadmin/vllm-qwen.yaml
kubectl -n llm-prod rollout status deploy/vllm-qwen --timeout=900s
llm-storage.yaml 固定使用 qwen-model-rwx、ReadWriteMany、cephfs-rwx 和 open-webui-rwo、ReadWriteOnce、fast-rwo。vllm-qwen.yaml 固定使用镜像 harbor.ops.internal/llm/vllm-openai:v0.6.4-cuda12.4;请求并限制一张 nvidia.com/gpu;只读挂载 /models/Qwen2.5-14B-Instruct;监听 TCP/8000;最大显存使用率 0.90、最大上下文 8192;只允许调度至 inference GPU 节点。
vllm-qwen.yaml 全文:
apiVersion: apps/v1 # Deployment API。
kind: Deployment # 管理 vLLM 副本。
metadata: {name: vllm-qwen, namespace: llm-prod} # 固定名称和命名空间。
spec:
replicas: 1 # 一个副本独占一张 GPU。
selector: {matchLabels: {app: vllm-qwen}} # 选择 vLLM Pod。
template:
metadata: {labels: {app: vllm-qwen}} # Service 发现标签。
spec:
nodeSelector: {gpu.ops.internal/role: inference} # 仅 GPU 推理节点。
tolerations: [{key: gpu.ops.internal/dedicated,operator: Equal,value: inference,effect: NoSchedule}] # 容忍专用污点。
imagePullSecrets: [{name: harbor-llm-pull}] # 使用 Harbor 凭据。
containers:
- name: vllm # 推理容器。
image: harbor.ops.internal/llm/vllm-openai:v0.6.4-cuda12.4 # 固定镜像。
args: [vllm,serve,/models/Qwen2.5-14B-Instruct,--served-model-name,Qwen2.5-14B-Instruct,--host,0.0.0.0,--port,"8000",--gpu-memory-utilization,"0.90",--max-model-len,"8192",--tensor-parallel-size,"1"] # 固定推理参数。
ports: [{name: http,containerPort: 8000}] # HTTP 端口。
resources: {requests: {cpu: "4",memory: 16Gi,nvidia.com/gpu: 1},limits: {cpu: "8",memory: 32Gi,nvidia.com/gpu: 1}} # 资源与 GPU 限制。
volumeMounts: [{name: model,mountPath: /models,readOnly: true}] # 模型只读卷。
readinessProbe: {httpGet: {path: /health,port: http},initialDelaySeconds: 60,periodSeconds: 10} # 就绪探针。
volumes: [{name: model,persistentVolumeClaim: {claimName: qwen-model-rwx}}] # PVC 绑定。
---
apiVersion: v1 # 核心 API。
kind: Service # 集群内推理服务。
metadata: {name: vllm-qwen,namespace: llm-prod} # 固定服务名。
spec: {selector: {app: vllm-qwen},ports: [{name: http,port: 8000,targetPort: http}]} # TCP 转发。
通过条件:模型 PVC 已 Bound,vLLM 为 1/1 Running,Pod limits 显示一张 GPU。失败时检查 PVC、模型路径与 kubectl -n llm-prod logs deploy/vllm-qwen;显存不足降低上下文长度,不提高显存上限硬顶。
6. Open WebUI、HTTPS、DCGM 与网页验收
执行机器:ops-bastion 的 opsadmin。
# 发布 Open WebUI、Nginx HTTPS Ingress 与 GPU 指标采集。
kubectl apply -f /home/opsadmin/open-webui-ingress.yaml
kubectl apply -f /home/opsadmin/dcgm-monitor.yaml
kubectl -n llm-prod rollout status deploy/open-webui --timeout=300s
kubectl -n monitor rollout status ds/dcgm-exporter --timeout=300s
# 验证 HTTPS、模型接口和采集目标。
curl --fail --silent --show-error --cacert /etc/ops-pki/llm/ca.pem https://llm.ops.internal/health
kubectl -n llm-prod exec deploy/vllm-qwen -- curl -fsS http://127.0.0.1:8000/v1/models
open-webui-ingress.yaml 固定把 Open WebUI 对接 http://vllm-qwen.llm-prod.svc:8000/v1,使用 open-webui-rwo 和 llm-ops-tls,域名为 llm.ops.internal,IngressClass 为 nginx。不得创建 NodePort。dcgm-monitor.yaml 仅在 inference GPU 节点运行;ServiceMonitor 位于 monitor,标签 release=prometheus-monitor,端口 metrics,30 秒采集。
open-webui-ingress.yaml 全文:
apiVersion: apps/v1 # Deployment API。
kind: Deployment # Web 对话入口。
metadata: {name: open-webui,namespace: llm-prod} # 固定名称。
spec:
replicas: 1 # 数据卷为 RWO,固定单副本。
selector: {matchLabels: {app: open-webui}} # 选择 WebUI Pod。
template:
metadata: {labels: {app: open-webui}} # Service 标签。
spec:
imagePullSecrets: [{name: harbor-llm-pull}] # 仓库凭据。
containers:
- name: open-webui # WebUI 容器。
image: harbor.ops.internal/llm/open-webui:v0.5.10 # 固定镜像。
env: [{name: ENABLE_OLLAMA_API,value: "False"},{name: OPENAI_API_BASE_URL,value: http://vllm-qwen.llm-prod.svc:8000/v1}] # 对接 vLLM。
ports: [{name: http,containerPort: 8080}] # Web 服务端口。
volumeMounts: [{name: data,mountPath: /app/backend/data}] # 数据卷。
volumes: [{name: data,persistentVolumeClaim: {claimName: open-webui-rwo}}] # RWO PVC。
---
apiVersion: v1 # 核心 API。
kind: Service # 集群内部 WebUI 服务。
metadata: {name: open-webui,namespace: llm-prod} # 固定服务名。
spec: {selector: {app: open-webui},ports: [{name: http,port: 8080,targetPort: http}]} # Service 端口。
---
apiVersion: networking.k8s.io/v1 # Ingress API。
kind: Ingress # HTTPS 七层入口。
metadata: {name: open-webui,namespace: llm-prod} # 固定入口名。
spec: {ingressClassName: nginx,tls: [{hosts: [llm.ops.internal],secretName: llm-ops-tls}],rules: [{host: llm.ops.internal,http: {paths: [{path: /,pathType: Prefix,backend: {service: {name: open-webui,port: {name: http}}}}]}}]} # TLS 和路由。
通过条件:网页显示 Open WebUI 登录页且证书匹配;模型接口返回 Qwen2.5-14B-Instruct;Grafana Dashboard 12239 显示 10 张 GPU 的利用率、显存、温度和功耗;DCGM Target 为 UP。GPU 利用率持续 10 分钟超过 95%、显存超过 90% 或温度超过 85℃时,由现有 Alertmanager 路由投递企业微信、短信或电话。
dcgm-monitor.yaml 全文:
apiVersion: apps/v1 # DaemonSet API。
kind: DaemonSet # 每台 GPU 节点一份 exporter。
metadata: {name: dcgm-exporter,namespace: monitor,labels: {app: dcgm-exporter}} # 固定对象标签。
spec:
selector: {matchLabels: {app: dcgm-exporter}} # DaemonSet 选择器。
template:
metadata: {labels: {app: dcgm-exporter}} # Service 发现标签。
spec:
nodeSelector: {gpu.ops.internal/role: inference} # 仅 GPU 节点。
tolerations: [{key: gpu.ops.internal/dedicated,operator: Equal,value: inference,effect: NoSchedule}] # 容忍专用污点。
containers: [{name: dcgm-exporter,image: harbor.ops.internal/base/dcgm-exporter:3.3.8-3.6.0-ubuntu22.04,ports: [{name: metrics,containerPort: 9400}]}] # 指标端口。
---
apiVersion: v1 # 核心 API。
kind: Service # 为 Prometheus 提供发现入口。
metadata: {name: dcgm-exporter,namespace: monitor,labels: {app: dcgm-exporter}} # 固定标签。
spec: {selector: {app: dcgm-exporter},ports: [{name: metrics,port: 9400,targetPort: metrics}]} # 指标 Service。
---
apiVersion: monitoring.coreos.com/v1 # Prometheus Operator API。
kind: ServiceMonitor # 定义 Prometheus 抓取目标。
metadata: {name: dcgm-exporter,namespace: monitor,labels: {release: prometheus-monitor}} # 与 release 匹配。
spec: {selector: {matchLabels: {app: dcgm-exporter}},namespaceSelector: {matchNames: [monitor]},endpoints: [{port: metrics,path: /metrics,interval: 30s}]} # 固定采集端口和间隔。
7. 故障、回滚、备份与恢复
Pod Pending 时执行 kubectl -n llm-prod describe pod -l app=vllm-qwen,检查 GPU、污点和 PVC。GPU 不可见时查看 kubectl -n kube-system logs ds/nvidia-device-plugin 并回查第 3 节。网页 502 时检查 Ingress、open-webui Endpoint 和日志。DCGM Down 时检查 DaemonSet、驱动和 ServiceMonitor。
# 记录版本并回滚异常推理发布。
kubectl -n llm-prod rollout history deploy/vllm-qwen
kubectl -n llm-prod get deploy,svc,ingress,pvc -o yaml > /srv/backup/llm/$(date +%F)-k8s.yaml
kubectl -n llm-prod rollout undo deploy/vllm-qwen
kubectl -n llm-prod rollout status deploy/vllm-qwen --timeout=900s
# 创建并校验当天恢复点。
mkdir -p /srv/backup/llm/$(date +%F)
cp /srv/backup/llm/$(date +%F)-k8s.yaml /srv/backup/llm/$(date +%F)/k8s.yaml
sha256sum /srv/backup/llm/$(date +%F)/k8s.yaml > /srv/backup/llm/$(date +%F)/SHA256SUMS
sha256sum -c /srv/backup/llm/$(date +%F)/SHA256SUMS
恢复顺序:校验恢复点和 StorageClass → 恢复模型卷与权重 → 密管重建 Harbor/TLS Secret → 恢复 vLLM → 恢复 Open WebUI、Ingress、DCGM → 重做网页、模型接口和 Grafana 验收。不得从普通备份恢复私钥、仓库凭据或模型 Token。
8. 最终核查
- 四台 GPU 节点均 Ready,nvidia.com/gpu 总数为 10。
- 宿主机和容器内 nvidia-smi 均通过。
- vLLM 使用一张 GPU、模型卷只读,Open WebUI 仅经 HTTPS 访问。
- DCGM、Prometheus、Grafana 和告警通知均已验收。
- 回滚、备份校验和恢复演练均已归档。