VLLMService Operator 开发第五篇:部署 Operator 并验证模型服务
前言前面几篇文章已经完成了 VLLMService Operator 的基础开发先定义 VLLMService 这个自定义资源再编写 Reconcile 逻辑让 Operator 能够根据用户声明的 VLLMService 自动创建 Deployment。到这里代码层面的逻辑已经基本打通但 Operator 最终还是要放到 Kubernetes 集群里运行所以这一篇开始进入真正的部署验证阶段。本文的目标很明确把前面写好的 VLLMService Operator 部署到 Kubernetes 集群中然后创建一个 VLLMService 自定义资源观察 Operator 是否能够自动创建 Deployment 和 Pod最后通过kubectl port-forward访问 vLLM 的 OpenAI-compatible API确认模型服务是否真正可用。本文验证的链路如下VLLMService 自定义资源 - VLLMService Operator - Deployment - Pod - vLLM OpenAI-compatible API需要注意的是本文只验证 Deployment 和 Pod 这一层暂时不会增加 Service 和 HTTPRoute。Service 会放到第六篇继续实现HTTPRoute 会放到后面的文章中再扩展。一、当前实验环境当前实验环境是一套单节点 Kubernetes 集群节点名称为master-01模型文件已经提前放在宿主机目录/data/models/Qwen2.5-1.5B-Instruct本文使用的模型、镜像和端口如下模型名称Qwen2.5-1.5B-Instruct 模型路径/data/models/Qwen2.5-1.5B-Instruct 推理镜像docker.m.daocloud.io/vllm/vllm-openai:latest 容器端口8000当前 Operator 项目路径为/root/projects/vllmservice-operator在开始部署之前项目里已经完成了以下内容1. 定义 VLLMService API 类型 2. 生成 VLLMService CRD 3. 编写 VLLMServiceReconciler 4. 在 Reconcile 中创建或更新 Deployment 5. 为 Deployment 设置 OwnerReference 6. 在 PodTemplate 中配置镜像、模型路径、资源、PVC 挂载、schedulerName 和 nodeSelector。二、构建并推送 Operator 镜像修改完controller.go之后先在项目目录下执行一次编译检查cd /root/projects/vllmservice-operator make buildmake build的作用是本地编译 Operator 代码。如果 controller 代码中存在字段写错、类型不匹配、包未导入、变量未使用等问题这一步就会直接报错。编译通过后再构建 Operator 镜像make docker-build IMGregistry.cn-hangzhou.aliyuncs.com/docker-test-dai/vllmservice-operator:v0.1这里构建的是 Operator 镜像不是 vLLM 推理服务镜像。这个镜像里运行的是 Controller Manager它的职责是监听 VLLMService 资源变化并根据 VLLMService 的 spec 自动创建和维护 Deployment。构建完成后将镜像推送到镜像仓库docker push registry.cn-hangzhou.aliyuncs.com/docker-test-dai/vllmservice-operator:v0.1三、安装 VLLMService CRDOperator 要监听 VLLMService 资源前提是 Kubernetes API Server 已经认识这个自定义资源所以需要先安装 CRDmake install安装完成后查看 CRD 是否存在kubectl get crd | grep vllm示例输出如下vllmservices.aiinfra.example.com 2026-06-23T08:28:16Z看到vllmservices.aiinfra.example.com之后说明 VLLMService 这个 CRD 已经成功安装到集群中。接着查看 CRD 的关键信息确认后面写 CR YAML 时应该使用哪个apiVersion和kindkubectl get crd vllmservices.aiinfra.example.com -o yaml重点看下面几项spec: group: aiinfra.example.com names: kind: VLLMService versions: - name: v1alpha1 served: true storage: true这里可以确定后续创建 VLLMService 时应该这样写apiVersion: aiinfra.example.com/v1alpha1 kind: VLLMService其中apiVersion由group version组成也就是aiinfra.example.com/v1alpha1kind则来自 CRD 中定义的VLLMService。如果这两个字段写错Kubernetes 就无法识别我们创建的自定义资源。四、部署 OperatorCRD 安装完成后就可以把 Operator 部署到 Kubernetes 集群中make deploy IMGregistry.cn-hangzhou.aliyuncs.com/docker-test-dai/vllmservice-operator:v0.1这个命令会使用config/default下的 Kustomize 配置把 Controller Manager、RBAC、ServiceAccount 等资源部署到集群中。部署完成后先查看 Operator 命名空间是否创建成功kubectl get ns | grep vllm示例输出vllmservice-operator-system Active 5m17s然后查看 Operator Pod 是否正常运行kubectl -n vllmservice-operator-system get pod示例输出NAME READY STATUS RESTARTS AGE vllmservice-operator-controller-manager-7d9d9f488b-4hq5b 1/1 Running 0 5m19s只要 Controller Manager Pod 处于Running状态并且READY是1/1就说明 Operator 已经启动。如果 Pod 没有正常运行可以先查看日志kubectl -n vllmservice-operator-system logs deploy/vllmservice-operator-controller-manager五、准备模型存储本文使用的是 hostPath 静态 PV因为模型文件已经提前放在master-01节点的/data/models目录下。先创建测试命名空间kubectl create namespace ai-demo然后编写 PV 和 PVCvim qwen-hostpath-pv-pvc.yaml内容如下apiVersion: v1 kind: PersistentVolume metadata: name: qwen-model-pv spec: capacity: storage: 50Gi accessModes: - ReadWriteOnce storageClassName: manual persistentVolumeReclaimPolicy: Retain hostPath: path: /data/models type: Directory nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - master-01 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: qwen-model-pvc namespace: ai-demo spec: accessModes: - ReadWriteOnce storageClassName: manual volumeName: qwen-model-pv resources: requests: storage: 20Gi应用这个 YAML后查看 PV 和 PVC 状态kubectl get pv qwen-model-pv kubectl -n ai-demo get pvc qwen-model-pvc正常情况下PVC 应该处于Bound状态。六、创建 VLLMService 自定义资源存储准备完成后就可以创建 VLLMService 资源了。编写 YAML 文件vim qwen-vllmservice.yaml内容如下apiVersion: aiinfra.example.com/v1alpha1 kind: VLLMService metadata: name: qwen-demo namespace: ai-demo spec: image: docker.m.daocloud.io/vllm/vllm-openai:latest modelPath: /data/models/Qwen2.5-1.5B-Instruct modelName: qwen2.5-1.5b-instruct replicas: 1 schedulerName: volcano nodeSelector: kubernetes.io/hostname: master-01 labels: aiinfra.example.com/model: qwen2.5 aiinfra.example.com/runtime: vllm aiinfra.example.com/team: infra aiinfra.example.com/scheduler: volcano port: 8000 resources: requests: cpu: 2 memory: 8Gi volcano.sh/vgpu-number: 1 volcano.sh/vgpu-memory: 6144 volcano.sh/vgpu-cores: 50 limits: cpu: 4 memory: 16Gi volcano.sh/vgpu-number: 1 volcano.sh/vgpu-memory: 6144 volcano.sh/vgpu-cores: 50 storage: pvcName: qwen-model-pvc mountPath: /data/models readOnly: true应用这个yaml后查看 VLLMService 是否创建成功kubectl -n ai-demo get vllmservice这里需要明确一点VLLMService 本身只是用户声明的期望状态它并不会直接运行模型。真正运行模型的是 Operator 根据这个 VLLMService 自动创建出来的 Deployment 和 Pod。也就是说用户提交的是一个更高层的抽象资源Operator 负责把这个抽象资源转换成 Kubernetes 里真正能运行的工作负载。七、验证 Deployment 和Pod是否自动创建创建 VLLMService 后Operator 的 Reconcile 逻辑会被触发。此时可以查看 Deploymentkubectl -n ai-demo get deploy正常情况下应该能看到类似输出NAME READY UP-TO-DATE AVAILABLE AGE qwen-demo 1/1 1 1 6m继续查看 Deployment 的详细信息kubectl -n ai-demo describe deploy qwen-demo这里重点关注三类信息。第一Deployment 的 labels 是否符合预期。Deployment 本身应该带有 Operator 固定设置的 labels也可以带有用户在 VLLMService 里声明的自定义 labels例如app.kubernetes.io/namevllmservice app.kubernetes.io/instanceqwen-demo app.kubernetes.io/managed-byvllmservice-operator aiinfra.example.com/modelqwen2.5 aiinfra.example.com/runtimevllm aiinfra.example.com/teaminfra aiinfra.example.com/schedulervolcano第二Deployment 的 selector 是否稳定。推荐 selector 只使用下面两个固定标签app.kubernetes.io/namevllmservice app.kubernetes.io/instanceqwen-demoDeployment 的 selector 创建后不能随便修改所以不要把用户可能会变更的自定义 labels 放进去。如果把team、runtime、model这类用户自定义 labels 放到 selector 里后续用户修改这些 labels 时Operator 可能会尝试修改 Deployment selector最终导致更新失败。第三Deployment 是否带有 OwnerReference。Operator 创建 Deployment 时应该把 VLLMService 设置成它的 owner这样当用户删除 VLLMService 时Kubernetes 的垃圾回收机制可以自动清理这个 Deployment。同时如果在SetupWithManager里配置了Owns(appsv1.Deployment{})当 Deployment 被人为修改时也可以重新触发对应 VLLMService 的 Reconcile从而体现 Operator 的自愈能力。Deployment 创建完成后继续查看 Pod 是否正常启动kubectl -n ai-demo get pod示例输出NAME READY STATUS RESTARTS AGE qwen-demo-78f5568f6b-rqghg 1/1 Running 0 6m57s看到 Pod 处于Running状态并且READY是1/1说明容器已经启动成功。接着可以查看 Pod 详细信息确认调度、资源和存储挂载是否符合预期kubectl -n ai-demo describe pod qwen-demo-78f5568f6b-rqghg重点检查以下内容1. Pod 是否调度到 master-01 2. schedulerName 是否为 volcano 3. nodeSelector 是否包含 kubernetes.io/hostnamemaster-01 4. 容器镜像是否为 docker.m.daocloud.io/vllm/vllm-openai:latest 5. 容器端口是否为 8000 6. PVC 是否挂载到 /data/models 7. requests 和 limits 中的 CPU、内存、vGPU 资源是否符合预期 8. Events 中是否存在调度失败、挂载失败、镜像拉取失败等异常。八、通过 port-forward 访问 vLLM API目前第五篇还没有创建 Service所以这里先直接对 Pod 做端口转发kubectl -n ai-demo port-forward pod/qwen-demo-78f5568f6b-rqghg 8888:8000这条命令的含义是把本机的127.0.0.1:8888转发到 Pod 的8000端口。vLLM 容器内部监听的是 8000 端口所以本地访问 8888 就相当于访问 Pod 内的 vLLM 服务。新开一个终端访问 vLLM 的模型列表接口curl http://127.0.0.1:8888/v1/models示例返回如下{ object: list, data: [ { id: qwen2.5-1.5b-instruct, object: model, created: 1782265769, owned_by: vllm, root: /data/models/Qwen2.5-1.5B-Instruct, parent: null, max_model_len: 4096, permission: [ { id: modelperm-a6379dd7d50b344e, object: model_permission, created: 1782265769, allow_create_engine: false, allow_sampling: true, allow_logprobs: true, allow_search_indices: false, allow_view: true, allow_fine_tuning: false, organization: *, group: null, is_blocking: false } ] } ] }这里重点看三个字段id: qwen2.5-1.5b-instruct owned_by: vllm root: /data/models/Qwen2.5-1.5B-Instruct这说明 vLLM 已经成功加载模型并且 OpenAI-compatible API 可以正常访问。到这一步VLLMService 到 Pod 再到 vLLM API 的链路已经打通。九、这次验证到底证明了什么通过这一轮部署和验证可以确认当前 Operator 已经具备最基础的模型服务编排能力。具体来说这次验证证明了以下几点1. VLLMService CRD 已经成功安装到 Kubernetes 集群 2. Operator Controller Manager 能够正常启动 3. 用户可以创建 VLLMService 自定义资源 4. VLLMService 创建后能够触发 Reconcile 5. Reconcile 能够根据 VLLMService 自动创建 Deployment 6. Deployment 能够拉起模型服务 Pod 7. Pod 能够挂载模型 PVC 8. Pod 能够使用指定的 schedulerName 和 nodeSelector 9. vLLM 容器能够正常启动并加载模型 10. /v1/models 接口能够返回模型列表。也就是说本文打通的是下面这条链路用户声明 VLLMService - Operator 监听并调谐 - 自动创建 Deployment - Deployment 创建 Pod - Pod 启动 vLLM - 通过 /v1/models 验证模型服务这正是 Operator 的核心价值用户不再直接编写底层 Deployment而是声明一个更贴近业务语义的 VLLMService由 Operator 负责把它转换成 Kubernetes 中真正运行的资源。十、当前阶段的不足虽然现在模型服务已经可以通过 port-forward 正常访问但这种方式并不适合作为最终访问方式因为当前访问依赖具体的 Pod 名称qwen-demo-78f5568f6b-rqghgPod 名称不是稳定入口。只要 Deployment 发生滚动更新、Pod 重建或者节点异常Pod 名称和 Pod IP 都可能变化。因此直接访问 Pod 只能用于开发和验证不适合真正的服务暴露。更合理的访问链路应该是VLLMService - Deployment - Pod - ServiceService 可以为一组 Pod 提供稳定的集群内访问入口。后续如果要接入 Gateway APIHTTPRoute 的后端也应该指向 Service而不是直接指向 Pod。本文只完成第一阶段验证下一篇会继续给 VLLMService Operator 增加 Service 自动创建能力。十一、本文总结本文完成了 VLLMService Operator 的部署验证整个过程包括1. 构建并推送 Operator 镜像 2. 安装 VLLMService CRD 3. 部署 VLLMService Operator 4. 创建模型存储 PV 和 PVC 5. 创建 VLLMService 自定义资源 6. 验证 Operator 自动创建 Deployment 7. 验证模型 Pod 正常运行 8. 通过 port-forward 访问 vLLM 的 /v1/models 接口 9. 确认模型服务能够正常返回。到这里VLLMService Operator 已经能够完成最基础的声明式编排用户只需要提交一个 VLLMServiceOperator 就可以自动创建 Deployment并拉起 vLLM 模型服务 Pod。不过这还只是第一步。当前访问方式仍然依赖 Pod port-forward不够稳定也不适合对外暴露。下一篇文章会继续扩展 Operator让它在创建 Deployment 的同时自动创建 Service为模型服务提供稳定的集群内访问入口。本人水平有限欢迎各位大佬批评指正。