ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

MongoDB改端口全指南:从配置原理到集群迁移避坑

MongoDB改端口全指南:从配置原理到集群迁移避坑

前段时间接手了一个实际项目,需要在一台物理机上同时跑三套 MongoDB 实例,分别给不同业务用。默认的 27017 端口显然不够分,再加上公司安全基线明确要求生产库不能挂默认端口,于是“修改 MongoDB 默认端口”这个听起来很简单的问题,实际落地时却被我翻来覆去折腾了好几轮:单机改端口确实一分钟搞定,但等到副本集、分片集群、防火墙、SELinux、连接串全部联动起来,坑就一个接一个冒出来了。

这篇文章就专门聊聊 MongoDB 改端口这件事。我会从端口配置的基本原理讲起,把配置文件修改、启动参数指定、重启验证、客户端连接调整、防火墙放行、集群环境迁移这些环节全部过一遍,最后附上我实际踩过的坑和排查思路。不管你只是想给单机 MongoDB 换个端口,还是在副本集或者分片集群里做端口调整,这篇都可以直接照着操作。

1. 动手之前:先搞懂 MongoDB 的端口配置和默认规则

1.1 默认端口到底有哪些

MongoDB 的默认端口不是只有 27017 一个,这点很多人会忽略。我整理了一下常见的默认端口分配:

组件默认端口说明
mongod(普通实例)27017最常见的数据库服务端口
mongos(分片路由)27017分片集群中应用连接的入口
mongod 加 --shardsvr27018分片集群中的分片数据节点
mongod 加 --configsvr27019分片集群中的配置服务器

也就是说,如果你启动 mongod 时带了--shardsvr或--configsvr参数,即使没写--port,它也会默认监听 27018 或 27019,而不是 27017。很多人改完端口发现“明明改了怎么还是老端口”,有时候就是这里在作怪——你改的是普通端口配置,但实例实际是以分片节点或配置节点模式启动的。

另外还有一个容易被忽略的细节:--port参数本身是可以覆盖配置文件的。MongoDB 的参数优先级是命令行参数优先于配置文件,这个特性在后面排查“改了配置文件却没生效”时非常关键。

1.2 什么场景下必须改端口

我梳理了一下,实际工作中最常见的是这几类需求:

第一,单机多实例。一台机器上部署多套 MongoDB,分别服务不同项目,必须给每套分配独立端口,同时还要分配独立的 dbpath、logpath 和 pidfilepath,否则根本起不来。

第二,安全合规要求。很多企业安全基线和等保检查明确要求数据库不能使用默认端口,这是最常见的改端口动机。需要说明的是,改端口本质上是一种“混淆”手段,不能替代认证授权和网络隔离,这点后面我会专门讲。

第三,端口冲突。比如机器上已经有别的服务占了 27017,或者云厂商的某些安全组件、监控 agent 占用了这个端口。

第四,迁移和架构调整。比如你从单机模式往分片集群迁移,或者把副本集从一套环境搬到另一套环境,新环境的端口规划变了,必须跟着改。

1.3 改之前先摸清当前状态

动手之前,我建议先用下面几个命令确认现状,避免改完才发现端口根本不是你以为的那个。

# 查看 mongod 进程实际监听的端口 ss -tlnp | grep mongod # 或者 netstat -tlnp | grep 27017 # 查看当前实例的启动参数和有效配置 mongosh --quiet --eval "db.adminCommand('getCmdLineOpts')"

getCmdLineOpts返回的内容里,parsed字段就是当前生效的配置,argv字段是启动时传入的命令行参数。这个命令能帮你确认两件事:实例是不是默认端口启动的,以及配置文件路径和命令行参数是否符合预期。

还有就是确认 MongoDB 版本。6.x 之后官方推荐使用 mongosh,旧的mongoshell 已经在新版本里移除了,下面的示例我统一用 mongosh。不同版本的配置项基本兼容,但连接方式有些差异,后面提到的时候我会标注。

2. 两种改法:配置文件修改与启动参数指定

2.1 推荐做法:改配置文件

生产环境最稳妥的方式就是修改 MongoDB 的配置文件,因为这种方式持久化、可审计、还能被 systemd 等服务管理工具正确加载。

Linux 上默认配置文件路径是/etc/mongod.conf,Windows 上是C:\Program Files\MongoDB\Server\<版本号>\bin\mongod.cfg,macOS 上用 Homebrew 安装的话一般是/usr/local/etc/mongod.conf。

核心配置项在net段下,YAML 格式是这样的:

# /etc/mongod.conf net: port: 28017 bindIp: 127.0.0.1,192.168.1.100

这里有个 YAML 的硬性要求:net必须是顶层 key,port和bindIp必须在net:下面缩进对齐,缩进要用空格而不是 Tab。port的值必须是整数,不能加引号写成"28017",否则 mongod 启动时会直接报错退出。

改完之后重启服务:

# systemd 管理的 Linux 发行版 sudo systemctl restart mongod # 老式 SysV init(比如部分 CentOS 6 环境) sudo service mongod restart # Windows 服务 net stop MongoDB net start MongoDB

重启后验证监听端口:

ss -tlnp | grep 28017

正常情况下你会看到 mongod 已经监听在 28017 上了。

2.2 临时做法:启动参数指定

如果你只是想临时测试,或者实例不是通过服务脚本管理、而是手动启动的,可以直接用--port参数:

mongod --port 28017 --dbpath /data/db --logpath /var/log/mongodb/mongod.log --fork

注意几个细节:手动启动时--dbpath和--logpath必须显式指定,否则 mongod 会用编译时的默认路径,很容易出现找不到数据目录的问题。加了--fork才能让进程后台运行,但该参数不能和--config一起用。

如果你是手动方式启动、但同时又想让配置文件里的其他参数生效,可以写成这样:

mongod --config /etc/mongod.conf --port 28017

这种情况下命令行里的--port会覆盖配置文件里的net.port,其余参数仍然从配置文件读取。这个组合技巧在我调试多实例时非常常用。

对于 systemd 管理的服务,理论上也可以修改服务单元文件里的ExecStart加上--port参数,但我强烈不建议这么干。第一,升级 MongoDB 版本时服务单元文件可能会被覆盖;第二,配置文件才是官方推荐的管理入口,改单元文件会让运维排查变得混乱。

2.3 两种方式的取舍建议

我用一张表总结一下两组方案的差异,方便你按场景选:

对比维度修改配置文件启动参数指定
持久性重启后仍然生效重启后需要重复指定
维护性集中管理,可审计分散在脚本或命令中
systemd 兼容性完美支持需要改单元文件,不推荐
适用场景生产环境、多实例、集群临时测试、手动启动调试
参数优先级被命令行参数覆盖高于配置文件

我的习惯是:凡是生产环境,一律用配置文件;临时起一个测试实例,才用命令行参数。还有一个场景,就是排查问题的时候用命令行参数覆盖配置,可以快速验证某个配置项是否有问题,比如怀疑配置文件里的 bindIp 导致连不上,就可以先用--bind_ip 0.0.0.0临时启动验证一下。

3. 改完端口后的连锁操作:验证、连接、防火墙

3.1 重启并确认端口生效

改完端口重启之后,先别急着连数据库,按顺序做三件事确认服务正常:

第一,确认进程状态:

systemctl status mongod

看到active (running)只是第一步,还要看日志里有没有报错。日志默认在/var/log/mongodb/mongod.log,重点看有没有Port、bind、listen相关的 ERROR 或 WARNING。

第二,确认端口监听:

ss -tlnp | grep 28017

如果这里什么都没输出,说明 mongod 根本没监听上,多半是启动失败或者配置被忽略了。这时候直接看日志比猜原因快得多。

第三,确认数据库实例能正常响应:

mongosh --port 28017 --eval "db.runCommand({ping: 1})"

能返回{ ok: 1 }就说明实例真正就绪了。

3.2 客户端连接字符串同步修改

端口改了,客户端连接串必须跟着改,这一步经常被遗漏。标准连接串格式是:

mongodb://[用户名:密码@]主机地址:端口/数据库名?参数

改端口之前你可能是这么连的:

mongodb://127.0.0.1:27017/mydb

改完之后要变成:

mongodb://127.0.0.1:28017/mydb

项目代码里如果用官方驱动,一般都有一个类似mongodb://host:port/db的配置项,或者是独立的port参数。我见过不止一次因为配置文件里端口写死了,导致改完服务端之后应用全部连不上,所以这里建议你在改服务端之前,先把所有客户端连接串相关的地方找齐,列个清单再动手。

还有 MongoDB Compass 这类 GUI 工具,连接时需要单独填端口号,也别忘了更新。如果你用了连接池,改完连接串之后最好让应用重启一次,否则连接池里可能还保持着旧端口的长连接。

3.3 防火墙、SELinux 和云安全组的坑

改完端口发现本机能连、远程连不上,这个问题十有八九出在防火墙和安全组上,而且经常是三座大山叠加:本机防火墙、SELinux、云平台安全组。

Linux 上如果是 firewalld:

# 放行新端口 sudo firewall-cmd --permanent --add-port=28017/tcp sudo firewall-cmd --reload # 确认规则生效 sudo firewall-cmd --list-ports

如果是 iptables:

sudo iptables -A INPUT -p tcp --dport 28017 -j ACCEPT

注意 iptables 这条规则要放在 REJECT 规则之前才有效,而且重启后可能失效,建议保存规则:

sudo service iptables save

RHEL/CentOS 系还有一个藏在暗处的大坑,SELinux。MongoDB 的 SELinux 策略默认只放行 27017、27018、27019 这几个端口,你改成别的端口后,即使防火墙放行了,SELinux 也可能直接拦截网络访问。日志里会看到avc: denied之类的记录,排查网络问题的时候很容易被忽略。

解决方法是用 semanage 放行新端口:

sudo semanage port -a -t mongod_port_t -p tcp 28017

如果没有 semanage 命令,先安装:

sudo yum install -y policycoreutils-python-utils # 或者 sudo dnf install -y policycoreutils-python-utils

这个坑我当年踩过一回,那次是帮一个客户迁移 MongoDB 到新机器,本地ss明明看到端口监听了,远程就是连不上,查了整整半天才发现是 SELinux 拦截,后来把这条命令写进了运维手册。

云上的话,别忘了云平台的安全组/防火墙规则。很多云服务器的安全组默认只放行少量端口,改了端口后需要在控制台把新端口加入入方向规则。这一步不能省,因为云安全组的优先级通常高于虚机内部防火墙。

3.4 容器和 Windows 环境的特殊情况

如果你的 MongoDB 跑在 Docker 里,端口映射逻辑是另一套。容器内部的 mongod 端口叫容器端口,宿主机上对外暴露的叫宿主机端口,两者通过-p参数映射:

# 容器内还是 27017,宿主机对外是 28017 docker run -d --name mongodb -p 28017:27017 mongo:7

这种场景下,你根本不需要改 mongod 的端口,只需要调整-p映射关系。当然你也可以两种都改,比如容器内也改成 28017,然后-p 28017:28017映射,但这样没有必要,反而增加了配置复杂度。容器场景我推荐不动容器内端口,只改宿主机的映射端口。

如果你想在容器里用配置文件指定端口,可以用-v挂载配置文件:

docker run -d --name mongodb \ -p 28017:28017 \ -v /data/mongo/mongod.conf:/etc/mongod.conf \ -v /data/mongo/data:/data/db \ mongo:7 --config /etc/mongod.conf

Windows 上改配置文件和 Linux 思路一致,都是改net.port,然后重启 MongoDB Windows 服务。需要注意 Windows 环境下 mongod.cfg 的文件编码和行尾符,我用 UTF-8 保存时遇到过解析问题,改成带 BOM 的 UTF-8 反而能正常解析,这点比较玄学,建议优先保持编辑器默认格式不动。

4. 集群环境改端口:副本集和分片

4.1 单机改端口容易,集群改端口要讲顺序

单机改端口是个体力活,但集群环境改端口就要讲套路了。因为副本集和分片集群的成员之间是通过host:port来互相识别的,光改某个节点的端口不更新集群配置,会导致其他成员按旧地址找它,表现就是成员状态一直显示UNKNOWN或者连接超时。

集群改端口的核心原则是:

  • 一次只动一个节点
  • 改完一个节点后,要把集群配置(replica set config)里对应的 host 更新为新地址
  • 确认集群恢复健康后再动下一个节点
  • 分片集群还要额外注意 mongos、config server、shard 三者的联动

4.2 副本集成员的端口迁移

假设有一个三节点副本集,成员分别是host1:27017、host2:27017、host3:27017,现在要把host2的端口改成 28017。完整步骤如下:

第一步,确认当前副本集状态,找出主节点:

// 连接到任意一个成员 mongosh --port 27017 --eval "rs.status()"

第二步,如果host2正好是主节点,建议先让它退位,避免在主节点上改配置导致短暂不可用:

// 在主节点上执行 rs.stepDown(60)

第三步,修改host2上的 mongod 端口配置,重启该节点。

第四步,在任意一个存活成员上更新副本集配置,把host2的地址改成新端口:

// 连接到 host1 或 host3 mongosh --port 27017 // 查看当前配置 var cfg = rs.conf() printjson(cfg.members) // 找到 host2 对应的 members 数组下标,假设是下标 1 cfg.members[1].host = "host2:28017" // 重新配置副本集 rs.reconfig(cfg)

第五步,再次查看状态,确认host2以SECONDARY状态正常同步:

rs.status()

注意,rs.reconfig会触发一次副本集重新选举和配置同步,这个操作在业务低峰期做最稳妥。另外在 MongoDB 3.6 之前,一次只能修改一个成员的地址,3.6 之后放宽了这个限制,但为了安全我还是建议逐节点修改。

还有一个容易踩的坑:如果你改了某个成员的主机名或者端口,同时这个成员的priority设得比较高,reconfig 之后可能会发生不必要的选举。这个在变更前最好评估一下,必要的时候先把 priority 临时改成 0,完成变更后再恢复。

4.3 分片集群的 config server 与 mongos

分片集群的端口调整比副本集还要复杂一点,因为涉及三类组件:mongos 路由、config server 配置服务器、shard 分片节点。

如果你只是在 shard 节点上改端口,本质上就是给每个 shard 的每个成员做一次副本集级别的端口迁移,操作方式和 4.2 一致。但改完之后 mongos 的路由配置也需要更新,否则 mongos 缓存里还是旧地址。

mongos 本身连接 shard 是通过分片元数据(shard catalog)来定位的,shard 地址变更后,一般需要重启 mongos,或者通过flushRouterConfig命令清理路由缓存:

// 连接到 mongos mongosh --port 27017 db.adminCommand({ flushRouterConfig: 1 })

如果是 config server 改端口,情况更麻烦,因为 shard 节点启动时通过--configdb参数指向 config server 的地址。这个变更通常意味着你需要依次修改所有 mongos 和 mongod(shard 节点)的--configdb参数,然后全部重启,工程量和风险都比较大。

所以我的建议是:分片集群尽量不要在运行时改 config server 的端口。如果一定要改,务必规划一个完整维护窗口,按“停 mongos -> 改 config server -> 改所有 shard 的 --configdb -> 启动 config server -> 启动 mongos”的顺序操作。

另外别忘了,分片集群里的 mongos 默认也是 27017 端口。如果应用之前是直连 mongos 的,mongos 端口变了,应用连接串同样要改。生产实践中应尽量避免 mongos 端口频繁变动,因为影响面是整个业务入口。

5. 高频踩坑与排查速查

5.1 改完连不上的第一排查顺序

我把实际中遇到最多的连接问题整理成了一个排查顺序,按这个顺序走,绝大多数问题能在十分钟内定位:

排查步骤命令/方法判读标准
1. mongod 进程是否活着systemctl status mongod或ps aux | grep mongod进程存在且无 ERROR
2. 端口是否真的在监听ss -tlnp | grep 28017能看到对应端口
3. 本机能不能连上mongosh --port 28017 --eval "db.runCommand({ping:1})"返回ok: 1
4. 远程能不能连通另一台机器telnet 目标IP 28017端口可通
5. 防火墙是否拦截firewall-cmd --list-all新端口已放行
6. SELinux 是否拦截/var/log/audit/audit.log搜avc无相关 denied 记录
7. bindIp 是否包含连接来源 IP配置文件net.bindIp连接来源在允许列表内

第 7 步非常关键。很多人改完端口后忽略 bindIp,如果 mongod 的bindIp只写了127.0.0.1,那么本机能连、远程必然连不上,这不是防火墙的问题,是监听地址本身就不对。需要远程访问时,bindIp 要写成服务器的内网 IP,或者用逗号分隔多个 IP,比如127.0.0.1,192.168.1.100。当然,生产环境我是强烈不推荐直接绑0.0.0.0的,风险太大。

5.2 YAML 配置解析失败的细节

改配置文件最常见的启动失败原因,一是缩进错误,二是数据类型错误。YAML 对缩进极度敏感,port必须缩进在net:下面:

# 错误写法:port 没有缩进 net: port: 28017 # 错误写法:用了 Tab 缩进 net: port: 28017 # 正确写法:两个空格缩进 net: port: 28017

第二个常见问题是端口写了字符串:

net: port: "28017" # 错误,引号包起来是字符串 net: port: 28017 # 正确,整数

这两个问题启动时 mongod 会直接退出,日志里会报Error parsing YAML config file之类的信息。遇到这种报错,我的做法是先用一个在线 YAML 校验器或者本地 Python 的 yaml 模块校验一下格式,再启动 mongod,能省很多反复试错的时间。

5.3 多实例在同一台机器上的端口分配

如果你需要在一台机器上部署多个 MongoDB 实例,每个实例除了独立的端口,还必须要有独立的 dbpath、logpath 和 pidfilepath。我看到过有人只改了端口,两个实例共用同一个 dbpath,第二个实例启动时直接报锁错误退出。

多实例的配置模板大致这样:

# instance1 的 mongod.conf systemLog: destination: file path: /var/log/mongodb/mongod1.log logAppend: true storage: dbPath: /data/mongodb/data1 net: port: 28017 bindIp: 127.0.0.1 processManagement: fork: true pidFilePath: /var/run/mongodb/mongod1.pid
# instance2 的 mongod.conf systemLog: destination: file path: /var/log/mongodb/mongod2.log logAppend: true storage: dbPath: /data/mongodb/data2 net: port: 28018 bindIp: 127.0.0.1 processManagement: fork: true pidFilePath: /var/run/mongodb/mongod2.pid

启动时分别指定配置文件:

mongod --config /etc/mongod-instance1.conf mongod --config /etc/mongod-instance2.conf

端口规划上,我建议一套 MongoDB 占用连续的端口段,方便防火墙规则统一放行,也方便记忆和审计。比如实例 A 用 28017,实例 B 用 28018,实例 C 用 28019,这样防火墙只需要放行一个端口段。

5.4 别忘了数据库层之外的配置

改端口看起来是数据库的事,但实际影响会扩散到数据库之外的整个技术栈。我总结了一下至少要同步更新的地方:

  • 应用代码里的 MongoDB 连接串
  • 配置文件中心里的连接参数(如果是微服务架构)
  • 监控系统里的 MongoDB 采集端端口
  • 备份脚本里的连接地址
  • 定时任务、批处理脚本里写死的端口
  • 内网 DNS 或者服务发现配置

我见过最典型的翻车案例:数据库端口改了,备份脚本没同步更新,备份连续失败了一周没人发现,直到恢复演练时才暴露。所以我建议改端口之前先写一份变更清单,把上面这些位置全部列出来,变更完成后逐项确认。

还有一点提醒:如果你用了云数据库托管服务或者通过连接串管理平台连接的 MongoDB,服务端的端口通常由平台统一管理,你自己一般改不了,只需要关注应用连接串就行,别在配置层面瞎折腾。

5.5 改端口不是安全方案

最后必须泼一盆冷水:改端口只是“安全通过混淆”的初级手段,它只能帮你挡住一部分扫描器的默认探测,但绝对挡不住有目的的攻击者。真正的 MongoDB 安全防护应该是多层组合:

  • 启用访问控制(--auth),给每个应用独立的账号密码
  • bindIp绑定到可信内网地址,不要轻易绑0.0.0.0
  • 通过防火墙严格限制访问来源
  • 开启 TLS/SSL 加密传输
  • 定期做好备份

端口改得再隐蔽,如果没开认证、bindIp 绑了0.0.0.0,数据库依然等于裸奔。所以我在安全评审的时候,从来不会把“改了默认端口”当作安全加分项,它只是众多安全措施里的一个基础项。

尾声

我个人在多次改端口的过程中,最大的体会是:改端口本身不复杂,复杂的是改完之后整个链路能不能对得上。节点地址、集群配置、客户端连接串、防火墙、SELinux、监控、备份,任何一个环节掉了链子,都会在某个不经意的时刻给你找麻烦。

最后分享一个我觉得很实用的小技巧:改端口之前,先在旧端口环境下把db.adminCommand('getCmdLineOpts')的输出保存下来,同时记录rs.status()或者sh.status()的完整输出。一旦变更过程中出了问题,这些快照能帮你快速回滚,比凭记忆去猜“原来配置是什么样的”要靠谱得多。另外,多实例场景下建议把每个实例的配置文件都加上实例标识的注释,端口规划表单独存一份文档,放在配置目录旁边,半年后回头看你会感谢自己这个习惯。

RELATED READING

延伸阅读

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