ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从按下电源键到systemd接管:Linux引导、initramfs与服务故障排查全解析

从按下电源键到systemd接管:Linux引导、initramfs与服务故障排查全解析 有一次半夜接到电话机房一台跑业务的虚拟机重启之后起不来了远程打开控制台输出卡在一个dracut提示符上报错信息是找不到根文件系统。排查到最后原因说出来其实不稀奇磁盘扩容之后initramfs里的LVM元数据没有刷新内核启动时根本没定位到卷组。这个故障让我重新把Linux引导过程从头到尾捋了一遍也顺便把systemd接管之后的服务控制逻辑做了一次系统梳理。很多朋友平时敲systemctl很溜可真要问到系统从按下电源键到出现登录提示符中间到底经历了什么未必能一口气说清楚。这篇文章把我的梳理过程和踩坑记录整理出来希望能帮你把这条链路彻底理清下次系统起不来的时候不用再靠瞎猜来碰运气。1. 从按下电源键到登录提示符谁把接力棒传给了谁很多教材喜欢按BIOS、引导加载器、内核、init、登录这样的顺序讲这个顺序本身没错但容易让人误以为每个环节是孤立的。实际上这条链跟接力赛一样每一棒都有自己的规则前一个环节彻底完成后下一个环节才进场。搞懂每个环节的边界排查时才能一眼看出现在是哪一棒出了问题。1.1 固件阶段BIOS与UEFI出场Linux一个字节都没参与机器通电后最先干活的是固件。传统BIOS会做POST自检在内存、键盘、磁盘控制器等硬件上转一圈然后按照启动顺序硬盘、U盘、光驱、网络去找可引导设备。如果走的是老式Legacy/MBR路径BIOS会读取硬盘第一个扇区的前446字节引导代码也就是MBR里的bootloader。而UEFI时代是完全不同的路径固件直接扫描FAT格式的ESP分区EFI System Partition在里面找.efi引导文件。GRUB2装好之后对应的EFI文件通常是shimx64.efi或grubx64.efiUEFI还多了一道Secure Boot的门签名不符的引导文件会被直接拒之门外。怎么判断你的机器走的是哪条路很简单执行ls /sys/firmware/efi如果目录存在就是UEFI不存在基本可以断定是Legacy BIOS。再用fdisk -l看分区表GPT对应UEFI/GPT组合看到dos分区表就是传统MBR。搞不清楚自己属于哪种后面改grub配置时很容易改完重启还是不对因为固件压根没走你预期的那套加载路径。这个阶段Linux一个字节都还没参与。所以别在为什么按电源键后黑屏几秒这件事上怀疑操作系统。固件阶段出问题的典型表现是屏幕上什么启动信息都没有或者直接提示No bootable device found。这时候该检查的是启动顺序、硬盘物理状态、ESP分区是否完整盯着Linux日志看反而浪费时间。1.2 GRUB剧本内核、initramfs与启动参数如何被装进内存固件找到GRUB之后GRUB先加载自己的核心镜像读取/boot/grub2/grub.cfgDebian系是/boot/grub/grub.cfg解析里面记录的内核文件路径、initramfs路径和启动参数。grub.cfg里最核心的是menuentry块里面通常有类似这样的内容menuentry Linux { load_video linux /vmlinuz-5.10.0-8-amd64 root/dev/mapper/vg-root ro quiet initrd /initrd.img-5.10.0-8-amd64 }linux那一行告诉GRUB去加载哪个内核文件并传递启动参数initrd那一行告诉GRUB把initramfs镜像一起装进内存。GRUB本身做的事情就这么多把这两个文件读进内存设置好参数跳转到内核入口。它不负责初始化磁盘驱动也不负责挂载根文件系统所以这里的文件路径必须是GRUB自己能认出来的文件系统。很多新手在加装新硬盘、调整分区之后启动失败本质就是GRUB找得到自己的配置文件却找不到后续的内核和initramfs文件或者内核参数里的root指向不对。这种情况下GRUB菜单通常一闪而过就进紧急Shell。解决思路是先在GRUB菜单按e临时修改linux那行的root参数确认能进系统后再把修改固化到grub.cfg别上来就重装GRUB。1.3 内核接管之后从压缩内核到第一个用户态进程GRUB把棒子交给内核后内核先做自解压把zImage/bzImage展开到内存。随后它开始初始化CPU、内存管理、中断、设备驱动等底层子系统这些做完之后才尝试挂载根文件系统。但这里有个先有鸡还是先有蛋的问题内核自身内置的驱动很少而真正的根文件系统可能挂在SCSI磁盘、NVMe盘、LVM逻辑卷、RAID阵列或者加密盘上读取这些介质需要对应驱动驱动又恰好放在根文件系统里不挂根就加载不了驱动。解决这个矛盾的关键就是initramfs。内核先把自己带着的initramfs镜像展开成临时内存根文件系统在这个临时环境里加载必要驱动、激活LVM卷组、挂载真正的根分区然后把控制权交给PID 1。从这一刻起系统从内核态切到用户态Linux的角色从管理硬件的内核变成组织进程的操作系统接下来就轮到systemd登场了。2. initramfs不是可选配件它是破解鸡生蛋问题的关键既然第一条链路里最容易被忽略、又最容易出问题的环节是initramfs我专门把它单独拎出来讲清楚。很多人只在GRUB菜单里见过它的文件名却从来没想过这个镜像里面到底装了什么以及为什么少了它系统就起不来。2.1 内核为什么要一个临时车间来挂载真正的根目录用生活里的例子类比你家小区门禁系统需要刷卡才能进可卡在物业手里物业却在外面进不了门。解决办法是物业先把钥匙从门缝递进来门的实在钥匙开大家里就都能进去了。initramfs扮演的就是这个从门缝递钥匙的临时身份它不做完整的用户态工作只负责打通内核到真实根文件系统这一段路。顺便说个容易混淆的概念initrd和initramfs不是一回事。早期发行版使用的initrd是一个块设备镜像内核要把镜像当成一个块设备来读取现在的initramfs本质是cpio格式的内存文件系统内核直接展开到内存里就能用速度更快、更灵活。你在/boot目录下看到的initrd.img-xxx文件很多其实已经是initramfs机制的产物只是文件名沿用了initrd的叫法。内核启动阶段initramfs里需要预置哪些工具取决于根文件系统用什么方式组织LVM根分区就需要lvm命令、udev规则和dm模块软RAID根分区需要mdadm;加密根分区需要cryptsetup。这些内容在安装系统时会被自动打包进initramfs但如果后续你把磁盘从普通分区改成LVM或者调整了卷组结构旧的initramfs里没刷进新的信息重启时就会翻车——这正好对应文章开头那个故障。2.2 用lsinitrd和dracut把initramfs拆开看不要把这个东西当黑盒。你可以直接拆开自己的initramfs看看里面有什么。CentOS/RHEL系执行lsinitrd | lessDebian/Ubuntu系执行lsinitramfs /boot/initrd.img-xxx里面内容会超出你的预料udev及一堆规则、modprobe、lvm命令、mdadm、cryptsetup还有大量内核模块。比如执行lsinitrd | grep lvm通常能看到lvm相关的可执行文件和模块这就是LVM根分区能开机自挂的原因。当根分区调整、换硬件、更换文件系统类型、或者LVM元数据变化之后里面的信息就可能过期这时候重建initramfs几乎成了标准动作。CentOS系命令dracut -f /boot/initramfs-$(uname -r).img $(uname -r)Debian系命令update-initramfs -u -k all重建过程会重新解析当前系统的fstab、硬件信息和LVM状态把新配置重新打包进镜像。我那个半夜故障最后就是chroot进系统跑了一遍dracut -f才解决。记住动手过磁盘布局之后重建initramfs是成本最低、见效最快的预防手段。2.3 内核启动参数GRUB传给内核的交代事项除了initramfs本身内核启动参数也很关键。执行cat /proc/cmdline你现在这台机器启动时用的参数一目了然。典型输出长这样BOOT_IMAGE/vmlinuz-5.10.0-8-amd64 root/dev/mapper/vg-root ro quiet逐个拆解root指定根文件系统位置可以用设备路径、LABEL或UUID生产环境更推荐写UUID因为设备名可能变化ro表示先以只读方式挂载根文件系统检查通过后再切rwquiet表示隐藏大部分启动日志平时没问题但排查故障时建议去掉这个参数能看到更详细的输出。LVM根分区通常还有rd.lvm.lvvg-root这类参数告诉initramfs在早期阶段激活哪个逻辑卷。想改默认启动参数别直接编辑grub.cfg而要去改/etc/default/grub里的GRUB_CMDLINE_LINUX然后重新生成引导配置。CentOS系用grub2-mkconfig -o /boot/grub2/grub.cfgDebian系直接update-grub。之所以要绕这一圈是因为grub.cfg在内核升级时会被脚本自动重写只有把改动写在/etc/default/grub里才能长期保留。3. systemd成为PID 1之后服务管理发生了哪些变化引导过程的终点是PID 1而当今主流发行版的PID 1几乎总是systemd。从这一棒开始管硬件的阶段结束管服务的阶段开启。很多运维老手经历过SysV init时代再看systemd会觉得变化翻天覆地但对新人来说可能从一开始接触的就是systemd反而不知道以前的世界是什么样。3.1 为什么SysV init会被淘汰串行启动和一堆if/else脚本在systemd之前主流发行版用的是System V init。开机时init读取/etc/inittab按运行级别0-6依次执行/etc/rc.d/rcX.d/目录下的脚本脚本通过S01、S02这类前缀强制排出先后顺序。这种设计在服务数量少的年代没问题但等到Web应用、数据库、容器、网络依赖变得复杂之后串行启动的低效就被放大了。更致命的是服务脚本本身的脆弱性。每个SysV脚本本质是个shell脚本里面要塞start、stop、restart、status四个分支还要手工处理各种依赖。如果你想让B服务在A服务之后启动得自己在脚本里写等A起来再继续写起来繁琐出错还难排查。systemd把这个模型彻底反转服务只负责声明自己是什么、怎么跑起来、依赖谁剩下排序、并行启动、崩溃重启这类通用逻辑全部交给systemd完成。这就是它快速上位的根本原因。3.2 Unit文件三件套[Unit]、[Service]、[Install]systemd把所有可管理对象抽象成Unit日常打交道最多的就是service类型。一个最小可用的service文件长这样假设我们有个简单的Python Web服务[Unit] DescriptionMy demo web service Afternetwork-online.target Wantsnetwork-online.target [Service] ExecStart/usr/bin/python3 /opt/demo/app.py WorkingDirectory/opt/demo Restarton-failure RestartSec2 Usernobody Groupnogroup [Install] WantedBymulti-user.target[Unit]段描述元信息和排序依赖说明这个服务想在什么条件之后启动[Service]段定义具体怎么跑起来ExecStart是主进程命令行Restart定义异常退出时的重试策略User/Group指定运行身份这条非常重要别让服务无脑以root身份运行[Install]段定义enable时的行为WantedBymulti-user.target意思是执行enable时把自己挂到multi-user.target的wants目录下这样系统进入多用户目标时就会把它拉起来。修改了service文件之后必须执行systemctl daemon-reloadsystemd不会自动感知文件变更。不执行的话就算你restart用的还是旧配置这是新手最容易踩的坑之一。3.3 After、Requires、Wants服务之间真正的关系网排序和依赖是两回事这是整个systemd单元关系里最容易搞混的点我见过太多人在这上面栽跟头。Afterb.target只规定我应该在b之后启动。b启动失败并不会拦住我启动。Requiresb.target是硬依赖意思是我必须有b。b启动失败我也会被取消或者受影响。Wantsb.target是软依赖systemd会尽量尝试启动b但b失败不一定会拖垮我。实际项目里最常用的组合是Afternetwork-online.target加Wantsnetwork-online.target服务希望网络就绪再启动又不希望网络完全不可用时把自己卡死。如果你的服务强依赖数据库通常写Requirespostgresql.service加上Afterpostgresql.service。如果只写After不写Requires数据库没起来你的服务照样会去启动大概率直接连库报错这时候你往日志一看又得回来改配置来回折腾。4. systemctl实用手册服务控制的日常操作服务管理绕不开systemctl但命令本身的记忆难度不大真正的难点在于什么时候该用哪个命令以及一条命令背后到底发生了什么。我自己带人干活时最常纠正的就是一上来就systemctl restart的习惯。4.1 查状态、看日志先搞清服务到底怎么了服务起不来是最高频的运维场景但很多人第一反应是重启试试。正确的第一步永远是systemctl status它一次性告诉你服务处于什么状态、主PID多少、最近几行日志是什么大部分问题光看这里就能判断个八九不离十。典型输出● nginx.service - A high performance web server Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled) Active: active (running) since Mon 2025-01-06 09:12:33 CST; 3h 5min ago Docs: https://nginx.org/en/docs/ Main PID: 23456 (nginx)常见状态含义active (running)是正在跑active (exited)是正常启动后主进程退出了只要业务逻辑没问题就属于正常inactive是停止状态failed是启动失败systemd已放弃它并记录错误状态。想看更深入的日志用journalctl。最常用的组合journalctl -u nginx -n 50只看最近50行加-f实时跟随加-b看本次开机之后的日志排查开机后某服务为何起不来时-b能帮你过滤掉历史干扰项。如果服务一直起不来先看启动失败时间点前后的日志再去找配置问题别猜是不是被谁杀了。4.2 start/stop/restart/reload千万别把热重载用成重启start、stop、restart没有太多歧义真正要留意的是reload。systemctl reload 服务名只是向服务发送重载配置的信号通常是SIGHUP要求它不中断主进程的情况下重新读取配置但前提是这个服务自己支持reload。Nginx、sshd、apache等大多数主流服务都实现了reload有些服务压根没实现执行reload会毫无反应或直接失败。如果服务支持reload改完配置就该用reload而不是restart。改nginx.conf之后执行systemctl reload nginx现网连接一个不断执行restart则会切断所有TCP连接。对跑着长连接或WebSocket的业务来说一个restart可能就是一次线上事故。操作行为适用场景start启动未运行的服务首次启动stop停止运行中的服务需要完全下线restart先stop再start断开所有连接版本升级、配置结构变化reload发送重载信号不中断主进程只改配置文件condrestart仅在服务已运行时才重启开机脚本里的防御性操作4.3 enable、disable、mask、daemon-reload开机自启的正确姿势enable、disable控制的是开机要不要自动启动和当前是否运行无关这是一个非常基础的认知但还是有不少人误以为systemctl enable之后服务马上就在跑了。systemctl enable nginx做的事是在wants目录里创建符号链接这个机制后面会细讲disable则是移除链接。而mask比disable更狠它会把服务单元链接到/dev/null即使你手动systemctl start也无法启动直到unmask为止。生产环境想要彻底禁止某个服务比如防止被其他服务通过依赖关系拉起来mask比disable更可靠。还有一个操作顺序问题修改unit文件后不执行daemon-reload直接restartsystemd用的还是内存里缓存的旧配置。改了文件先把新配置让systemd读进来再reload或restart否则你可能反复改配置-重启-没生效白折腾半天。5. target与开机启动链路enable之后你的服务为什么能活过来前面提到enable就是创建符号链接但符号链接是创建在哪里为什么创建在那里就能实现开机自启这就要把target这个概念彻底搞清楚。一旦明白了你的服务管理思路会清晰很多。5.1 运行级别3和图形界面5在systemd里叫什么SysV时代用数字表示运行级别systemd把数字映射成了target单元。对应关系如下SysV运行级别systemd target0poweroff.target1rescue.target2、3、4multi-user.target5graphical.target6reboot.target2/3/4都映射到multi-user.target因为systemd认为多用户字符界面是一个能力集合不需要再分成三个级别。查看当前默认target用systemctl get-default修改默认启动级别用systemctl set-default multi-user.target。想把图形界面改成命令行启动set-default到multi-user.target就够了和以前改inittab默认级别的作用一样。5.2 systemctl enable靠的是符号链接带你看看.wants目录执行systemctl enable nginx之后去/etc/systemd/system/multi-user.target.wants/目录下看一眼你会发现多了一个符号链接/etc/systemd/system/multi-user.target.wants/nginx.service - /usr/lib/systemd/system/nginx.service这就是enable的本质朴素到让人意外。系统启动进入multi-user.target时systemd会遍历这个target的wants目录启动里面所有链接指向的单元。所以要理解开机自启链路你就得看target之间的依赖关系。前面说过[Install]段里可以写WantedBymulti-user.target也可以写别的target比如写WantedBymulti-user.target就代表进入多用户模式时启动。想查看某个target启动时会拉起哪些单元用systemctl list-dependencies multi-user.target它会递归展开一大堆依赖。当你怀疑某个服务enable了却开机没起多半就是它挂到了一个不会被默认target拉起的target下面或者依赖关系写错导致排序混乱。检查思路用systemctl is-enabled 服务名确认enable状态再用systemctl list-dependencies 默认target对照链路基本能定位问题。5.3 临时切换启动级别rescue.target、emergency.target与isolate开机进不了正常系统时我们经常要进救援模式。rescue.target类似SysV的单用户模式会挂载基本文件系统并启动基础服务真实根文件系统挂载成功后可用emergency.target更底层几乎不挂载任何业务分区适合在根文件系统挂载失败时做手动作业。进入方式可以不用改任何持久配置重启后到GRUB菜单按e在linux那一行末尾临时追加systemd.unitrescue.target或systemd.unitemergency.target相当于告诉内核这次的PID 1走哪个target只在当前启动有效。平时也可以用systemctl isolate rescue.target切到救援模式处理完再切回multi-user.target。但isolate操作要小心它会根据目标target关闭所有不在依赖链里的服务。如果你在跑着生产服务的机器上随手执行systemctl isolate multi-user.target图形界面、桌面相关服务会被全部停掉数据库被误伤也不是没可能。所以isolate只建议在明确的维护窗口使用。6. 引导失败与服务故障排查实录我踩过的坑你大概率也会踩讲完机制最后落地到实战。下面这几个故障场景要么是我自己经历过的要么是在帮别人排查时见过的都有完整的链路和方法你照着这个思路走能少绕不少弯路。6.1 卡在dracut紧急模式一次磁盘扩容引发的根目录失踪回到开头那个事故。症状是虚拟机重启后屏幕停在dracut:/#提示符前面有几行timeout waiting for device dev-mapper-vg\x2droot.device之类的日志。看到dracut提示符说明内核和initramfs都已成功加载问题出在挂载根目录这一步。我的排查链路是这样的在dracut提示符下先执行ls /dev/mapper/发现里面没有vg-root逻辑卷设备根本没出现。执行vgscan结果报找不到卷组说明LVM元数据在当前环境里没被识别。用blkid看物理分区分区都还在但PV的UUID信息变得和扩容前不一样。在临时Shell里执行vgchange -ay尝试激活卷组成功后手动mount /dev/mapper/vg-root /sysroot能挂上说明根文件系统数据本身没坏。关键步骤重启进入能用的系统或live环境挂载根分区后chroot进去。在chroot环境里执行dracut -f重建initramfs把新的LVM元数据刷进镜像。执行chroot前要先把虚拟文件系统挂好否则很多命令会异常mount /dev/mapper/vg-root /mnt mount /dev/sda1 /mnt/boot mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys mount --bind /dev /mnt/dev chroot /mnt dracut -f重建之后重启卷组正确激活系统恢复正常。这个案例的核心教训动过磁盘布局、分区表、LVM卷组后一定要把重建initramfs当作标配动作而不是等到重启开不了机才想起来。6.2 启动中途掉进emergency mode是文件系统问题还是服务问题另一种很常见的场景是启动到一半不进系统直接进入emergency mode屏幕提示Welcome to emergency mode。这时候别急着把所有锅甩给服务更常见的原因是文件系统挂载失败大半根源在/etc/fstab里的UUID写错、设备路径不对、或者某个需要挂载的分区设备找不着了。我的处理流程在emergency shell里先执行journalctl -xb看本次启动日志里failed的挂载单元和具体报错。执行mount -a测一遍fstab里的所有挂载项看到哪一行报错问题基本就在哪一行。用blkid对照真实UUID如果是UUID写错直接修正fstab。确认是配置错误注释掉对应行修复后续重启。如果确认文件系统损坏那才需要fsck。但别对正在挂载的根分区无脑fsck先mount -o remount,rw /再对非根分区执行fsck。这里有个容易忽略的点如果fstab里写了依赖网络的挂载比如NFS网络还没就绪时挂载会一直卡到超时拖慢整个启动过程最后可能被拉进emergency mode。生产环境建议给这类网络挂载加_netdev挂载选项并避免设置默认auto让它等到网络真正可用后再挂。6.3 服务enable了却不在开机启动项里一个纠结很久的nginx案例有一次我把nginx加入了开机自启systemctl is-enabled nginx也返回enabled结果重启后服务就是没起来登录一看状态是inactive。查了很久最后在journal里看到nginx试图绑定的端口443在启动时还没被分配或者说它启动太早依赖的网络还没就绪启动尝试失败后因为没有配Restart策略直接放弃了。修复方法是在nginx的service文件里把网络依赖从Afternetwork.target改成Afternetwork-online.target并加上Wantsnetwork-online.target。这里就涉及到前面讲的network细节network.target只表示网络管理单元已被激活不代表IP已经配好network-online.target才表示网络真正可用。同理依赖外部数据库、NFS、容器网络的服务都要想想自己的依赖到底需要等到哪一步。这个案例的通用价值在于enable只是给了服务一张开机启动的入场券服务能不能活下来还要看依赖编排和Restart策略。给重要服务配上Restarton-failure和RestartSec2很多瞬时失败会自动重试过去不会一挂到底。6.4 用systemd-analyze抓出拖慢开机的元凶如果系统开机时间越来越长用systemd自带的命令可以快速定位。systemd-analyze systemd-analyze blame systemd-analyze critical-chainsystemd-analyze输出内核态和用户态分别耗时多少blame按耗时从高到低排列每个单元的启动时间哪个服务拖了几十秒一目了然critical-chain则显示关键链路里每个环节的先后和耗时看卡在哪一步非常直观。我见过最多的两个拖慢启动的原因一是网络挂载没加_netdev开机时傻等网络超时二是某个服务启动失败后systemd在反复重试和等待直接拖慢后续依赖。这两种情况用blame一眼就能找出来然后针对性修复就好。最后分享一个我多次踩坑后养成的检查习惯遇到开机异常先别急着动配置用journalctl -xb、systemd-analyze blame这类只读命令把现场信息留下来卡在dracut提示符时第一件事是blkid看盘符而不是删grub配置对服务配置做了修改宁多敲一次systemctl daemon-reload也不要带着旧内存配置去重启。引导链路和服务控制说到底就是先看清责任链再动手检修的问题。这套排查思路在CentOS、Ubuntu、Debian以及各类基于systemd的发行版上都通用希望这篇整理能让你少走我走过的弯路。
返回列表