
如果你在公有云或自建虚拟化平台上创建了一个 Rocky 10 云镜像重启实例后要等接近两分钟才能 SSH 上去第一反应会是什么绝大多数人的第一反应是网络配置坏了。于是改 DNS、换镜像源、调网卡、重置网络服务折腾一圈下来重启还是慢两分钟。这不是个别现象。Rocky 10 云镜像首启慢是一个典型的“多因一果”问题cloud-init 的数据源探测、SSH host key 重新生成、熵源不足、fstab 里挂不上的磁盘、systemd 等待超时每一项都可能产生完全相同的表象——启动后迟迟无法登录。这篇文章要解决一个具体问题在 Rocky 10 云镜像上当你看到首启慢两分钟时如何快速定位时间消耗在哪里以及如果不是网络问题真正该查什么。读完你会得到一套排障的次序感先用工具量化再沿启动链路逐段确认最后才动手改配置。而不是凭直觉把网络相关配置挨个试一遍。1. 先别急着怀疑网络1.1 一个典型的“怪网络”场景假设你在 KVM 或某云平台上用 Rocky 10 通用云镜像创建了一台实例SSH 端口通了安全组也没问题但每次全新启动都要等将近两分钟才能连上。登录进去之后ping 通、curl 通DNS 正常网卡状态正常一切看起来都和网络没关系。这时候人很容易陷入一种思维定式既然“连不上”发生在前那一定就是网络问题。可事实上你真正等待的不是“网络连通”而是“系统就绪”。这两者在云环境里经常被混为一谈。1.2 为什么网络经常背锅“启动慢”在云上有一个很迷惑人的表现你看到的是 SSH 连不上而 SSH 连不上在直觉上等于网络不通。再加上云平台控制台、安全组、子网路由、DNS 解析这些环节确实也可能导致连不上所以大家第一轮排查几乎都是在网络相关配置里打转。但从排障的角度看“完全 ping 不通”和“系统没就绪所以端口没监听”是两种完全不同的现象。前者网络嫌疑大后者更应该去查系统启动链路。1.3 这篇文章的核心判断首启慢不等于网络慢。即便最终定位到NetworkManager-wait-online.service或systemd-networkd-wait-online.service这类和网络沾边的服务问题的本质也不是物理网络不通而是网络管理服务在等待一个“永远等不到”的事件触发超时。所以正确的排障顺序是先搞清楚时间到底消耗在哪个阶段再决定要不要动网络配置。这个顺序一旦颠倒就容易做大量无效操作。2. 云镜像首启链路里到底有哪些环节2.1 首启和后续启动有什么区别通用云镜像在发布前会做一次清理清空/etc/machine-id、删除 SSH host key、清空临时文件、重置 cloud-init 的状态。这样每个实例第一次启动时才会生成自己独立的 machine-id 和主机密钥避免同一镜像克隆出的所有实例使用相同的身份。正因如此首次启动要额外完成很多工作重新生成 machine-id。重新生成 SSH host key。cloud-init 运行各个阶段脚本。growpart 扩展根分区。注入用户数据、设置主机名、添加用户。首启稍微比后续启动慢一点是正常的。但慢到两分钟、甚至更久就不是常规初始化开销能解释的了。2.2 一次云实例启动的完整阶段用 systemd 视角看一次典型的 Linux 云主机启动过程大致如下固件引导BIOS/UEFI云上虚拟化平台这部分通常很快。内核解压加载驱动初始化设备。systemd 作为 1 号进程启动开始并行拉起各类单元。挂载/etc/fstab里定义的文件系统。启动网络管理服务NetworkManager 或 systemd-networkd。启动 sshd、cloud-init、auditd、rsyslog 等基础服务。cloud-init 依次执行 local、network、config、final 几个阶段。用户态服务就绪SSH 端口开始监听。任何一个环节卡住都会推迟“能 SSH 登录”的时间点。而你感知到的“首启慢两分钟”很可能不是某一个环节用了两分钟而是某个环节等待超时后后续任务继续依次排队造成的累积效果。2.3 “两分钟”这个数字能说明什么排障的时候数字本身就是线索。如果慢的时间恰好接近 120 秒大概率是某个连接型超时比如 cloud-init 探测云平台 metadata 服务超时或 DHCP 获取 IP 超时。如果慢的时间接近 90 秒则要优先怀疑 systemd 默认的TimeoutStartSec90s被触发——某个单元启动失败后systemd 会等满 90 秒才放弃。如果慢的时间由两个 60 秒组成那可能是两次独立的超时串联在一起。换句话说启动时间不是均匀分布在所有环节里的它往往集中在少数几个“超时等待”上。这就是为什么第一步要做量化分析而不是靠猜。3. 先量化再排障学会读 systemd 启动时间3.1 用 systemd-analyze time 看整体耗时登录进故障机器后第一条命令应该是systemd-analyze time输出大致如下Startup finished in 3.821s (kernel) 115.372s (userspace) 119.193s graphical.target reached after 115.372s in userspace注意看 userspace 的时间。如果 kernel 时间只有几秒而 userspace 时间接近两分钟说明问题几乎可以确定在内核之后的 systemd 启动链路里。3.2 用 systemd-analyze blame 找最大耗时单元systemd-analyze blame | head -20这条命令会按单元启动耗时从大到小排序。它给出的是每个单元从开始到完成消耗的真实时间包括等待依赖的时间。通常 top 5 里就能看见嫌疑对象常见名字包括cloud-init-local.servicecloud-init.serviceNetworkManager-wait-online.servicesshd-keygen.service某个挂载点对应的.mount单元3.3 用 critical-chain 看关键串行链路blame 是并行视角critical-chain 是串行视角。它展示从某个 target 反推到开机根节点的关键路径只有这条路径上的慢单元才会直接拖慢启动结束时间。systemd-analyze critical-chain输出类似下面这样graphical.target 115.372s └─multi-user.target 115.370s └─cloud-init.service 114.900s 470ms └─network.target 13.200s └─NetworkManager-wait-online.service 10.100s 3.100s └─NetworkManager.service 7.500s 2.600s如果你看到cloud-init.service在这个关键路径上且耗时极长那么启动慢的主因基本锁定。3.4 用 journalctl 确认服务在等什么量化只能指路确证还看日志。查看某个单元本次启动的完整日志journalctl -b -u cloud-init.service如果日志里反复出现连接某个 IP 超时、重试、再次超时那么你找到了真正的原因。查看 sshd 和网络管理服务同理journalctl -b -u sshd.service journalctl -b -u NetworkManager.service3.5 生成可视化启动图systemd 还支持直接输出一张 SVG 启动时间轴图systemd-analyze plot boot.svg把生成的boot.svg下载到本地用浏览器打开可以直观看到各单元在时间轴上的起止位置和重叠关系。排查那种“两个服务互相等待”的死锁型问题这张图尤其好使。4. 网络嫌疑什么时候真是网络问题4.1 wait-online 服务到底在干什么Rocky 10 默认使用 NetworkManager 管理网络。NetworkManager 本身启动很快但有一个服务经常被忽略——NetworkManager-wait-online.service。它做的事情是等待 NetworkManager 报告“网络连接已可用”然后再让后续单元继续。问题是在很多云环境里“网络可用”的判断标准并不总是很快达标。比如网卡配置成了 DHCP但平台侧 DHCP 响应较慢或者连接配置里设置了连通性检测 URL检测一直无法通过service 就会在等待中消耗大量时间。检查它是否在故障机上处于活跃等待状态systemctl status NetworkManager-wait-online.service4.2 DHCP 超时怎么判断如果启动日志里出现 DHCP 反复尝试、超时重试那网络服务本身确实拖慢了启动。这个时候要区分是物理网络不通还是 DHCP 获取 IP 的配置问题。查看 NetworkManager 日志journalctl -b -u NetworkManager.service | grep -i dhcp对于不需要动态 IP 的云主机可以考虑把网卡改为静态 IP或者在 NetworkManager 连接配置里限制 DHCP 超时时间nmcli connection modify eth0 ipv4.dhcp-timeout 10 nmcli connection up eth0这只是设置 DHCP 最大等待 10 秒没拿到地址就放弃等待并继续启动流程而不是无限重试。4.3 能不能直接关掉 wait-online很多运维人员会选择禁用NetworkManager-wait-online.service这确实能消除一部分首启延迟systemctl disable NetworkManager-wait-online.service但必须理解这个操作的含义它只是不让 systemd 在启动阶段等待网络完全就绪并不会关闭 NetworkManager 本身。如果你的业务依赖“SSH 登录时网络一定已经可用”比如登录后立刻要挂载远程目录、启动容器并映射端口那么盲目禁用可能带来后续服务初始化失败的问题。更稳妥的做法是先把时间定位清楚确认 wait-online 是瓶颈再结合业务实际情况决定是否禁用。4.4 cloud-init 探测 metadata 本质上也是一种网络等待注意cloud-init 在探测云平台数据源时本质上是向网段内的 metadata 服务发起 HTTP 请求。如果请求超时它的表现会和网络故障高度相似但这并不是你改网卡配置能解决的。这个问题和网络相关又不仅仅是网络问题下面单独展开。5. 真正常见的非网络元凶5.1 cloud-init 数据源探测超时这是 Rocky 10 云镜像首启慢最常见的原因之一。cloud-init 启动时需要确认自己运行在哪个云平台上方法是探测各个云平台的数据源服务。通用云镜像不会只适配一个平台它会在编译期内置多个数据源比如 AWS、OpenStack、Azure、GCE、Aliyun 等。实际运行时cloud-init 按顺序向这些数据源发起探测请求。问题出在探测顺序上。如果镜像运行在自建 KVM 平台而镜像默认先探测 AWS 的169.254.169.254metadata 服务那么每一次探测都要走到 TCP 连接超时才会放弃然后才能尝试下一个数据源。多个数据源串联探测累积起来就是一两分钟。排查方法journalctl -b -u cloud-init.service | grep -i datasource如果日志显示连续多次连接超时说明问题就在这里。解决办法是显式指定当前平台的数据源。比如运行在 OpenStack 平台创建/etc/cloud/cloud.cfg.d/99_custom.cfg# 文件路径/etc/cloud/cloud.cfg.d/99_custom.cfg datasource_list: [OpenStack, ConfigDrive, None]如果运行在纯 KVM 且不需要 metadata可以直接写# 文件路径/etc/cloud/cloud.cfg.d/99_custom.cfg datasource_list: [None]修改后需要清理 cloud-init 状态再测试一次完整首启cloud-init clean --reboot这个操作会清除 cloud-init 的缓存和状态文件模拟第一次启动是验证镜像初始化逻辑的标准手段。5.2 SSH host key 重新生成阻塞通用云镜像在构建时会删除/etc/ssh/ssh_host_*密钥文件。首次启动时sshd 检测到没有 host key会调用ssh-keygen生成。正常情况下这个过程很快但在某些虚拟化环境下系统熵源不足随机数生成会严重变慢。排查方法journalctl -b -u sshd.service | grep -i key systemctl status sshd-keygen.service对应地检查当前系统里是否已经有 host key 文件ls -lh /etc/ssh/ssh_host_*如果你的虚拟化平台没有提供 virtio-rng 等硬件熵源系统熵不足时可以考虑安装 rng-tools 并确保虚拟机挂载了随机数生成设备dnf install -y rng-tools systemctl enable --now rngd不过在现代 CPU 普遍支持 RDRAND 指令的情况下这类问题没有以前那么常见不必上来就怀疑熵不足。它更适合放在系统日志确认之后再看。5.3 fstab 里不可达的挂载点如果/etc/fstab里写了 NFS 挂载点或某个云盘但启动时对应设备不存在systemd 会等待该 mount 单元成功默认超时时间 90 秒。这个现象经常被误判为系统卡死。实际排障时先检查 fstabcat /etc/fstab再运行systemctl status data.mount或直接看关键链路里有没有.mount单元排在耗时前列。如果确定该挂载点不是系统必需的可以给挂载参数加上nofail192.168.1.10:/data /data nfs4 defaults,_netdev,nofail,x-systemd.device-timeout10 0 0nofail表示挂载失败不影响开机x-systemd.device-timeout10表示设备等待上限 10 秒。生产环境调整 fstab 前务必确认当前已经挂载的文件系统不会被误卸载。5.4 主机名解析拖慢服务一些服务在启动时会解析自己的主机名或某个外部域名如果/etc/resolv.conf里的 DNS 配置有问题每次解析都要等满超时时间。排查方法看看启动日志里是否大量出现与 “resolve” 或 “hostname” 相关的报错journalctl -b | grep -iE resolve|hostname | head -20如果是因为主机名无法解析最简单的方式是在/etc/hosts里加上本机主机名映射127.0.0.1 myhostname这样服务启动时解析主机名走 hosts 文件不再依赖 DNS 服务器。5.5 SELinux 重新标记文件系统如果镜像构建过程中 SELinux 策略发生变化或者某个目录的文件标签不正确系统可能在首次启动时执行文件系统重新标记。这个过程会遍历大量文件耗时和磁盘速度直接相关。如果你在启动日志里看到setfiles、restorecon、relabel之类的关键词可以检查日志确认journalctl -b | grep -iE selinux|setfiles|restorecon大多数云镜像在发布前已经完成 labels 清理这种情况不算高频但遇到“首启比后续启动慢很多”且软硬件配置都正常时值得列入排查清单。5.6 机器 ID 重置与随机种子/etc/machine-id被清空后首次启动需要生成新的 machine-id。这个操作依赖随机数生成理论上和 SSH host key 生成一样在熵不足的虚拟化环境中会变慢。如果上面链路全部排查完还找不到原因可以留意一下:systemd-analyze blame | grep systemd-random-seed这里真正容易踩坑的地方在于很多操作者改完网络配置后没有清理 cloud-init 的旧状态导致测试时 cloud-init 并没有真正重跑首启流程耗时的现象依然存在。6. Rocky 10 首启慢完整排查示例这一节演示一个自建 KVM 平台上 Rocky 10 云镜像首启慢 120 秒的完整排查过程。6.1 确认 userspace 是主要耗时登录后先执行systemd-analyze time输出显示 userspace 消耗了约 115 秒kernel 只有几秒。结论问题在 systemd 启动链路不在内核。6.2 用 blame 找出耗时最长的单元systemd-analyze blame | head -10输出里cloud-init.service耗时 100 秒以上排在第一位。与此同时NetworkManager-wait-online.service也有一定耗时但远小于 cloud-init。6.3 用日志确认 cloud-init 在等什么journalctl -b -u cloud-init.service | tail -50日志里出现了多次向169.254.169.254连接超时的记录。这确认了问题cloud-init 在按内置顺序探测 AWS 等数据源而当前 KVM 平台根本不提供这类 metadata 服务每次探测只能等待 TCP 超时。6.4 指定数据源并清理状态创建配置# 文件路径/etc/cloud/cloud.cfg.d/99_custom.cfg datasource_list: [ConfigDrive, None]然后清理状态cloud-init clean --reboot6.5 验证效果重启后再执行systemd-analyze time如果 userspace 时间回落到 15 秒以内说明问题解决。如果问题仍在继续用 critical-chain 查看新的时间瓶颈在哪里重复上面的定位流程。7. 常见问题与排查思路问题现象可能原因排查方式解决方案首启刚好慢约 120 秒cloud-init 数据源探测超时journalctl -b -u cloud-init.service指定datasource_list后清理状态首启慢约 90 秒systemd 默认单元超时触发systemd-analyze critical-chain定位挂载点或等待单元加nofail日志显示 wait-online 长时间活跃网络连接状态判断未达标systemctl status NetworkManager-wait-online确认业务依赖后禁用 wait-onlinesshd 服务启动耗时异常host key 重新生成阻塞journalctl -b -u sshd.service检查系统熵源安装 rng-tools能登录但命令执行卡顿DNS 反向解析慢查看/etc/ssh/sshd_config将UseDNS设为nofstab 挂载点无响应远端存储不可达systemctl status xxx.mount加nofail和x-systemd.device-timeout表格里列的每一类问题都要先看日志确认再改配置。不要只凭现象对号入座。8. 最佳实践与工程建议8.1 构建镜像时就把数据源定死通用云镜像为了方便适配多个平台默认会保留多个数据源探测项。但如果你只在固定平台使用镜像在构建阶段写死数据源列表是性价比最高的优化。做法是在构建脚本里提前放置/etc/cloud/cloud.cfg.d/99_custom.cfg内容指向唯一平台。这样发布出来的镜像从第一次启动就不会触发多余探测。8.2 保留首启日志和启动时间基线很多排障难是因为没有参照。建议在镜像里加一个最小化的启动耗时记录脚本每次启动后把systemd-analyze time的 stats 写入日志文件保留最近若干条记录。下次再遇到首启慢直接对比不同启动之间的差异比临时分析更快。8.3 不建议在通用镜像里预置固定 SSH host key把 host key 预先写进镜像确实能省去首启生成时间但所有实例共享同一把 key 是严重的安全隐患。每个实例必须有唯一的主机密钥。正确思路是让密钥生成阶段足够快而不是跳过生成。8.4 调整 systemd 超时参数要谨慎systemd 单元的超时时间可以在/etc/systemd/system.conf里全局调整DefaultTimeoutStartSec30s调整前要评估业务依赖比如慢盘机器上数据库服务启动时间本来就长把全局超时压得太低反而会导致正常服务被误杀。更推荐的做法是针对具体单元单独设置systemctl edit cloud-init.service在 override 文件里修改超时时间。8.5 生产环境变更前的必要动作修改 cloud-init 配置、fstab、网络管理服务之前至少做三件事备份原配置。在测试环境完整走一遍首启验证。确认回滚方式比如保存原配置文件路径。对于清理 cloud-init 状态这类操作注意cloud-init clean会重置实例身份相关信息只应在测试环境或明确知道后果的情况下执行。9. 总结与后续学习方向Rocky 10 云镜像首启慢两分钟真正卡住的往往不是物理网络而是启动链路上某个“等待外部事件超时”的单元。可能是 cloud-init 在探测永远不存在的 metadata 服务可能是 systemd 在等待一个永远挂不上的磁盘也可能是熵不足导致密钥生成迟迟无法完成。排障的顺序感比记忆力更重要。拿到一台首启慢的机器先跑systemd-analyze time确认耗时区间再跑blame和critical-chain缩小范围最后用journalctl落到具体服务日志定位准确后再决定改哪里。下一步可以继续深入两个方向一个是 systemd 的单元依赖图和执行模型理解network.target、multi-user.target等关键点之间的依赖关系另一个是 cloud-init 的阶段模型搞清楚 local、network、config、final 每个阶段的触发条件和阻塞点。有了这两块基础以后再遇到“启动慢”就不需要靠猜了。下一次你再遇到首启慢先别急着打开网卡配置文件。拿起终端跑一条systemd-analyze blame让日志替你说话。