虚拟机里的Ubuntu连着连着突然SSH就断了,ping也不通,你跑到控制台一看,IP居然从192.168.1.100变成了192.168.1.137——这种情况我遇见过太多次,而且十次里有八次是DHCP租约更新导致的,另外两次要么是虚拟机网卡配置被重置,要么是Netplan配置跟系统网络服务打架了。
这几年给开发环境、测试集群和CI机维护虚拟机,几乎每隔一段时间就要处理一次“Ubuntu虚拟机IP丢失”的问题。这标题看着是个小问题,真踩进去会发现它牵扯到DHCP租约、网卡命名、Netplan、NetworkManager、systemd-networkd好几层东西。这篇文章我就把排查思路和解决方案整理出来,包括我自己踩过的坑、写过的配置、验证过的工作流,希望能帮你少走点弯路。
这个内容适合谁看呢?主要是这么几类人:用VMware、VirtualBox或KVM跑Ubuntu虚拟机的开发者和运维;刚接触Linux网络配置、被IP地址搞到头疼的新人;以及需要在虚拟机里跑固定服务(比如自建Git、内网DNS、构建节点)但不想每次开机都改配置的人。
1. IP丢失的本质:问题产生与排查思路
1.1 哪些场景最容易触发IP丢失
先说结论,IP丢失不是一个单一故障,而是一类现象的共同称呼。我把它分成三种常见表现:
第一种是IP变化但没断网。昨天连得好好的,今天打开终端执行ssh user@192.168.1.100连不上,结果进控制台一看,地址变成了192.168.1.137。这种情况通常不影响虚拟机上网,但所有依赖固定IP的配置全废了。
第二种是虚拟机启动后没有IP。控制台登录进去,执行ip addr发现网卡下面只有lo回环地址,ens33或eth0没有IPv4地址。这种情况常见于克隆虚拟机之后,因为克隆出来的系统MAC地址变了,网络服务没正常启动或者配置文件里还写着旧网卡的名字。
第三种是IP配置了但路由不对。地址有了,但ping网关不通,ping外网不通。这种情况翻车率最高,因为很多人只改了IP和掩码,没配网关和DNS,或者忘了重启网络服务让生效。
我自己遇到的比例大概是这样的:DHCP租约问题占50%,克隆或模板导致的网卡混乱占25%,Netplan配置错误占20%,剩下5%是一些莫名其妙的原因(比如虚拟化平台的虚拟交换机重启、快照回滚后配置丢失)。
1.2 先别慌,按这三步定位问题
排查IP丢失我有一个固定的流程,不管哪台机器出问题都先走一遍,效率很高。
第一步,进虚拟机控制台(不是SSH,因为SSH很可能已经断了),执行ip addr看网卡状态。重点看两件事:网卡的名字是什么,它是UP还是DOWN。如果是DOWN,先执行sudo ip link set <网卡名> up把它拉起来看看。
第二步,执行ip route看路由表。默认路由在不在了,网关有没有配上。如果默认路由没了,就算有IP地址也出不了网。
第三步,看系统日志。Ubuntu上用journalctl -xe看最近的错误日志,或者直接看sudo journalctl -u systemd-networkd -u NetworkManager --since "10 minutes ago"。很多时候DHCP客户端尝试获取地址失败的记录就写在这里。
这三步走完,问题基本就定位了。然后再分情况处理:是DHCP没获取到地址,就去检查DHCP服务和网卡绑定;是路由缺失,就补默认网关;是网卡名字变了,就去改Netplan或者网络配置文件的网卡名。
2. Ubuntu虚拟机网络栈到底怎么工作
2.1 三个管家:Netplan、NetworkManager、systemd-networkd
一开始我也很懵,Ubuntu明明就一个系统,怎么网络管理工具这么多,而且各有各的脾气。后来梳理清楚了,它们的关系其实是一条流水线。
Netplan是Ubuntu 18.04之后引入的“配置前台”,它做的事情是把YAML格式的配置翻译成后端工具能理解的文件。Netplan自己不管网卡,它只负责生成配置。后端有两个:一个是NetworkManager(桌面版默认),一个是systemd-networkd(服务器版默认)。
所以你在/etc/netplan/下面写了一个01-netcfg.yaml,然后执行sudo netplan apply,Netplan做的事情是把这个YAML翻译成NetworkManager的连接配置或者systemd-networkd的network文件,然后通知后端重新加载。
理解这个流程之后,很多问题就通了。比如你明明写了静态IP,但系统起来之后IP还是DHCP拿到的——大概率是Netplan配置语法不对,apply的时候报错了,或者优先级不对,另一个配置把它覆盖了。
再比如你用的是桌面版Ubuntu,你在图形界面里把IP改成了手动,然后去/etc/netplan下面写配置,重启之后发现配置不生效——因为桌面版用的是NetworkManager,Netplan生成的配置文件在每次系统启动时都会被NetworkManager接管,你手动改的Netplan文件可能被NM自己的配置优先级压住了。
理解这几层关系,排查的时候就不抓瞎了。
2.2 DHCP租约的生命周期,为什么地址会换
DHCP地址为什么会“丢”?很多人不理解,我翻译成大白话。
DHCP服务器给你的虚拟机发了一个地址,它不是永久给你的,而是“借”给你一段时间,这个时间叫做租约(lease)。默认的租约时间在常见的虚拟化环境里通常是24小时,有的甚至只有几小时。
租约快到期的时候,虚拟机的DHCP客户端会去续租。续租成功了,IP不变;续租失败(比如虚拟机当时没开机、网络断开了、DHCP地址池满了),之前拿到的地址就自动释放了。下次再开机,重新请求地址,拿到什么就什么——这就是IP变化的直接原因。
你还会碰到一个情况:虚拟机一直开机,但IP还是变了。这通常是DHCP服务器端的保留设置或者地址池的分配策略变了,触发了重新分配。还有可能是虚拟机休眠之后唤醒,网络栈重建,DHCP客户端重新发起请求,服务器认为旧租约过期了,就重新给了一个地址。
所以纯靠DHCP来保持IP固定,本身就是不靠谱的。想要稳定,至少要做三层保护:DHCP保留绑定(MAC和IP绑死)、虚拟机MAC地址固定、系统里写静态IP兜底。
2.3 网卡命名规律:ens33、eth0是怎么来的
排查IP问题的时候,还有一个容易迷惑的环节:网卡名字怎么不是eth0了,而是ens33、enp3s0这种。
这是Ubuntu从16.04开始用的可预测命名规则。简单说,系统根据网卡插在哪个PCI插槽、哪个总线位置上编名字。en代表以太网,p3s0代表PCI槽位3的端口0,s33不同平台可能含义稍有差异。它的好处是名字稳定,不像以前那样每次启动可能因为驱动加载顺序不同把eth0和eth1搞混。
问题出在哪呢?当你给虚拟机调整硬件配置,或者克隆虚拟机的时候,网卡的PCI槽位变了,名字就可能从ens33变成ens35。新名字对应的网络配置文件不存在,系统就默认用DHCP去试,等了好长时间拿不到地址或者拿到一个完全陌生的地址。
所以排查的时候第一件事就是看清楚当前网卡真实名字,用ip addr或lspci确认,然后再去想配置的事。别一上来就照着记忆里的eth0去改配置,配置文件写得再对也没用。
3. 方案一:用Netplan写死一个静态IP
3.1 先备份再看当前配置
不管你信不信,我第一次改Netplan配置的时候没备份,结果改坏了之后想把原配置捞回来,发现原配置文件已经被我覆盖成新的了。幸亏当时用的是测试机,重新装一遍也没啥代价,但从此我就长记性了。
动手之前先备份:
sudo cp /etc/netplan/01-netcfg.yaml /etc/netplan/01-netcfg.yaml.bak.$(date +%F)然后看看当前生效的配置到底长什么样:
sudo netplan getnetplan get 可以输出当前的生效配置,比直接看YAML更准确,因为系统会把它和后端实际配置合并起来。
接着确认网卡当前的名字和状态:
ip addr ip route记下当前网卡名、当前IP、网关、DNS这几个信息。写静态IP的时候要用到它们。
3.2 最小化静态IP配置怎么敲
先看一个最简配置:
network: version: 2 renderer: systemd-networkd ethernets: enp3s0: dhcp4: false addresses: - 192.168.1.100/24 routes: - to: default via: 192.168.1.1 nameservers: addresses: - 192.168.1.1 - 223.5.5.5这个配置有四个关键点:
dhcp4: false是把DHCP关掉,不然你写了静态IP,后端还可能去发DHCP请求,两个配置打架。
addresses写IP和掩码,注意不是分开写的,是带掩码位数的写法192.168.1.100/24。很多人习惯填掩码255.255.255.0,在Netplan里不认这种写法,必须是CIDR形式。
routes里面写默认路由,语法是to: default配via: 网关IP。只写了IP没写默认路由,地址有了但出不了网,这是新手最容易漏的。
nameservers写DNS。写内网环境的网关地址就行,想上网顺便加一个公共DNS兜底。
缩进特别重要。Netplan配置不是逗号分隔的,是严格按YAML缩进解析的。同样一段配置,缩进错了,系统可能直接给你报一个无法解析的错,然后apply失败。我见过有人缩进空格数不对,netplan apply的时候报了一堆错,最后回滚配置才救回来。
3.3 改完配置文件之后netplan apply发生了什么
写完之后执行:
sudo netplan apply这条命令干了几件事:先把YAML配置解析并校验一遍,如果有错就立刻报错停在这里;然后生成后端配置文件,如果是systemd-networkd就写到/etc/systemd/network/下面,如果是NetworkManager就生成对应的连接文件;最后通知网络服务重新加载配置。
执行完netplan apply之后别急着走,验证一下:
ip addr show <网卡名> ip route ping -c 3 192.168.1.1 ping -c 3 223.5.5.5如果网关能ping通但外网不通,多半是DNS没配好;如果网关都不通,检查一下网段和掩码是不是写错了,虚拟网卡是否真的连接到了正确的虚拟网络上。
还有一点要特别注意:netplan apply只是让当前系统立即生效,但如果你配置了/etc/netplan/*.yaml文件,并且系统重启后要求配置必须从Netplan重新生成,那保存文件这一步就必不可少。写静态IP的正确姿势是配置文件写了、apply也执行了、验证也通过了,最后还要重启一次虚拟机确认配置能活下来——不能开机就回到解放前。
4. 方案二:从虚拟化平台层面保IP不漂移
4.1 固定MAC地址是第一步
如果你用的是VMware、VirtualBox、KVM这类虚拟化平台,有一个比在虚拟机系统里改配置更先要解决的问题:虚拟机网卡的MAC地址是不是稳定的。
我踩过一次印象很深的坑。某次用模板克隆了一台虚拟机,克隆后启动,系统自动把网卡识别成一个新设备,然后DHCP给了个新地址。我一度以为纯粹是网络配置丢了,折腾了半天才发现是克隆的时候MAC地址没保留,虚拟机的网卡在系统看来是一个全新的网卡,旧配置自然就不生效了。
解决方法是到虚拟机设置里把网卡的MAC地址固定下来。
VirtualBox里,在【设置-网络-高级】里,把MAC地址的“新地址”改成“与现有网卡相同”,或者手动填一个固定值。
VMware里,编辑虚拟机设置,在网卡的高级参数里可以做MAC地址的固定或手动指定。注意不要跟局域网里的其他设备冲突。
KVM/libvirt里,可以在虚拟机XML配置文件的interface段里指定<mac address='52:54:00:xx:xx:xx'>。
MAC地址固定之后,再去DHCP服务器上做保留,或者直接在系统里写静态IP,网卡的“身份证”稳定了,策略才能稳定。
4.2 给你的VM配置DHCP保留或直接改NAT网段
固定好MAC之后,下一步在DHCP层面做保留。
如果你用的是家用路由器或企业路由器自带的DHCP服务,进去找到LAN设置,按MAC地址绑定固定IP,这个功能一般叫“DHCP静态分配”或“保留”。
如果你用的是自建的DHCP服务,比如dnsmasq,可以在配置文件里加一行:
dhcp-host=52:54:00:1a:2b:3c,192.168.1.100这行的意思就是:MAC地址为52:54:00:1a:2b:3c的设备永远分配192.168.1.100。
如果你跑虚拟机的宿主机用的是NAT模式(比如VirtualBox的NAT网络、VMware的NAT模式),宿主机的虚拟网卡软件(VirtualBox的vboxnet、VMware的vmnet8)其实也内置了DHCP服务。这种情况下有两个选择:一种是把虚拟机改成Bridged(桥接)模式,让虚拟机直接从局域网路由器拿IP,再在路由器上做保留;另一种是保留NAT模式,直接进虚拟网卡的DHCP设置里改保留配置。不同平台入口不一样,但思路都是一样的——把MAC和IP绑定在DHCP服务器那边。
4.3 桌面版用户的NetworkManager路径
如果你用的是Ubuntu桌面版,网络管理默认是NetworkManager,网卡配置不一定在Netplan里能完全管得住。这种情况下,用nmcli命令操作会更直接。
先看当前连接的名字:
nmcli connection show输出里会有一个连接名,比如“Wired connection 1”。接下来修改这个连接为静态IP:
nmcli connection modify "Wired connection 1" ipv4.method manual ipv4.addresses 192.168.1.100/24 ipv4.gateway 192.168.1.1 ipv4.dns "192.168.1.1 223.5.5.5"然后重启连接:
nmcli connection down "Wired connection 1" && nmcli connection up "Wired connection 1"就这么简单。有人可能会有疑问:如果桌面版用了NetworkManager,那Netplan还管用吗?
答案是:Netplan在桌面版上默认渲染到NetworkManager,你在Netplan里写的配置会变成NetworkManager连接配置。但如果你同时在图形界面和Netplan里都改过配置,可能会出现两张皮的情况。最省心的做法是在桌面版上只用一种方式管网络,要么全程图形界面,要么全程Netplan或nmcli。混着用,迟早要出问题。
5. 方案三:彻底一点,探一下管理型网络手段
5.1 用systemd-networkd直接接管静态配置
如果你追求极致的稳定和可控,可以跳过Netplan这层,直接给Ubuntu用systemd-networkd。因为Netplan还是要翻译一层,翻译过程中偶尔会出现一些玄学问题(比如应用了配置但没通知到位、重启后配置被重置),直接用systemd-networkd的native配置文件虽然写得稍微麻烦一点,但胜在透明、稳定。
停用NetworkManager(如果系统装了的话):
sudo systemctl stop NetworkManager sudo systemctl disable NetworkManager启用systemd-networkd:
sudo systemctl enable --now systemd-networkd然后在/etc/systemd/network/下面新建一个10-static-eth0.network文件:
[Match] Name=enp3s0 [Network] Address=192.168.1.100/24 Gateway=192.168.1.1 DNS=192.168.1.1 DNS=223.5.5.5再重启服务:
sudo systemctl restart systemd-networkd注意 [Match] 段的 Name 一定要跟实际网卡名一致。另外如果机器上有多块网卡,你还要注意优先级,systemd-networkd按文件名顺序匹配,文件名前面数字越小优先级越高。
这个方案的优点是不依赖Netplan和NetworkManager哪一层,所有配置你都能直接看到、直接控制。缺点是写成原生配置之后,Ubuntu上很多网络管理工具就没法感知它的变化了,适合有经验的人用。
另外,如果你用cloud-init来初始化虚拟机,有还有一种路径——直接在cloud-init的配置里指定网络。cloud-init里面可以写network: {version: 2, ethernets: ...}这种结构,它会在虚拟机首次启动的时候生成网络配置。如果你反复从模板克隆虚拟机,这个方式最省事,克隆完的机器起来就自动是静态IP,不用每次手动改系统内部的配置文件。
5.2 mDNS与SSH的联动:找不到IP也能连
严格来说这不是解决IP丢失,而是绕开“忘IP就想不起来”这个痛点。
Ubuntu默认装了avahi-daemon(也就是mDNS实现),支持.local主机名解析。如果你只是SSH连虚拟机,经常记不住IP,那么给虚拟机设置一个主机名,然后直接用ssh user@hostname.local去连就行。
前提是:主机名先设置好:
sudo hostnamectl set-hostname ubuntu-dev然后在/etc/hosts里把127.0.1.1 ubuntu-dev写上,这样部分工具解析ubuntu-dev.local的时候能对上。之后在同一局域网内,只要虚拟机开着,你就可以用ssh user@ubuntu-dev.local连接,IP怎么变都无所谓。
这个方法非常适合家庭网络、测试环境这种单播环境。如果有防火墙或者禁用了mDNS,就要在虚拟机的防火墙设置里放行5353端口才行。用这个方法做兜底,即便IP临时变了,你依然能用主机名直接连上虚拟机去处理问题,而不需要跑到机房或者宿主机控制台去把IP问出来。
6. 避坑指南:那些年我踩过的IP丢失坑
6.1 配置写对了还是不上网,多半是网关
这是我见了最多的问题:静态IP写了,地址也有了,掩码也是对的,可就是ping不通外网。
排查的时候用ip route看一眼,如果默认路由那一行长得不对,比如网关写成了192.168.2.1但在192.168.1.0/24网段里,系统会非常明确地在route表里写一条不可达的条目。这种情况下,你的报文根本发不出去。
解决办法:确认你填的网关IP确实属于跟虚拟机同一个网段。比如我的虚拟机IP是192.168.1.100/24,那网关必须是192.168.1.1到192.168.1.254之间的地址。如果拿的是宿主机虚拟网卡的IP,先确认这个IP是不是这个网段。
还有一个容易忽略的:VMware的NAT模式默认网关通常不是192.168.x.1,而是192.168.x.2。VirtualBox的NAT模式又不一样,默认网关通常是10.0.2.2。所以别拿网上抄的配置直接填,先去看你当前环境里DHCP给出的默认网关是多少,然后再写静态配置。
6.2 别把DHCP和静态混着写
以下配置看着是没错的:
ether nets: enp3s0: dhcp4: yes addresses: - 192.168.1.100/24实际上这会导致一个很尴尬的后果:DHCP先给网卡分配了一个地址,你写的静态地址又试图往网卡上叠加。两个地址都能看到,但路由可能乱掉,流量可能从错误的地址发出。排查的时候你看到网卡上有两个地址,自然一头雾水。
我见过更隐蔽的:配置里写的是dhcp4: yes,但地址段是从DHCP池之外的地址,DHCP和静态地址完全不冲突,看起来啥问题没有。但DHCP租约一刷新,系统可能因为路由策略的问题把网络切到不可用的状态。
标准做法就是二选一。想用DHCP就用纯DHCP,別混静态。想固定,就把dhcp4关掉,只写静态。
6.3 多网卡同时捣乱的排查方向
虚拟机如果创建的时候加了不止一块网卡,网络问题就复杂多了。比如VMware的虚拟机可能一块NAT网卡连外网、一块仅主机网络连内部集群,VirtualBox也可以加一块桥接网卡一块仅主机网卡。
这种场景下经常出现的问题是IP丢失、或者默认路由被抢了。两个网卡都启用DHCP的话,每一张网卡都会尝试申请IP,默认路由可能由其中一块拉了,而另一块的配置又找不到网关,导致内部集群环境访问不了。
排查多网卡问题,先跑一遍:
ip addr ip route show table main看看默认路由表里到底走了哪张网卡的网关。然后决定哪块网卡需要做静态、哪块只留内部网段就行。有需要的时候,可以在Netplan里把某块网卡上不去外网的网段显式写在自有路由里,不让它参与默认路由竞争。
我习惯的做法是:外部接口用DHCP或者静态配网关,内部接口直接给静态IP且不写默认路由,这样两个网卡各司其职,不会互相抢活。
6.4 快照回滚后IP丢失的隐蔽元凶
最后说一个比较隐蔽的场景。如果你用快照功能做备份,某天为了回滚系统状态,恢复了旧快照。恢复之后发现虚拟机网络不通了,IP变了。
原因很简单:快照里保存的网络配置可能是旧网卡命名或旧MAC绑定状态,而回滚后虚拟机当前硬件状态和快照里的配置不一致。
遇到这种情况,我最常用的处理方法是:先到虚拟化平台里把网卡MAC固定成快照里的那个MAC地址,再启动虚拟机,看IP是不是回来。如果还不通,再进系统把 /etc/netplan 或 /etc/network/interfaces 里的配置跟实际网卡名对齐一次。快照用多了,这种问题就特别常见,尤其是经常做实验改网络配置的人。
7. 我的日常维护小习惯
文章写的都是具体的排查和配置方法,我最后再分享一个我自己的习惯,也算是个管理心法。
我现在给任何一台Ubuntu虚拟机做网络配置的时候,都会保留一份文本的“网络基线记录”。记录内容很简单:虚拟机名称、网卡MAC、网卡名、期望IP、网关、DNS、DHCP保留配置位置、配置文件路径。每次动网络配置之前,先对照这份记录看一次,动完之后再对照一次。
这套习惯帮我省了不少时间。从模板克隆机器、快照回滚、虚拟交换机扩容、网络配置迁移,每次都能快速把网络恢复成预期的状态,而不是反复试错,靠运气把IP修回来。
IP丢失这个问题不是因为难,而是因为牵扯的层次多、容易忽略细节。你只要把虚拟化平台的MAC稳定性和系统里的静态配置这两件事做到位,再从DHCP服务器上顺手做一个保留绑定,几乎不会再有IP漂移的烦恼。如果锦上添花再用mDNS或者固定的主机名做一层兜底,那就是把这份运维工作做得相当扎实了。