很多人第一次接触 CSGHub都是被它的定位吸引过去的——一个可以放在自己机器上、专门管模型和数据集仓库的平台界面像模像样功能也齐。可真到动手那一步卡人的往往不是平台本身而是 Docker 方式的安装这一关编排文件拉下来一看十几个服务排在一起数据库、对象存储、认证、Git 服务、前端、后端各占一块谁先起谁后起、哪个卷该挂哪、密码怎么传全靠猜是猜不出来的。我自己第一次在离线环境的机器上装 CSGHub前前后后折腾了两轮才把整条链路跑通中间踩的坑基本都是文档里一句话带过、实际部署全靠悟的那种。这篇就按我实际操作的顺序把 CSGHub 用 Docker 方式装起来这件事从头讲一遍每个组件为什么存在、宿主机要提前准备什么、编排文件哪几个字段是非改不可的、启动之后怎么验证、报错怎么排查。适合手上有台闲置服务器、想自己搭一套模型资产管理平台的人看也适合已经装了一半卡在某个红灯上的同学照着对一遍。1. 先弄清 CSGHub 是由哪几块拼起来的1.1 为什么它不可能一个容器跑起来我在不少群里见过类似的提问能不能给一个docker run命令把 CSGHub 直接起来答案是不能而且这不是打包偷懒是架构决定的。CSGHub 要解决的事情是托管模型、数据集、代码仓库并且支持浏览、下载、版本管理、权限控制这四件事拆开看每一件背后都需要一整套独立的基础设施。模型仓库要有 Git 语义来支持版本和 LFS 大文件所以需要一个 Git 服务元数据谁建的、什么标签、多少下载量要落库所以需要关系型数据库模型文件动辄几十 GB不可能塞进数据库的 BLOB 字段所以需要对象存储多用户场景下要有登录、组织、角色所以需要一套认证服务再加上前端门户、后端 API、反向代理网关一套下来就是一套小型分布式系统。所以你在编排文件里看到七八个甚至十几个 service是完全正常的。反过来说如果你手上的编排文件里只有两三个服务那大概率是精简版或者是别人二次裁剪过的得先确认功能是否完整再动手。提示不同版本、不同发行渠道拿到的 compose 文件服务命名会有差异比如后端可能叫csghub-server也可能叫server卷名和网络名同理。本文里的服务名以常见命名为准你实际操作时一律以自己手上的文件为准别硬套。1.2 各个服务到底在干什么把每个服务的职责搞清楚后面排查问题时你才知道该看谁的日志。下面这张表是我按实际部署经验整理的服务名一列请对照自己的编排文件服务角色常见服务名干什么用的出问题时的典型表现前端门户portal提供 Web 界面登录、浏览、上传入口页面空白、静态资源 404后端 APIserver核心业务逻辑仓库管理、权限、元数据接口 500、前端报网络错误网关nginx统一入口转发前后端请求控制上传体积502、413、长连接断开关系型数据库postgres存用户、仓库元数据、任务记录后端启动即退出、连接超时对象存储minio存模型权重、数据集原始文件上传成功但下载 404认证服务casdoor统一登录、OAuth、组织与角色登录跳转死循环、token 失效Git 服务gitea提供仓库的 Git 与 LFS 能力clone 失败、push 被拒缓存redis会话、热点数据、任务队列偶发登录丢失、接口变慢我之所以强调这张表是因为部署阶段九成的报错都能归到某个服务没起来或者两个服务之间连不上。你只要知道每个角色负责什么看日志的时候就能一秒定位该去看哪个容器的输出不用把所有日志刷一遍。1.3 单机部署和集群部署的界线在哪用 Docker 方式装 CSGHub绝大多数人是奔着单机或者单节点去的——一台机器所有服务跑在同一个 Docker 网络里靠卷做持久化。这个方案能撑住的规模大概是小团队内部使用十几到几十个活跃用户仓库数量几十到几百单个模型文件几十 GB 级别。超出这个量级你会先撞上磁盘 IO 和内存的墙而不是软件本身的墙。真正要往上走思路是把有状态的服务拆出去数据库换托管实例、对象存储换独立集群、Git 服务单独一台机器宿主机上只留无状态的前端后端网关。这个过程不需要改代码改的是编排文件里对应服务的地址和凭据。所以我建议你从第一天起就把所有连接信息写在环境变量文件里别硬编码进 compose 文件后面拆的时候能省下大把时间。2. 宿主机准备那些装之前就该检查的事2.1 内核参数与硬件底线先说硬件。按我的实际观察跑一套完整的 CSGHub 加全部依赖建议至少 8 核 16 GB 内存起步磁盘留给数据目录的部分不少于 500 GB。内存这一块要特别注意数据库、认证服务、Git 服务都是常驻进程空载状态下全套跑起来吃个 6 到 8 GB 很正常再留点给模型文件上传时的内存缓冲16 GB 是比较舒服的底线。磁盘方面SSD 是强需求不是可选项——Git LFS 的读写模式对随机 IO 很敏感机械盘上你做一次 push 能等到怀疑人生。内核参数里最容易被忽略的是文件句柄数和进程数上限。容器里的服务会打开大量连接宿主机限制太低会导致容器内出现莫名其妙的 too many open files。修改方式# /etc/security/limits.conf 追加 * soft nofile 655350 * hard nofile 655350 * soft nproc 655350 * hard nproc 655350 # /etc/sysctl.conf 追加 fs.file-max 1000000 net.core.somaxconn 32768 net.ipv4.ip_local_port_range 1024 65000 vm.swappiness 10改完执行sysctl -p生效ulimit -n确认一下。另外记得把 swap 关掉或者压到很低容器里跑数据库带着 swap 会引入不可预测的延迟抖动。还有一项容易踩的宿主机时钟。容器默认继承宿主机时间如果宿主机没有做时间同步前端登录会莫名出现 token 立刻过期、或者刚登录就被踢回登录页的情况。timedatectl status看一眼有没有同步源没有就配上这一步花两分钟能省掉后面半小时的玄学排查。2.2 Docker 与 Compose 的版本选择版本这一块有个很实际的取舍。CSGHub 的编排文件通常依赖 Compose 的较新特性比如depends_on的condition: service_healthy所以Compose V2 是硬性要求docker-compose那个 Python 版的老命令基本跑不动。安装 Docker 时直接用官方脚本或者发行版的仓库装新版装完确认docker --version docker compose version两条命令都要能正常输出特别要注意docker compose带空格能跑而不是只有docker-compose。提示如果你的机器以前装过老的docker-compose二进制建议把它删掉或者改名避免脚本里误调用到旧版本症状是报一些不认识的配置项的错误看起来很莫名其妙。Docker 守护进程本身还有两个参数值得提前调一是默认地址池。Docker 默认用172.17.0.0/16和172.18.0.0/16之类的网段如果你公司内网正好占用了这段容器网络会和宿主机路由打架表现是宿主机能通外网、容器里 DNS 全挂。改法是在/etc/docker/daemon.json里指定bip和default-address-pools挑一个内网绝对不用的段比如10.200.0.0/16下的子段。二是日志驱动默认的 json-file 不限制大小跑几个月能把磁盘写满建议直接配max-size: 100m和max-file: 3。2.3 端口与目录的规划端口冲突是新机器上最常见的启动失败原因。规划之前先用ss -lntp把所有监听端口过一遍重点看这几个默认值有没有被占用途常见默认端口备注平台访问入口80 / 443如果宿主机已有 Web 服务改映射端口前端门户3000 或 8080仅容器内网访问一般不用暴露后端 API8080同上认证服务8000首次初始化可能需要直连对象存储 API9000大文件上传走这里注意超时配置对象存储控制台9001排查时可以直连看桶内容数据库5432强烈建议不要映射到宿主机缓存6379同上Git 服务3000 / 2222 端口经常和宿主机 sshd 冲突关于数据库和缓存的端口我的习惯是绝对不往外映射。这些服务一旦暴露在公网被扫到就是灾难。编排文件里如果默认写了ports: - 5432:5432我一般会直接删掉需要调试的时候用docker compose exec进容器操作或者临时加映射调完就删。目录规划同样重要。我习惯把所有持久化数据集中到一个根目录下比如/data/csghub/底下一层一层分postgres/、minio/、gitea/、logs/、backup/。这样带来两个好处一是备份时打包一个目录就够了二是磁盘告警时能一眼看出是谁在涨。这几个目录最好挂在一块独立的数据盘上别和系统盘混在一起系统盘写满会导致 Docker 自身出问题那时候连日志都看不到。2.4 镜像拉取链路提前捂热镜像体积是这个安装流程里最耗时间的一环。全套镜像拉下来几个 GB 是起步如果网络链路一般卡在拉取阶段半小时以上很常见。我的做法是提前把拉取单独做一遍不要等docker compose up的时候一起拉那样出错信息混在一起不好判断。# 先只拉镜像不启动 docker compose pull # 如果中途断了重复执行已完成的层会走缓存 docker compose pull对于完全离线的环境思路是在一台能联网的机器上pull完用docker save导出成 tar 包拷过去docker load。这里有个细节docker save要按镜像逐个导别指望一条命令导全部镜像名和 tag 一定要记清楚到了离线机器上docker tag改回原名的步骤不能漏否则 compose 会认为镜像不存在转而去仓库拉然后报网络错误。我吃过这个亏导了十几个镜像过去结果有三个 tag 写错了up的时候直接卡在 pulling 上。注意如果你们有内部的镜像缓存服务记得在daemon.json里配好registry-mirrors能省掉大量重复下载。没有的话就老老实实用docker save的方案。3. 编排文件的修改逻辑哪几个字段非改不可3.1 环境变量文件的分层写法拿到编排文件之后别急着up。先看有没有.env或者config.env这类文件绝大多数发行版本都会把需要你改的东西集中到这里。我习惯把它拆成两层一层是base.env放跟环境无关的公共配置另一层是local.env放当前这台机器的密码、域名、端口。compose 文件里用env_file把两个都引进来后者覆盖前者。这样做的理由是将来换机器或者搭第二套环境时只需要复制local.env改几个值其它不用碰。必须检查的变量大致有这么几类数据库相关主机名、端口、库名、用户名、密码。对象存储相关endpoint、access key、secret key、桶名、是否用 TLS。认证服务相关对外地址、客户端 ID、客户端密钥、回调地址。Git 服务相关对外地址、数据库连接、LFS 相关配置。平台对外地址这个最容易漏写错了会导致登录后跳转到一个打不开的地址。这里有一条血泪经验密码里不要带特殊字符。数据库连接串通常是把用户名密码拼进 DSN 的密码里一旦有、#、/、:这类字符不做 URL 编码就会把连接串解析坏掉报出来的错误是认证失败或者数据库不存在非常误导人。我第一次部署就是随手用了一个带的密码排查了快一个小时才发现问题在密码本身。稳妥做法是用大小写字母加数字的随机串长度 24 位以上。平台对外地址这个变量常见命名类似CSGHUB_PORTAL_URL或EXTERNAL_URL要填成用户浏览器能访问到的完整地址带协议和端口。填成localhost只在本机浏览器能用别的机器访问就会跳到一个错误地址。填成容器内网地址更糟浏览器根本解析不了。3.2 数据卷挂载三个必须核对的点卷挂载是 Docker 部署里最容易出问题的地方因为默认值往往能跑起来但数据会丢。核对三件事第一有状态服务必须有绑定挂载或者命名卷。数据库、对象存储、Git 服务的仓库目录这三处如果没有持久化容器一重建数据就没了。我见过有人为了干净直接把数据放在容器可写层结果升级镜像的时候手一抖docker compose down -v几百 G 的模型文件归零。第二宿主机目录的属主和权限。PostgreSQL 官方镜像默认以 UID 999 运行MinIO 类似如果宿主机的挂载目录是 root 所有且权限是 755容器会报 permission denied 然后反复重启。处理方式sudo mkdir -p /data/csghub/{postgres,minio,gitea,logs} # 按镜像实际的运行用户调整具体 UID 可以 docker inspect 镜像看 sudo chown -R 999:999 /data/csghub/postgres sudo chown -R 1000:1000 /data/csghub/minio sudo chmod -R 750 /data/csghub如果不确定镜像用哪个 UID可以先起一个临时容器docker run --rm -it image id看一眼比瞎试快。第三不要把宿主机目录挂到容器里的系统路径上。比如有人想持久化日志把宿主机目录挂到/var/log结果把容器里的系统日志目录覆盖了服务启动直接异常。要挂就挂到应用自己定义的路径比如/app/logs这种。3.3 启动顺序与健康检查编排文件里通常会用depends_on表达依赖关系但要注意区分两种写法。老的写法只有- postgres意思是容器起来了就行不代表服务可用。新的写法带condition: service_healthy会等健康检查通过再启动下一个。这两种差别很大数据库容器启动到能接受连接中间有十几秒的空窗期用老写法的话后端会在这段时间里连不上库然后退出容器就停在那了。services: server: depends_on: postgres: condition: service_healthy redis: condition: service_started如果你手上的编排文件用的是老写法有两个选择一是给它补上健康检查二是接受第一次启动后端会重启几次这个事实。我一般选前者因为后者在自动化脚本里很难判断到底是真的挂了还是还在重试。健康检查的写法也有讲究。PostgreSQL 用pg_isreadyRedis 用redis-cli pingMinIO 用自带的/minio/health/live这些都比较标准。间隔给 5 到 10 秒重试次数给 10 次左右别给太紧机器负载高的时候容易误判。3.4 资源限制与日志治理单机部署最容易忽略的是资源限制。不加限制的话某个服务内存泄漏会把整台机器的内存吃光触发内核 OOM然后随机杀进程——通常被杀的是数据库因为它的内存占用最高。表现是过一段时间平台突然不可用重启后又好了反复循环。我的做法是给每个服务加deploy.resources.limits按角色的实际占用给一个宽松但有上限的值数据库 4G对象存储 2G认证服务 1GGit 服务 2G前端 512M后端 2G。这些值不是最优解是我在几十人规模的实例上观察到的合理区间你可以按机器规格等比调整。日志治理同样是必做项。前面提过在daemon.json里配全局日志上限这里再补一层把应用日志也挂出来。很多服务会把日志写到容器内固定路径挂到宿主机上之后既能用熟悉的工具查也不会因为容器重建丢日志。logging: driver: json-file options: max-size: 100m max-file: 34. 把服务拉起来之后要做的事4.1 第一次启动该怎么盯日志docker compose up -d回车之后别急着刷浏览器。这时候大部分服务还在初始化正确的做法是先看整体状态docker compose ps重点看 STATUS 那一列有没有unhealthy或者Restarting。如果有按下面的顺序处理先docker compose logs --tail100 服务名看单个服务如果信息量不够加-f持续跟踪。我一般会开三个终端窗口一个盯数据库一个盯认证服务一个盯后端因为这三个是最先出问题的。一个很实用的小技巧docker compose logs -f --tail0 server postgres casdoor可以同时跟踪多个服务的日志输出会带服务名前缀方便对照时间线看谁先出错。数据库的日志要重点确认有没有出现database system is ready to accept connections出现这句才算真正就绪。认证服务要确认有没有完成数据库迁移。后端要确认有没有打印出监听端口的信息。这三条都齐了再去碰浏览器。4.2 初始化管理员与存储桶平台起来之后第一件事是初始化管理员账号。不同版本的流程不太一样有的会在首次访问时引导创建有的依赖认证服务自己的初始化页面。如果编排里带了认证服务通常需要先访问它的管理界面默认在 8000 端口用初始凭据登录把组织、应用、回调地址配好再回到平台门户登录。这一步最容易错的是回调地址。认证服务里配置的回调地址必须和平台对外地址严格对应多一个斜杠、协议写成 http 而不是 https、端口不一致都会导致登录后跳转失败或者停在空白页。改的时候建议直接把平台对外地址变量复制过去拼接不要手打。存储桶一般是初始化脚本自动创建的但也有的版本需要手动建。验证方法很简单进对象存储控制台看一眼目标桶在不在桶的访问策略是不是预期的私有或公开。如果桶不存在后台上传模型会报错但错误信息往往只写了内部错误看不出是桶的问题。提示对象存储的 access key 和 secret key 一旦改了记得同步更新后端服务的环境变量并重启否则后端还在用旧凭据表现是上传一直失败但服务本身不报错。4.3 一条端到端链路验证装完之后光看首页能打开是不够的得跑一条完整链路。我通常按这个顺序验用管理员登录能看到仓库列表页面哪怕空的。创建一个测试仓库类型选模型随便填个名字。上传一个几十 MB 的小文件确认进度条走完不报错。在仓库详情页点下载确认能下回来并且文件大小和上传的一致。用 Git 方式 clone 这个仓库确认 LFS 拉取正常。创建一个普通用户加进组织确认权限生效。这六步里第三步和第五步最容易出问题。第三步挂了通常是对象存储配置不对或者网关的上传体积限制太小第五步挂了通常是 Git 服务的 LFS 地址配错或者网关没把 LFS 相关的路径转发过去。关于上传体积限制补充一句网关层nginx默认的client_max_body_size往往只有 1M 或者 8M模型文件随便就超了。要在网关配置里放开client_max_body_size 0; proxy_request_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s;client_max_body_size 0表示不限制配合proxy_request_buffering off让上传流式转发避免大文件先在网关落盘占满磁盘。超时时间也得放大默认 60 秒对几十 GB 的文件完全不够。5. 几个我实际踩过的坑和排查链路5.1 容器全是 Up 状态但页面 502这个场景特别典型docker compose ps一片绿但浏览器打开就是 502 Bad Gateway。排查链路我是这么走的。第一步确认网关容器能不能通后端。进网关容器内部curl一下后端的服务名加端口docker compose exec nginx sh curl -v http://server:8080/healthz如果这里就通不了说明两个容器不在同一个网络里或者后端根本没在监听。查docker compose config看两者的networks配置是否一致。第二步如果容器内能通但外面不通问题就在网关的转发规则。看 nginx 配置里的proxy_pass指向的主机名对不对容器网络里必须用服务名写成localhost或者127.0.0.1就跑到网关自己身上去了自然 502。这个问题我第一次部署时踩得很结实配置文件里明晃晃写着localhost:8080看了半天才反应过来这是容器内的 localhost。第三步如果后端容器在反复重启往前翻日志找退出原因。最常见的是连不上数据库错误信息里会带连接串密码可能被打码对着环境变量核一遍主机名、端口、库名、密码。5.2 大文件上传到一半中断这个问题的成因比想象中多。按出现频率排一下现象可能原因验证方式传到固定百分比断网关client_max_body_size超限看网关错误日志 413传一两分钟就断网关超时太短看日志里的 upstream timed out断得很随机对象存储磁盘满或 inode 耗尽df -h和df -i前端进度条卡死后端到对象存储这一段慢看后端日志有无上传耗时记录我遇到过一次很迷惑的每次传到 2 GB 左右就断日志里没有任何错误。最后发现是对象存储所在的分区 inode 用完了——df -h看着还有空间df -i一看 100%。原因是有个服务在疯狂生成小文件把 inode 耗尽了。这个坑提醒我磁盘监控只看容量是不够的inode 也得盯。还有一种情况是上传走了前端直传对象存储的路径这时候网关的限制不生效问题出在对象存储的 CORS 配置上。浏览器控制台会明确报 CORS 错误看到这个直接去对象存储那边加允许的来源就行。5.3 权限与属主导致的启动失败前面提过 UID 的问题这里补充一个更隐蔽的变种挂载目录权限没问题但容器的安全上下文或者 SELinux 拦住了。在开启了强制访问控制的系统上容器挂载宿主机目录可能被拒绝访问日志里的错误是permission denied但你ls -l看权限明明是好的。判断方法临时把容器以特权模式跑一下如果能起来就说明是被安全策略拦了。正式环境不要长期开特权正确的做法是给挂载目录打上合适的标签或者在编排文件里给服务加对应的安全选项。另一个常见的是 Git 服务的仓库目录。如果这个目录的属主和容器内运行用户不一致会出现仓库能创建但 push 失败的情况错误信息是仓库不存在或者没有权限非常容易误导人去查认证配置。遇到 push 失败先别急着看 token先docker compose exec进容器ls -l看一下仓库目录的属主。5.4 平台跑几天之后变慢这个问题通常不是装的时候埋下的是运行一段时间后积累出来的。我遇到过两次一次是数据库表膨胀没做清理一次是容器日志把磁盘写到 90% 以上。排查顺序建议这样先df -h和df -i看磁盘再docker system df看镜像和卷占了多少然后进数据库看几个大表的数据量。如果发现是日志问题检查前面配的日志上限有没有生效——注意daemon.json改完要重启 Docker 守护进程而且只对之后新建的容器生效已有容器得重建。数据库那块定期做一次 vacuum 和索引检查能解决大部分越用越慢的问题。这块我不展开属于另一个话题了。6. 装好之后备份、升级和日常维护6.1 备份到底该备什么一套 CSGHub 的备份分三块数据库、对象存储、Git 仓库目录。三块必须一起备只备其中一块恢复出来是残的。数据库用逻辑备份最省事pg_dump导成 SQL 文件每天一次保留七天。对象存储的数据量大全量备不现实我的做法是依赖它自己的版本或者副本机制只对元数据做定期快照。Git 仓库目录直接用文件级同步工具增量同步到另一台机器。这里有个顺序问题值得注意备份的时候先备数据库还是先备文件我的做法是先停写入再备数据库再备文件最后恢复写入。不停写入直接备数据库和文件之间会产生不一致——数据库里有一条仓库记录但对应的文件还没同步过来恢复之后这条记录指向空文件。小团队可以接受短暂停机就老老实实按这个顺序来。6.2 版本升级的动作顺序升级 Docker 部署的 CSGHub动作顺序错了很容易把数据搞坏。我的流程是完整备份三块都备备份完实际验证一下能不能恢复至少能解压、能连上。拉新镜像docker compose pull注意看有没有服务改名或者增减。对比新旧编排文件和环境变量模板新版本可能加了新的必填变量。这一步最容易漏漏了会导致服务启动时因为缺变量而退出。先升级数据库之外的组件最后再处理数据库迁移。有的版本带自动迁移跟着后端一起跑有的需要单独执行迁移命令。起来之后按前面那条端到端链路验一遍。回滚方案要提前想好。镜像 tag 一定要固定不要用latest否则回滚的时候你都不知道该回哪个版本。我的习惯是把当前版本的 compose 文件和环境变量文件一起打个包存档回滚就是换回那个包再up -d。6.3 平时该盯哪几个指标平台稳定运行之后日常只需要盯四个地方磁盘使用率和 inode、数据库连接数、对象存储的请求错误率、容器重启次数。前两个用系统的监控工具就行。数据库连接数要特别注意连接池配置不当会导致连接数缓慢上涨最终打满表现是接口开始超时。对象存储的错误率可以看它的控制台或者日志。容器重启次数用docker ps看 RESTARTS 那一列这个数字只要不是 0 就值得看一眼。我个人的习惯是每周挑一个时间把这四个指标过一遍顺便看一次告警日志。花不了十分钟但能提前发现大部分缓慢恶化的问题。最后分享一个小技巧把整个部署目录compose 文件、环境变量、nginx 配置、初始化脚本用一个 Git 仓库管起来每次改动都提交。这样出了问题能git diff看改了什么换机器部署能直接 clone。我吃过没做版本管理的亏——某次为了调一个参数随手改了几行调完之后忘了改的是什么最后只能把整个文件重写一遍。这套东西一旦进了版本管理维护成本会低很多。
阅读完成 · 觉得有帮助?