前言刚好最近公司业务需求需要搭建一套CVAT (Computer Vision Annotation Tool)计算机视觉标注系统开源标注工具。虽然官方提供了 Helm Chart但在面对内网环境、老版本 K8s (v1.21)、Ceph 存储以及特定节点调度等现实需求时官方文档过于 “简洁” 了。本文完整记录了从 Chart 下载到最终成功登录的全过程重点分析了部署过程中遇到的OPA 服务连接拒绝、浏览器安全策略报错等“深坑”及其解决方案。官方 GitHub: cvat-ai/cvatHelm 版本: v3.19CVAT Chart 版本: 2.56.0Kubernetes 版本: 1.21.10 (注官方要求 1.23本文包含强行兼容方案)一、 背景与硬性需求在开始之前明确一下我们的部署环境限制这也是后续修改配置的依据节点调度限制CVAT 必须运行在名为gpusrv14的特定节点上。难点该节点被打上了污点Taintdedicatedceph:NoSchedule普通 Pod 无法调度上去。我们需要配置容忍度Tolerations。持久化存储必须对接公司现有的Ceph集群StorageClass:rook-cephfs且容量要求大。内网环境服务器无法直接连接公网依赖 HTTP Proxy。二、 部署准备与 Chart 处理1. 下载 Chart首先拉取官方 Chart 包并解压helm pull oci://registry-1.docker.io/cvat/cvat --version2.55.0tar-xzf cvat-2.55.0.tgzcdcvat2. 解决 K8s 版本兼容性问题K8s 1.21 版本官方 Chart 的Chart.yaml里硬性规定了kubeVersion: 1.23.0-0。如果你像我一样还在用 K8s 1.21直接 install 安装会报错。解决方案修改Chart.yaml强行降低版本要求。虽然有风险但实测 CVAT 2.56 在 1.21 上核心 API 依然兼容。# Chart.yamldescription:A Helm chart for KuberneteskubeVersion: 1.21.0-0# 修改此处3. 初始化配置文件为了保持原文件整洁我们复制一份配置进行覆盖cpvalues.yaml values.override2.yaml# 或(一样)helm show values.values.override2.yaml三、 核心配置修改values.yaml1. 存储配置对接 Ceph默认的 PVC 只有 20Gi 且不指定 StorageClass。我们需要修改cvat和kvrocks(Redis) 两处的存储配置。修改values.override2.yamldefaultStorage:enabled:truestorageClassName:rook-cephfs# 重点指定你的 Ceph SC 名称accessModes:-ReadWriteMany# 建议多读写模式方便扩展size:1000Gi# 生产环境给大点2. 调度配置亲和性与污点容忍重点这是最复杂的一步。我们要把 Pod 钉死在gpusrv14节点上并且要能“忍受”它的污点。步骤 A给节点打标签kubectl labelnodegpusrv14.aa.com -n gpu cvat-nodetrue步骤 B检查节点污点kubectl describe node/gpusrv14.aa.com -n gpu|grepTaints# 输出: dedicatedceph:NoSchedule# 这意味着如果没有对应的 tolerationPod 无法调度上来。步骤 C修改 Helm 配置找到cvat.backend部分。CVAT Chart 设计比较好的一点是backend 下的配置会被 worker 等子组件继承所以不需要每个微服务都改一遍。cvat:backend:# 1. 强行指定节点双重保险affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:-matchExpressions:-key:kubernetes.io/hostnameoperator:Invalues:-gpusrv14.aa.com# 2. 简单的节点选择器nodeSelector:cvat-node:true# 3. 污点容忍必须配置否则 Pendingtolerations:-key:group# 多余的operator:Equalvalue:gpueffect:NoSchedule-key:dedicated# 对应 dedicatedceph:NoScheduleoperator:Equalvalue:cepheffect:NoSchedule四、 启动安装与踩坑实录执行安装命令helminstallgpu-cvat.-n gpu-cvat --create-namespace -f values.yaml -f values.override2.yaml安装命令下去后并没有想象中的“一键成功”而是迎来了两个深坑。坑一OPA 服务无限重启 (Connection refused)现象gpu-cvat-opaPod 状态异常查看日志发现健康检查失败requests.exceptions.ConnectionError: HTTPConnectionPool(hostopa, port8181): ... Connection refused原因分析OPA (Open Policy Agent) 是 CVAT 的权限策略引擎。默认情况下OPA 启动时只监听localhost(127.0.0.1)。K8s 的 Liveness Probe探针是通过 Pod IP 访问的属于“外部”访问。因为监听地址限制探针连接被拒绝K8s 判定服务不健康导致无限重启。解决方案临时暴力有效尝试通过 Helm--set传参失败可能是 Chart 里的参数映射有问题直接修改 Deployment 最快。kubectl edit deployment gpu-cvat-opa -n gpu-cvat找到args部分手动添加--addr0.0.0.0:8181强制监听所有网络接口containers:-args:-run---server---addr0.0.0.0:8181# 【关键修改】让它监听所有IP---bundle修改保存后Pod 重启状态瞬间变为 Running。其中尝试过 upgrade 这些服务但不生效helm upgrade... --set cvat.opa.extraArgs{--addr0.0.0.0:8181}--reuse-values可能某些版本的 CVAT Helm Chart 中extraArgs 的传递方式或者 OPA 的默认启动命令Entrypoint优先级更高导致修改根本没有生效。永久解决方案未验证手动编辑的配置会在下一次helm upgrade时丢失。为了让配置持久化需要将参数写入values.override2.yaml。在配置文件中找到cvat.opa部分加入extraArgscvat:opa:# 强制让 OPA 监听所有接口解决健康检查不通的问题extraArgs:---addr0.0.0.0:8181这样下次执行 Helm 更新时这个参数就会自动带上不用再手动改 Deployment 了。坑二浏览器白屏与 405 报错 (HTTP vs HTTPS)现象配置好 Nginx HTTP 转发后访问页面出现白屏或报错F12API 请求报405 Not Allowed或401 Unauthorized。控制台报错The Cross-Origin-Opener-Policy header has been ignored...JS 报错ReferenceError: structuredClone is not defined。原因分析这是现代浏览器的安全机制。CVAT 是一个重型前端应用使用了SharedArrayBuffer等特性来实现多线程处理。浏览器规定这些高级特性必须在“安全上下文” (Secure Context) 下才能运行。所谓安全上下文要么是localhost要么必须是HTTPS。如果是 HTTP这些 API 直接被禁用导致代码崩盘。解决方案配置 Nginx SSL 安全头我们必须配置 HTTPS并且在 Nginx 中显式添加 COOP/COEP 响应头。Nginx 配置参考server { listen 443 ssl; server_name gpu-cvat.aa.com; # 统一使用一个域名 ssl_certificate /etc/nginx/conf.d/sans_crt2/server.crt; ssl_certificate_key /etc/nginx/conf.d/sans_crt2/server.key; # 1. 后端 API 转发 location ~ ^/(api|auth|admin|django-static)/ { proxy_pass http://gpu-cvat-backend-service.gpu-cvat.svc.cluster.local:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 2. 前端页面转发 location / { proxy_pass http://gpu-cvat-frontend-service.gpu-cvat.svc.cluster.local:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 必须加上这两个 Header否则 CVAT 内部 JS 会报错 add_header Cross-Origin-Opener-Policy same-origin; add_header Cross-Origin-Embedder-Policy require-corp; } }关于Nginx 转发规则虽然官方文档未明说后端路径但通过 F12 开发者工具观察网络请求并参考 Chart 源码中 templates/ingress.yaml 的分流定义可以明确 /api、/auth、/admin 及 /django-static 这类涉及到数据交互和 Django 后台的请求必须精准转发至后端 Service而其余流量则指向前端。五、 创建管理员部署成功且页面能打开后发现——没有账号登录。CVAT 默认不创建任何账号需要手动进入容器执行 Django 命令来创建超级管理员教程下一步。一键创建命令# 你部署 CVAT 的命名空间exportHELM_RELEASE_NAMESPACEgpu-cvat# 你给这个 Helm 实例起的名称exportHELM_RELEASE_NAMEgpu-cvatBACKEND_POD_NAME$(kubectl get pod --namespace $HELM_RELEASE_NAMESPACE -ltierbackend,app.kubernetes.io/instance$HELM_RELEASE_NAME,componentserver -ojsonpath{.items[0].metadata.name})kubectlexec-it --namespace$HELM_RELEASE_NAMESPACE$BACKEND_POD_NAME-c cvat-backend -- python manage.py createsuperuser按照提示输入用户名、邮箱和密码即可。创建好后就可以使用这个账号登录系统。六、 存储验证前面在values.yaml中配置了storageClassName: rook-cephfs后并且已经部署完成了现在验证一下是不是用了 ceph 的存储。1. 查看集群内 PVC 状态通过kubectl get pv命令可以看到CVAT 的后端数据backend-data和 kvrocks 已经成功绑定了我们预设的1000Gi大容量存储且状态均为Bound。# 查看 CVAT 相关的持久化卷kubectl getpv|grepgpu-cvat注意可以看到gpu-cvat-backend-data的模式是RWX(ReadWriteMany)这对于多副本部署至关重要。2. 追踪底层的 Ceph Subvolume为了确认数据确实落在了 CephFS 上我们可以随机挑选一个 PV 进行describe查看它在 Ceph 内部的subvolumePathkubectl describe pv/pvc-ec561f53-c54b-47d7-9ddb-6e01850046df|grepsubvolumePath# 输出结果# subvolumePath/volumes/csi/csi-vol-cee516.../de776093...3. Ceph 后台验证最后登录Ceph Dashboard或直接在 Ceph 节点查看文件系统可以看到该路径已经自动创建并开始记录数据。总结这次 Helm 部署过程暴露了 CVAT Chart 的几个痛点微服务拆分过细虽然架构先进但运维成本高。如果不是backend配置支持继承单独配置每个 Pod 的调度策略会非常痛苦。默认配置不合理OPA 默认只监听localhost导致健康检查失败这是一个非常低级的默认配置坑迫使我们必须手动介入。文档缺失官方文档未强调 HTTPS 对于前端功能的强制性导致很容易在 HTTP 环境下浪费时间排查 JS 报错。内网友好度低内网环境下Grafana 插件下载、依赖包拉取都需要自己在values.yaml里注入 Proxy 环境变量。感觉这个 Helm-Chart 做得不够完善并且加上公司内网原因导致没法一键部署。