资讯动态

套娃虚拟主机访问配置全攻略:从端口转发到SSH隧道

发布时间:2026/10/19 9:39:55 来源:尧图企业网站定制
三个人挤一个卧室还得开两扇门。套娃虚拟主机就是这么个处境——你在一台物理机里跑了一个虚拟机A为了测试或隔离需求又在A里面塞了一个虚拟机B或者把容器嵌进虚拟机、把虚拟机嵌进容器、再或者用nginx在容器里套了一层又一层虚拟主机。外面的人要访问最里层的服务时问题就来了怎么配访问才能从外层网络一路通到最里面的服务这篇文章就从我在实际项目里被问到最多的套娃访问问题出发讲清楚访问链路的本质、常见配置方法、以及排查思路。不管你是在实验室里搭模拟环境还是在某公司做内部系统隔离甚至只是自己折腾着玩只要碰到一层套一层里面能通外面不通端口怎么转发都不对这类问题都可以照着我这套思路去配能省下大把的排错时间。1. 套娃环境里的访问链路为什么里面能通不等于外面能进先别急着动手配端口得先搞清楚套娃环境下访问请求是怎么走的。很多人在这一步就栽了跟头——在里面那台机器上自己访问自己一切正常换个网络环境就立马挂掉于是就开始怀疑服务没起来、防火墙乱挡其实根子都在链路理解上。1.1 从物理机到最内层一次访问要穿过几道门我用一个最常见的结构举例物理宿主机上跑着虚拟机A虚拟机A里跑着容器B容器B里跑着某Web服务监听在8080端口。这时候你在自己的笔记本上想打开这个Web服务路径是这样的笔记本电脑 - 局域网 - 物理宿主机 - 虚拟网卡/桥接 - 虚拟机A - Docker0网桥 - 容器B:8080每一层都相当于一道门。物理宿主机有自己的IP和防火墙虚拟机A有自己独立的网络命名空间容器B又套了一层网络命名空间。请求要能到达最里面每一层都得给这个流量放行并且指明方向。任何一个环节断了整体就访问不了。这也是套娃环境最坑的地方你在一层做端口转发另一层做iptables规则第三层再改配置文件结果三层之间各转各的根本没接上。最经典的错误就是——容器端口映射配了宿主机防火墙也放行了但虚拟机A里根本没开路由转发导致流量进到虚拟机A就断头。1.2 多层NAT环境下的三个常见误判套娃访问不通的时候我观察到的误判通常集中在三个地方误判一以为外层ping得通就能访问内层服务。完全不成立。ping用的是ICMP协议走的是网络层你访问Web服务用的是TCP的80或8080端口走的是传输层。ping通只能说明网络链路通到了某一层压根不代表你想要的TCP端口有人监听。误判二以为端口映射配好就万事大吉。端口映射要配在流量经过的那个接口上不是随便找一层配一下就完事。比如说你用VMware Workstation跑虚拟机A给A配了NAT模式那虚拟机的NAT网关在做转发如果A里面又跑了个Docker容器那Docker自己又做了一轮端口映射。这两层映射必须串起来漏掉任意一层都完蛋。误判三忽略路由方向。我在某次排障里遇到过一个混合套娃场景里宿主机里的虚拟机A设了双网卡一个网卡连外网一个网卡连内网虚拟交换机。人在配置时只想着外网网卡的端口转发却忘了内网网卡这边应该走的路由和默认网关配置结果外部流量进得去返回流量出不来表现为请求超时SSH偶尔能连上但网页死活打不开。理解了链路结构后面所有的配置才有根有据。这也是我向来强调的别迷信网上某个命令清单先把你的套娃拓扑图画出来标清楚每一层的IP、网段、网关、端口再动配置。2. 开始配置前的宿主环境准备网络模式取舍与固定地址链路图画完之后第二步就是检查宿主环境。这里说的宿主不单指物理机还有每层虚拟化软件承担网络网关角色的那台机器。网络模式选对了后面配置量直接少一半。2.1 桥接、NAT还是Host-Only套娃场景下该选哪个虚拟化软件的三种常见网络模式很多人分不清使用边界我这里直接做个对比模式外部能否主动访问是否需要端口转发典型用途套娃场景建议桥接模式能虚拟机像局域网里的一台独立主机不需要需要对外提供服务的场景只有一层套娃、且地址充足的场景NAT模式不能直接访问需要宿主机做端口映射需要虚拟机要上网但不对外暴露多层套娃时每层内部通信用Host-Only仅宿主机与虚拟机互通需要调试、隔离环境不建议跨层访问用一个很现实的经验套娃层数越多越不要每一层都用桥接模式。因为桥接模式要求每层都有独立的IP地址段可用三层套娃你得准备三个网段的地址DHCP管理器还得保证不冲突维护成本直线上升。NAT模式虽然表面上麻烦一点但每层只需要在网关设备上配好转发规则其他层不用感知反而更可控。我搭过的最舒服的套娃结构是物理宿主机和虚拟机A之间用NAT虚拟机A和容器B之间用Docker的bridge网络然后全部访问统一收敛到物理宿主机的一个入口上做转发。这样你只需要维护一张映射表入口端口 - 内层服务地址端口查起来方便改起来也快。2.2 固定IP是整个链路稳定的前提如果每次重启套娃里的某台机器它的IP就变了那你配的所有端口转发、反向代理、SSH配置全部白搭。所以配置访问之前务必先把每层的关键网卡IP固定下来不要依赖DHCP分配的动态地址。Linux下最常用的做法有两种。老派一点的是直接改网卡配置文件CentOS类系统是/etc/sysconfig/network-scripts/ifcfg-ensXXXDebian/Ubuntu类系统是/etc/network/interfaces或/etc/netplan/*.yaml。新派的方式是直接用nmclinmcli connection modify ens33 ipv4.addresses 192.168.122.100/24 nmcli connection modify ens33 ipv4.gateway 192.168.122.1 nmcli connection modify ens33 ipv4.method manual nmcli connection up ens33固定IP之外还要顺手把MAC地址固定。因为很多虚拟化软件的DHCP是按照MAC下发地址的只要虚拟网卡生成时的MAC每次启动都变你就算写了静态配置也可能被覆盖。常规做法是在虚拟机的配置XML文件或WEB管理界面里锁定虚拟网卡的MAC地址。这一步看着无关紧要但环境一重启就少一个IP怎么又变了的坑。还有一个容易漏的细节虚拟机的网卡类型最好统一。比如物理宿主机是Linux虚拟化用的某虚拟化平台虚拟网卡选virtio还是e1000在不同平台的性能差异很大而且某些平台在不同网卡类型下网卡名称会变eth0变ens33之类你写的配置文件路径和名字就对不上了。我吃过这个亏某次给虚拟机迁移网卡类型结果系统起来后网卡名变了网络配置失效外面根本连不进来。3. Web与命令行访问的三种配置路径端口转发、内网互通与代理跳板环境准备好、IP固定好下一步才进入正题怎么配置访问链路。根据访问对象不同我通常把配置路径分成三类访问Web服务、访问SSH命令行、访问文件传输或数据库等非Web端口。其中Web服务最常用SSH是日常管理刚需两者思路有重叠但细节不太一样。3.1 Web服务访问通用入口加端口转发先说最典型的Web服务访问。假设你物理宿主机是192.168.1.100虚拟机A是192.168.122.100A里面容器B的Web服务监听在172.17.0.2:8080。目标是让局域网里的其他机器通过http://192.168.1.100:8080访问到容器B。第一步排查的是容器B的端口有没有正确映射到虚拟机A上。启动Docker容器时docker run -d --name web-service -p 8080:8080 your-image或者容器已经在跑可以用iptables规则补上Docker会自动生成一部分但你手动指定更可控iptables -t nat -A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:8080这里有个老手才知道的细节Docker启动时-p参数虽然是把容器端口映射到宿主机端口但它会绑定在Docker默认的网桥上宿主机的其他网卡默认不一定能访问。所以你需要先验证虚拟机A本机访问curl http://127.0.0.1:8080是否通再用curl http://192.168.122.100:8080验证从虚拟机A自身IP通不通。两个都通才说明容器映射层面是正常的。第二步把虚拟机A的8080端口转发到物理宿主机上。以物理宿主机是Linux为例iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.122.100:8080 iptables -t nat -A POSTROUTING -p tcp --dport 8080 -j MASQUERADE sysctl -w net.ipv4.ip_forward1不只是iptables内核转发开关这一步一定要确认否则DNAT规则配了也白配因为流量进到宿主机后根本不允许被转发到其他网卡。你可以用echo 1 /proc/sys/net/ipv4/ip_forward临时打开但要永久生效得写入/etc/sysctl.confnet.ipv4.ip_forward 1如果你是在Windows宿主机上用某虚拟化软件跑虚拟机A那更简单直接在该软件的端口转发设置里添加规则宿主机8080 - 虚拟机A的192.168.122.100:8080。图形界面操作但道理一模一样——本质都是把外层入口映射到内层地址。三层以上套娃的规律就是递归物理宿主机8080 - 虚拟机A:8080 - 容器B:8080。每一层的操作方式相同只是对象不同。3.2 SSH访问用别名和ProxyJump跳过冗长配置命令行访问最讲究效率。要知道你在套娃环境里想进入最内层机器如果每次都要SSH宿主机 - SSH虚拟机A - SSH容器B手打三遍命令密码输三遍那基本上一周之后就没人愿意碰这套环境了。所以在套娃环境里配置访问SSH层面的体验直接决定大家用不用得起来。推荐用OpenSSH 7.3以上版本的ProxyJump能力配合~/.ssh/config里配置的别名来串联多层跳转。举个例子Host jump-host HostName 192.168.1.100 User root Port 22 Host inner-a HostName 192.168.122.100 User root Port 22 ProxyJump jump-host Host container-b HostName 172.17.0.2 User root Port 22 ProxyJump inner-a配好之后你在笔记本上直接执行ssh container-b就能一键直达最内层容器中间不需要任何手动跳转。这个做法不仅省时还避免了在跳板机上留密码的安全习惯问题——你可以把自己的公钥统一分发到每一层登录链路全程无密码。如果你的SSH版本比较老不支持ProxyJump也可以用LocalForward的方式来绕ssh -J root192.168.1.100:22 -J root192.168.122.100:22 root172.17.0.2注意-J可以写多个效果等同多级跳转。这套方案在真实团队环境里非常受欢迎因为每个人都只需要改一次自己的SSH配置以后进最内层就是一条命令的事。顺带提一下如果你需要从最内层向外传输文件别在中转层手动scp。直接在本地用scp -J多级跳转就能完成规则跟SSH一致。我见过有人在套娃环境里传文件是一层层传的完全没必要。3.3 非Web端口访问数据库和内部接口怎么办套娃环境里还有一类高频场景最内层的服务提供的是数据库端口比如MySQL的3306、消息队列端口比如RabbitMQ的5672或者一些内部HTTP API但不想走公网入口暴露出来。这种时候纯端口转发也能用但更推荐用SSH隧道来做访问——安全性更高而且不需要在每一层都改防火墙规则。本地笔记本想访问容器B里的MySQL可以用SSH隧道ssh -N -L 3306:172.17.0.2:3306 container-b这条命令的意思是把本地的3306端口映射到容器B的3306端口行走的通道是SSH加密链路。本地开发工具直接连127.0.0.1:3306实际上就是连到了最内层。好处非常明显每一层只要SSH能通这个隧道就自动能通你不需要关心各层的网关、iptables、NAT规则也不需要单独给数据库端口打开防火墙。如果你不想让隧道一直挂在终端上加个-f参数放到后台或者用autossh来保活autossh -M 0 -N -L 3306:172.17.0.2:3306 container-b我在某次配置里遇到过一个真实问题因为隧道长期占用一个本地端口用户嫌麻烦就把隧道挂在后台结果第二天发现隧道断了。原因很简单——某个中间跳板的SSH连接因为空闲超时被服务端杀掉。后来换成autossh之后问题就消失了。所以涉及长期隧道的场景一定别偷懒用裸ssh挂后台autossh这类保活工具是必须的。4. 排错链路从连不上22端口到页面转了圈就没反应就算按前面说的做好了每一步套娃环境依然可能有各种意外。这里我给一份我实践中沉淀下来的排查链路按症状分门别类配合每一步的验证命令能解决绝大多数访问问题。4.1 症状对照表先对号入座我先把常见症状和对应排查方向列成一张表症状可能的故障层优先排查方向外面完全连不上超时无响应任意一层防火墙/路由断了从外层逐步向内ping测定位中断点能ping通宿主机但SSH连不上宿主机SSH服务没开/端口改了宿主机的ss -tlnp看端口监听情况SSH能连上宿主机连虚拟机A超时虚拟机A没有开启路由转发/NAT配置缺失检查宿主机iptables规则和ip_forward状态虚拟机A能连容器B访问不了Docker端口映射没配或映射到别的网桥docker ps查看端口映射docker port复查页面能打开但访问接口/后台无反应反向代理转发目标错误/路径不对查看nginx等代理日志检查upstream配置时通时不通主动播放一会断一会中间层连接数限制/防火墙活跃规则误伤检查各层iptables -L -n -v的计数与DROP策略这张表能帮你快速收敛问题范围但排错的原则始终是一层层来不要跨层猜测。4.2 完整排错实战一个小项目的三层套娃某次在某公司内部搭模拟项目X的时候用户反映外面浏览器访问不了最内层的Web服务但能ping通宿主机。我顺着链路做了完整排查。第一步验证宿主机监听情况ss -tlnp | grep 8080发现宿主机根本没有8080监听的迹象。再仔细看原来服务映射是宿主机8080 - 虚拟机A的8080 - 容器B的8080但宿主机上的iptables每条规则都被我看了一遍发现PREROUTING里的DNAT规则写得是--dport 8080但宿主机用来对外提供服务的IP绑定的是另一块网卡规则少了-i指定入口网卡导致流量从外面进来时匹配不到这条DNAT规则。把规则改成iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 192.168.122.100:8080之后流量倒是能进去了但紧接着发现虚拟机A内部访问不了容器B的8080。此时在虚拟机A上面执行docker ps docker port web-service发现docker的端口映射配置为0.0.0.0:8080-8080/tcp看起来正常但从虚拟机A的curl访问172.17.0.1:8080不通。再一看容器B启动时的网络模式不是默认bridge而是之前某次测试残留的host网络实际监听端口已经变了。重新用bridge网络启动容器、配上端口映射之后链路才完全打通。这个案例想说明的是排错不能光靠看配置大体对必须用命令验证每一层。而且每验证完一层马上从外层重新测试一次不要等到所有层改完再统一验证——那样就算最后通了你也不知道具体是哪一步修好的下次遇到同类问题还得重新踩。4.3 防火墙和网络策略的优先级问题套娃环境里每层几乎都有防火墙。有些是系统自带的firewalld或ufw有些是虚拟化平台自带的网络安全策略。这些规则叠在一起就产生一个优先级问题哪一层的防火墙先执行我的经验是三条第一宿主机上的防火墙优先级最高。它挡在最前面入口不通后面全白搭。所以宿主机上尽量对需要的端口做精确放行比如firewall-cmd --permanent --add-forward-portport8080:prototcp:toport8080:toaddr192.168.122.100 firewall-cmd --reload第二中间层的防火墙不要开得太激进。在套娃环境里虚拟机A作为中间层它的防火墙如果开启了默认DROP策略那么宿主机转发进来的包可能被拦掉。调试阶段建议先把中间层防火墙策略临时设为允许或者直接停掉firewalld只保留iptables里必要的规则确认链路通后再慢慢收紧。第三Docker层的网络策略容易被忽略。Docker会往iptables里注入大量规则如果你在宿主机上手动加了iptables规则放到了Docker链的后面优先级可能就低于Docker自己的规则。我自己习惯用一个专门的链或者放到首条规则确保不会被Docker规则截胡。5. 套娃场景下的工程化经验更稳、更省事、更好维护配置能通只是第一步。套娃环境最怕的是配好了不敢动动一下就崩所以最后聊一点工程化层面的沉淀让你下次再搭套娃环境时能一步到位少走弯路。5.1 别让套娃变成无底洞控制层数引入轻量级方案先说一个看着像废话但特别重要的观点套娃层数绝对不能无限叠加。每多一层网络路径就多一道故障点排障时的心智负担也跟着翻倍。很多环境里所谓的套娃其实完全可以用更轻量的方案替代。举个例子如果你在虚拟机A里跑Docker只是需要一套隔离的测试环境完全可以直接在物理宿主机上用多套docker-compose来模拟虚拟层。各套网络之间天然隔离端口映射在宿主机上统一管理效果跟多级虚拟机套娃一样的隔离体验维护却简单得多。如果你确实需要嵌套虚拟化比如里面要再跑一个不同内核的虚拟机那套娃不可避免。这时候尽量固定一个标准结构——比如统一都是物理宿主机 一层虚拟机 容器不要出现物理宿主机 虚拟机A 虚拟化软件套虚拟机B 容器这种四层结构。我在某次项目中就吃过这个亏四层链路里某一层改了IP另外三层因为各写各的配置文件没人记得需要同步更新结果链路上的端口转发全部失效费了一整天定位。5.2 统一配置入口用一个入口管所有服务层数一多端口映射表就会变得很乱。我建议在宿主机上统一做一个入口收敛原则是对外暴露的端口一律收敛在物理宿主机上。每层尽量用同一个端口号减少记忆成本。所有规则集中放在一个脚本文件或者一个配置目录里方便审阅。具体做法是配一个nginx反向代理把所有Web服务的请求统一转发到内部各服务。比如宿主机上跑一个nginx监听80端口按域名或路径分发到不同虚拟机、不同容器server { listen 80; server_name app1.example.test; location / { proxy_pass http://192.168.122.100:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } server { listen 80; server_name app2.example.test; location / { proxy_pass http://172.17.0.2:8081; proxy_set_header Host $host; } }这样一来外部用户只记得一个入口IP和一个域名完全不需要关注内部到底套了多少层。内部服务再怎么调整端口、换IP只要nginx配置跟着改对外访问不感知。排查问题也只需要看nginx的access和error日志就能定位是哪个内部目标出了问题。5.3 SSH配置也应该纳入版本管理说个不太起眼但实际帮助很大的习惯把~/.ssh/config纳入版本管理。我见过太多人每次新加一层机器就在ssh config里手动加一段过一阵机器攒多了配置乱到自己也看不懂。我在团队里做的一件小事是把SSH配置整理成按层分组的格式每台机器写清楚它的角色、上级入口、用途。比如# 宿主机层 Host base-host HostName 192.168.1.100 User root # 虚拟机组 Host vm-a HostName 192.168.122.100 User root ProxyJump base-host # 容器组 Host ctr-b HostName 172.17.0.2 User root ProxyJump vm-a版本管理的好处是出问题的时候可以直接diff看最近改了什么新同事加入只需要clone一份配置不用到处问你那份ssh配置能不能发我一下。另外建议在每台套娃机器上也把known_hosts和密钥文件位次搞清楚。多级跳转时SSH客户端默认会校验每一跳的主机指纹如果中间层的主机名和IP发生过变化会直接拒绝连接。遇到这种情况在本地用ssh-keygen -R host清除旧指纹即可不用慌着动配置。5.4 日志和监控要深入到每一层套娃环境排障难很大一部分原因是出了错不知道哪一层出了问题。如果在每一层的关键服务上都配好了日志和监控排障速度会快十倍。不需要上多复杂的监控系统最简单的方式是宿主机上记录iptables转发的统计计数用iptables -L -n -v就能看到每个包的命中次数。虚拟机A里面给nginx、SSH、防火墙都打开日志记录。容器B的Web服务日志用docker logs -f跟踪同时可以再挂一个文件输出到宿主机的日志目录。我在上一份项目的经验是给最外层的宿主机配一个通断检查脚本每隔30秒去curl一次最内层服务的健康检查接口失败时连发三次确认然后写日志并触发告警。这样能第一时间知道访问断了再顺着链路排查。这个脚本逻辑很简单但价值巨大——套娃环境里最怕的就是问题发生了很久没人发现事后查日志也查不到案发时间点。最后再聊点实际操作中的体会这一整套配置下来我心里最深的感触是套娃虚拟主机的访问配置本质上不是在配一个功能而是在治理一条复杂的网络通路。通路上的每一层都要清楚自己的角色该转发的转发该放行的放行该固定的固定缺一环整条链路就断。我踩过的最大的坑是想当然地认为某一层应该没问题结果每次都是在最不起眼的那层浪费最多时间。所以请你务必记住一条铁律配访问之前画拓扑排障的时候一层层验证不要跳步不要猜。小技巧留给你的还有一条在你把最内层的服务映射到外层之后记得从外网机器上实际访问一次而不是只在本机验证。本机能通和外网能通是两码事——我就见过有人配完之后在宿主机上curl了一下以为通了结果过了几天用户反馈还是访问不了一问才发现宿主机上的本机访问走了回环地址压根没经过端口映射链路。这类细节只有实际的外来访问才能暴露别省这一步。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑