ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

隔离内网MongoDB离线部署:从环境核查到安全认证的完整实践

隔离内网MongoDB离线部署:从环境核查到安全认证的完整实践

这周刚在隔离内网里完成了一套MongoDB的离线部署,趁着手边的记录还热乎,把整个流程、踩过的坑和排查思路完整写下来。离线部署mongodb这件事表面看很简单——把安装包用U盘拷进去、解压、启动,但真正在隔离网段里动手才发现,依赖库的版本、glibc的兼容性、甚至一条systemd配置写错,都能让服务卡在原地半天起不来。这篇内容适合两类人:一类是需要在政务内网、企业隔离区、研发测试网这类没有外网权限的环境里搭建MongoDB的运维或后端工程师;另一类是虽然暂时没遇到离线场景,但想系统地把MongoDB从安装到调优整个细节捋一遍的同学。

1. 离线部署的整体思路与方案选型

1.1 为什么会有离线部署需求:先看清场景

离线部署最常见的触发条件是网络隔离。我之前接触过某单位的内部数据平台,服务器在独立的业务专网里,物理上和互联网完全断开,连yum源都不能访问外网,所有软件安装都得靠本地介质。这类环境有几个共同特征:机器数量不多但配置要求严格;操作系统版本五花八门,有的CentOS 7,有的Ubuntu 20.04;安全策略复杂,对端口、账号、权限都有审计要求。

在这种环境里部署MongoDB,首先要想清楚一件事:你是只装一台单机,还是需要Master-Slave或者副本集?这一点决定了后续的物料准备和配置复杂度。单机部署只需要一个安装包和一份配置文件;副本集部署则要考虑多台机器之间的网络互通、oplog大小、心跳地址等参数。

还有一个容易忽略的点:内网环境的机器往往没有配置域名解析,集群节点间的通信只能靠IP地址。所以部署时绑定IP和配置复制集成员时,都要直接填IP,不能写主机名。这些细节我后面会展开说明。

1.2 三种可行的离线部署路径对比

离线部署MongoDB有三条主流路径,我在不同项目里都试过,这里直接给结论:

  • 二进制包直接解压部署。这是最推荐的方式,尤其是官方提供的.tgz压缩包,本身是静态编译风格,依赖项相对少,只要系统glibc版本满足要求,基本上解压就能跑。优势是可控性强,所有文件都在指定目录,卸载时直接删目录即可,便于内网环境的资产管理和安全审计。
  • Docker镜像离线导入。适合已经建立容器化标准的内网环境。在外网机器上docker pull mongo镜像,再用docker save导出为tar包,拿到内网后用docker load导入。这条路要求目标机器已经装好了Docker,而且内网环境的docker镜像仓库也需要事先搭好,否则又要处理一层离线依赖。
  • 源码编译安装。极度不推荐,MongoDB的编译过程会拉取大量第三方依赖,而且在离线环境里解决编译期依赖会让人痛不欲生。除非有极其特殊的定制需求,否则不要走这条。

我个人的选型逻辑是:如果内网机器是裸金属或者虚拟机,直接用官方二进制包;如果内网已经有成熟的Kubernetes或Docker体系,优先走镜像导入。两条路不冲突,但大多数场景下二进制包更省事。

2. 离线部署前的环境核查与物料准备

2.1 目标机器的硬件与系统版本核查

离线环境最大的问题是“不能试错”,因为每次测试失误都要重新走流程,找包的代价又高。所以动手之前,务必把所有信息核查一遍。

要记录的关键信息包括:

  • 操作系统发行版和内核版本:执行cat /etc/os-release和uname -a查清楚。不同发行版对MongoDB的兼容性差异很大,比如CentOS 7最高支持到MongoDB 6.x,而MongoDB 7.x要求glibc 2.28以上,这就直接把CentOS 7排除掉了。
  • CPU架构:大部分服务器是x86_64,但我也遇到过ARM架构(如鲲鹏、飞腾)的机器,需要下载对应的ARM版安装包。
  • 内存和磁盘:MongoDB对内存的消耗不像传统关系型数据库那么“有界”,它依赖操作系统的文件缓存,建议至少给到4GB以上内存;磁盘建议用SSD,并且预留至少和预期数据量一样大小的空间。dbPath所在分区需要足够余量,日志和数据盘最好分开。
  • 是否已安装必要的动态库:MongoDB依赖于libcurl、libssl等基础库,后面会细说核查方法。

提示:在离线环境里,最容易让人忽视的就是操作系统小版本。比如都是CentOS 7,但7.4和7.9的内核版本不同,导致依赖库行为不完全一致。先update到最新小版本,或者选择与目标环境一致的系统镜像做预验证,能省掉很多后续麻烦。

2.2 glibc版本的兼容性核查:最大的隐藏雷区

如果只能讲一个离线部署的坑,我把票投给glibc版本。MongoDB的官方二进制包是针对主流Linux发行版编译的,它对glibc的最低版本有明确要求。比如MongoDB 7.0要求glibc 2.28以上,这意味着Ubuntu 18.04和CentOS 7都装不了;MongoDB 6.0对glibc的要求相对宽松一些,在CentOS 7上还能跑。

核查命令很简单:

ldd --version

看到输出的第一行,比如ldd (GNU libc) 2.28,就是当前glibc版本。如果你的内网机器glibc低于安装包要求,两种解决办法:一是换用兼容的MongoDB大版本;二是找一个在相同系统版本上能运行的旧版MongoDB(比如4.4或5.0)。

另外还有一个容易被忽略的动态库依赖,MongoDB启动时需要libcrypto、libssl这些OpenSSL相关的库。在Ubuntu上确认:

ldd mongodb-bin/mongod | grep "not found"

如果有任何一行显示not found,那就需要从操作系统的安装镜像里找到对应的deb或rpm包,带进内网去装。这一步在外网环境完全感受不到,在离线环境却非常致命。

2.3 物料清单:一次性列全

综合我几次实操的经验,离线部署前需要准备的物料如下:

物料项说明注意事项
MongoDB安装包从官网下载对应发行版的.tgz包架构、glibc、版本三要素匹配
依赖库安装包libssl、libcurl等从系统对应版本的安装镜像iso里提取
配置文件模板mongod.conf预设一份端口、路径、bindIp等参数
systemd服务文件mongod.service模板或init.d脚本,视系统版本而定
校验工具sha256sum验签、vmtools等确认包完整性与传输一致性
运维命令工具mongosh或旧版mongo shellMongoDB 6.0以上默认不带mongo shell,需要单独下载

3. 单机离线部署完整实操过程

3.1 创建运行用户与目录结构

永远不要用root直接跑MongoDB,这是部署和运维的底线原则。MongoDB官方文档明确要求以非root用户运行,而且内网安全审计通常会检查进程的运行身份。我习惯于建立独立的mongod用户和目录结构:

groupadd mongod useradd -m -d /app/mongodb mongod -g mongod

目录规划上,我把数据和日志分开:

  • /app/mongodb/:程序安装目录
  • /app/mongodb/data/db:数据存储目录
  • /app/mongodb/log/:日志文件目录
  • /app/mongodb/conf/:配置文件目录

创建之后要给mongod用户对应目录的读写权限,不然启动时会报权限错误。这一步在内网环境尤其重要,因为很多审计策略要求“最小权限运行”,甚至要求数据目录挂载在独立的加密分区上。

3.2 解压安装包与环境变量配置

把安装包传到内网机器后,先做完整性校验。收到安装包时记录下官方发布的SHA256值,然后:

sha256sum mongodb-linux-x86_64-*.tgz

对比一致后,解压到指定目录:

tar -zxvf mongodb-linux-x86_64-*.tgz -C /app/mongodb/

解压后会得到一个以版本号命名的文件夹,比如mongodb-linux-x86_64-7.0.5。我习惯把这个目录保持原样,然后做一个软链指向当前版本:

ln -s /app/mongodb/mongodb-linux-x86_64-7.0.5 /app/mongodb/current

这样后续升级时可以保留旧版本目录,只切换软链,回滚非常方便,这个思路在做内网资产版本管理时也很实用。

接下来配置PATH环境变量,在/etc/profile.d/mongod.sh里写入:

export PATH=/app/mongodb/current/bin:$PATH

然后source /etc/profile.d/mongod.sh,验证一下:

mongod --version

输出里能看到版本号和编译参数。这一步如果提示动态库缺失,就回到第2.2节那个检查流程去补库。

3.3 mongod.conf配置文件详解

配置文件是部署环节的重头戏,离线内网环境的配置讲究“稳”和“安全”,我贴一份经过多次实践打磨的配置模板:

systemLog: destination: file path: /app/mongodb/log/mongod.log logAppend: true logRotate: reopen storage: dbPath: /app/mongodb/data/db directoryPerDB: true engine: wiredTiger wiredTiger: engineConfig: cacheSizeGB: 1 processManagement: fork: true pidFilePath: /app/mongodb/mongod.pid net: port: 27017 bindIp: 127.0.0.1,内网IP maxIncomingConnections: 2000 security: authorization: disabled

这里说几个关键参数的选择逻辑:

  • storage.engine:默认就是wiredTiger,不需要改。但要注意cacheSizeGB不要想当然地设成内存的一半,在内存只有4GB的机器上,建议设为1至1.5GB,否则操作系统缓存压力会非常大。
  • net.bindIp:内网环境里我通常绑定内网IP,不要只绑127.0.0.1,否则其他应用服务器连不上。但要避免绑定0.0.0.0,减少暴露面。具体绑定哪个网卡地址,以业务拓扑为准。
  • security.authorization:首次部署可以先关闭认证,方便初始化,但正式交付前必须开启。后面我会单独讲认证配置。
  • logAppend和logRotate:日志追加而不是覆盖,并用reopen方式让systemd的日志轮转工具可以平滑切割日志。
  • directoryPerDB:每个数据库单独一个目录,做备份和空间分析时更方便。

提示:不要把dbPath放在root家目录或/tmp下。内网机器如果目录挂载在/根分区,一旦数据量暴涨,很容易直接把系统盘撑爆。最好把数据和系统盘分开挂载。

3.4 使用systemd托管MongoDB服务

手动mongod -f mongod.conf启动只能在前台或后台临时运行,真正交付到生产环境必须用systemd托管,实现开机自启和崩溃自动拉起。

新建/etc/systemd/system/mongod.service:

[Unit] Description=MongoDB Database Server After=network.target [Service] User=mongod Group=mongod ExecStart=/app/mongodb/current/bin/mongod --config /app/mongodb/conf/mongod.conf PIDFile=/app/mongodb/mongod.pid LimitNOFILE=65535 LimitNPROC=65536 Restart=on-failure TimeoutStartSec=60 [Install] WantedBy=multi-user.target

这里面的LimitNOFILE和LimitNPROC一定要设置。MongoDB在并发连接高的时候,文件描述符数量很容易突破系统默认的1024限制,导致连接被拒。内网监控平台上的告警,有一半以上都是这类“文件描述符耗尽”引起的。设置完后:

systemctl daemon-reload systemctl enable mongod systemctl start mongod

用systemctl status mongod观察运行状态,再确认日志目录下生成了mongod.log。如果启动失败,用journalctl -u mongod -n 100查日志,绝大多数问题都能在这里看到明确线索。

3.5 初始化验证:不只是连上就完事

简简单单打一个mongosh能进入shell,并不能说明部署已经成功。我建议按以下顺序做一轮验证:

  • 基础连接测试:mongosh --host 内网IP --port 27017能正常进入。
  • 写读验证:插入一条测试数据再读出来。
  • 视图与服务状态验证:systemctl status mongod显示running。
  • 日志无异常:执行db.adminCommand({serverStatus: 1})前先检查日志中是否有warning或error级别的输出。
  • 服务重启验证:systemctl restart mongod后,确认数据目录的锁文件能正常回收,启动无报错。

这轮验证最好写成一个简单的checklist,交付给后续的运维同事。内网环境里规范化和文档化比外网更宝贵,因为排查问题的路径更窄,有据可查能大幅缩短故障时间。

4. 配置认证与安全基线

4.1 创建管理员账号

前面配置里我把security.authorization先设成了disabled,这是为了方便首次初始化和功能验证。但正式交付前,必须将认证打开,否则内网扫描器一查就能发现裸奔的数据库端口。

第一步,在认证还没开启的状态下,进入shell创建管理员:

db.getSiblingDB("admin").createUser( { user: "adminUser", pwd: "一个足够复杂的密码", roles: [ { role: "root", db: "admin" } ] } )

创建完后,修改mongod.conf,把security.authorization改为enabled,重启服务。重启后,再用db.auth()或直接在连接串里携带用户名密码验证。

需要特别注意的是:root角色权限极大,适合管理用途,不适合业务连接。给业务系统用的话,可以单独立库、单独建应用账号,只授予该库的读写权限:

use "业务库名" db.createUser( { user: "appAccount", pwd: "业务密码", roles: [ { role: "readWrite", db: "业务库名" } ] } )

这样即使应用账号泄露,也只是该库的读写权限,不会波及其他数据库。在内网多租户场景下,这个习惯能避免很多安全事故。

4.2 开启认证后的连接方式

认证开启后,连接方式就要区分场景了:

  • 运维人员使用:通过mongosh -u adminUser -p --authenticationDatabase admin交互式连接。
  • 业务应用使用:连接串统一写成mongodb://appAccount:密码@内网IP:27017/业务库名?authSource=业务库名这样的格式。
  • 脚本自动化场景:不把密码明文写在命令行里,用环境变量传入,或者干脆使用MongoDB的配置文件方式管理凭证。

内网环境里我强烈建议把监控系统的账号也单独建立,只给clusterMonitor角色,不要复用管理员账号。这样审计日志里可以看到不同账号的使用轨迹,后续排查问题时也更清晰。

4.3 离线环境下的额外加固项(可选)

离线环境不代表不需要安全加固,只是实现方式要适配。比如开启TLS加密传输,MongoDB要求提供自签证书或内网CA证书。虽然内网链路相对可信,但安全审计如果检查到明文传输,是可能不通过的。

自签证书生成在配置了openssl的外网机器上完成,然后用离线方式拷入:

openssl req -newkey rsa:2048 -nodes -keyout/mnt/certs/mongod.key -x509 -days 365 -out /mnt/certs/mongod.crt cat /mnt/certs/mongod.key /mnt/certs/mongod.crt > /mnt/certs/mongod.pem

然后在mongod.conf里增加:

net: tls: mode: requireTLS certificateKeyFile: /app/mongodb/certs/mongod.pem

重启后,所有客户端连接都需要使用mongosh --tls或者连接串里的?tls=true。这一步在离线环境下操作并不复杂,但对安全评级加分很多。

5. 常见问题排查与避坑实录

5.1 启动失败类问题

我在离线部署中遇到最多的一类问题,就是服务启动直接失败。典型报错和解决办法如下:

现象:Failed to start MongoDB Database Server,journalctl日志里出现Failed to open /app/mongodb/log/mongod.log

原因基本都是目录权限。MongoDB进程以mongod用户运行,但日志目录或数据目录属主是root。解决方式是把整个目录属主递归改为mongod:

chown -R mongod:mongod /app/mongodb

现象:insufficient free space for journal files

这个比较隐蔽。MongoDB启动时要预分配journal文件,如果磁盘剩余空间低于一定阈值,它会直接拒绝启动。解决办法是清理磁盘空间或将dbPath指向空间更充足的挂载点。不要以为是配置文件写错了。

现象:unsupported version of OpenSSL

说明系统的OpenSSL库版本和MongoDB二进制包期望的不一致。建议要么选择对应发行版官方仓库里的MongoDB版本,要么手动装匹配版本的libssl。注意不要用系统自带的python或工具链去升级OpenSSL,那会引发连锁依赖崩溃。

5.2 连接异常类问题

服务起来了,但外部连不上,这是第二类高频问题。先看防火墙策略,内网机器往往有主机防火墙,检查27017端口是否放行:

systemctl status firewalld iptables -L -n | grep 27017

没有放行规则就加上。如果安全组策略在物理交换机层管理,那还需要联系网络管理员放行目标端口。

另一个容易被忽略的点是bindIp配置。如果只绑定了127.0.0.1,即使防火墙放行了,其他机器也连不上。内网环境里连不上时,第一反应不要先怀疑应用配置,先检查bindIp和端口监听状态:

netstat -tlnp | grep 27017

看到监听地址是127.0.0.1:27017,基本就能锁定问题了,再回去改bindIp。

5.3 权限与复制集相关问题

单机部署还好,一旦上复制集,问题会多一点。我踩过的一个典型坑是副本集成员的高可用地址写成了localhost,导致节点启动后主节点一直选举不出来。在initiate副本集时,要明确指定成员的host为内网IP:

rs.initiate( { _id: "rs0", members: [ { _id: 0, host: "内网IP1:27017" }, { _id: 1, host: "内网IP2:27017" }, { _id: 2, host: "内网IP3:27017" } ] } )

还有权限类问题:如果开启了认证,副本集节点之间通信也需要认证。最简单的方式是在第一步就开启keyFile认证,三个节点的keyFile内容要完全一致,而且属主必须是mongod、权限必须设置为600。任何一个节点keyFile不一致,都会导致同步失败。

5.4 问题排查速查表

最后整理一份我在现场排查时经常翻看的问题速查表:

现象可能原因排查命令/动作
启动时提示终止glibc不兼容执行ldd --version比对版本
端口未监听bindIp配置错误检查net.bindIp配置与netstat结果
日志目录无权限目录属主错误执行ls -ld /app/mongodb/log查看属主
连接被拒绝防火墙未放行执行iptables -L -n检查规则
认证失败用户不存在或库名错误执行db.getUsers()确认用户所在库及角色
主节点选举失败复本集成员的host配错执行rs.config()查看members地址
CPU飙升cacheSizeGB配置过大调低wiredTiger的cacheSizeGB值
磁盘被写满日志轮转未配置检查logRotate和系统logrotate定时任务

结尾

离线部署MongoDB这件事,本质上不是技术难度的比拼,而是“准备度”的比拼。外网环境下有问题可以随手搜索、随手下载依赖,但在隔离内网里每一次失误的代价都更大,物料核查、依赖验证、流程记录缺一不可。我个人在实际操作中的体会是:先花40%的时间把环境信息摸透,再花20%的时间准备物料,最后只用40%的时间去真正执行部署和验证,这个时间分配比闷头直接装要高效得多。

最后再分享一个小技巧:在离开外网环境前,把你所有要用的安装包、依赖包、配置文件全部打包,并且在其中放一个README文件,把所有版本的对应关系写清楚。我看到太多同事在离线环境里翻遍了移动硬盘才找到目标rpm包,如果有这样一份清单,整个过程能从容很多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进