基于K8s的高性能Web服务器构建实践
在AIGC浪潮席卷内容生产的今天,如何将强大的AI模型转化为稳定、可扩展的在线服务,已成为工程落地的核心挑战。尤其是文本到视频(T2V)这类计算密集型任务,既要保证生成质量,又要实现秒级响应和高并发处理——这对底层基础设施提出了极高要求。
我们选择Wan2.2-T2V-5B这款轻量级但高效的50亿参数模型作为切入点,尝试在一个基于消费级GPU的Kubernetes集群中,构建一套面向真实业务场景的高性能Web服务系统。目标很明确:让AI视频生成像调用普通API一样简单、可靠,并能随负载自动伸缩。
整个系统不仅需要完成基础部署,更要集成持久化存储、统一接入、自动扩缩容与全链路监控等关键能力。这不仅是技术组件的堆叠,更是一次对MLOps工程化思维的实战检验。
架构设计与环境准备
项目采用单Master双Worker的经典K8s架构,所有节点运行 CentOS 7.9,Kubernetes 版本为 v1.28,Docker 使用 24.0.2。两台 Worker 节点均配备 NVIDIA T4 GPU,用于承载模型推理负载。
各主机IP规划如下:
| 主机名 | IP 地址 | 角色说明 |
|---|---|---|
| master | 192.168.0.20 | Kubernetes 控制平面节点 |
| worker1 | 192.168.0.21 | 工作节点(含 NVIDIA T4 GPU) |
| worker2 | 192.168.0.22 | 工作节点(含 NVIDIA T4 GPU) |
| nfs-server | 192.168.0.36 | NFS 文件共享服务器 |
| harbor | 192.168.0.34 | 私有容器镜像仓库 |
| prometheus | 192.168.0.33 | Prometheus + Grafana 监控中心 |
| ansible | 192.168.0.30 | 运维自动化中控机 |
在正式搭建前,需确保所有节点的基础环境一致:关闭防火墙与SELinux是必须步骤,否则可能引发网络策略或挂载异常。
systemctl stop firewalld && systemctl disable firewalld sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config静态IP配置也需提前完成,避免因DHCP变动导致集群通信中断。以ens33接口为例:
BOOTPROTO=static ONBOOT=yes IPADDR=192.168.0.20 NETMASK=255.255.255.0 GATEWAY=192.168.0.2 DNS1=8.8.8.8 DNS2=114.114.114.114重启网络服务后,务必通过ping和ssh验证连通性。
批量运维:Ansible 的引入
面对多节点协同管理的需求,手动操作显然不可持续。我们在独立主机ansible上部署 Ansible,实现对整个集群的批量控制。
首先生成SSH密钥对并分发至各目标主机:
ssh-keygen -t rsa -b 2048 ssh-copy-id master ssh-copy-id worker1 # ... 其他主机同理随后安装 Ansible 并定义主机清单:
[masters] 192.168.0.20 [workers] 192.168.0.21 192.168.0.22 [nfs] 192.168.0.36 [harbor] 192.168.0.34 [prometheus] 192.168.0.33使用ansible all -m ping测试连通性成功后,即可编写 Playbook 实现初始化脚本统一下发、软件包批量安装等任务。这种“声明式+幂等”的管理模式,极大降低了后期维护成本。
持久化存储:NFS 与 PV/PVC 设计
AI视频生成会产生大量输出文件,这些结果必须持久化保存且可供多个Pod共享访问。我们选用 NFS 作为共享存储方案,因其配置简单、兼容性好,非常适合中小规模部署。
在nfs-server上安装并启动 NFS 服务:
yum install nfs-utils -y mkdir -p /nfs/videos && chmod 777 /nfs/videos编辑/etc/exports允许子网内所有节点读写:
/nfs/videos 192.168.0.0/24(rw,sync,no_root_squash,no_all_squash)导出并启用服务:
exportfs -arv systemctl restart nfs-server && systemctl enable nfs-server在每个 K8s 节点上测试挂载:
mkdir /mnt/video-share mount -t nfs 192.168.0.36:/nfs/videos /mnt/video-share df -Th | grep nfs确认无误后,在K8s中创建PV和PVC资源对象:
# pv-video.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv-video-output spec: capacity: storage: 50Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /nfs/videos server: 192.168.0.36# pvc-video.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-video-output spec: accessModes: - ReadWriteMany resources: requests: storage: 20Gi storageClassName: nfs应用后验证状态是否为Bound:
kubectl apply -f pv-video.yaml kubectl apply -f pvc-video.yaml kubectl get pvc pvc-video-output这一设计使得后续部署的模型服务可以安全地将视频写入统一目录,避免数据丢失或分散。
模型服务封装与镜像构建
Wan2.2-T2V-5B 模型本身是一个PyTorch项目,我们需要将其封装成可通过HTTP触发的微服务。为此,在worker1上开发一个轻量Go语言API层。
编写服务入口(main.go)
package main import ( "fmt" "log" "net/http" "os/exec" "time" ) func generateVideo(w http.ResponseWriter, r *http.Request) { prompt := r.URL.Query().Get("prompt") if prompt == "" { http.Error(w, "missing prompt", http.StatusBadRequest) return } outputPath := fmt.Sprintf("/shared/videos/%d.mp4", time.Now().Unix()) cmd := exec.Command("python", "/app/generate.py", "--prompt", prompt, "--output", outputPath) err := cmd.Run() if err != nil { http.Error(w, "generation failed: "+err.Error(), http.StatusInternalServerError) return } w.Write([]byte(fmt.Sprintf("Video generated at %s\n", outputPath))) } func healthz(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) } func main() { http.HandleFunc("/generate", generateVideo) http.HandleFunc("/healthz", healthz) log.Println("Server starting on :8080...") log.Fatal(http.ListenAndServe(":8080", nil)) }其中/healthz是健康检查接口,供探针调用。
Dockerfile 构建镜像
使用 NVIDIA 官方 PyTorch 镜像为基础,预装依赖库:
FROM nvcr.io/nvidia/pytorch:23.10-py3 WORKDIR /app COPY . /app RUN pip install torch torchvision transformers diffusers flask pillow \ && chmod +x /app/start.sh EXPOSE 8080 ENTRYPOINT ["python", "server.py"]注:
generate.py为实际调用 HuggingFace Diffusers 加载 Wan2.2-T2V-5B 模型并执行推理的脚本。
构建并推送至私有 Harbor 仓库:
docker build -t 192.168.0.34:5001/ai/wan2.2-t2v-5b:v1 . docker login 192.168.0.34:5001 docker push 192.168.0.34:5001/ai/wan2.2-t2v-5b:v1镜像托管于内部Harbor,既保障安全性,又提升拉取效率。
K8s部署:GPU支持与资源配置
要让Pod正确调度到GPU节点并使用显卡资源,必须先在所有Worker节点安装nvidia-container-toolkit。
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | \ sudo tee /etc/yum.repos.d/nvidia-docker.repo yum install -y nvidia-container-toolkit systemctl restart docker接着创建带GPU限制的Deployment:
# deployment-wan22.yaml apiVersion: apps/v1 kind: Deployment metadata: name: wan22-t2v-deployment spec: replicas: 2 selector: matchLabels: app: wan22-t2v template: metadata: labels: app: wan22-t2v spec: containers: - name: wan22-t2v image: 192.168.0.34:5001/ai/wan2.2-t2v-5b:v1 ports: - containerPort: 8080 resources: limits: nvidia.com/gpu: 1 memory: "8Gi" cpu: "4" requests: nvidia.com/gpu: 1 memory: "6Gi" cpu: "2" volumeMounts: - name: video-storage mountPath: /shared/videos livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 60 periodSeconds: 30 timeoutSeconds: 5 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 3 volumes: - name: video-storage persistentVolumeClaim: claimName: pvc-video-output这里的关键点包括:
- 显式声明nvidia.com/gpu: 1,确保调度器识别GPU需求;
- 设置合理的内存与CPU请求/限制,防止资源争抢;
- 挂载PVC实现视频输出持久化;
- 添加 Liveness 和 Readiness 探针,增强服务自愈能力。
部署后查看Pod分布情况:
kubectl apply -f deployment-wan22.yaml kubectl get pods -o wide | grep wan22正常情况下,两个副本应分别运行在worker1和worker2上。
再创建NodePort类型Service暴露端口:
# service-wan22.yaml apiVersion: v1 kind: Service metadata: name: wan22-service spec: selector: app: wan22-t2v type: NodePort ports: - protocol: TCP port: 8080 targetPort: 8080 nodePort: 31080kubectl apply -f service-wan22.yaml此时可通过任意Worker节点IP加31080端口访问服务。
统一接入:Ingress-Nginx 的部署与配置
NodePort虽然可用,但在生产环境中不够灵活。我们通过 Ingress 实现基于域名的统一入口管理。
使用 Helm 安装 Ingress-Nginx Controller:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm install ingress-nginx ingress-nginx/ingress-nginx \ --set controller.nodeSelector."node-role\.kubernetes\.io/master"="" \ --set service.type=NodePort注意:若Master节点默认污点存在,需添加容忍或设置节点选择器。
然后定义Ingress规则:
# ingress-wan22.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: wan22-ingress annotations: kubernetes.io/ingress.class: nginx spec: rules: - host: t2v.sanchuang.ai http: paths: - path: / pathType: Prefix backend: service: name: wan22-service port: number: 8080应用后,在本地hosts添加解析:
192.168.0.21 t2v.sanchuang.ai即可通过域名发起请求:
curl "http://t2v.sanchuang.ai/generate?prompt=a%20cat%20dancing%20in%20the%20rain" # 返回:Video generated at /shared/videos/1714567890.mp4这种方式更贴近真实线上环境,也为未来多服务共用同一个LB打下基础。
自动扩缩容:HPA 的实现逻辑
当用户并发增加时,固定副本数可能导致响应延迟甚至超时。我们借助 HorizontalPodAutoscaler(HPA)实现基于CPU利用率的动态扩缩。
首先部署 Metrics Server,它是HPA获取指标的数据源:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml修改其Deployment配置,加入跳过证书验证选项(适用于测试环境):
args: - --kubelet-insecure-tls - --kubelet-preferred-address-types=InternalIP等待几分钟后,验证指标采集是否正常:
kubectl top nodes kubectl top pods接着创建HPA策略:
# hpa-wan22.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: wan22-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: wan22-t2v-deployment minReplicas: 2 maxReplicas: 6 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60应用后观察HPA状态:
kubectl apply -f hpa-wan22.yaml kubectl get hpa -w初始状态通常显示类似12%/60%,表示当前平均CPU使用率为12%,低于60%的目标阈值,因此不会扩容。
全链路监控:Prometheus + Grafana 搭建
没有监控的系统如同盲人驾车。我们独立部署 Prometheus 与 Grafana,实现从节点到应用层的全方位观测。
在prometheus主机解压并安装二进制包:
wget https://github.com/prometheus/prometheus/releases/download/v2.43.0/prometheus-2.43.0.linux-amd64.tar.gz tar xvf prometheus-2.43.0.linux-amd64.tar.gz mv prometheus-2.43.0.linux-amd64 /opt/prometheus配置 systemd 服务开机自启:
[Unit] Description=Prometheus After=network.target [Service] ExecStart=/opt/prometheus/prometheus --config.file=/opt/prometheus/prometheus.yml Restart=always [Install] WantedBy=multi-user.targetsystemctl daemon-reload systemctl start prometheus systemctl enable prometheus关键在于prometheus.yml中的抓取配置:
scrape_configs: - job_name: 'kubernetes-nodes' kubernetes_sd_configs: - role: node relabel_configs: - source_labels: [__address__] regex: '(.*):10250' target_label: __address__ replacement: '${1}:9100' - job_name: 'wan22-service' static_configs: - targets: ['192.168.0.21:31080', '192.168.0.22:31080']其中第一个任务通过Node Exporter采集节点资源使用率;第二个则直接轮询服务端口,可用于记录QPS、延迟等业务指标。
Grafana 安装同样简单:
yum install grafana-enterprise-9.1.2-1.x86_64.rpm -y systemctl start grafana-server systemctl enable grafana-server登录http://192.168.0.33:3000,添加Prometheus为数据源,并导入社区流行的 Node Exporter 和 K8s 监控仪表板模板,即可获得直观的可视化视图。
性能压测与弹性验证
一切就绪后,使用ab工具模拟高并发请求,验证系统的稳定性与弹性表现。
安装工具:
yum install httpd-tools -y发起1000次请求,保持50个并发连接:
ab -n 1000 -c 50 "http://t2v.sanchuang.ai/generate?prompt=a%20dog%20running"同时开启监控面板,重点关注以下变化:
kubectl get hpa --watch kubectl top pods理想情况下会看到:
- CPU利用率迅速上升,突破60%后HPA开始扩容;
- 新Pod陆续启动并加入服务,负载逐渐回落;
- Grafana中QPS曲线平稳上升,无明显错误率 spike;
- NFS共享目录持续生成新视频文件,命名唯一不冲突。
这表明整套系统具备良好的弹性和健壮性,能够应对突发流量冲击。
实践反思与演进建议
回顾整个部署过程,有几个关键经验值得总结:
GPU驱动与CUDA版本匹配至关重要。初期曾因驱动版本过低导致容器内无法识别GPU设备,最终通过升级至CUDA 12.1配套驱动解决。
NFS权限问题容易被忽视。若未设置
no_root_squash,容器以root身份写入时会被映射为nobody,造成权限拒绝。建议生产环境结合UID/GID映射做精细化控制。探针配置需合理延时。模型首次加载耗时较长(约30~60秒),若
initialDelaySeconds设置过短,会导致Pod反复重启。根据实际warm-up时间调整是必要操作。轻量化模型的价值凸显。尽管Wan2.2-T2V-5B画质不及百亿参数大模型,但其能在消费级GPU上实现秒级生成,非常适合快速原型验证、短视频模板生成等高频交互场景,性价比极高。
未来可进一步优化方向包括:
- 引入 Kafka 实现异步任务队列,避免长时间请求阻塞HTTP连接;
- 使用 Redis 缓存热门提示词的结果,降低重复推理开销;
- 增加 JWT/OAuth 认证机制,提升API安全性;
- 将部分静态资源(如前端页面)交由Nginx或CDN托管,减轻核心服务压力。
在AIGC时代,算法创新固然重要,但真正决定技术能否落地的,往往是背后的工程体系。本次实践证明,借助 Kubernetes 的强大编排能力,即使是50亿参数级别的AI模型,也能被轻松封装为高性能、可伸缩的Web服务。这种“轻量化+实时性”的组合,正在成为新一代AI应用的标准范式。
下一步,我们将探索更大规模模型的服务化路径,比如通过TensorRT优化推理性能,或采用KEDA实现事件驱动的细粒度扩缩,持续推动AI能力向业务前线高效输送。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考