做运维这些年,跨网段访问内网MySQL一直是个高频需求。处理这种问题,我脑子里第一个浮现的往往不是frp这类功能全家桶,而是那个只有几十KB、配置文件几行就能跑起来的rinetd。很多人听到"rinetd"第一反应是"这年头还有人用这个?"——说实话,在端口转发这件事上,它恰恰是最省心的选择。
这篇文章就围绕"rinetd + 跨网段 + 内网MySQL"这条线,完整记录我从选型、配置、启动到排障的整个实操过程。也把原理讲透,告诉大家为什么这个轻量方案在很多场景下比上SSH隧道、搬frp更实在。如果你手头正好有一台带公网IP的跳板机、需要在别的网段连内网数据库,这篇可以直接照着抄作业。
1. 先说清楚:跨网段访问MySQL到底难在哪
1.1 你面对的真实环境是什么样
跨网段访问数据库,最常见的场景有三类。第一类是公司内网办公网段和数据库网段做了隔离——比如办公机在192.168.10.0/24,数据库在192.168.5.0/24,中间隔着一堆交换机,ACL只放行了特定端口。第二类是你在家办公,要从家里访问公司机房的数据库,但机房入口只有一台跳板机。第三类是云上业务,两个VPC之间要做单向数据同步,结果安全组规则卡得死死的。
这些场景的共同点是:**内网MySQL实例本身没有公网入口,你不能直接拿mysql -h去连。**数据库通常只监听内网网卡,外部客户端根本摸不到它。很多人的第一反应是去改数据库的bind-address,把它改成0.0.0.0,然后让云安全组放开端口——这条路在隔离网络里往往走不通,因为你改完数据库,防火墙那层还是过不去,而且直接把数据库暴露到公网也够不安全。
1.2 主流方案横向对比,为什么最后选了rinetd
处理跨网段访问,我测过不少方案,简单说下各自表现。
| 方案 | 部署成本 | 内存占用 | 转发能力 | 典型问题 |
|---|---|---|---|---|
| SSH隧道 | 低,一条命令 | 低 | 单端口或动态转发 | 要维护长连接,容易断,且跳板机得开SSH权限 |
| frp | 中,服务端+客户端 | 中等 | 多端口、TCP/UDP都行 | 配置项多,跨平台客户端的维护有点琐碎 |
| ngrok | 中,依赖官方/自建服务 | 中等 | HTTP/TCP隧道 | 依赖外部服务,生产环境一般不会用它去连数据库 |
| iptables DNAT | 低,一行规则 | 极低 | 内核态转发,性能最好 | 要求目标主机回包路由可达,跨网段场景常走不通 |
| rinetd | 极低,单文件+单配置 | 极低 | TCP端口转发 | 不支持UDP,不支持加密 |
iptables DNAT在纯内网路由互通时很香,但跨网段场景下回包路由经常成问题,数据包被DNAT过去了,源主机的回复又不知道怎么回来,很折腾。SSH隧道在临时用一下挺方便,但要做成常驻服务,稳定性就靠不住了——隧道一旦断掉,所有连接全部中断,没人盯着的话只能等报障。
最终选rinetd,理由很直接:**它是一个纯用户态的TCP端口转发小工具,不做隧道、不加密、不搞域名绑定,就是把一个端口的TCP流量原封不动接到另一个主机的端口上。**整个程序通常不到100KB,配置文件几行就够。不需要在客户端装agent,不需要对数据库做改动,也不需要改路由。对"跨网段访问一个MySQL端口"这种单一需求来说,它是匹配度最高的方案。
1.3 rinetd能做什么,不能做什么
先划清边界。rinetd能做的是:监听本机某个端口,把进入的TCP连接转发到指定的目标IP和端口,支持同时配很多条规则,可以指定允许连接的来源地址。它的性能在单机端口转发里是够用的,一个rinetd进程转发几千个并发连接没太大问题,毕竟就是read/write搬数据。
它不能做的是:**UDP转发。**如果你要用Syslog、DNS这类UDP协议,rinetd派不上用场。它也没有TLS终结能力,数据是明文搬的,所以它适合放在可信网络中做访问入口,而不是当作加密隧道去用。别拿rinetd和frp比,两者定位不同——frp是"把内网服务变成一个可达的隧道体系",rinetd是"把A口的数据搬到B口",简单粗暴,用完即走。
2. 核心机制:看懂那个极简配置文件
2.1 一行规则拆解:bindaddress、bindport、connectaddress、connectport
rinetd的配置逻辑极其直白,每行一条规则,格式是四个字段:
[源地址] [源端口] [目标地址] [目标端口]文本里也就是一行:
0.0.0.0 3306 192.168.5.88 3306这条规则读起来就是:这台机器的任意网卡的3306端口收到TCP请求后,原样转发给192.168.5.88这台主机的3306端口。源地址写0.0.0.0表示监听本机所有IP,如果只想让特定网卡上的请求进来,就写那个网卡的地址。
确切地说,源地址还支持用CIDR形式限制允许访问的来源网段:
10.2.0.0/16 3306 192.168.5.88 3306这样写的意思就是只有10.2.0.0/16这个网段的机器可以连接本机3306,其他来源一律拒绝。这个特性对安全控制非常有用,之后我会反复强调。
默认安装后,配置文件在/etc/rinetd.conf,日志默认写到/var/log/rinetd.log。很多手册没细说的一点是:配置里的注释用#,每行规则字段之间用空格或tab分隔都可以。修改完配置不用重启进程,执行rinetd -c /etc/rinetd.conf重新加载即可,这一点在调试多条规则时非常方便。
2.2 安装是小事,但别小看这两个细节
Linux发行版自带的仓库基本都有rinetd包。Ubuntu/Debian直接:
apt-get install rinetdCentOS/RHEL如果默认仓库没有,需要先装EPEL:
yum install epel-release yum install rinetd安装完先看版本:
rinetd --version如果你需要较新版本,可以走源码编译。从GitHub拉取最新发布的源码:
wget https://github.com/samhocevar/rinetd/archive/refs/tags/v0.70.tar.gz tar xzf v0.70.tar.gz cd rinetd-0.70 make sudo make install源码编译的成功率其实很高,依赖只有libc,不像其他软件要装一堆开发库。但日常使用我建议直接用系统包,原因是系统包自带init脚本或者可以直接配systemd,升级维护省心。
这里有个新手容易踩的坑:**rinetd绑定端口需要root权限。**如果配置里写的源端口小于1024,比如转发3306,必须以root用户启动。用systemd管理时,默认就是root跑,没什么问题。你要是手动用普通用户执行rinetd,它会直接报bind权限错误,别到时候一脸懵。
2.3 防火墙和内核转发:责任边界要分清
很多第一次用rinetd的人,会在跳板机上把net.ipv4.ip_forward打开,以为要开启内核转发才能转发端口。这个其实没必要。ip_forward是内核做IP路由转发时用的,比如当作路由器转发数据包。rinetd是用户态程序,它自己accept连接之后,再主动connect目标端口,然后把两边的数据流互相copy,整个过程根本不经过内核的IP转发逻辑。所以就算ip_forward=0,rinetd照样正常工作。
真正要盯的是防火墙。跳板机上如果有iptables或firewalld,需要把源端口放行。以firewalld为例:
firewall-cmd --permanent --add-port=3306/tcp firewall-cmd --reloadiptables的放行方式:
iptables -A INPUT -p tcp --dport 3306 -j ACCEPT顺便说,如果跳板机部署了云安全组,云平台的安全组规则也得放行对应的入方向端口。三层关系要理清:**云安全组 → 本机防火墙 → rinetd进程,三层全部放行才能连上。**很多人排查半天,最后发现是云安全组没加规则,这个坑我踩过一次之后就长记性了。
3. 完整实操:把内网MySQL暴露给跨网段客户端
3.1 先画清楚拓扑
用一个真实例子来讲。假设场景如下:
- 内网有一台MySQL服务器,IP是192.168.5.88,监听3306端口。
- 有一台带公网IP的跳板机,公网IP为1.2.3.4,内网IP为192.168.5.99,它和内网MySQL是互通的。
- 你人在办公网,办公机的IP是10.2.0.55,想直接用
mysql -h 1.2.3.4 -P 3306 -u app_user -p连上内网那台MySQL。
目标就一句话:**让办公网这台机器能连上内网MySQL。**数据库不动,路由不动,只在跳板机上做一次端口转发。
3.2 安装并验证
先登录跳板机,用root执行安装。我这里演示CentOS 7环境,Debian系把包管理命令换成apt-get即可。
uname -a cat /etc/redhat-release yum -y install rinetd rinetd --version这条版本命令如果正常输出版本号,安装就过关了。如果你发现仓库里找不到rinetd,那就去装EPEL源,执行上面说的epel-release。装完顺手确认一下二进制路径:
which rinetd正常输出会是/usr/sbin/rinetd。
3.3 写配置:一行规则的完整过程
编辑配置文件:
vim /etc/rinetd.conf我最终的配置内容如下:
# 将本机3306端口转发到内网MySQL 192.168.5.88:3306 0.0.0.0 3306 192.168.5.88 3306如果只允许办公网段访问,就把源地址改成CIDR形式:
10.2.0.0/16 3306 192.168.5.88 3306还要确认配置开头的日志路径设置。默认配置里有行logfile /var/log/rinetd.log,保留即可。有了日志后面排障会轻松很多。
保存退出后,需要让rinetd重新加载配置。最稳妥的做法是重启服务,或者直接执行:
systemctl restart rinetd如果你用的是旧版系统没有systemd,就执行:
/etc/init.d/rinetd restart查看进程是否起来:
ps -ef | grep rinetd netstat -ant | grep 3306netstat能看到tcp 0.0.0.0:3306在监听,说明转发规则已经生效。
3.4 配置systemd实现开机自启
系统包装的rinetd通常会自带service文件。如果缺,或者你想彻底掌控启动参数,可以自己写一个。这个unit值得存一下:
[Unit] Description=rinetd TCP port forwarder After=network-online.target Wants=network-online.target [Service] Type=simple ExecStart=/usr/sbin/rinetd -c /etc/rinetd.conf Restart=always RestartSec=3 [Install] WantedBy=multi-user.target保存到/etc/systemd/system/rinetd.service,然后:
systemctl daemon-reload systemctl enable rinetd systemctl start rinetd启用开机自启后,还要检查一下日志目录的权限。rinetd默认以root身份写/var/log/rinetd.log,这个一般没问题。但如果你把logfile指定到别的位置,比如/var/log/rinetd/,就要确保目录存在且权限可写,否则进程起不来,这种小坑会让很多人排查半天。
3.5 客户端侧连库验证
回到办公网那台机器,先做端口连通性测试:
telnet 1.2.3.4 3306如果通了,telnet窗口通常会显示MySQL的版本欢迎信息或者直接黑屏无报错,按Ctrl+]然后quit退出。这一步确认网络层面通了,接下来用真实客户端连:
mysql -h1.2.3.4 -P3306 -uapp_user -p如果连上并能执行show databases;,事情就成了一大半。要确认流量确实走了rinetd转发,去跳板机上同时看连接和日志:
ss -ant | grep 3306 tail -f /var/log/rinetd.log你会看到两条关键连接:一条是客户端IP连到跳板机3306,另一条是跳板机主动连到192.168.5.88的3306。看到这两条,就说明rinetd把数据搬起来了。
日志里通常会出现一行类似:
CONNECT 10.2.0.55 55555 192.168.5.88 3306这行记录说明一条转发连接成功建立。日志默认记录新连接、正常关闭、拒绝事件。如果你看到大量连接记录但客户端说卡,就要看是不是目标MySQL性能慢,而不是转发有问题。
3.6 场景扩展:一个跳板转发多个MySQL实例
生产环境经常遇到不止一台MySQL要转发。比如有三套环境,分别是192.168.5.88、192.168.5.89、192.168.5.90,端口都是3306。你当然可以写三个规则都用3306,但那会冲突,因为跳板机只有一个3306端口。更稳妥的方案是外部用不同端口,比如33061、33062、33063,分别映射到三台内网主机的3306。
配置这样写:
0.0.0.0 33061 192.168.5.88 3306 0.0.0.0 33062 192.168.5.89 3306 0.0.0.0 33063 192.168.5.90 3306客户端连接就是:
mysql -h1.2.3.4 -P33061 -uapp_user -p这个思路在管理多个数据库实例的时候很好使,一组端口对应一个实例,不用维护一堆SSH隧道。有些团队甚至用rinetd对外暴露统一的3306端口,配合内网DNS做流量分发,效果也还不错。
4. 实战中的坑:连接失败、权限异常与安全问题
4.1 客户端telnet通,但mysql客户端报错
这是最经典的现象:telnet能通,说明rinetd端口在听,链路没问题。但mysql客户端直接报错或者卡住。原因通常是这几个方向:
检查MySQL授权。在MySQL服务端执行:
SELECT user, host FROM mysql.user WHERE user = 'app_user';确认app_user的host里包含了跳板机的内网IP,也就是192.168.5.99。因为从MySQL的角度看,连接来源不是办公机的10.2.0.55,而是跳板机的192.168.5.99——rinetd转发后,源IP会被替换成跳板机IP,这个点99%的踩坑都是因为它。
如果授权缺失,在MySQL里补上:
CREATE USER 'app_user'@'192.168.5.99' IDENTIFIED BY 'your_password'; GRANT SELECT, INSERT, UPDATE, DELETE ON your_db.* TO 'app_user'@'192.168.5.99'; FLUSH PRIVILEGES;另外检查MySQL的skip-networking是否被禁用。如果配置里有skip-networking,MySQL只监听本地socket,外部TCP连接一律拒绝,rinetd转发得再勤也没用。
4.2 连接被拒绝:bind地址和防火墙的博弈
telnet直接connection refused,多数情况是rinetd进程没监听端口或者防火墙拦了。先到跳板机上确认:
ss -ant | grep 3306如果看到0.0.0.0:3306在LISTEN,那就查防火墙。如果什么都没有,说明rinetd没起来,看日志。
还有一种隐蔽情况是配置里的bind地址写成了127.0.0.1,结果外部机器根本访问不到。配置里写127.0.0.1和0.0.0.0的区别,可以类比成"只有小区内部能走的小门"和"临街大门"。Cross网段访问必须从外面进来,源地址写0.0.0.0是常态。
4.3 MySQL 8.0与客户端认证插件不一致
MySQL 8默认认证插件是caching_sha2_password,老版本的Navicat或MySQL客户端连上去会报认证错误。这个不算rinetd的问题,但既然走的是跨网段链路,报错往往让人误以为是转发问题。
在MySQL里查看用户插件:
SELECT user, host, plugin FROM mysql.user WHERE user = 'app_user';如果plugin是caching_sha2_password,而客户端不支持,有两个办法。一是升级客户端驱动到支持新插件;二是把用户改回兼容插件:
ALTER USER 'app_user'@'192.168.5.99' IDENTIFIED WITH mysql_native_password BY 'your_password';顺手说明下,mysql_native_password在新版本MySQL里属于兼容模式,生产环境要不要动它,需要你们DBA评估。但作为跨网段转发的排障点,值得留意。
4.4 rinetd进程异常退出与日志定位
进程挂掉是运维中必然会遇到的事。常见原因有三类:配置写错导致解析失败、端口被其他服务占用、日志目录不可写。判别方法都聚在日志里。
立刻查看:
journalctl -u rinetd --no-pager -n 50 tail -n 50 /var/log/rinetd.log如果journal里没有任何输出,而系统日志也没记录,那很可能进程被OOM Killer干掉了。查一下:
dmesg | grep -i rinetd grep -i rinetd /var/log/messages这种情况建议在systemd配置里加Restart=always,并把LimitNOFILE调大,应对大量短连接导致的文件描述符不足:
[Service] LimitNOFILE=65535 Restart=always4.5 转发性能与大连接数问题
rinetd本质上就是在用户态搬数据,性能上限不算高,但应对MySQL这种OLTP场景完全够用。实测一个rinetd进程承载几百个并发连接,CPU占用也就个位数。需要注意的反而是单个客户端的长时间空闲连接,它会占住rinetd的一个进程线程。默认配置下,rinetd每个连接会fork一个子进程来处理(新版也有线程模式),大量空闲连接会把进程数顶上去。监控时留意一下:
ps -eLf | grep rinetd | wc -l想控制空闲连接,可以在MySQL侧设置wait_timeout和interactive_timeout,让闲置连接自动断开,减轻rinetd压力。
4.6 安全加固:别让公网裸奔一个3306
最后说说安全。rinetd只做转发,没有任何认证能力,你把它暴露在公网,等于把自己的MySQL入口挂到网上。虽然流量最终由MySQL密码保护,但暴力破解和扫描的压力会非常大。我一般至少做三重加固:
第一,配置里限制源网段。把bind地址写成CIDR形式,只允许办公网段访问:
10.2.0.0/16 3306 192.168.5.88 3306第二,配合iptables限制来源IP。就算rinetd配置先放行,防火墙层再罩一层:
iptables -A INPUT -p tcp --dport 3306 -s 10.2.0.0/16 -j ACCEPT iptables -A INPUT -p tcp --dport 3306 -j DROP第三,不在公网暴露默认端口。把外部端口改成高位端口,比如33061,降低被扫描命中的概率。这些措施叠加起来,才比较放心。
最后再分享一个经验
用rinetd这几次最深的体会是,很多网络问题根本不是"不通",而是"走了两条不同的路"。比如你从办公网telnet跳板机通,但从客户端机器直连却不通,这时候就要想到NAT和路由的差异,别一头扎进rinetd配置里反复改。先把链路分段画出来,每段单独测,问题范围一下就缩小了。
还有一个小技巧收尾:如果跳板机上要临时看当前有哪些转发连接正在使用,不需要抓包,直接看/var/log/rinetd.log里的CONNECT记录,再配合ss -ant | grep 目标端口,基本能还原所有连接的全貌。排查效率会高很多,下次遇到"数据库卡了是不是转发问题"这种疑问,一分钟就能给出结论。