从第一次把nginx装到服务器上,高高兴兴重启了机器,结果发现网站打不开,终端里敲systemctl status nginx,看到一堆红字,那一刻我是真懵圈。后来才明白,服务和守护进程在Linux里不是“装上就能用”这么简单,它有一整套管理体系,从init到systemd,从service命令到systemctl,这套东西搞不定,Linux运维就是空中楼阁。
这篇东西我计划讲清楚一套完整的服务控制链路:守护进程到底是个什么东西,systemd为什么成了现代Linux的事实标准,日常用systemctl怎么把服务管得明明白白,自建一个服务要怎么写单元文件,最后是故障排查的完整思路。适合刚接触Linux没多久、被“服务起不来”折磨过的同学,也适合要准备面试、想把基础补齐的运维新人,内容可以直接对着敲。
1. 先搞懂守护进程这回事
1.1 什么是守护进程,它到底“守护”了什么
中文语境里,“守护进程”这个词听上去有点玄乎,甚至有人误以为它是某种安全程序。其实它对应的英文是daemon,在Unix世界里指的就是“在后台长期运行、不依赖任何终端、不跟用户交互”的进程。
你去跑一个普通的程序,比如在终端敲python main.py,这个进程会挂在当前终端下面,终端一关它就跟着没了。但守护进程不一样,它的生命周期不绑在终端上,而是绑在系统上,只要系统不关机,它就一直活着,比如nginx、mysqld、sshd、docker,这些名字末尾带个“d”的,全是daemon的缩写习惯。
daemon到底是怎么做到“脱离终端”的?这里有个技术上的经典套路。一个程序要变成守护进程,通常要经历fork出子进程、让父进程退出的过程;调用setsid创建一个新的会话,让自己成为会话首进程;再紧接着绕开对控制终端的依赖,把标准输入、标准输出、标准错误全部重定向到/dev/null;还要把工作目录切到根目录或者指定目录,避免占用挂在别处的文件系统。这一套组合拳打下来,这个进程就彻底“自由”了,不跟任何终端绑定,也没有会话关系,系统里就多了一个真正意义上的守护进程。
这套手动套路在今天用得其实不多了,因为systemd直接帮你做了这些事,你只是写个单元文件,告诉它“这个服务用什么命令启动”,systemd已经替你完成了脱离终端、重定向输出这些底层细节。但你得理解底层逻辑,后面写单元文件遇到诡异问题时,就知道根源在哪。
1.2 从SysV到systemd:Linux服务管理的前世今生
现在的Linux发行版,服务管理基本被systemd一统天下了,但我不建议直接跳到systemctl,还是值得花两分钟了解那段历史,因为很多老教程、老服务器、甚至面试题里都会碰到旧指令。
早期Linux用的是SysV init体系,启动流程是串行的。系统的启动级别分为0到6(单用户、多用户、重启、关机这些),每个级别下有对应的启动脚本目录,比如/etc/rc.d/rc3.d目录里放着一堆S01xxx、K01xxx之类的链接,指向/etc/init.d/下的真实脚本。启动系统的时候,它按序号一个个执行这些脚本,启动服务也是靠这些脚本来完成的。这套体系的优点是设计简单、脚本即逻辑,你想改启动方式就改脚本;缺点是串行启动太慢,而且依赖关系写起来非常痛苦,脚本A必须在脚本B之后启动,你得靠命名序号控制,上一套服务要等好久。
后来Ubuntu做过一阵子Upstart,改动了一部分机制,引入了事件驱动,算是个过渡品。再后来,更大规模的替代者登场,就是我们熟悉的systemd。它的核心进步在于:启动是并行的,有依赖关系的服务之间系统会自动排序,而且服务进程全部纳入cgroup统一管理,进程的生命周期、资源限制、日志输出都变得可控了。
现在你打开任何主流的Linux发行版(基于Debian、RHEL、openSUSE、Arch这些系的),基本全是systemd,所以咱们这篇文章也会以systemd为主线展开,SysV命令我点到为止,但你在老服务器上遇到了至少知道它是什么。
1.3 单元文件:systemd眼里服务的“身份证”
systemd里有个核心概念叫Unit,它不仅仅是服务,还有挂载点、设备、套接字、定时器都算Unit。咱们平时最常接触的是.service这一类,它代表一个由systemd管理的服务。每个服务都对应一个单元文件,这个文件定义了服务怎么启动、什么时候启动、失败了怎么办。
单元文件的存放位置主要有两个:发行版自带的软件包一般把单元文件放在/lib/systemd/system或者/usr/lib/systemd/system目录,这些是“出厂配置”;你自己写的、或通过systemctl edit生成的覆盖配置放在/etc/systemd/system目录。要注意的是,/etc/systemd/system的优先级高于/usr/lib/systemd/system,所以你要改一个内置服务的行为,最好的方式不是在原文件里乱改,而是在/etc/systemd/system下做覆盖,这才是规范做法。
来看一个典型的单元文件长什么样,以nginx为例,它大概是这样:
ini复制[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target
[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPI
