ARTICLE DETAIL

资讯详情

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

systemd时代守护进程管理:systemctl、单元文件与日志排障实战

systemd时代守护进程管理:systemctl、单元文件与日志排障实战 1. 这一章在讲什么守护进程在 systemd 时代的管理思路RH124 作为红帽的系统管理入门课程前面几章在讲文件系统、用户权限、网络配置到了第 7 章突然切换到控制服务和守护进程很多人一开始会觉得有点跳。但实际上这一章才是从会用 Linux迈向会管 Linux的分水岭。守护进程daemon这个词有 UNIX 经验的老兵都不陌生它的特点就是脱离终端、常驻后台、默默干活——比如你服务器上的 sshd、httpd、chronyd全是守护进程。问题是这些进程怎么启动、怎么随开机自动拉起、怎么在崩溃后自动恢复、怎么查到它到底干没干活不同的时代有不同的答案。在 RHEL 7 之后这个答案统一成了 systemd。RH124 作为红帽官方教材很自然地把 systemd 和 systemctl 作为这一章的主线取代了老教材里那些 init 脚本和 chkconfig 的内容。学完这一章你应该能回答几个问题怎么查看一个服务的运行状态怎么让服务开机自启怎么临时停掉一个服务而不影响下次开机系统从启动到字符界面和图形界面到底切换了什么 target服务跑挂了去哪里找日志说实话这些操作如果只靠死记命令过两天就忘。我在实际带新人和维护服务器的过程中发现真正能拉开差距的是对单元文件unit file的理解。systemd 所有行为都是围绕单元文件来定义的systemctl 只是操作这些单元的外壳工具。所以这篇记录我不会只给你列命令会把命令背后的设计逻辑、单元文件长什么样、排障时该看哪儿都尽量讲清楚学完你再去操作心里会踏实得多。适用读者准备考 RHCSA/RHCE 的学员或者刚从命令行玩家转变成系统管理员的运维新人。如果你已经能熟练管理 systemd这篇还可以帮你查漏补缺尤其后面第 5 部分的排障实录都是我在真实环境里踩过的坑。2. systemctl 命令管理服务的日常操作全解2.1 查看服务状态别只看 running 三个字任何人第一次接触 systemctl几乎都是从这行命令开始的systemctl status sshd输出长这样● sshd.service - OpenSSH server daemon Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled) Active: active (running) since Fri 2024-11-01 09:12:33 CST; 3 days ago Docs: man:sshd(8) man:sshd_config(5) Main PID: 1234 (sshd) Tasks: 26 Memory: 14.6M CGroup: /system.slice/sshd.service很多初学者只看 Active 那一行见到 active (running) 就觉得一切正常。但实际上值得关注的信息非常多。Loaded 那一行里的 enabled 表示开机自启已启用如果这里显示 disabled说明服务虽然现在跑着但重启机器后它就不会自动起来了。Main PID 告诉你主进程号配合ps -p 1234可以快速确认进程归属。CGroup 行则显示这个服务把所有子进程都纳入了自己的控制组这其实是 systemd 对进程隔离的一种管理方式。查看服务状态还有几个高频命令我习惯称它们为状态三连systemctl is-active sshd systemctl is-enabled sshd systemctl is-failed sshd这三个命令输出非常简洁分别只回答现在是不是活的开机会不会自启有没有进入失败状态非常适合在脚本里做条件判断。我写监控脚本时常用systemctl is-active做健康检查而不是用ps aux | grep去碰运气因为后者经常被同名进程干扰。要补充一个查看全体服务的视角systemctl list-units --typeservice --staterunning systemctl list-unit-files --stateenabled第一条展示当前正在运行的 service 单元第二条展示哪些单元被配置成了开机自启。第二条在生产环境里尤其有用排查怎么这台机器一开机就启动了一堆奇怪进程时基本靠它定位。2.2 启停重载start、stop、restart、reload 的区别这组命令平时太常见了但很多人并不清楚 restart 和 reload 的差异甚至不知道有 reload 这个选项。systemctl start httpd systemctl stop httpd systemctl restart httpd systemctl reload httpdstart 和 stop 不用说就是启动和停止。restart 是彻底停止进程再重新拉起适合配置改动较大、或者进程内部状态已经乱掉的情况。reload 则是向进程发送 SIGHUP 信号让进程在不中断服务的情况下重新读取配置文件。对 httpd 和 nginx 这类支持平滑重载的服务来说日常改完配置后应该用 reload 而不是 restart。为什么我强调这一点因为 restart 会断开现有的 TCP 连接线上环境如果有正在进行的请求一次 restart 就直接把它们掐断了。而 reload 能保证服务进程的监听端口不关闭连接不断只是应用新配置。2018 年我见过一位同事在生产库房服务器上改完 nginx 配置习惯性敲了 restart结果秒级中断导致一批正在上传的数据失败场面一度很尴尬。所以说分清这俩不仅是考试考点更是生产事故的避坑点。如果你不确定某个服务是否支持 reload可以看服务的单元文件里有没有定义ExecReload或者在 man 文档里查。大多数现代服务都有保守起见也可以用验证配置的方式先确认配置没问题httpd -t nginx -t确认 syntax ok 之后再 reload更稳妥。2.3 enable 与 disable控制开机自启的底层逻辑enable 和 disable 是这一章里必须扎实掌握的一对命令。很多人以为是启动和取消启动实际它的准确含义是设置开机自启和取消开机自启。所以常见操作是systemctl enable httpd systemctl disable httpd前者会在 /etc/systemd/system 下生成一个指向 /usr/lib/systemd/system/httpd.service 的软链接具体放在 multi-user.target.wants 目录里后者则把这个软链接删掉。理解到这层很多疑问就迎刃而解了enable 一个服务后并不会让它立刻启动它只是告诉 systemd 下次开机进入 multi-user.target 时你要把这个单元带上。如果现场就要它马上跑起来得再执行 systemctl start或者直接用一条命令systemctl enable --now httpd--now 表示 enable 的同时立即启动省得敲两次命令。反过来禁用一个服务最常用的做法systemctl disable --now httpddisable 还有一个很多人不知道的兄弟命令——preset。RH124 课程里会提到它的作用是按照发行版预设的策略批量设置 enable/disable。红帽官方预设文件在 /usr/lib/systemd/system-preset/ 目录下。手动管理少量服务时不太用得上但在批量部署、初始化镜像时非常高效。我做自动化装机后的初始化脚本时就经常用systemctl preset来把一堆服务恢复到发行版默认的启停状态减少人为判断。2.4 mask 与 unmask彻底屏蔽一个服务的“硬手段”在学习启停时有个场景 enable 和 disable 都搞不定A 服务被 B 服务依赖你停掉 AB 一启动又把 A 拉起来了或者你在安全加固时想彻底禁用一个服务禁止任何人包括依赖它的其他单元拉起它。这时候需要的就是 mask。systemctl mask httpd systemctl unmask httpdmask 的原理比 disable 更彻底它把 httpd 的单元文件符号链接到 /dev/nullsystemd 在解析这个单元时发现目标是空设备就会直接认为该服务不存在、不可启动。disable 只是移除了开机自启的软链接但 start 仍然可以手动拉起来。mask 则连手动启动都会失败报错信息类似 Unit httpd.service is masked。什么场景下适合 mask比如服务器上装了 postfix 但又不想让它运行、不想让它开机自启、还想防止别人或某个依赖它的应用不小心把它拉起来直接 mask 一劳永逸。在 RHEL 7 以上systemctl mask在考试里也会出现要能分辨它和 disable 的强弱关系。3. 单元文件解密从看懂到写自己的守护进程配置3.1 单元文件的位置和优先级改哪里才对systemd 里的每个服务、每个 target、每个 socket都是一段文本配置叫单元文件unit file。以 sshd 为例它的配置文件路径是 /usr/lib/systemd/system/sshd.service。这个目录是发行版自带的单元文件的存放地一般不建议直接改——因为软件包更新时可能会覆盖。管理员自建或修改的单元文件应该放在 /etc/systemd/system/ 下。如果你把同名文件放在这个目录它的优先级会高于 /usr/lib/systemd/system 下的版本。除了这两个位置还有一个临时目录 /run/systemd/system它的优先级介于两者之间主要用于运行时临时修改系统重启后即失效。三个位置的优先级从高到低依次是/etc/systemd/system/run/systemd/system/usr/lib/systemd/system这个设计跟 PATH 环境变量的覆盖思想很像基础版本由软件包提供定制版本由管理员维护临时版本用于运行时调整。理解优先级之后一个常见的困惑就解决了为什么我改了 /usr/lib/systemd/system/httpd.service 里的配置却不生效很可能因为 /etc/systemd/system 下已经有一份同名文件把它覆盖了。在 RH124 考试里经常会考到如何让系统使用管理员自定义的单元文件而不是默认的这类场景答案就是放到 /etc/systemd/system 下优先级保证系统会优先加载它。3.2 单元文件结构Unit 段、Service 段、Install 段各管什么打开一个典型的服务单元文件看看结构[Unit] DescriptionOpenSSH server daemon Documentationman:sshd(8) man:sshd_config(5) Wantssshd-keygen.target Afternetwork.target sshd-keygen.target [Service] Typenotify EnvironmentFile-/etc/crypto-policies/back-ends/opensshserver.config ExecStart/usr/sbin/sshd -D $OPTIONS ExecReload/bin/kill -HUP $MAINPID KillModeprocess Restarton-failure RestartSec42s [Install] WantedBymulti-user.target[Unit] 段是所有单元共用的描述这个单元的元信息。Description 就是你在systemctl status里看到的第一行描述。After 和 Wants 控制依赖关系After 表示本单元要在 network.target 之后启动但不强制要求它必须启动Wants 是想要依赖如果被依赖的单元存在就启动它失败也不影响本单元。比 Wants 更强的是 Requires它表示硬依赖被依赖单元启动失败本单元也会被标记为失败。还有 Conflicts用于声明互斥关系比如 A 和 B 配置了 Conflict启动 A 会自动停掉 B。[Service] 段是服务单元的灵魂。Type 字段定义了 systemd 如何判断服务启动成功常见的有 simple、forking、oneshot、notify。这一块值得展开讲一下因为选错 Type 会导致服务状态判断错位问题很隐蔽。simpleExecStart 指定的进程启动就算服务成功了适用于前台运行的进程。forking父进程启动后 fork 出子进程父进程退出systemd 认为服务成功。常见于传统 double-fork 式守护进程比如早期的 sshd、nginx。需要配合 PIDFile 指定 pid 文件systemd 靠它定位主进程。oneshot执行完 ExecStart 配置的命令就算完成常用于一次性任务比如初始化、挂载。notify进程启动完成后主动通知 systemd我准备好了需要进程自身支持 sd_notify 协议。KillMode 控制停止服务时如何杀死进程。默认值是 control-group意思是把这个服务控制组里的所有进程全部杀掉。但有些服务 fork 出去的子进程不希望被连带杀死可以用 process 模式只杀主进程。Restart 字段定义服务崩溃退出后的重启策略常用值有 no、on-failure、on-abnormal、always。生产服务器上的关键服务很多配置是 Restarton-failure配合 RestartSec 设定重启前的等待秒数能有效避免崩溃后疯狂重启的抖动。[Install] 段只跟 enable/disable 有关。WantedBymulti-user.target 表示在 enable 时生成一个软链接放入 /etc/systemd/system/multi-user.target.wants/ 目录。目标 target 允许想要本服务于是开机进入 multi-user.target 时本服务就被带上。如果单元文件里没有 [Install] 段执行 enable 时会报错提示 Unit file has no Install section这意味着它不适合被 enable。3.3 实操自己编写一个守护进程服务单元只看不写很难理解单元文件的要点这里带大家实际写一个例子。假设我在 /usr/local/bin/ 下放了一个脚本 mydaemon.sh它会在后台循环收集系统负载信息写入 /var/log/mydaemon.log#!/bin/bash while true; do echo $(date) $(uptime) /var/log/mydaemon.log sleep 60 done我给它加上可执行权限后写一个单元文件 /etc/systemd/system/mydaemon.service[Unit] DescriptionMy custom daemon for load logging Afternetwork.target [Service] Typesimple ExecStart/usr/local/bin/mydaemon.sh Restarton-failure RestartSec10s [Install] WantedBymulti-user.target写完先做三件事重新加载配置、启用开机自启、启动服务。systemctl daemon-reload systemctl enable --now mydaemon systemctl status mydaemon这里有个非常重要的习惯我强调无数次改完单元文件必须先执行 systemctl daemon-reload否则 systemd 不会感知到你新增或修改了单元文件。初次接触这个机制的人90% 的我明明写了配置却不起作用问题都出在忘了这一步。本质原因很简单systemd 为了性能会缓存已经加载的单元配置不会每次命令实时去重新解析文件。再做一个 forking 类型的练习。假设我有一个名为 myserver 的程序启动后它会 fork 一个子进程常驻后台同时把主进程 PID 写到 /var/run/myserver.pid。单元文件可以这么写[Service] Typeforking PIDFile/var/run/myserver.pid ExecStart/usr/local/bin/myserver ExecStop/bin/kill -TERM $MAINPID Restartalways注意 ExecStop 里的 $MAINPID 变量它自动替换为主进程的 PID配合 KillModeprocess 可以做到只杀主进程不杀子进程当然如果子进程也需要回收就得用默认的 control-group 模式。这一段的配置逻辑RHCSA 考试里经常结合实际问题考尤其 Type 类型的选择可以说是单元文件中价值最高的一门学问。4. target现代“运行级别”如何控制开机状态4.1 从 runlevel 到 target一张映射表让你看懂系统状态在老 Linux 时代系统启动到什么状态用运行级别 runlevel 描述从 0 到 6 一共七个级别0 是关机6 是重启3 是字符界面多用户5 是图形界面。当时管理开机状态靠的是 /etc/inittab 和 rc.local。systemd 时代这套概念被 target 替代了。target 不是一个数字而是一组单元的集合相当于把多个服务编排在一起用一个目标名字来引用。RHEL 7 专门保留了 runlevel 和 target 的映射关系这不是历史包袱而是为了迁移方便。映射表如下runleveltarget 单元含义0poweroff.target关机1rescue.target单用户救援模式2multi-user.target多用户字符界面无图形3multi-user.target多用户字符界面无图形4multi-user.target多用户字符界面无图形5graphical.target多用户图形界面6reboot.target重启可以看到 runlevel 2、3、4 都映射到同一个 multi-user.target这正是 systemd 简化模型的关键思路不再为细微差别制定不同的级别而是做组合。查看和切换默认 target 用的是这两条命令systemctl get-default systemctl set-default graphical.target服务器上通常保持 multi-user.target 就够了省掉图形界面占用的内存和 CPU。切换之后不会立即生效需要重启系统才会进入新的默认 target。这和传统的 init 3 命令不一样set-default 只是改默认配置不改变当前运行目标。4.2 临时切换 targetisolate 的用途与危险当前运行状态用什么 target也能临时切换命令是 isolatesystemctl isolate multi-user.target systemctl isolate graphical.target这条命令会把当前系统切换到指定 target即停止不需要的单元、启动需要的单元。对服务器管理员来说最常见的场景是图形环境桌面上误开了桌面环境想让机器回到纯字符模式省资源直接systemctl isolate multi-user.target桌面相关的服务会被停掉。危险也要说清楚。isolate 是一个破坏性操作它会停掉当前 target 不需要的单元。如果你在远程 SSH 连接时执行systemctl isolate poweroff.target机器会直接关机这比啥都狠。我亲眼见过有人误操作把远程服务器关机的事情。所以这条命令敲之前务必确认你 isolate 到一个对的 target 上。救援模式也有专门入口systemctl rescue systemctl emergencyrescue 相当于传统的单用户模式会挂载所有文件系统但只启动最基础的服务适合修复 root 密码、修复 fstab 错误这类操作。emergency 更激进只挂载根文件系统为只读主要用于修复比较严重的启动故障。这两者都是排障场景的主力技能在 RHCSA 里考忘记 root 密码怎么进单用户重置时就是靠它们来完成的。4.3 理解 target 的依赖关系为什么一个 target 能带动一堆服务target 之所以能把一堆服务编排起来靠的就是 [Unit] 段里的 Wants、Requires 和 After。拿 graphical.target 来说它在自己的单元文件里定义Requiresmulti-user.target意思是图形界面必须先有多用户字符界面作为底座。multi-user.target 又通过 .wants 软链接关联了一堆 enable 过的服务。这就解释了为什么systemctl enable一个服务时默认的 WantedBymulti-user.target 能让它在开机时启动因为开机过程中 systemd 在加载 multi-user.target 时会把 multi-user.target.wants 目录里所有软链接指向的单元一并启动。你可以亲手验证一下ls -l /etc/systemd/system/multi-user.target.wants/里面会有各种服务文件的软链接正是 enable 命令生成的。理解这个机制后遇到为什么服务开机就是没起来的问题可以先到对应 target 的 .wants 目录里看看有没有软链接比在配置文件里翻半天快得多。5. journald 日志系统与真实排障实录5.1 journalctl从服务日志里查出问题真相第 7 章还有一个绕不开的重要内容就是系统日志。systemd 时代统一使用 journald 收集日志配套的查看命令是 journalctl。我平时排查服务问题第一步永远是先看日志再猜原因而不是漫无目的地重启服务。journalctl 的常用姿势journalctl -u httpd只看 httpd 这个单元的日志。如果服务正在出问题加上 -f 实时跟踪输出journalctl -u httpd -f按时间过滤也是高频需求比如昨天上午十点到十一点之间的日志journalctl -u httpd --since 2024-11-01 10:00:00 --until 2024-11-01 11:00:00支持相对时间--since -1 hour表示最近一小时。按日志级别过滤也很实用journalctl -p err只看 error 及其更高级别的消息。把多个条件组合起来用基本能覆盖大部分排查场景journalctl -u sshd -p err --since -30 min这里有一个关键知识点journald 日志默认是存在内存里的/run/log/journal系统重启后内存日志就没了。想让日志持久化到磁盘需要创建 /var/log/journal 目录或修改 /etc/systemd/journald.conf 中的 Storage 参数然后重启 systemd-journald。我在生产环境中基本上都会第一时间配置持久化否则机器一重启崩溃前的日志就人间蒸发了排障无从谈起。配置持久化的方法并不复杂mkdir -p /var/log/journal systemctl restart systemd-journald重启后 journald 会自动开始向 /var/log/journal 写日志。至于日志占用磁盘空间的问题可以在 /etc/systemd/journald.conf 里设置 SystemMaxUse500M 之类的上限避免日志把 /var 分区塞满。5.2 实战排障五类常见故障的识别与处理单元文件改完不生效的问题前面说过这里不再重复。下面整理几个真实环境里常见的服务排障实录每个都值得重点记一下。故障一服务启动失败status 显示 failed现象Active: failed (Result: exit-code)排查流程先看日志journalctl -u myservice -n 50关键看有没有权限拒绝、路径不存在、端口占用之类的关键字。比如 nginx 启动失败日志里报 bind() to 0.0.0.0:80 failed (98: Address already in use)那就是端口被占用了。定位占用进程用ss -lntp | grep :80看到 PID 后要么 kill 掉冲突进程要么改 nginx 的监听端口。故障二明明 start 成功了但几秒后进程消失大概率是进程自行退出后systemd 因为 Restart 策略把它拉了起来。看状态时会发现服务反复启动又失败。排查思路手动在前台运行 ExecStart 对应的程序看输出报什么错。很多守护进程在前台会直接打印错误到 stderr日志里反而不一定有。故障三服务 kill 不掉停了又起来如果 Restartalways 被配置在单元文件里stop 服务时 systemd 会先执行停止动作但因为重启策略是 always它会立刻把服务重新拉起来。这种时候正确的处理顺序是systemctl stop myservice systemctl disable myservice如果 disable 还不行比如它有依赖方就上 masksystemctl mask myservicemask 之后连依赖方都无法拉起它。这个场景在管理自研服务时特别常见因为研发同学往往喜欢在单元文件里写 Restartalways。故障四socket 激活的服务监听在 socket 上但 service 没跑RHEL 7 之后有些服务比如 sshd可以被 socket 激活。系统只监听 socket有连接进来才真正启动 sshd.service。所以你看systemctl status sshd.socket可能是 active (listening)而 sshd.service 反而是 inactive。这不是故障是设计行为。如果希望 sshd 一直常驻需要屏蔽 socket 激活并直接启动 servicesystemctl stop sshd.socket systemctl mask sshd.socket systemctl start sshd.service故障五服务状态显示 active (exited)但它还要继续干活这种常见于 Typeoneshot 的服务执行完就正常退出。只要没有报错active (exited) 就是正常完成任务的状态。很多新手看到 exited 以为服务挂了其实不是。判断标准很简单看是否为预期的 oneshot 型单元以及日志里有没有 error。5.3 systemd-analyze排查启动性能的辅助工具这一节内容教科书里着墨不多但实际管理中很实用。systemd 这套体系自带一个性能分析工具 systemd-analyze能告诉你系统启动花了多少时间、哪些服务最耗时。systemd-analyze time systemd-analyze blametime 显示固件、内核、用户空间的启动耗时blame 按启动耗时从高到低排序所有单元。接手一台响应很慢的服务器时先用 blame 看看有没有某个服务拖了后腿比如启动耗时长到离谱的 NetworkManager-wait-online 之类再决定要不要禁用或调优。这在当前的云服务器场景里特别常用很多云主机的启动慢就是被 wait-online 类服务耽误的。6. 这部分内容还想再多说几句的经验之谈这一章学到这里命令你基本都会了但如果只停留在会命令层面是不够的。我个人的体会是systemd 最值得花时间理解的是它的声明式设计哲学。老系统里管理服务靠的是脚本的顺序执行服务之间的依赖关系都埋在脚本里很难看清全貌。systemd 把一切抽象成单元、target、依赖、socket 这些声明式的配置然后由 init 进程统一调度。你写的不是怎么启动而是这个服务该在什么条件下启动、依赖什么、失败了怎么办剩下的交给系统去执行。这种思路转变才是第 7 章真正的价值。还有一个小技巧分享给即将考试的读者RHCSA 机考环境里系统会大量使用 systemd。考试时遇到服务启动失败千万不要反复 restart先去看日志。系统故意给你设的故障点八成都在日志里给了明示只是很多人一紧张就只顾敲 restart。冷静下来用 journalctl 查一眼比盲目操作高效十倍。最后分享一个我怎么快速上手陌生服务器的小流程接手机器后我会先跑systemctl list-units --typeservice --staterunning看当前跑着什么再跑systemctl list-unit-files --stateenabled看哪些开机自启然后systemctl get-default确认默认 target。三分钟就能大致摸清这台服务器的服务布局。这套流程在排查这台机器怎么多了这么多进程的困惑时几乎每次都能定位到问题源头你也可以试试。
返回列表