前言
本文中,你将了解如何使用systemd配置Linux服务在重启或崩溃后自启动。
第一部分包含一般的Linux服务管理概念,像是init守护进程和runlevels。它最终是以systemd的服务管理来实现的。这里你将了解targets,wants,requires以及unit文件。
第二部分提供了逐步的教程来实现一个常见的systemd任务。特别是,你将配置一个MySQL数据库服务器来自动在崩溃或重启后自启动。
前提条件
为了完成本文的内容,你需要:
init是在Linux系统启动以及内核加载到内存之后的第一个进程。除了其他的事项之外,它还决定了用户进程或者系统服务应该如何加载,以何种顺序,以及是否应该自动启动。
随着Linux的发展,init守护进程的行为也在变化。最开始,Linux使用SystemV的init来进行启动,与Unix使用的一致。之后,Linux就实现了Upstart的init守护进程(由Ubuntu实现),现在是systemd的init守护进程(首先Fedora实现)。
大部分现代的Linux版本都逐渐从System V移植到目前的systemd。保留旧的init是为了向后兼容。FreeBSD是Unix的变种,使用一种不同的System V的实现,即BSD init。
在本文中我们将涵盖systemd,因为它是Linux版本中最现代也是最常用的服务管理器。然而,必要时我们也会讨论System V以及Upstart,看看systemd是如何发展至此的。下面是它们的大致介绍:
- System V是最老的init系统,用于
- Debian 6及以前
- Ubuntu 9.04及以前
- CentOS 5及以前
- Upstart在System V之后,用于
- Ubuntu 9.10到Ubuntu 14.10,包含Ubuntu 14.04
- CentOS 6
- systemd是最新的Linux服务管理器,用于
- Debian 7及以后
- Ubuntu 15.04及以后
- CentOS 7及以后
为了理解init守护进程,先看看一个叫runlevel的概念。
Runlevels
一个runlevel代表当前Linux系统的状态。例如,runlevel可以是Linux服务器的关闭状态,单用户模式,重启模式等。每个模式都指导着什么服务可以在该状态中运行。
一些服务可以运行在一个或更多的runlevel而不能在其他的里面运行。runlevel由0到6的数值来表示。下面展示了这些等级都代表了什么:
- runlevel 0:系统关机
- runlevel 1:单用户,救援模式
- runlevel 2, 3, 4:多用户,文本模式,启用网络
- runlevel 5:多用户,启用网络,图形模式
- runlevel 6:系统重启
runlevel 2,3,4根据发布版本不同而不同。例如,一些Linux版本没有实现runlevel 4,而其他的有实现。一些版本在这三个等级之间有着清晰的划分。通常,runlevel 2,3,4意味着Linux启动了多用户,启用网络,文本模式的状态。
当你启用服务自启动,Linux实际上做的就是将它添加到一个runlevel中。例如,在System V中,系统会以一个特殊的runlevel启动;当启动的时候,它会尝试启动所有与该runlevel相关的服务。在systemd中,runlevel变成了target,在服务被指定为自启动的时候,就会被添加到target中。之后将讨论target相关内容。
介绍System V的init守护进程
System V使用inittab文件,之后init方法也保留着进行向后兼容。我们先看看SystemV的启动序列:
- init守护进程从二进制文件/sbin/init中创建
- init守护进程第一个读取的文件是/etc/inittab
- 该文件的入口之一是决定机器以什么runlevel来启动。例如,如果runlevel的值为3,那么linux将会启动为多用户的文本模式,启用网络。(这也是默认的runlevel)
- 下面,init守护进程会进一步读取inittab文件,读取init脚本在该runlevel下需要的东西。
因此,当init守护进程发现init脚本在该runlevel下需要什么的时候,也就是发现了它需要启动什么服务。这些init脚本就是你可以为单独服务配置启动行为的地方。
一个init脚本是在System V中控制特定服务的。它要么是由应用提供商提供,要么由Linux提供(本地服务)。你也可以创建自己的init脚本来设置自定义服务。
当一个进程或者服务(比如MySQL服务器)在System V下启动,它的二进制程序文件被加载到内存中。根据服务的配置方式,该程序可能持续在后台运行(接收客户端请求)。该二进制程序的启动,停止,重新加载都是由服务的init脚本控制的。
在System V中,init脚本就是shell脚本。也被称作rc(run command)脚本。脚本位于/etc/init.d文件夹中。这些脚本是/etc/rc目录的超链接。在/etc文件夹中,有许多rc目录,每一个在名称中都有一个数字。数字代表着不同的runlevel。因此我们就有了/etc/rc0.d,/etc/rc1.d,/etc/rc2.d等等。
为了使服务在崩溃或者系统重启后重启,你通常可以添加一行代码到init脚本:
$ ms:2345:respawn:/bin/sh /usr/bin/service_name
为了开启SystemV服务用于在系统启动时启动,运行下面的命令
$ sudo chkconfig service_name on
如果要禁用,运行该命令
$ sudo service service_name status
介绍Upstart守护进程
随着System V序列化加载任务和服务的时间变得更久,更加复杂,Upstart守护进程可以进行操作系统的更快加载,更优雅地清理崩溃服务,以及系统服务之间的可预测依赖。
Upstart的init比System V的更好,有以下几个方面:
- 它没有处理复杂难懂的shell脚本来加载和管理服务,而是使用简单地配置文件进行,容易理解和修改。
- 服务没有如System V那样序列加载,这样可以减少启动时间。
- 使用灵活的事件系统,来自定义服务如何在不同的状态下处理。
- Upstart有更好的关于崩溃服务重启的方式。
- 不需要保持大量的冗余超链接,所有都指向一个脚本。
为了保证一切简单,Upstart与System V向后兼容。/etc/init.d/rc脚本还是可以运行来管理本地System V服务。主要的区别是一个服务允许关联不同的事件。这种基于事件的架构允许Upstart成为一个灵活的服务管理器。使用Upstart,每个事件可以触发一个处理该事件的shell脚本。这些事件包含:
- 启动中
- 已启动
- 停止中
- 已停止
在这些事件中,一个服务可以有许多状态,像是等待中,预启动,启动中,运行中,预停止,停止中等。Upstart可以也可以为这些每一个状态做出行为,创建一个非常灵活的架构。
在启动中,Upstart将会正常地运行任何System V的init脚本。然后会查看/etc/init文件夹,在每个服务配置文件中执行shell命令。这些文件控制了服务的启动行为。
文件的命名样式为service_name.conf,其中有不同部分的纯文本内容,称作节。每一节描述服务的不同方面以及行为如何。为了使服务在崩溃或重启之后自动启动,你可以在服务配置文件中添加respawn命令,如下的cron服务所示。
...description "regular background program processing daemon"start on runlevel [2345]stop on runlevel [!2345]expect fork**respawn**exec cron
介绍systemd守护进程
最新的Linux的init守护进程是systemd。事实上,它不仅仅是一个init守护进程了:systemd是一个包含了现代Linux系统地许多组件的框架。
其功能之一就是作为一个Linux的系统和服务管理器。在这方面,systemd控制了服务在崩溃或机器重启后行为如何。
systemd向后兼容System V命令和初始化脚本。也就意味着任何System V服务也能在systemd下运行。这样可以实现是因为大部分Upstart和System V的管理命令已经被修改为可以在systemd下面运行的版本。
systemd配置文件:单元文件
systemd的核心就是单元文件。每个单元文件都代表了特定的系统资源。资源的信息在单元文件中保持跟踪。服务单元文件只是带有声明式语法的简单的文本文件(像是Upstart的.conf文件)。这可以让文件容易理解和修改。
systemd与其他两个init方法的主要不同点是systemd负责服务守护进程的初始化,以及其他类型的资源例如设备操作系统了UI那个,挂载点,串口等。单元文件的命名样式为service_name.unit_type。因此,你会看到这样的文件,dbus.service,sshd.socket,或者home.mount。
文件夹结构
在基于Red Hat的系统(如CentOS)中,单元文件位于两个位置。主要的位置是/lib/systemd/system/。自定义的单元文件或者系统管理员修改的已有的单元文件位于/etc/systemd/system下。
如果一个同样名称的单元文件位于两个路径下,systemd将会使用/etc下面的那个。假设一个服务可以在系统启动时或者其他target/runlevel下启动。这样的话,就会在/etc/systemd/system下的合适位置创建该服务单元文件的符号链接。/etc/systemd/system下的单元文件实际上是/lib/systemd/system的同一文件的符号链接。
systemd的init序列:目标单元Target Units
一类特殊的单元文件类型是目标单元。
一个目标单元文件的文件名的后缀是.target。目标单元不同于其他单元文件,因为它们不代表一个特定的资源。它们代表的是系统在任何时间点的状态。目标单元通过分子并且启动多个单元文件来做到。systemd的target因此可以松散地与System V的runlevel相比,尽管实际上并不相同。
每个target都有一个名称而非数字。例如,我们有multi-user.target而非runlevel 3或者reboot.target而非runlevel 6。但linux服务器使用multi-user.target启动,相当于给了服务器runlevel 2, 3或者4,意味着多用户文本模式带网络。
如何启动服务是不同之处所在。与System V不同,systemd没有序列化启动服务。它可以检查其他服务或资源的存在,以决定以何种加载方式。这可以让并行加载服务称为可能。
另一个目标单元与runlevel之间的不同是System V中,Linux系统只可以存在于一个runlevel中。你可以修改runlevel,但是系统只会存在于新的runlevel中。使用systemd,目标单元可以是包含的,意味着当目标单元激活的时候,可以确保其他的目标单元加载为其部分之一。
例如,一个Linux系统启动了graphical.target的图形界面,也自动确定加载了multi-user.target,并且激活。在System V中,相当于同时启动了runlevel 3和5。
下面的表格将runlevel和target做了对比。
| Runlevel(System V) | Target Units(Systemd) |
|---|---|
| runlevel 0 | poweroff.target |
| runlevel 1 | rescue.target |
| runlevel 2, 3, 4 | multi-user.target |
| runlevel 5 | graphical.target |
| runlevel 6 | reboot.target |
systemd default.target
default.target等同于System V的默认runlevel。
System V默认的runlevel定义在一个叫做inittab的文件中。在systemd中,该文件替换为default.target。默认的目标单元文件位于/etc/systemd/system文件夹下。它是/lib/systemd/system中目标单元文件的符号链接。
当你修改默认的target时,本质上你是在重新创建该符号链接并且修改系统地runlevel。
System V中的inittab文件也指定了Linux要从哪个文件夹执行init脚本:可以是任何一个rcn.d文件夹。在systemd中,默认的目标单元决定了什么资源单元会在启动时加载。
随着单元被激活,它们所有以并行或者以序列的方式进行激活。大哥资源单元如何加载可能依赖于其他资源单元want或者requires的。
systemd依赖:Wants和Requires
systemd的wants和requires控制着systemd如何描述服务守护进程之间的依赖。
如此前所说,Upstart使用配置文件确保了服务的并行加载。在System V中,服务可以以特定的runlevel启动,但是也可以等待另一个服务或者资源可用之后启动。类似地,systemd服务可以加载到一个或多个target中,或者等待其他服务或者资源激活。
在systemd中,一个单元需要另一个单元加载激活来启动。如果需要的单元由于某些原因在第一个单元激活的情况下启动失败,第一个单元也会停止。
这样就确保了系统的稳定性。一个requires特定目录的服务因此也可以等待该目录的挂载点激活再开启。另一方面,一个wants另一个单元的单元不会强制这样的约束。当调用者激活时,如果想要的单元停止,它也不会停止。一个例子就是在图形模式中没必要启动的服务。
示例:理解systemd启动序列
为了理解systemd下的服务启动行为,我们使用了CentOS 8.3 Droplet。我们将尽可能使用使用.target的systemd启动序列以及一长串的依赖。
首先,运行该命令来列出默认的target单元文件。
$ sudo ls -l /etc/systemd/system/default.target
输出如下:
lrwxrwxrwx. 1 root root 37 Dec 4 17:42 /etc/systemd/system/default.target -> /lib/systemd/system/multi-user.target
如你所见,默认的目标实际上是/lib/systemd/system/下的多用户目标文件的符号链接。因此,系统应该启动multi-user.target,类似于System V中的runlevel 3。
multi-user.targets.wants
下一步,运行下面的命令来检查所有multi-user.target文件的wants:
$ sudo ls -l /etc/systemd/system/multi-user.target.wants/*.service
这应该会显示一个符号链接文件的列表,指向实际的位于/usr/lib/systemd/system下面的单元文件。
lrwxrwxrwx. 1 root root 38 Dec 4 17:38 /etc/systemd/system/multi-user.target.wants/auditd.service -> /usr/lib/systemd/system/auditd.servicelrwxrwxrwx. 1 root root 39 Dec 4 17:39 /etc/systemd/system/multi-user.target.wants/chronyd.service -> /usr/lib/systemd/system/chronyd.servicelrwxrwxrwx. 1 root root 37 Dec 4 17:38 /etc/systemd/system/multi-user.target.wants/crond.service -> /usr/lib/systemd/system/crond.servicelrwxrwxrwx. 1 root root 42 Dec 4 17:39 /etc/systemd/system/multi-user.target.wants/irqbalance.service -> /usr/lib/systemd/system/irqbalance.servicelrwxrwxrwx. 1 root root 37 Dec 4 17:41 /etc/systemd/system/multi-user.target.wants/kdump.service -> /usr/lib/systemd/system/kdump.service...
除了multi-user.target之外,还有不同类型的目标,例如system-update.target或者basic.target。为了查看multi-user.target依赖什么目标,运行下面的命令:
$ sudo systemctl show --property "Requires" multi-user.target | fmt -10
输出如下:
Requires=basic.target
basic.target
运行下面的命令查看basic.target需要什么单元。
$ sudo systemctl show --property "Requires" basic.target | fmt -10
运行后可以看到basic.target需要的是sysinit.target
Requires=sysinit.target-.mount
而且它也想要几个目标
$ sudo systemctl show --property "Wants" basic.target | fmt -10
输出如下:
Wants=slices.targetpaths.targettimers.targetmicrocode.servicesockets.targetsysinit.target
递归往下,你可以看到sysinit.target需要其他的目标进行运行
$ sudo systemctl show --property "Requires" sysinit.target | fmt -10
这即将显示none。然而,sysinit.target有其他想要的目标:
$ systemctl show --property "Wants" sysinit.target | fmt -10
输出如下:
Wants=systemd-random-seed.servicedev-mqueue.mountrngd.servicesystemd-modules-load.serviceproc-sys-fs-binfmt_misc.automountlocal-fs.targetsys-fs-fuse-connections.mountsystemd-sysusers.servicesystemd-update-done.servicesystemd-update-utmp.servicesystemd-journal-flush.servicedev-hugepages.mountdracut-shutdown.serviceswap.targetsystemd-udevd.serviceimport-state.servicesys-kernel-debug.mountnis-domainname.servicesystemd-journald.serviceselinux-autorelabel-mark.servicekmod-static-nodes.serviceloadmodules.serviceldconfig.servicecryptsetup.targetsystemd-sysctl.servicesystemd-ask-password-console.pathsystemd-journal-catalog-update.servicesystemd-udev-trigger.servicesystemd-tmpfiles-setup.servicesystemd-hwdb-update.servicesys-kernel-config.mountsystemd-binfmt.servicesystemd-tmpfiles-setup-dev.servicesystemd-machine-id-commit.servicesystemd-firstboot.service
分析systemd单元文件
再进一步,看看一个sshd服务的单元文件
$ sudo vi /etc/systemd/system/multi-user.target.wants/sshd.service
其内容如下:
[Unit]Description=OpenSSH server daemonDocumentation=man:sshd(8) max:sshd_config(5)After=network.target sshd-keygen.targetWants=sshd-keygen.target[Service]Type=notifyEnvironmentFile=-/etc/crypto-policies/back-ends/opensshserver.configEnvironmentFile=-/etc/sysconfig/sshdExecStart=/usr/sbin/sshd -D $OPTIONS $CRYPTO_POLICYExecReload=/bin/kill -HUP $MAINPIDKillMode=processRestart=on-failureRestartSec=42s[Install]WantedBy=multi-user.target
你可以看到服务单元文件是干净的,且容易理解。
第一个重要的部分是[Unit]中的After部分。意思是sshd服务需要在network.target和sshd-keygen.target之后加载。
[Install]部分显示了multi-user.target想要的服务。这意味着multi-user.target将会加载sshd守护进程,但是如果sshd在加载过程中失败也不会关闭或者崩溃。
因为multi-user.target时默认的目标,sshd应该在系统启动时启动。在[Service]部分中,Restart参数是on-failure。这个设置允许sshd守护进程在崩溃或者异常退出之后重启。
使用systemd配置MySQL在系统重启后自启动
在MySQL安装的情况下,检查服务的状态
$ sudo systemctl status mysqld.service
输出显示服务正在运行,但是守护进程禁用了。
mysqld.service - MySQL 8.0 database serverLoaded: loaded (/usr/lib/systemd/system/mysqld.service; disabled; vendor preset: disabled)Active: active (running) since Thu 2020-12-24 23:48:56 UTC; 1h 6min agoProcess: 30423 ExecStartPost=/usr/libexec/mysql-check-upgrade (code=exited, status=0/SUCCESS)Process: 30294 ExecStartPre=/usr/libexec/mysql-prepare-db-dir mysqld.service (code=exited, status=0/SUCCESS)Process: 30270 ExecStartPre=/usr/libexec/mysql-check-socket (code=exited, status=0/SUCCESS)Main PID: 30378 (mysqld)Status: "Server is operational"Tasks: 40 (limit: 4763)...
如果服务启用了,禁用掉它。首先我们想在做修改之前探索禁用掉的行为。
$ sudo systemctl disable mysqld.service
然后,运行该命令来检查MySQL是否是multi-user.target想要的。
$ sudo systemctl show —property “Wants” multi-user.target | fmt -10 | grep mysql
没有返回的内容。现在检查符号链接是否存在。
$ sudo ls -l /etc/systemd/system/multi-user.target.wants/mysql*
会出现一条符号链接文件不存在的消息
ls: cannot access '/etc/systemd/system/multi-user.target.wants/mysql*': No such file or directory
现在,如果你喜欢的话,重启服务器并检查MySQL服务。它是不会运行的。
不管你是否重启服务器,现在重新启用MySQL服务。
$ sudo systemctl enable mysqld.service
这一次,系统将在/etc/systemd/system/multi-user.target.wants/下面创建一个符号链接。
Created symlink /etc/systemd/system/multi-user.target.wants/mysqld.service → /usr/lib/systemd/system/mysqld.service.
再次运行ls命令并确认。
$ sudo ls -l /etc/systemd/system/multi-user.target.wants/mysql*
你可以看到下面的输出
lrwxrwxrwx 1 root root 38 Aug 1 04:43 /etc/systemd/system/multi-user.target.wants/mysqld.service -> /usr/lib/systemd/system/mysqld.service
