
一台 Rocky 10 云镜像的实例首次开机竟然要卡两分钟很多人的第一反应是网络问题。我遇到过类似场景最后定位下来问题根本不在网卡、DHCP 或者 DNS而是启动阶段一堆服务在“排队等待”。这篇文章想分享一个不一样的排障思路云镜像首次启动慢网络只是嫌疑人之一真正让你等两分钟的往往是 systemd 服务等待链、cloud-init 超时和镜像初始化顺序。与其反复改网卡配置不如先把启动时间线拆开看看到底是哪一步卡住了。1. 首启慢两分钟为什么“网络”总是第一个背锅1.1 网络确实很值得怀疑云镜像第一次启动时系统要做很多和网络相关的初始化动作。具体来说至少包括这几个环节网卡驱动加载等待硬件就绪。连接 DHCP 服务器获取私有 IP。如果是 IPv6 环境还有 SLAAC 和 Router Advertisement 的等待。NetworkManager 或者 network daemon 启动并等待设备进入 connected 状态。cloud-init 尝试访问云平台的 metadata 服务拉取主机名、SSH 公钥、用户数据等。如果配置了 DNS某些服务启动时要解析主机名或者软件源域名。任何一个环节长时间没有响应都可能导致启动时间被拉长。所以遇到首启慢先怀疑网络非常正常这不算误判只是下一步的排查方向要更精确。但问题在于“网络服务最终起来了”和“启动过程中没有被网络拖住”是两码事。你 SSH 进去之后执行ip addr能看到地址ping也能通并不代表启动阶段没有发生过等待。systemd 不是所有服务都并行启动的它会让某些服务等待网络栈达到某个状态再继续拉起后续任务。1.2 但“能访问”和“没卡在网络”不是一回事我在处理这类问题时经常看到这样一种有意思的现象用户通过云控制台的 VNC 或者串口登录界面一直停在启动日志里最后跳出来的却是一行和 network 相关的超时提示。于是大家开始排查网卡驱动、私有网络、安全组、路由表折腾半天把静态 IP 都配了一遍问题依然存在。后来发现系统实际上是在等待 metadata 服务返回。这个服务请求走的是内网地址并不是传统意义上“你家里能否上网”的网络问题。很多云厂商的 metadata 服务正常情况下解析很快但如果你用的是自制镜像或者 cloud-init 的配置文件指向了不存在的地址它就会一直重试直到超时。这一类“网络等待”很难通过常规网络诊断发现。你pingmetadata 地址可能会通也可能完全不可达但真正影响启动时间的是 cloud-init 内部的超时策略。所以我的第一步判断是不要急着进入“网络配置”这一个方向先把启动时间线拿出来看看哪些服务耗时最多再决定下一步。2. 先不要拍脑袋用 systemd-analyze 给启动过程做一次时间线拆解2.1 最小命令集Rocky 10 使用的 systemd 版本比较新systemd-analyze的工具链已经很成熟。第一次登录进系统后如果还能进入 shell先执行这几个命令systemd-analyze time这条命令会给出总体启动时间包括固件、引导加载器、内核、用户空间的时间。如果你只关心用户空间里 systemd 管理了哪些慢服务继续执行systemd-analyze blame | head -20这条命令会把用户空间的服务按耗时从高到低排列。排在最前面的基本就是导致首启慢的主要嫌疑对象。为了看得更清楚还可以生成一个启动过程的矢量图systemd-analyze plot boot-plot.svg然后把这个 SVG 文件下载到本地用浏览器打开能够直观看到每个服务在时间轴上的位置。2.2 三个指标怎么看systemd-analyze time给出的时间只是让你有个整体概念。真正要分析的是blame里的服务耗时以及critical-chain里的依赖链。systemd-analyze critical-chain service例如systemd-analyze critical-chain cloud-init.service依赖链会告诉你这个服务到底等了哪些前置资源。很多时候一个服务在blame中耗时很长并不是它自身执行慢而是它前面的某个依赖迟迟没有满足。2.3 判定顺序先看“谁在等”而不是“谁最慢”这里有个很容易犯的错误看到blame里某个服务耗了 90 秒就急着去禁用或者优化它。但你应该先看一下这条依赖链里还有没有其他服务。举例来说NetworkManager-wait-online.service经常上榜。它不是一个“干活”的服务它只是告诉系统“我刚等完了网络在线”。真正让它等待的可能是你某个网卡配置里的 DHCP 一直没成功也可能是你在/etc/sysconfig/network-scripts/ifcfg-*里定义了额外路由或者网关导致 NetworkManager 判断“上线”的标准一直无法满足。所以排查顺序应该是先看blame中最耗时的几个服务。再对每个服务执行critical-chain。如果多个慢服务都指向同一个依赖说明卡点在依赖上。如果某个服务自身执行时间长再进去翻服务日志。这样就不会被表面数字带到错误方向。注意systemd-analyze blame显示的时间是服务启动到进入 active 状态所需时间不包含它在队列里等待的时间。如果你发现多个服务都在 8090 秒附近往往是同一个上游阻塞。3. 被低估的几个常见卡点cloud-init、密钥生成、udev settle3.1 cloud-init 的“服务依赖感”Rocky 云镜像一般都会安装 cloud-init它的启动流程分好几个阶段cloud-init-local.servicecloud-init.servicecloud-config.servicecloud-final.service它们在启动早期依次运行。第一个阶段处理本地数据源第二阶段开始和云平台的 metadata 服务交互。如果 metadata 服务在短时间内无法访问cloud-init 会进行重试这个重试间隔和最大等待时间直接影响首启速度。检查 cloud-init 日志时可以执行journalctl -b -u cloud-init.service --no-pager或者直接看tail -n 100 /var/log/cloud-init.log日志里经常会出现类似 “Timed out waiting for ...”或者 “unable to read metadata ...” 的记录。如果确认是 metadata 访问超时要先去核对镜像里配置的 datasource 和云厂商是否匹配。有些自建镜像会把 datasource 配置成某个固定地址但当你把它放到另一个云平台后该地址不可达。系统不会马上失败而是反复重试一段时间后才放弃最终表现为“首启慢两分钟”。3.2 SSH host 密钥生成与系统熵另一个容易被忽略的卡点是 SSH host 密钥生成。云镜像首次开机时如果没有预先生成密钥sshd 相关服务会在启动阶段调用ssh-keygen生成 host key。生成 RSA、ECDSA、Ed25519 等密钥需要足够的随机数。在刚刚启动的虚拟机里内核熵池可能还没有积累足够多的噪声getrandom()或/dev/random读取可能会阻塞。这就会导致 sshd-keygen 长时间挂在那边看起来也像卡住。排查方法也比较直接systemctl list-units | grep sshd或者systemd-analyze blame | grep sshd如果确实是密钥生成拖慢了启动可以考虑在制作镜像时预生成 host key并在镜像内部提前执行systemctl preset sshd-keygen相关动作。还有一种做法是安装并启用 haveged 或者 systemd-random-seed避免启动阶段熵不足。3.3 udev settle 与硬件探测有些自定义云镜像里会出现一个叫systemd-udev-settle.service的服务。这个服务的历史作用是等 udev 处理完所有设备事件。听起来合理但问题是它没有精确判断“处理完”的标准在某些驱动加载慢或者虚拟化设备较多的环境中它很容易等过头。如果你看到blame结果里有这个服务先不要急着禁用它要确认你的存储或网卡驱动是否真的需要依赖 udev 完成设备命名。现代 systemd 已经不太建议使用systemd-udev-settle.service只保留少量兼容场景。当你确认当前环境不需要之后可以把它 mask 掉systemctl mask systemd-udev-settle.service但我要提醒一句mask 之后某些旧式脚本或第三方服务如果仍然依赖它可能会遇到设备节点未准备好的问题。所以改之前最好先跑一轮你的业务启动脚本。4. 网络环节仍然要排查但按“依赖关系”来不要乱改配置4.1 先判断网络服务是否真的故障虽然文章重点说“不一定卡在网络”但你不能真的完全不排查网络。理想的做法是先确认网络服务状态。在 Rocky 10 里默认一般使用 NetworkManager。检查状态systemctl status NetworkManager也可以看它启动阶段有没有明显报错journalctl -b -u NetworkManager --no-pager | tail -n 50如果你判断启动慢是和“等待网络在线”相关可以查看systemctl status NetworkManager-wait-online.service如果它里面出现类似 “Timed out waiting for network connectivity” 的记录说明有设备迟迟没有达到在线状态。4.2 如果发现网络服务耗时怎么区分“等待外网”还是“等待内网元数据服务”这一步很关键。网络服务耗时未必代表“公网不通”更常见的其实是某个网络的 DHCP 地址获取失败后系统还在反复重试。某些网卡配置文件里设置了DEFROUTEno或额外网关导致路由判断异常。DNS 解析出现问题服务在解析外部域名时超时。firewall 或者 nftables 规则导致 metadata 请求被拦截。区分方法很简单抓一下系统在启动阶段访问的地址。在等待卡住的时候打开另一个 SSH 会话执行journalctl -b | grep -i metadata\|datasource\|http或者journalctl -b | grep -i timed out如果发现超时对象指向的是云平台内网地址那就不是传统网络故障而是 cloud-init 和云平台之间的适配问题。如果超时对象是外网域名那可能和 DNS 配置、镜像源地址、系统代理设置有关。4.3 不要一上来就禁用 DHCP很多人看到卡在网络服务第一反应是把网卡改成静态 IP。但云镜像的 IP 是由云平台灵活分配的你改成静态 IP可能当时能起来换一台主机或者网络分片就会失败。在公共云场景下我建议优先保持 DHCP但可以调整 NetworkManager 的wait-online超时时间或者直接让一些重要服务不等待网络在线。修改连接配置nmcli connection modify eth0 connection.autoconnect yes nmcli connection modify eth0 ipv4.method auto如果希望 systemd 等待网络的时间缩短可以在/etc/systemd/system/NetworkManager-wait-online.service.d/override.conf里加入[Service] ExecStart ExecStart/usr/bin/nm-online -s -q --timeout10这样就把等待时间限制在 10 秒至少不会因为它卡满两分钟。要注意如果你有依赖网络才能挂载的远程目录或需要申请证书等业务缩短等待时间可能会让这些任务失败。5. 如果这是你正在做的云镜像启动优化应该在“打包阶段”完成5.1 镜像内部可以提前完成的事如果你不是只是开一台云主机而是在制作一个自定义 Rocky 10 云镜像那很多首启问题完全可以在打包阶段规避。下面这几件事我建议在镜像构建脚本里处理掉预生成 SSH host key并清空machine-id让每台实例启动后能生成独立 ID。清空 cloud-init 在旧实例上留下的缓存和 machine-id。安装systemd-random-seed或haveged避免熵不足。禁用不需要的等待类服务比如NetworkManager-wait-online.service。检查 cloud-init 的 datasource 配置确保它和目标云平台匹配。清理不需要的内核模块和固件包减少 udev 探测时间。如果镜像支持开启串口控制台方便后续排障。5.2 制作云镜像时的验证清单格式可以做成表格方便在构建时逐项确认。检查项具体命令期望结果启动时间systemd-analyze time整体时间符合预期慢服务排序systemd-analyze blame | head -20没有明显异常超时cloud-init 日志journalctl -b -u cloud-init*无 metadata 访问超时SSH host keyls -l /etc/ssh/ssh_host_*文件已存在machine-idcat /etc/machine-id构建时为空或占位值NetworkManager 等待systemctl status NetworkManager-wait-online不长时间卡住串口输出dmesg | grep ttySttyS0 等端口可输出日志验证时最好做一个“干净首启测试”用镜像起一台新实例观察第一次启动到 SSH 可达、cloud-init 完全退出需要多久。如果这次测试已经达到生产预期再发布镜像。5.3 长期维护版本升级带来的回归还有一个容易被忽略的点Rocky 的版本升级或者内核更新后某些服务的启动时间可能发生变化。比如新版本内核里某个驱动探测变慢或者 NetworkManager 的连接判断逻辑不一样。镜像如果长期不重建只在旧实例上原地升级遇到首启慢的概率会高一些。我建议镜像发布前都跑一遍上面的验证清单并且保留一份基线数据。如果升级后启动时间明显变长就能快速定位是新版本引入的回归。6. 沉淀一个通用的首启慢排查框架6.1 五步排查法把前面所有内容收敛一下可以形成一套通用的云镜像首启慢排查框架拿时间线用systemd-analyze time和blame看整体分布。看依赖链对前几名慢服务执行critical-chain找共同上游。翻日志针对疑点服务检查 journal、cloud-init 日志和网络日志。区分类型判断是“外部服务等待”“本地服务执行慢”还是“资源不足而阻塞”。回到镜像层如果是自制镜像直接修复镜像如果是云厂商公开镜像先检查实例类型和区域差异。这套框架的好处是不会让你一开始就陷入网络配置的细节。先知道“哪一步花了多少时间”再决定修什么。6.2 适用边界和不适用场景这套方法最适合的是“系统最终能启动只是启动慢”的情况。如果系统完全无法启动、内核崩溃、磁盘损坏或者文件系统只读那就要先走另一条更底层的排障路线比如查看串口控制台早期的内核日志。同时如果你的慢启动发生在云平台网络策略层面比如安全组把 metadata 服务端口封了那你在系统内部看到的日志可能只有超时没有明确报错。这时要结合云厂商平台侧的网络访问日志或者控制台状态来确认。还有一个边界是有些时候启动慢是云厂商底层虚拟化调度导致和镜像内部完全无关。如果你在多个不同规格实例上测试发现只有某一类宿主机上慢那就要联系云厂商查宿主机负载或存储热迁移。注意不要把镜像内部的一切启动问题都归因于 “cloud-init 或 NetworkManager”。如果只有特定实例类型出现延迟先做一个交叉验证换一个 CPU 型号或存储类型试试。6.3 什么时候才需要“改配置”只有当系统确认某个服务确实超时并且这个服务对当前业务没有关键影响时才建议通过 systemd override 或者 mask 方式去掉等待。比如你不依赖 DHCP 等待外部存储挂载可以缩短NetworkManager-wait-online超时。你的实例不使用 cloud-init 拉取用户数据可以调整 cloud-init 的超时策略。你的环境不依赖传统 udev settle 设备命名可以 mask 相应服务。但不要为了“刷启动时间”而批量禁用服务。很多服务在登录后你感觉不到它的存在但实际承担着网络、密钥、硬件初始化等重要职责。最稳妥的方式是保留服务只调整超时时间。回到这次 Rocky 10 云镜像首启慢两分钟的问题。我最大的体会是云环境里的“慢”往往比“完全不可用”更难排查。它不会给你一个醒目的红字而是默默地在某个服务上等待直到超时后才继续往下走。网络只是显眼的表象真正的卡点可能藏在 systemd 依赖链、cloud-init 的重试逻辑、SSH 密钥生成甚至 udev 的设备等待里。下次如果你也遇到类似情况建议先别急着改网络配置文件。执行一条systemd-analyze blame把启动时间线拉出来看到底是谁在拖后腿。多数时候答案会比你想的更清晰。