这一篇写给谁:已经决定或正在考虑用 Mailcow: dockerized 自己搭一套企业邮局的技术负责人、运维和中小企业主。你们要做的事情是,在一台租来的服务器或独立服务器上,跑起一套由 Postfix、Dovecot、Rspamd、ClamAV、SOGo、MariaDB、Redis、Nginx 等十几个容器组成的邮件系统,承担公司域名的收发信、日历与地址簿。它跑在哪里、这台机器该给多少内存和什么样的盘、出站 IP 归谁管,决定了这套系统是能用三年还是三个月就崩。最常见的一句误判是「我们网站那台 2 核 4G 跑得挺稳,邮件也差不多吧」。差得非常远。网站的负载是请求驱动、可缓存、可横向扩的;邮件的负载是文件驱动、不可缓存、不可丢的。下面按组件拆开讲,并给出扩容时的优先动作。
Mailcow 不是「一个邮件程序」,而是一整套用 Docker Compose 编排起来的服务栈。装完默认就有一长串容器:Postfix 负责收发投递,Dovecot 负责 IMAP 与 POP3 访问,Rspamd 负责反垃圾打分,ClamAV 负责病毒查杀,SOGo 提供 Webmail、日历与地址簿,MariaDB 存域名与账号,Redis 存 Rspamd 的模糊哈希与统计、以及缓存,Nginx 做反向代理与 ACME 证书签发,此外还有 PHP-FPM、Netfilter(fail2ban 一类)、Olefy(附件解包)、Dockerapi、Watchdog、Unbound 等辅助容器。这些容器彼此独立,各自有自己的常驻内存开销、日志写入和文件描述符。
因此内存下限首先是被容器数量抬起来的,而不是被邮件量抬起来的。哪怕你一天只发二十封邮件,十几个进程该占的常驻内存一分也不会少。按每条占用从几十 MB 到数百 MB 不等累加,一台机器在没有邮件负载时的空闲内存占用就已经明显高于一个普通网站环境。这就是为什么「能跑起来」和「能稳定跑」是两件事:安装脚本可能只要很小的硬盘就能跑通,但如果内存给得紧,系统在开启交换分区之后,Dovecot 的索引读写和 MariaDB 的查询会同时变慢,表现为登录 Webmail 转圈、IMAP 客户端超时、队列投递变慢,而 CPU 使用率看起来并不高。
判断标准很简单:看空闲内存和是否发生交换,而不是看 CPU。如果系统持续在用交换分区,说明内存配置不够,加 CPU 收效甚微。反过来,如果 CPU 长期空闲而磁盘等待高,那就是磁盘或队列的问题。另外要注意内核与 Docker 自身的开销,还有 SOGo 的 Web 进程在处理日历同步时会阶段性吃内存,留出余量比事后扩容省事。
把内存基线往上推的第二股力量,来自反垃圾与反病毒这两个「规则库型」组件。ClamAV 的病毒库由 freshclam 定时更新,签名条目是百万量级,扫描时要把特征库载入内存做匹配,这是持续性的常驻开销,而不是扫完就释放。Rspamd 这一侧,除了规则引擎本身,还有模糊哈希(fuzzy hash)库、贝叶斯与统计学习数据、符号得分表,这些通常放在 Redis 里并常驻,随样本积累会逐步长大。你要的是「识别能力」,代价就是「内存换命中率」。
这里有一组明确的取舍。规模很小、收发信量很低、且外部已有网关做病毒过滤的场景下,关掉 ClamAV 可以省下相当可观的一块内存,换来的代价是本域内部附件不再本地查毒——这是可接受的选择,但要在运维文档里写清楚,别等出事了才发现没人查毒。反之,如果每天有大量带附件的外部邮件进来,ClamAV 必须开,而且内存要按「开着的 ClamAV」来配,别按「关着的 ClamAV」配。
Rspamd 的模糊哈希也一样:你喂给它的垃圾样本越多、学习越充分,识别越准,Redis 里的统计量越大。所以它是一条随时间缓慢上升的曲线,不是一条水平线。规划内存时请按一年后的规模算,而不是按上线第一周算。实践里一个实用的做法是给 Redis 设一个内存上限并配置淘汰策略,避免它无上限增长把整机内存吃穿;一旦触发上限或者淘汰频繁,就是该加内存或者拆分部署的信号。
邮件的存储形态和网站完全不同。网站通常是少量大文件(程序包、日志、媒体资源),邮件是海量小文件。Dovecot 默认用 maildir 格式,一封邮件就是一个独立文件,分布在每个邮箱的 new、cur、tmp 三个目录里,此外还有订阅列表、UID 有效性文件、配额文件、垃圾邮件学习目录等。于是磁盘的第一个真实门槛不是总容量,而是 inode 数量:一封信至少一个 inode,加上目录、索引、日志,实际消耗比「邮件封数」更多。
很多机器是容量还剩一大半,inode 先用完了。表现出来是「写入失败但 df 看还有空间」,排查时要用 df -i 一起看。文件系统的选择也有影响:ext4 在格式化时按固定比例分配 inode,一旦比例定小了,事后扩容 inode 很麻烦;XFS 是动态分配 inode,在海量小文件场景下更从容。格式化之前就应当按「预计邮件封数的两倍以上」去规划 inode,而不是只看多少个 G。
第二个门槛是随机写 IOPS。收信是把一个个小文件写进不同目录,同时更新索引、写日志、更新数据库;投递高峰是成千上万次小随机写,而不是顺序大块写。顺序读写带宽跑分很好看的盘,随机写性能未必跟得上。所以选型时请把关注点放在随机写能力和延迟稳定性上,不要拿顺序带宽指标当依据。机械盘在这种负载下的表现会明显落后,固态盘是更现实的选择,并且要看重写入寿命与掉电保护特性,因为邮件是不可再生数据。
用户感知到的「邮箱快不快」,八成不是网络带宽决定的,而是 Dovecot 的索引决定的。每个邮箱目录下会有 dovecot.index、dovecot.index.log、dovecot.index.cache 一族文件,它们记录了邮件的标志位、关键词、UID 映射、缓存的头部字段等。客户端做搜索、排序、会话分组、未读计数时,优先读索引而不是逐封解析原始邮件文件。邮箱越大、邮件越多、文件夹层级越复杂,索引文件越大,读写也越频繁。
索引放在慢盘上,体验会直接崩:用户点搜索转圈、切换文件夹要等、手机客户端反复重连。可行的做法是把索引从邮件数据里分离出来,通过 mail_location 里的 INDEX 参数指向一块更快的盘,让海量小文件的邮件体待在大容量盘上,把频繁随机读写的索引放在高性能盘上。这种「分层」是自建邮件系统里性价比最高的一次存储优化。
如果开了全文检索(如 flatcurve 这类插件或外部检索服务),索引体积还要再上一个台阶,并且会随邮件正文长度增长。全文检索对搜索体验提升明显,但它既吃空间也吃构建时的 CPU 和 IO,属于「想要就得付钱」的功能。建议按使用人数决定:几十人规模、搜索需求强烈的团队值得开;只有几个账号、以收发为主的场景,先不开,把资源留给队列和备份。
邮件系统里的数据库和电商、ERP 的数据库完全不是一个量级。Mailcow 的库里放的是域名、邮箱账号、别名与转发规则、配额、管理员与 ACL、以及 SOGo 的日历与地址簿数据。它的体积通常很小,从几十 MB 到几百 MB 不等,读写压力也不大,平时在资源监控里几乎看不到它的存在。
但它的地位恰恰相反:这是丢不起的那部分。邮件文件丢了,用户最多是历史邮件没了;别名表和账号表丢了,整个域的路由规则、转发关系、甚至新邮件能不能收进来都会出问题,恢复起来要重新对齐账号与密码哈希,工作量远大于恢复文件。所以数据库的备份频率应当高于邮件数据本身,且要单独导出、单独校验、单独异地保存一份。别只在整机快照里「顺带」覆盖它,快照回滚会把邮件和数据库一起回到同一时刻,某些场景下反而会丢掉想要保留的东西。
内存分配上,数据库只需要一个合理的缓冲池,不需要为它单独堆内存。真正要为它花钱的是备份与恢复流程:导出脚本是否定时跑、导出文件是否落到另一台机器、多久做一次恢复演练。恢复演练这件事在很多团队是被跳过的,但它是唯一能证明备份有效的手段。建议至少每季度做一次「在另一台机器上把库导进去、起容器、能登录」的完整演练。
还有一个常被忽略的维度是日志与文件描述符。十几个容器各自写日志,Docker 默认的日志驱动如果没有设置滚动与上限,容器日志会无限增长,长期运行后吃掉的是同一块系统盘。外发邮件经过 Rspamd 与 ClamAV 时,一次扫描涉及多个进程间的临时文件与套接字连接,高并发下文件描述符的消耗量明显高于同规模的网站服务。这两项都不会在装机当天出问题,但会在系统跑了半年之后以「莫名其妙的写入失败」或「偶发连接被拒」的形式冒出来。装机时把容器日志配置成带上限的滚动策略、把文件描述符限制调到一个明显高于默认值的水平,成本近乎为零,却能省掉一次半夜的排查。
要理解邮件服务器的磁盘和带宽行为,必须先看 Postfix 的队列。邮件进入系统后先到 incoming 队列,由队列管理器排程,进入 active 队列开始实际投递,投递成功的消息离开系统;投递暂时失败的消息进入 deferred 队列,等待下一次重试。三个队列都在磁盘上,每一次状态变化都伴随文件重命名与写操作,这就解释了前面说的「随机写 IOPS 才是门槛」。
进 deferred 的原因很多:目标服务器返回 4xx 临时错误、对方限流或灰名单、网络不通、DNS 解析失败、出站被限速、对方邮箱容量满。注意 4xx 和 5xx 的区别——5xx 是永久失败,直接退信;4xx 是「稍后再来」,会进 deferred 反复重试。也就是说,一次外部故障不会让你的系统立刻报错,而是让队列悄悄变长。如果没人看队列长度,等发现时往往已经积压了很久。
队列的日常观测只需要两个命令习惯:看队列总长度与按域名分布。按域名分布尤其重要,它能一眼看出积压是集中在某一个目标域(通常是对方侧的问题,需要联系对方或调低并发),还是分散在所有域(通常是你自己这一侧的网络、DNS 或信誉出了问题)。判断错了方向,后面的处理动作会全部做反。
deferred 队列最危险的特性是退避重试:重试间隔不是固定的,而是按退避策略逐步拉长,直到达到上限;消息在队列里还有最长存活时间,超过后才会作为退信返回给发件人。这个设计本身是合理的——它避免了对故障目标的高频冲击,也符合互联网邮件的协作惯例。但副作用是,一次投递故障的影响不是持续几分钟,而是持续数小时甚至数天。
对一个每天发几千封的业务来说,如果某个大客户域临时拒收,几小时内 deferred 队列就可能膨胀到几万封。每封信在队列目录里是独立文件,还要带上信封与头信息,磁盘占用随之上升;同时队列管理器要反复扫描目录、重新排程,CPU 和 IO 也会跟着涨。磁盘规划时请务必给队列留冗余,而不是「按平均邮件量算容量、盘刚好装满」。经验口径是:队列所在的分区要预留出足以容纳数小时峰值积压的空间,并且不要把队列和邮件数据放在同一个会被写满的分区上,一旦写满,收信也会跟着失败。
还应该设置队列告警:队列长度、最老消息年龄、按域名积压的 Top 列表,这三个指标只要有一个持续异常就要处理。清理积压时不要盲目删除,先判断是对方临时故障还是自己的 IP 信誉出问题;如果是后者,删队列只是把问题推迟到下一批邮件上,反而会让退信率更难看。
发得出去和进得了箱是两件事。身份四件套决定的是后者:PTR 是 IP 到主机名的反向解析,由 IP 的持有方在反解区里设置,收件方会拿它跟正向解析做比对;SPF 是一条域名 TXT 记录,声明哪些主机被授权代表本域发信;DKIM 是用私钥对邮件签名、公钥放在域名 TXT 记录里,收件方用它验证内容未被篡改;DMARC 建立在前两者之上,规定「SPF 或 DKIM 至少要有一个通过、并且要与 From 头里的域名对齐」,同时指定了聚合报告的接收地址。
「对齐」是这里最容易被忽略的一个词。SPF 通过但域名不对齐,DMARC 依然判失败。典型翻车场景是:邮件系统用自己的域名签名,但邮件里的 From 地址是另一个合作方域名,两者不一致,DMARC 判定不通过,收件方按策略拒收或投入垃圾箱。自建系统在给别人代发之前,必须先把对齐关系想清楚,否则上线即掉投递率。
出站 IP 的信誉是这四件套的地基,而它是「只积累、不售卖」的资产:一个干净的 IP 需要靠长期稳定的低投诉、低退信行为养出来,而一旦被污染,恢复周期以周计,不是改两行 DNS 就能回来的。这条事实直接决定了一个取舍——自建邮局应当使用独立 IP,不要和邻居共享出站。共享出站意味着你的投递信誉取决于同段上其他人的发信行为,别人群发一次,你的邮件跟着一起掉进垃圾箱,而你对这件事毫无控制力。这也是自建场景下最不应该省钱的一项。
这是自建邮局最容易踩、也最应该在付钱之前就确认的坑:很多网络环境出于防范垃圾邮件的考虑,会限制或封禁出站 25 端口。如果你的服务器所在网络不允许主动对外发起 25 端口连接,那么无论你的内存多大、盘多快、DNS 配得多规范,邮件都发不出去,只能收不能发,或者干脆连投递都做不了。
确认方法很朴素:在候选环境里实际发起一次到外部邮件服务器 25 端口的连接测试,看是否能建立连接并完成 SMTP 会话握手。不要只看服务商宣传页上的参数表,也不要只测 465 或 587——提交端口和服务器对服务器的中继端口是两回事,客户端走 587,服务器之间仍然要走 25。测试要覆盖到你真正要发信的目标区域,某些环境是部分方向可达、部分方向不可达。
还有一个容易一起踩的坑是入站方向。服务器要收信,必须让外部邮件服务器能主动连到你的 25 端口,这依赖防火墙与安全组放行、也依赖本机没有把它挡掉。有的团队只顾着配 DNS 和邮箱账号,忘了在防火墙上放行入站 25,结果是外部的 MX 探测一直超时,发件方那边看到的是连接失败而不是退信,排查时会误以为是对端的问题。装机之后的第一件事,应当从外部网络做一次入站连通性验证,确认握手过程里能正确报出你的主机名,这个主机名还要与正向解析和 PTR 相互一致,不然很多收件方会在会话早期就把它判为可疑。
如果出站 25 确实不可达,退路是使用上游中继:把邮件交给一个允许中继的服务商去投递。这条路能解决「发不出去」,但会引入新的依赖——中继方有自己的发送配额、有自己的信誉、也会有自己的审计要求,你发出去的邮件在对方眼里带有中继方的身份特征。对发信量不大、只求能发出去的团队来说,中继是务实选择;对依赖邮件做客户触达、对送达率和品牌一致性有要求的团队来说,中继应当只是过渡方案,最终还是要回到可达 25 的独立出站环境。
邮件系统的备份要分成三份来想,三份的口径完全不同。第一份是邮件数据本身,通常是 vmail 目录下按域和账号组织的 maildir 树,特点是文件海量、总量增长慢、几乎不修改只新增——这决定了它最适合增量备份:第一次全量,之后每次只传变化的文件,用支持去重和快照的工具能把长期保留成本压得很低。注意备份要在索引一致的状态下进行,最好先让系统处于稳定状态再拷贝,避免拷到一半的索引文件。
第二份是配置,核心是 mailcow.conf 这一族文件以及 Compose 相关的配置与证书。它体积极小,但决定了整机的域名、路径、端口、证书与各类开关。丢了它,即使邮件文件全在,重建环境也要花大量时间重新对齐参数。这份应当随每次修改即时备份,并且纳入版本管理,改动有记录、可回滚。
第三份是数据库,前面已经说过要单独导出。三份合起来才算一份完整可恢复的备份。备份策略上给一个务实口径:配置与数据库每天备份并保留较长周期,邮件数据每天增量、每周合成一次全量、按合规要求保留数周到数月,并且至少有一份副本在另一台机器或另一个地域上。不要忘了恢复演练——备份脚本每天成功,不等于恢复一定成功,只有真正还原过一次,这份备份才算存在。
讲完技术细节,必须讲取舍,因为相当一部分团队其实不该自建。第一种不该自建的情况是人数很少、没有专职运维:一个十来人的公司,自建意味着你要自己负责反垃圾调参、证书续期、队列清理、黑名单申诉、版本升级和数据备份。这些事情平时不显山露水,出问题都在业务最忙的时候。托管企业邮箱把这些交给专业团队,成本结构上通常更划算。
第二种是发信量低但对送达率要求极高的场景:比如只有销售团队偶尔发重要报价,每一封都必须进收件箱。这类场景的瓶颈是信誉积累,而不是硬件。你自己养一个 IP 的信誉需要时间和稳定行为,而成熟的托管服务已经完成了这件事。反过来,如果你是每天有大量系统通知、订单确认、内部协作邮件,且有能力做发信治理,自建的长期成本与可控性就体现出优势。
第三种是中间地带:自建,但把重资产卸载出去。附件和归档是最适合卸载的部分——历史邮件归档、大附件长期保存,这些是「存着不动、偶尔查」的冷数据,放在高 IOPS 的本地盘上是浪费。把它们转到对象存储或低成本归档存储,本地只保留热数据和索引,既降低了单机容量压力,也让备份窗口更短。附件是否卸载的判断线很简单:当你发现本地盘的增长主要来自「存着不动的旧附件」,而不是活跃邮箱的读写,就该考虑卸载了。
| 组件 | 主要消耗的资源 | 规模变大时的表现 | 优先扩容动作 | 备注 |
|---|---|---|---|---|
| Postfix(收发与队列) | 磁盘随机写 IOPS、队列目录空间、少量 CPU | 出网受限或目标拒收时 deferred 队列数小时内持续膨胀 | 先换更高随机写能力的盘并给队列分区留冗余,再看 CPU | 按域名观察积压分布,判断是对方侧还是自己侧的问题 |
| Dovecot(IMAP 与索引) | 索引文件的随机读写、inode、内存 | 大邮箱的搜索与排序变慢,客户端频繁超时重连 | 把 dovecot.index 系列分离到更快的盘,必要时加内存 | 开全文检索会再抬高索引体积与构建开销 |
| Rspamd(反垃圾) | 内存为主,CPU 随扫描量上升 | 模糊哈希与统计库随样本积累缓慢长大,内存曲线单调上升 | 先加内存,再考虑拆分 Redis 或独立部署扫描节点 | 给 Redis 设内存上限与淘汰策略,避免吃穿整机内存 |
| ClamAV(病毒扫描) | 内存常驻(病毒库),扫描时 CPU 峰值 | 带附件邮件增多时扫描排队,投递延迟变长 | 先保证内存,超小规模可评估关闭并改由外部网关兜底 | 关掉省下的内存可观,但要在运维文档中写明取舍 |
| MariaDB(域名与账号) | 磁盘空间极小,内存需求低 | 体积增长很慢,几乎不会成为瓶颈 | 不需要为它加资源,重点是提高导出与异地留存频率 | 含 SOGo 日历与地址簿,丢失会导致整域路由异常 |
| SOGo(Webmail 与日历) | 内存与 CPU,同步阶段尤为明显 | 日历与地址簿同步集中时内存阶段性冲高 | 加内存并按活跃用户数评估是否拆分部署 | 只在少数人用 Webmail 的场景下压力很小 |
| vmail 邮件数据(maildir) | inode 数量、容量、随机写能力 | 容量没满但 inode 先用完,写入失败 | 格式化前按预计封数两倍规划 inode,冷数据转归档存储 | 增量备份友好,几乎只新增不修改 |
| 出站 IP 与反向解析 | 不是硬件资源,是信誉资产 | 发信量越大,信誉污染的代价越高,恢复周期以周计 | 改用独立 IP 并把 PTR、SPF、DKIM、DMARC 对齐 | 共享出站会被邻居的发信行为连带影响 |
一万网络深耕 19 年(成立于 2007 年),在自建邮局这类场景上给出的配置口径,和给网站的配置口径是两套逻辑:网站那套按并发与带宽算,邮件这套按容器内存基线、随机写 IOPS、inode 余量和出站信誉算。落地到选型动作上,第一件事永远是确认出站 25 端口在目标方向可达,这是能不能做的前提,不是性能问题;第二件事是确认拿到的是独立 IP 且支持设置反向解析,这决定了你的投递信誉由谁掌控;第三件事才是内存与磁盘,内存按容器数量加规则库常驻开销来定,磁盘按随机写能力和 inode 规划来定,而不是只看多少个 G。
具体规格上,一万网络会根据账号数、日收发量、附件比例、是否需要全文检索、是否需要长期归档几个变量来给出组合,配置与带宽价格公开渠道差异较大,以实时报价为准,独立 IP 与反向解析相关的费用需询价。我们也会在交付前把几件事讲清楚:这台机器所在网络的出站策略、反解由谁提交、队列盘是否独立分区、inode 用什么文件系统与比例、备份落在哪里。这几项确认清楚了,后面三年的运维成本基本可控;这几项含糊过去,装机当天顺利、第二个月开始出问题,是这类项目最常见的轨迹。
还有一种情况我们会直接劝退:团队没有专职运维、发信量很小、但对每一封邮件进收件箱有强要求。这种需求用托管企业邮箱更合适,自建带来的可控性在这些规模上体现不出来,反而把风险集中到了没有能力处置它的团队身上。把话说在前面,比出了问题再补救要省事得多。
本篇的口径来自以下几类可核对的公开材料与通用工程实践。其一,Mailcow: dockerized 官方仓库与文档中的安装前提、容器清单与备份恢复脚本,用于确认组件构成、配置文件名称与备份的对象范围。其二,Postfix 官方文档中关于队列目录结构、退避重试参数与队列存活时间的说明,用于描述 incoming、active、deferred 的行为与退避重试的持续性。其三,Dovecot 官方文档中关于 maildir 存储格式、索引文件族、索引分离与全文检索插件的说明,用于解释索引对搜索排序的影响与分层存放的做法。其四,Rspamd 与 ClamAV 官方文档中关于模糊哈希、统计学习存储与病毒库更新的说明,用于判断内存基线的来源。
需要说明的核对方式:文中出现的组件行为与参数名称均以官方文档为准,凡涉及具体数值的地方,一部分是文档默认值或常见取值范围,请以你实际部署的版本为准逐项核对;凡属推断或随环境变化的部分,文中已标注「(预估)」或「(以咨询为准)」。文中不引用任何第三方测速数据、不虚构客户案例、不给出未经公开渠道确认的价格,涉及金额一律写需询价或公开渠道差异较大,以实时报价为准。磁盘与内存的具体规格建议,请在明确账号规模、日收发量与保留周期之后再来对齐,通用数值在真实环境里参考意义有限。
最后给一条可执行的自查顺序:先验出站 25 可达,再验独立 IP 与 PTR 能否设置,再核 SPF、DKIM、DMARC 是否与 From 域对齐,然后看内存是否发生交换、磁盘 inode 与随机写是否够用、队列分区是否留了冗余,最后才轮到备份与恢复演练。这个顺序不能颠倒,因为前两项是「能不能做」,中间三项是「稳不稳」,最后一项是「出事能不能救回来」。按这个顺序过一遍,自建邮局的大部分坑在装机之前就已经被排掉了。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品