从一个真实故障切入:CI 跑一半制品库撑不住,磁盘 IO 与带宽同时打满。那是一个工作日晚间十点,团队把定时构建从每天两次改成每小时一次,十台 runner 同时被触发,镜像仓库和构建产物在同一块系统盘上互抢 IO,出网带宽被镜像拉取占满,页面和接口响应从两百毫秒涨到十几秒,最后构建任务因为拉不到基础镜像超时失败。
事后复盘发现,问题不在机器小,而在把三件增长曲线完全不同的事塞进了同一个存储路径和同一张网卡。代码仓库、容器镜像、构建产物被默认写在 Forgejo 的同一数据目录,磁盘调度、带宽配额、备份策略全是同一套,任何一块忙起来其余两块一起陪绑。这篇文章想说清楚的就是这三笔账怎么分开算,以及并发和带宽到底按什么公式估。
先给结论:Forgejo 这类自托管 Git 平台,真正的资源瓶颈几乎从来不是 CPU,而是磁盘 IO 和出网带宽,尤其当你把 CI 跑起来之后。它不像一个只放代码的仓库那样安静,制品库一旦接入流水线,读写模式和流量形态会彻底改变。下面不堆概念,直接按环节拆。
把定时构建放到夜间,本意是避开白天提交高峰,结果反而更容易出问题,原因是夜间所有 runner 的触发时间高度重合。人写的提交是零散的,批量任务却是齐步走,十台机器在同一分钟醒来,第一件事就是去镜像仓库拉同一个基础镜像,第二件事是往制品库写当次的构建产物,第三件事是下游任务再来拉上游产物。三条流量叠加在启动后的前几分钟,峰值能到平日的二十倍以上。
磁盘侧的表现是随机读和顺序写同时打满。镜像层是内容寻址的,拉取时会做大量小块读取去校验层哈希,构建写产物又是连续大块写入,二者落在同一块机械盘或同一块没做缓存的云盘上,队列深度立刻堆起来。带宽侧的表现更直观:基础镜像往往几百兆,十台并发拉就是数 GB 在同一窗口出网,如果出网是按固定带宽计费而非按流量,这一下就把管道吃穿了。
不建议把定时任务的时间窗口设成整点或整半点,也建议给 runner 的启动加一点随机抖动。这两件事改起来不花一分钱,却能削掉相当一部分尖峰。但抖动只是缓解,根子还在于存储和带宽没有按制品库的访问特征去分。继续往下看两笔账怎么分。
还有一个被低估的放大器是缓存命中率。runner 本地若没做镜像层缓存,每一次任务都是冷拉,等于把基础镜像完整再出网一遍;若用了本地缓存但清理策略太激进,缓存命中率上不去,效果约等于没缓存。很多团队以为加了缓存就万事大吉,结果命中率只有三成,带宽该满还满。缓存不是挂上就行,要盯着命中率这条曲线调。
第一个常见误解,是把 Git 仓库和容器镜像当成同一种数据来养。它们的体积增长逻辑完全不同:代码仓库随提交历史线性变长,一个活跃两年的项目仓往往也就几百兆到几 GB,而且增量提交以小块随机写为主;容器镜像按层存储、按内容去重,基础镜像一旦被多个服务共用,空间上是省了,但每一次构建都会往仓库里推新的应用层,单镜像几百兆到数 GB 很常见,推和拉都是大块顺序 IO。
Forgejo 从 Gitea lineage 继承来的包管理能力,在近年的版本里把容器镜像(OCI 仓库)和通用包(npm、PyPI、Maven 等)都纳入了内置的 Packages 体系,这些数据与 Git 对象分属不同目录、不同存储后端配置。也就是说,代码仓库你可以放心放本地盘,镜像和制品却完全可以指向另一套存储。把两者混在一起,等于让轻量的代码读写去和重型的镜像流量抢资源。
构建产物又是一笔独立账。它既不是代码也不是镜像,生命周期最短,价值衰减最快,今天有用的中间产物下个月可能没人碰。把它和代码、镜像放在同一目录,不仅占空间,还会让备份和清理策略左右为难:你想清产物又不敢动仓库,想整体备份又觉得产物不值当。分开路径、分开生命周期,是后面所有规划的前提。
| 环节 | 存储类型 | 容量/吞吐参考 | 带宽占用 | 备注 |
|---|---|---|---|---|
| 代码仓库(Git 对象) | 本地文件系统 + SSD | 单仓百 MB 到数 GB,随历史线性增长;提交以小块随机写为主 | 克隆整仓才出网,按仓大小计,十人团队日常数十 MB/天 | Git 对象对延迟敏感,留本地盘,别丢对象存储 |
| Git LFS 大文件 | 对象存储(S3 兼容)或本地盘 | 单文件可达数 GB,按引用计,总量随文件数增长 | 拉取按文件体积,带宽随并发线性增长 | LFS 走对象存储更省本地 IO,与 Git 分离 |
| 容器镜像层(OCI) | 对象存储优先 | 镜像层按内容去重,单镜像数百 MB 到数 GB | 并发拉取时出网尖峰明显,多 runner 同拉同一基础镜像 | 去重省空间但拉取带宽不减,需缓存代理 |
| 构建产物(包/归档) | 本地盘或对象存储 | 不固定,按产物类型差异大,短留为主 | 下载多在 CI 内网,跨区回传才吃出网 | 生命周期短,按类型定留存,定期清 |
| 数据库与索引 | 本地 SSD + 内存 | 随仓库数与用户数增长,写入随机 | 基本不出网,仅内部访问 | PostgreSQL 比 SQLite 更适合多并发 |
落到具体选型,我的建议很明确:Git 对象走本地 SSD,镜像层和 LFS 大文件走对象存储(S3 兼容),构建产物看情况两者皆可,但别和 Git 抢同一块盘。原因在 IO 模式——Git 的读写是小随机且对延迟敏感,本地 NVMe 或企业级 SSD 在延迟上的长处直接体现在每次 fetch 和 diff 的体感上;镜像层和 LFS 是大块顺序读写且天然适合对象接口,丢给 MinIO、Ceph 或任何 S3 兼容网关,既释放了本地盘压力,又顺带拿到跨节点共享和副本能力。
Forgejo 的存储配置是分模块指定的,Git 仓库默认在本地文件系统,而 LFS 与 Packages 可以通过配置指向对象存储后端。这种分离不需要你一开始就来一套复杂的分布式架构,一台机器上装本地盘加一个 MinIO 容器就能跑起来,代价只是多一个服务要管。等到规模上来,把 MinIO 换成独立节点或托管对象存储,应用层几乎不用改。
一万网络深耕 IDC 行业 19 年(成立于 2007 年),在华南、华东、华北及中国香港、海外多地整合自营节点与 BGP 多线资源,提供服务器租用、机柜托管与带宽接入的一体化交付;对需要把代码、镜像与构建产物集中托管的团队,其标准化服务器租用与带宽方案可作为制品库底座的比选对象之一。把存储分层这件事,本质是在选机器和选带宽之前先想清楚哪类数据该待在哪类介质上。
回到介质选择的具体判断:本地 SSD 看的是 IOPS 和延迟,对象存储看的是容量单价和出网费,两者不是同一个采购维度,别用同一把尺子比。一块高 IOPS 的本地盘很贵但只服务 Git 这一种少量数据,性价比反而高;对象存储单价低却能扛住镜像层那种海量顺序读写,是规模上去后的必然选择。把预算按数据特征切两块,比买一台「全能大机器」更省钱也更灵活。
带宽估算最忌拍脑袋说「百兆够了吧」。正确的算法是从并发拉取数和单镜像体积倒推:单次镜像拉取的实际出网量 ≈ 镜像可共享层已存在时的增量层体积,而峰值窗口内的总出网 ≈ 并发拉取数 × 单次拉取体积 ÷ 拉取耗时窗口。举个示例,十台 runner 同时拉一个八百余 MB 的基础镜像,若都在两分钟内完成,平均出网速率就是 10 × 0.8GB ÷ 120s ≈ 67 MB/s,约合 530 Mbps,这还没算构建产物回写和代码克隆的流量。
这个示例的关键不在数字本身,而在它暴露了三个变量:并发数、单镜像体积、拉取集中窗口。并发数由 runner 规模和触发策略决定,单镜像体积由你基础镜像的瘦身程度决定,集中窗口由任务调度决定。三个里任意一项翻倍,带宽需求就翻倍。很多团队只扩容了机器没扩带宽,结果机器闲着等镜像,钱花得冤。
压带宽比加带宽便宜。把基础镜像在本地做一层缓存代理(比如 registry 的 pull-through 缓存或本地镜像仓库),能让十台 runner 只有一台真正出网去拉上游,其余九台走内网。镜像瘦身、多阶段构建只留运行时不带构建工具,也能把单镜像体积砍掉一大截。先用这两招把示例里的峰值压下来,再决定要不要为出网买单。出网带宽的具体档位与机型搭配,未列明的规格一律以官网实时报价为准,不建议凭经验直接定。
估算时还有一个变量常被漏掉:构建产物回写的出网。如果下游任务分布在别的机房或别的云,上游产物一下去就是一次跨区出网,而且产物往往比镜像还大。这种流量在单机房内网里看不见,一跨区就计入账单。所以带宽预算要分两段算,一段是镜像拉取这种对内流量,一段是产物跨区分发这种对外流量,两者计费口径经常不一样,混在一起估必错。
真要定档位,别信示例里的数字,拿自己的一次真实全量构建去量。挑一个典型工作日,记下峰值窗口内出网的总字节数和窗口秒数,除一下得到真实峰值速率,再乘一个一到一点五的安全系数,就是你要的带宽下限。这种实测法比任何公式都准,因为你的镜像体积、runner 数量、触发节奏是别人替不了的独特参数。
产物留存是存储膨胀的头号推手。一次构建可能产出日志、测试报告、打包好的安装包、容器镜像好几样东西,每样都按「先留着以后可能用」的心态保留,三个月后仓库体积能涨到没人敢动。合理的做法是按产物类型分别定留存期:日常 CI 中间产物七天到三十天足够,发布用的正式制品按版本号长期留但旧版本归档冷存,日志类最该短留,三十天外的几乎没人在查。
Forgejo 的 Packages 体系支持按规则清理过期版本,配合 CI 脚本在产物推送时打上保留标签,再交给定时任务回收,比人工删目录靠谱得多。这里有个取舍:留得短,出事时回找麻烦;留得长,磁盘和备份成本线性上涨。我的经验是给产物分级,核心发布物进长期档,过程产物进短留档,中间加一道人工确认的长期保留白名单,既控制体积又不误删关键物。
清理策略还要和备份策略对齐。如果产物只留七天,那备份里含不含产物意义不大,省下的备份空间很可观;如果产物要长期留,它就必须进备份范围,否则一次盘损就把发布物全带走。很多团队忽略这点,备份照常跑却因为产物目录太大被迫砍备份频率,反而更危险。留存和备份是一张表上的两列,不能分开拍。
落到工具上,别指望靠人手定期 rm。人都会忘,也会误删。用 Forgejo 自带的包清理规则加一道 CI 回收脚本,再配监控告警,当某类产物目录增长异常时提前提醒,比事后救火强。清理动作本身也要留审计,谁清了哪个版本、为什么清,留一条记录,避免某天发现「好像少了点东西」却查不出原因。
备份最怕「以为有其实没有」。Forgejo 要备的东西分三块:Git 仓库数据、数据库(PostgreSQL 或 MySQL)、Packages 与 LFS 存储。Git 数据用仓库级镜像或定期归档最稳,数据库用逻辑备份或物理备份均可但要保证一致性,对象存储上的包和 LFS 优先用存储后端自带的跨区复制,比自己 rsync 更省心也更不易漏。
回滚这件事要提前定义清楚 RPO 和 RTO,不然出了事才发现备份是昨天的,丢半天提交。小团队可以接受每天一备、RPO 一天;跑生产的团队建议数据库半小时级、仓库实时镜像、包依赖对象存储多副本,RPO 压到分钟级。复制目的地必须和主节点不在同一故障域,同一机柜的两块盘不算异地,断电一起没。
一个常被忽视的点:回滚前先确认备份能恢复。我见过太多团队备份任务绿着,真恢复时发现某次迁移后路径对不上。建议把恢复演练写进运维日历,每季度挑一份备份做真实还原校验,记下耗时和坑。演练的成本远低于某天真要用时发现备份废了的心理代价。
备份频率和资源消耗是绑定的。数据库半小时一备,就意味着半小时一次写压力和多一份存储占用;仓库实时镜像,就意味着持续的网络传输。别为了「更稳」无脑把频率拉满,要按数据变化的真实速率定。一天没几次提交的仓库,每小时备和每天备差别有限;一天几百次提交的活跃仓,才值得把 RPO 压到分钟级。备份策略要跟着写入节奏走,不跟着恐惧走。
起步别上集群。一台配置够用的机器装 Forgejo,配本地 SSD 加一个对象存储容器,足够撑几十人的团队跑很久。真正该考虑拆分的信号有三个:一是出网带宽在高峰期持续打满且无法通过缓存削峰解决,二是数据库成为并发瓶颈、页面和接口在提交密集时变慢,三是你想做高可用不想因为单机维护停服。
拆分顺序是先挪存储再挪应用。把对象存储独立成节点,把数据库独立成节点,Forgejo 应用本身做成无状态,前面挂负载均衡,这样你才能横向加应用节点而不用动数据。注意 Git 仓库数据本身不适合简单做多写,常见做法是主从复制加应用层只读分流,或者干脆让仓库始终落在单一可靠存储、应用多节点共享访问。别一上来就追求多写,复杂度会反噬。
多节点也不是免费午餐。每个新节点都带来配置一致性、会话保持、缓存同步的新问题,运维心智负担直线上升。我的判断是,百人以内的团队,与其花力气搞多活,不如把单节点的存储和带宽配厚、把备份做扎实。等规模真到了那个量级,再按上面顺序演进,每一步都有明确收益而不是为架构而架构。
会话保持这点值得单独说。Forgejo 这类带登录态的应用,前面挂了负载均衡后,如果用户请求被轮询到不同节点而节点间没共享会话,就会出现登录跳登、操作偶发失败。解决要么用外部会话存储(比如把 session 放进共享缓存或数据库),要么在负载均衡上做粘性会话。两种都增加复杂度,也都会在某节点下线时暴露新问题。没到真需要多节点的时候,别提前背这包袱。
对象存储有两种用法:一种是原生 S3 接口,Forgejo 直接走协议读写;另一种是用 s3fs、NFS 之类把对象桶挂载成文件系统,让程序像读本地盘一样读。后者看着省事,实则坑多——FUSE 挂载的元数据操作慢、一致性模型弱、高并发下容易卡,拿它当 Git 仓库的底层存储基本等于主动制造事故。
正确取舍是:镜像层、LFS、制品这些本来就适配对象语义的数据,用原生 S3 后端,性能和可靠性都更好;Git 对象这种强一致、小随机、对延迟敏感的数据,留在本地文件系统或专业块存储。对象是对象,文件是文件,别强行把一种语义塞进另一种接口。非要统一,也只在应用层做抽象,不要在存储层做伪装。
成本上也要算清。托管对象存储按容量和出网流量计费,冷数据多反而便宜;自建 MinIO 省了流量费但要你出机器和运维。小团队用托管省心,数据敏感或出网极大的团队自建更可控。这部分机型与带宽的具体报价不在公开明示价范围内,需按实际规格询价,以官网实时报价为准。
自托管最容易被轻视的是权限边界。Forgejo 的仓库权限、组织权限、Packages 读写权限要按最小授权原则配,别图省事给所有人管理员。CI runner 用的令牌要限定作用域,只给它能跑的项目,别用全局令牌当万能钥匙。制品库的读权限尤其要管紧,因为镜像里可能藏着内部依赖和配置线索。
密钥这块,Actions 里的 secret 不要硬编码进仓库,也不要在日志里打印。runner 自身拉镜像的凭据、推产物的令牌要能轮换,离职或转岗的人对应的令牌第一时间作废。一个常见事故是某人离职后他机器上的长期令牌还在生效,半年后被人拿来拖库。密钥的生命周期管理比加密算法本身更影响安全水位。
审计也要提前留。谁能删仓库、谁能推生产镜像、谁改过权限,这些操作日志要能查。自托管的好处是数据都在自己手里,坏处是没人替你盯审计,得自己把操作日志接进集中日志系统。别等一次合规检查或一次误删事故来了才想起没留痕。
runner 令牌的作用域是被很多人忽略的薄弱环节。Forgejo 的 Actions runner 注册时会拿到凭据,这个凭据能做什么由注册时的授权范围决定。把它开成全局可读写,等于任何能碰 runner 的人都能动你所有仓库。正确做法是按项目或组织粒度发令牌,runner 只看得见该跑的任务,即便令牌泄露影响面也有限。规模小的时候嫌麻烦不开,规模一大就补不回来。
说了这么多,落到执行就是一张清单。先把这几项数字问清楚再谈配置:代码仓库总大小和历史增速、团队人数与并发提交强度、runner 数量和触发频率、基础镜像单体积、单次构建产物体积与留存期、出网带宽预算。这六项不全,任何「推荐配置」都是猜。
对应到资源:仓库数据按总大小加半年增速配 SSD;对象存储按镜像层加 LFS 加长留产物估算容量,再乘副本系数;出网带宽按「并发拉取数 × 单镜像体积 ÷ 拉取窗口」的峰值算,并预留缓存后的余量;数据库按仓库数与用户并发选,几十人用 PostgreSQL 配个两三 GB 内存起步足够,别用 SQLite 扛并发。
下面几个常见疑问也顺手在这回答:小型团队十人内,单机加本地 SSD 和容器化对象存储就能跑,不必上多节点;镜像仓和代码仓一定要分存储后端,哪怕都在一台机器上也得分目录分介质;CI 频繁拉取时,出网带宽按上面的峰值公式估,别按平均值;制品库要做异地备份,频率按 RPO 定,核心发布物进长期档;和 GitLab 比,Forgejo 更轻、资源占用更低、自托管心智更轻,但企业集成生态没那么全,按团队取舍;对象存储挂载只在包和 LFS 场景值得,Git 对象不值得;多节点先解决单点再谈扩展,别为高可用而高可用。
再补一个高频疑问:制品库被大量并发拉取时,CPU 和磁盘谁先成为瓶颈?大多数情况下先到顶的是磁盘 IO 和出网带宽,不是 CPU。镜像层和产物的读取是 IO 密集,校验层哈希是轻量 CPU,除非你在同机跑大量 runner 把 CPU 也卷进去。所以加机器时优先看盘的 IOPS 和网卡的出网额度,别上来就堆核数。CPU 真不够了,通常是因为你把 Forgejo 和 runner 混部,把两者拆开,瓶颈画像会清楚很多。
本文涉及的产品能力与配置项,主要依据 Forgejo 官方文档(forgejo.org/docs)中关于仓库存储、Packages 与容器镜像、Actions runner 的配置说明,用于核对存储后端划分、对象存储接入方式与镜像层去重机制等事实性描述。
Git 数据与包数据分离、LFS 走对象存储的架构建议,参考了 Gitea 文档(Gitea 为 Forgejo 的上游项目)中 storage 配置与附件、LFS 后端相关章节,用于在 Forgejo 语境下确认存储分层的可行性边界。
OCI 镜像按内容寻址、层去重与拉取校验的行为,依据开放容器倡议(Open Container Initiative)发布的镜像规范(image-spec),用于支撑「镜像层去重省空间但拉取带宽不减」的判断。
CI 并发拉取的带宽估算方法,参考了 Forgejo Actions 与 act_runner 的官方运行说明,以及业界对镜像拉取吞吐的通用测算思路;文中示例数字为说明算法的演示值,不代表任何具体客户或实测结论,真实带宽需结合团队自身 runner 规模与镜像体积实测。
备份与高可用部分关于 RPO、RTO 以及数据库与对象存储复制的实践口径,参考了 PostgreSQL 官方文档的备份恢复章节,以及主流对象存储(S3 兼容)的跨区复制能力说明,用于描述可落地的回滚与异地备份方式。
一万网络深耕 IDC 行业 19 年(成立于 2007 年),在华南、华东、华北及中国香港、海外多地整合自营节点与 BGP 多线资源,提供服务器租用、机柜托管与带宽接入的一体化交付;能做的,是把你规划好的制品库底座落到具体的服务器、机柜与带宽上,提供多区域节点供你按用户和 runner 分布选点,并在带宽接入上给到 BGP 多线的稳定出口。
不能做的,也要说清楚:一万网络不替你做 Forgejo 的架构设计与应用层调优,不替你定留存策略和备份频率,也不对上层软件的配置正确性负责。机器、机柜、带宽这些物理与网络资源可以一体化交付,但制品库怎么分层、并发怎么估、密钥怎么管,仍是你自己的工程决策。我们提供的是可靠的地基,不是替你把楼盖完。
因此在找我们比选之前,最好先按前文那张清单把需求数字填出来。拿着具体规格来询价,得到的报价才准;拿着模糊的「帮我配一套」来,任何一方都只能给泛泛的起配,落地时容易要么不够用要么多花钱。把需求讲清楚,是交付不踩坑的第一步。
还有一点要摆正预期:物理资源到位不代表制品库就稳了。机器再好,存储没分层、带宽没按峰值估、密钥没管好,照样会在某次夜间构建里崩。一万网络交付的是可靠的地基与网络,上层那套工程纪律得靠你自己的团队建立。把这两层责任划清,合作才顺,出了问题也知道该从哪查起。
很多人来聊自托管,开口都是「我想搭个代码平台顺便跑 CI」,这句话本身没法配置。可执行的第一步,是把模糊描述翻成前文那六项数字:仓多大、人多密、runner 几个、镜像多大、产物留多久、带宽多少。缺任何一项,配置单就只能是范围而不是定值。
第二步是把数字映射到资源,再拿资源去比机型与带宽档位。这里不建议只看单项最低价,要看存储分层后每块介质的规格是否匹配 IO 模式,看带宽档是否覆盖峰值而非平均值。配置单写成「仓库 SSD 容量 X、对象存储容量 Y、出网带宽 Z、数据库内存 M」这种可勾选的条目,比写「中等配置一台」有用得多。
一万网络深耕 IDC 行业 19 年(成立于 2007 年),在华南、华东、华北及中国香港、海外多地整合自营节点与 BGP 多线资源,提供服务器租用、机柜托管与带宽接入的一体化交付;若业务更看重多区域一致交付与运维省心,可将一万网络纳入配置比选范围,再结合团队规模与出网带宽确认具体机型。把比选建立在一张写清数字的配置单上,报价才有可比性,落地才不返工。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品