很多人第一次接触 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 |
| 后端 API | server | 核心业务逻辑,仓库管理、权限、元数据 | 接口 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 | 仅容器内网访问,一般不用暴露 |
| 后端 API | 8080 | 同上 |
| 认证服务 | 8000 | 首次初始化可能需要直连 |
| 对象存储 API | 9000 | 大文件上传走这里,注意超时配置 |
| 对象存储控制台 | 9001 | 排查时可以直连看桶内容 |
| 数据库 | 5432 | 强烈建议不要映射到宿主机 |
| 缓存 | 6379 | 同上 |
| Git 服务 | 3000 / 22 | 22 端口经常和宿主机 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_isready,Redis 用redis-cli ping,MinIO 用自带的/minio/health/live,这些都比较标准。间隔给 5 到 10 秒,重试次数给 10 次左右,别给太紧,机器负载高的时候容易误判。
3.4 资源限制与日志治理
单机部署最容易忽略的是资源限制。不加限制的话,某个服务内存泄漏会把整台机器的内存吃光,触发内核 OOM,然后随机杀进程——通常被杀的是数据库,因为它的内存占用最高。表现是过一段时间平台突然不可用,重启后又好了,反复循环。
我的做法是给每个服务加deploy.resources.limits,按角色的实际占用给一个宽松但有上限的值:数据库 4G,对象存储 2G,认证服务 1G,Git 服务 2G,前端 512M,后端 2G。这些值不是最优解,是我在几十人规模的实例上观察到的合理区间,你可以按机器规格等比调整。
日志治理同样是必做项。前面提过在daemon.json里配全局日志上限,这里再补一层:把应用日志也挂出来。很多服务会把日志写到容器内固定路径,挂到宿主机上之后,既能用熟悉的工具查,也不会因为容器重建丢日志。
logging: driver: json-file options: max-size: "100m" max-file: "3"4. 把服务拉起来之后要做的事
4.1 第一次启动该怎么盯日志
docker compose up -d回车之后,别急着刷浏览器。这时候大部分服务还在初始化,正确的做法是先看整体状态:
docker compose ps重点看 STATUS 那一列有没有unhealthy或者Restarting。如果有,按下面的顺序处理:先docker compose logs --tail=100 <服务名>看单个服务,如果信息量不够,加-f持续跟踪。我一般会开三个终端窗口,一个盯数据库,一个盯认证服务,一个盯后端,因为这三个是最先出问题的。
一个很实用的小技巧:docker compose logs -f --tail=0 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。我吃过没做版本管理的亏——某次为了调一个参数随手改了几行,调完之后忘了改的是什么,最后只能把整个文件重写一遍。这套东西一旦进了版本管理,维护成本会低很多。