Helm 实战指南(四):生产环境 Helm 部署 CVAT 全记录:Ceph 存储、GPU 节点污点调度与 HTTPS 指南

前言

刚好!最近公司业务需求,需要搭建一套 CVAT (Computer Vision Annotation Tool) 计算机视觉标注系统,开源标注工具。

虽然官方提供了 Helm Chart,但在面对内网环境、老版本 K8s (v1.21)、Ceph 存储以及特定节点调度等现实需求时,官方文档过于 "简洁" 了。

本文完整记录了从 Chart 下载到最终成功登录的全过程,重点分析了部署过程中遇到的 OPA 服务连接拒绝浏览器安全策略报错 等"深坑"及其解决方案。

  • 官方 GitHub : cvat-ai/cvat
  • Helm 版本: v3.19
  • CVAT Chart 版本: 2.56.0
  • Kubernetes 版本: 1.21.10 (注:官方要求 >=1.23,本文包含强行兼容方案)

一、 背景与硬性需求

在开始之前,明确一下我们的部署环境限制,这也是后续修改配置的依据:

  1. 节点调度限制
  • CVAT 必须运行在名为 gpusrv14 的特定节点上。
  • 难点 :该节点被打上了污点(Taint)dedicated=ceph:NoSchedule,普通 Pod 无法调度上去。我们需要配置容忍度(Tolerations)。
  1. 持久化存储
  • 必须对接公司现有的 Ceph 集群(StorageClass: rook-cephfs),且容量要求大。
  1. 内网环境
  • 服务器无法直接连接公网,依赖 HTTP Proxy。

二、 部署准备与 Chart 处理

1. 下载 Chart

首先拉取官方 Chart 包并解压:

bash 复制代码
helm pull oci://registry-1.docker.io/cvat/cvat --version 2.55.0
tar -xzf cvat-2.55.0.tgz
cd cvat

2. 解决 K8s 版本兼容性问题(K8s 1.21 版本)

官方 Chart 的 Chart.yaml 里硬性规定了 kubeVersion: '>= 1.23.0-0'。如果你像我一样还在用 K8s 1.21,直接 install 安装会报错。

解决方案

修改 Chart.yaml,强行降低版本要求。虽然有风险,但实测 CVAT 2.56 在 1.21 上核心 API 依然兼容。

yaml 复制代码
# Chart.yaml
description: A Helm chart for Kubernetes
kubeVersion: '>= 1.21.0-0'  # 修改此处

3. 初始化配置文件

为了保持原文件整洁,我们复制一份配置进行覆盖:

bash 复制代码
cp values.yaml values.override2.yaml
# 或(一样)
helm show values . > values.override2.yaml

三、 核心配置修改(values.yaml)

1. 存储配置:对接 Ceph

默认的 PVC 只有 20Gi 且不指定 StorageClass。我们需要修改 cvatkvrocks (Redis) 两处的存储配置。

修改 values.override2.yaml

yaml 复制代码
defaultStorage:
    enabled: true
    storageClassName: rook-cephfs  # 重点:指定你的 Ceph SC 名称
    accessModes:
      - ReadWriteMany  # 建议多读写模式,方便扩展
    size: 1000Gi       # 生产环境,给大点

2. 调度配置:亲和性与污点容忍(重点)

这是最复杂的一步。我们要把 Pod 钉死在 gpusrv14 节点上,并且要能"忍受"它的污点。

步骤 A:给节点打标签

bash 复制代码
kubectl label node gpusrv14.aa.com -n gpu cvat-node=true

步骤 B:检查节点污点

bash 复制代码
kubectl describe node/gpusrv14.aa.com -n gpu | grep Taints
# 输出: dedicated=ceph:NoSchedule
# 这意味着如果没有对应的 toleration,Pod 无法调度上来。

步骤 C:修改 Helm 配置

找到 cvat.backend 部分。CVAT Chart 设计比较好的一点是,backend 下的配置会被 worker 等子组件继承,所以不需要每个微服务都改一遍。

yaml 复制代码
cvat:
  backend:
    # 1. 强行指定节点(双重保险)
    affinity:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
            - matchExpressions:
                - key: kubernetes.io/hostname
                  operator: In
                  values:
                    - gpusrv14.aa.com

    # 2. 简单的节点选择器
    nodeSelector:
      cvat-node: true

    # 3. 污点容忍(必须配置,否则 Pending)
    tolerations:
      - key: "group"    # 多余的
        operator: "Equal"
        value: "gpu"
        effect: "NoSchedule"
      - key: "dedicated"  # 对应 dedicated=ceph:NoSchedule
        operator: "Equal"
        value: "ceph"
        effect: "NoSchedule"

四、 启动安装与踩坑实录

执行安装命令:

bash 复制代码
helm install gpu-cvat . -n gpu-cvat --create-namespace -f values.yaml -f values.override2.yaml

安装命令下去后,并没有想象中的"一键成功",而是迎来了两个深坑。

坑一:OPA 服务无限重启 (Connection refused)

现象
gpu-cvat-opa Pod 状态异常,查看日志发现健康检查失败:

requests.exceptions.ConnectionError: HTTPConnectionPool(host='opa', port=8181): ... Connection refused

原因分析

OPA (Open Policy Agent) 是 CVAT 的权限策略引擎。

  • 默认情况下,OPA 启动时只监听 localhost (127.0.0.1)。
  • K8s 的 Liveness Probe(探针)是通过 Pod IP 访问的,属于"外部"访问。
  • 因为监听地址限制,探针连接被拒绝,K8s 判定服务不健康,导致无限重启。

解决方案(临时暴力有效)

尝试通过 Helm --set 传参失败(可能是 Chart 里的参数映射有问题),直接修改 Deployment 最快。

bash 复制代码
kubectl edit deployment gpu-cvat-opa -n gpu-cvat

找到 args 部分,手动添加 --addr=0.0.0.0:8181,强制监听所有网络接口:

yaml 复制代码
      containers:
      - args:
        - run
        - --server
        - --addr=0.0.0.0:8181  # 【关键修改】让它监听所有IP
        - --bundle

修改保存后,Pod 重启,状态瞬间变为 Running。

其中,尝试过 upgrade 这些服务,但不生效:

bash 复制代码
helm upgrade ... --set cvat.opa.extraArgs="{--addr=0.0.0.0:8181}" --reuse-values

可能某些版本的 CVAT Helm Chart 中,extraArgs 的传递方式或者 OPA 的默认启动命令(Entrypoint)优先级更高,导致修改根本没有生效。

永久解决方案(未验证)

手动编辑的配置会在下一次 helm upgrade 时丢失。为了让配置持久化,需要将参数写入 values.override2.yaml

在配置文件中找到 cvat.opa 部分,加入 extraArgs

yaml 复制代码
cvat:
  opa:
    # 强制让 OPA 监听所有接口,解决健康检查不通的问题
    extraArgs:
      - "--addr=0.0.0.0:8181"

这样下次执行 Helm 更新时,这个参数就会自动带上,不用再手动改 Deployment 了。

坑二:浏览器白屏与 405 报错 (HTTP vs HTTPS)

现象

配置好 Nginx HTTP 转发后,访问页面出现白屏或报错(F12):

  1. API 请求报 405 Not Allowed401 Unauthorized
  2. 控制台报错:The Cross-Origin-Opener-Policy header has been ignored...
  3. JS 报错:ReferenceError: structuredClone is not defined

原因分析

这是现代浏览器的安全机制。CVAT 是一个重型前端应用,使用了 SharedArrayBuffer 等特性来实现多线程处理。
浏览器规定:这些高级特性必须在"安全上下文" (Secure Context) 下才能运行。

所谓安全上下文,要么是 localhost,要么必须是 HTTPS。如果是 HTTP,这些 API 直接被禁用,导致代码崩盘。

解决方案(配置 Nginx SSL + 安全头)

我们必须配置 HTTPS,并且在 Nginx 中显式添加 COOP/COEP 响应头。

Nginx 配置参考:

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 命令来创建超级管理员(教程下一步)。

一键创建命令

bash 复制代码
# 你部署 CVAT 的命名空间
export HELM_RELEASE_NAMESPACE="gpu-cvat" 

# 你给这个 Helm 实例起的名称
export HELM_RELEASE_NAME="gpu-cvat"

BACKEND_POD_NAME=$(kubectl get pod --namespace $HELM_RELEASE_NAMESPACE -l tier=backend,app.kubernetes.io/instance=$HELM_RELEASE_NAME,component=server -o jsonpath='{.items[0].metadata.name}')

kubectl exec -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

bash 复制代码
# 查看 CVAT 相关的持久化卷
kubectl get pv | grep gpu-cvat

注意 :可以看到 gpu-cvat-backend-data 的模式是 RWX (ReadWriteMany),这对于多副本部署至关重要。

2. 追踪底层的 Ceph Subvolume

为了确认数据确实落在了 CephFS 上,我们可以随机挑选一个 PV 进行 describe,查看它在 Ceph 内部的 subvolumePath

bash 复制代码
kubectl describe pv/pvc-ec561f53-c54b-47d7-9ddb-6e01850046df | grep subvolumePath
# 输出结果:
# subvolumePath=/volumes/csi/csi-vol-cee516.../de776093...

3. Ceph 后台验证

最后,登录 Ceph Dashboard 或直接在 Ceph 节点查看文件系统,可以看到该路径已经自动创建并开始记录数据。


总结

这次 Helm 部署过程暴露了 CVAT Chart 的几个痛点:

  1. 微服务拆分过细 :虽然架构先进,但运维成本高。如果不是 backend 配置支持继承,单独配置每个 Pod 的调度策略会非常痛苦。
  2. 默认配置不合理 :OPA 默认只监听 localhost 导致健康检查失败,这是一个非常低级的默认配置坑,迫使我们必须手动介入。
  3. 文档缺失 :官方文档未强调 HTTPS 对于前端功能的强制性,导致很容易在 HTTP 环境下浪费时间排查 JS 报错。
  4. 内网友好度低 :内网环境下,Grafana 插件下载、依赖包拉取都需要自己在 values.yaml 里注入 Proxy 环境变量。

感觉这个 Helm-Chart 做得不够完善,并且加上公司内网原因,导致没法一键部署。

相关推荐
Zach_yuan2 小时前
从零理解 HTTP:协议原理、URL 结构与简易服务器实现
linux·服务器·网络协议·http
小李独爱秋16 小时前
计算机网络经典问题透视:如何探知无线AP的SSID与BSSID?
网络协议·计算机网络·wireshark·信息与通信
..过云雨18 小时前
五种IO模型与非阻塞IO
网络·网络协议·tcp/ip
源远流长jerry19 小时前
dpdk之kni处理dns案例
linux·网络·网络协议·ubuntu·ip
2501_9151063220 小时前
当 Perfdog 开始收费之后,我重新整理了一替代方案
android·ios·小程序·https·uni-app·iphone·webview
db_murphy1 天前
知识篇 | net.ipv4.ip_forward 参数
网络·网络协议·tcp/ip
Vect__1 天前
TCP Socket编程详解
网络协议·tcp/ip·php
2501_915918411 天前
中小团队发布,跨平台 iOS 上架,证书、描述文件创建管理,测试分发一体化方案
android·ios·小程序·https·uni-app·iphone·webview
B2_Proxy1 天前
如何使用代理服务解决“您的 ASN 被阻止”错误:全面策略分析
网络·爬虫·网络协议·tcp/ip·安全·代理模式