systemd是为了替换Linux系统中init进程而提出来的,是为系统的启动和管理提供直接服务的,而从它的名字也可以看出来,作为一个守护进程,systemd就是用来守护整个系统的。在systemd提供的一整套工具中,最常用的就是systemctl命令。
systemd要解决的问题
init进程是Linux用来启动服务的传统进程。一般需要自己配置/etc/init.d的服务,都是使用init进程启动的。
使用init来启动服务不是那么容易的,首先init服务是串行的,不是并行的,不能同时运行多个进程。其次,init进程是通过脚本来启动服务,所以脚本的编写就会变得十分复杂。
systemd成功的解决了init进程的这些问题,虽然systemd引入了一个十分庞大复杂的架构体系。systemd目前在大多数的Linux发行版中都得到了应用,并且取代了init进程成为了系统的1号进程(PID: 1)。我们日常最经常用到的功能就是systemd中的系统管理功能,也就是systemctl命令。
除了systemctl命令以外,systemd还提供了许多其他的命令,用来管理系统的每个方面。例如systemd-analyze可以用来分析系统启动耗时,hostnamectl可以用来查看主机信息。
可以被管理的系统资源
作为系统的1号进程,可以管理系统中的所有资源,systemd将这些资源划分成了不同的Unit,具体可见下表。
| Unit名称 | 对应系统资源 | 文件命名后缀 |
|---|---|---|
| Service Unit | 系统服务 | .service |
| Target Unit | 由多个Unit组成的一个组 | .target |
| Device Unit | 硬件设备 | .device |
| Mount Unit | 文件系统的挂载点 | .mount |
| Automount Unit | 自动挂载点 | .automount |
| Path Unit | 文件或路径 | .path |
| Scope Unit | 不由systemd启动的外部进程 | .scope |
| Slice Unit | 资源控制组 | .slice |
| Snapshot Unit | systemd快照 | |
| Socket Unit | 进程间通信的套接字 | .socket |
| Swap Unit | 交换区文件 | .swap |
| Timer Unit | 任务计划 | .timer |
其实Unit文件在系统中就是ini格式的纯文本文件,其中书写了所有Unit对象的信息。
Unit资源加载路径
systemd会从一组在编译是就已经设定好的路径中加载Unit文件,并且不同的路径的优先级不一样,高优先级目录中的Unit文件会覆盖地优先级目录中的同名文件。鉴于systemd可以以系统实例和用户实例两种模式运行,所以会有两种Unit文件加载顺序。
以系统实例模式(--system)运行时:
| 系统单元目录 | 目录所包含内容 | 优先级 |
|---|---|---|
/etc/systemd/system.control |
通过dbus API创建的永久系统单元 | 0 |
/etc/systemd/system.control |
通过dbus API创建的临时系统单元 | 1 |
/etc/systemd/transient |
动态配置的临时单元(系统与全局用户共用) | 2 |
/etc/systemd/generator.early |
生成的高优先级单元(系统与全局用户共用) | 3 |
/etc/systemd/system |
本地配置的系统单元 | 4 |
/run/systemd/system |
运行时配置的系统单元 | 5 |
/run/systemd/generator |
生成的中优先级系统单元 | 6 |
/usr/local/lib/systemd/system |
本地软件包安装的系统单元 | 7 |
/usr/lib/systemd/system |
发行版软件包安装的系统单元 | 8 |
/run/systemd/generator.late |
生成的低优先级系统单元 | 9 |
当systemd以用户实例模式(--user)运行时,所使用的目录的基本上与以系统实例模式类似,只是system目录一般都换成user目录,系统单元也替换成用户单元,并且会受到环境变量$XDG_CONFIG_HOME和$XDG_RUNTIME_HOME的影响。
一般来说,需要开机不登录就可以运行的程序,都需要存放在系统服务里,即/usr/lib/systemd/system目录里,如果需要用户登录以后才可以运行的程序,可以放在相应的用户实例模式目录中,即/usr/lib/systemd/usr目录里。
所有的优先级描述,数字越小优先级越高。
systemctl命令功能
systemctl命令就是针对这些Unit进行管理。
命令格式
|
|
正如systemctl命令格式中所描述的一样,systemctl命令的功能是通过不同的子命令来实现的。
列举Unit
列举Unit可以采用以下命令格式:
|
|
直接运行这个命令可以列出目前已经存在于内存中的Unit,如果需要继续列出其他的Unit,那么就需要增加pattern了。常用的pattern有以下这些。
--all,所有的Unit,包括没有找到配置文件和启动失败的。--failed,仅列出加载失败的Unit。--type,仅列出指定类型的Unit。这里的type值可以使用以上表格中的Unit名称(不带Unit字样且使用小写)。--state,列出拥有指定状态的Unit。可以使用的状态有:activereloadinginactivefailedactivatingdeactivating
其他相似的列举命令还有:
list-sockets,列举所有内存中的套接字Unit。list-timers,列举所有内存中的定时任务Unit。list-unit-files,列举目前系统中已经安装的Unit文件。is-active, 列举所有已经激活的Unit。is-failed,列举所有加载失败的Unit。
查看Unit状态
查看Unit状态需要使用status命令,格式为:
|
|
这里的pattern则是各个Unit文件的名称了,但是不需要包括文件后缀。例如systemctl status bluetooth。status命令将会列出指定Unit的全部信息。对于Unit的状态是通过Unit名称前面的图标来表现的。
启用和禁用Unit
支持systemd程序在安装的时候,一般都会在/usr/lib/systemd/system目录中添加一个Unit配置文件,这时就可以使用systemctl提供的命令来完成启用和禁用的任务。
systemctl中用于支持启用和禁用Unit的命令是以下两个:
enable,启用Unit配置。disable,禁用Unit配置。
这两个命令在使用的时候均只需要书写Unit配置文件的文件名即可,不需要书写文件名后缀。这两个命令的主要是操作/etc/systemd/system目录中的符号链接。当调用enable命令的时候,systemctl会在/etc/systemd/system目录中创建一个指向/usr/lib/systemd/system中文件的符号链接。而调用disable命令的时候,systemctl会撤销/etc/systemd/system目录中的符号链接。
开机的时候,systemd只会执行
/etc/systemd/system目录中的配置文件。所以如果要修改系统服务的配置,只需要修改这个目录中的文件即可。
控制Unit状态
控制Unit的状态应该是日常使用systemctl命令最频繁的地方了。常用的Unit状态控制命令主要有以下这些:
start,启动Unit。stop,停止Unit。reload,重新加载Unit的配置。restart,重新启动Unit。try-restart,尝试重新启动Unit,如果Unit启动失败,不会报错。reload-or-restart,重新加载Unit的配置,如果Unit不支持reload,那么则以重新启动代替。try-reload-or-restart,尝试重新加载Unit的配置,如果Unit既不能重新加载配置也不能重新启动,那么便什么也不做。kill,强行终止Unit的运行。clean,清理Unit的配置、缓存等一切运行痕迹。freeze,挂起并冻结Unit的所有进程。thraw,解封被冻结的Unit。
编写一个Unit配置文件
要编写一个Unit配置文件,最快的学习方法就是参考系统本身已经存在的配置文件。要查看Unit配置文件,并不需要实地去保存有配置文件的目录中使用编辑器打开,而是可以借助systemctl提供的命令:systemctl cat [Unit]。
Unit配置文件就是一个ini格式的文本文件,其中可以分为三个区块:Unit、Sevice和Install。每个区块都是以下形式的键值对。
|
|
Unit区块
Unit区块通常是配置文件的第一个区块,主要用来定义Unit的元信息和与其他Unit的关系。例如会形成以下这样的配置项。
|
|
以上示例表示这个Unit描述是A Sample Service,依赖于sshd.service运行。在Unit区块里可以用来定义Unit元信息的项目主要有以下这些。
Description,Unit的简短描述。Documentation,Unit的文档所在位置。Required,当前Unit所依赖于的其他Unit,只有在这些Unit启动之后,当前的Unit才会开始启动。如果列在这里被依赖的Unit没有启动,那么这些Unit会先被启动。Wants,与当前Unit配合的其他Unit,如果哪些Unit不存在,当前Unit也不会出现启动失败的情况。Requesite,与Required类似,但是如果列在这里的Unit没有启动,那么当前的Unit会启动失败。BindsTo,当前Unit所要绑定到的其他Unit,会随目标Unit一同启动,也会随之停止。PartOf,与Required类似,但是如果列在这里的Unit发生了停止或者重启事件,那么当前Unit也会跟随做相同的操作。Before,指示当前Unit必须在指定Unit之前启动。After,指示当前Unit必须在指定Unit之后启动。Conflicts,指示当前Unit不能与指定Unit一同运行。OnFailure,指示当当前Unit处于failed状态的时候,需要启动哪些Unit来进行处理。FailureAction,SuccessAction,指示当当前Unit处于相应的状态时,要激活哪些系统或者用户活动,可以取以下值。none,不做任何动作,可在用户模式中使用。reboot,重启系统。reboot-force,强制重启系统。reboot-immediate,强制立刻重启系统。poweroff,关闭系统。poweroff-force,强制隔壁系统。exit,退出程序,可在用户模式中使用。exit-force,强制退出,可在用户模式中使用。
除了以上这些配置项以外,还有一系列的配置项是用来定义Unit的启动条件的,这些配置项通过对一些条件的定义确定了Unit的启动时机。常用的条件配置项都是Condition...前缀格式的。,默认情况下配置项中所列举的条件都是与的关系,但是也可以使用Condition...=|...格式来将配置项变为或的关系,如果在配置值前使用了!,那么这个条件就会成为一个否定条件。
Condition...配置项可以常用的条件后缀主要有以下这些:
Architecture,系统架构,常用值有x86、x86-64、arm、arm-be、arm64等。Virtualization,判断系统是否运行在虚拟化环境中,常用值有qemu、kvm、vmware、microsoft、oracle、docker、podman、rkt等。Host,判断系统的Hostname是否是指定值。KernelVersion,判断系统内核的版本,可以使用<、>等关系比较符。Environment,判断制定的环境变量是否已经设置。Security,判断指定的安全技术已经在系统中被启用,常用的值有selinux、apparmor、audit等。Capability,判断服务管理器是否拥有指定的能力。ACPower,判断系统是否使用的是外接电源。FirstBoot,判断系统是否是第一次启动。PathExists,判断指定的路径是否存在。PathIsDirectory,判断指定的路径是否是一个目录。PathIsSymbolicLink,判断指定路径是否是一个符号链接。PathIsMountPoint,判断指定路径是否是一个挂载点。PathIsReadWrite,判断指定路径是否是可读写的。PathIsEncrypted,判断指定路径是否已被加密。DirectoryNotEmpty,判断指定路径是否非空。FileNotEmpty,判断指定文件是否非空。FileIsExecutable,判断指定文件是可执行的。Memory,判断系统中所安装的内存容量是否满足要求,可以使用<、>等关系比较符。CPUs,判断系统中所安装的CPU数量是否满足要求,可以使用<、>等关系比较符。
如果一个配置项目需要有多个值,那么可以直接用空格分隔书写。
通过对于Unit元信息的配置,可以确定Unit的启动条件,使Unit在启动的时候不至于因为缺少必要的资源而出现启动失败。
Service区块
Service区块是Service Unit专有的配置区块,其中配置的是要启动何种程序以及程序要如何启动、如何停止、如何重启等。常用的配置项有以下这些。
Type,程序以何种方式启动,可以取以下值。simple,普通方式。forking,程序以fork()方式启动。oneshot,程序只执行一次,systemd会等待程序执行结束再继续启动其他服务。dbus,程序会等待DBus信号以后启动。notify,程序启动后会发出通知信号,systemd才会去继续启动其他服务。idle,程序会等待其他任务都执行结束才会开始执行。
ExecStart,要启动的程序及其参数。配置值前可以添加以下符号来实现一些特殊的功能,这些符号中功能不相近的可以同时使用。@,指示将第二个参数映射给argv[0]。-,报错信息将会被压制,只会被记录。:,环境变量不会被应用进去。+,程序会拥有最大权限。!,程序会使用提升的权限执行。
ExecStartPre,ExecStartPost,指示在主程序启动之前或者之后启动其他的程序。ExecReload,指定执行systemctl reload的时候如何激活程序的重载功能,命令中可以使用$MAINPID来引用程序的PID。ExecStop,指定执行systemctl stop时使用何命令来停止程序的运行。ExecStopPost,指定在程序停止之后还需要执行的命令。RestartSec,指定重新启动程序的时间间隔。TimeoutStartSec,指定启动程序之前需要等待的时间。TimeoutStopSec,指定停止程序之前需要等待的时间。Restart,程序的重启策略,可取值有no、on-success、on-failure、on-abnormal、on-watchdog、on-abort和always。OOMPolicy,设定程序出现Out-Of-Memory状态时系统可以采取的策略,可取值有continue、stop、kill。
Install区块
Install区块是被systemctl enable和systemctl diasble命令使用确定Unit要被如何安装和卸载的。这个区块中可以使用的配置项有以下这些。
Alias,定义Unit的别名。Unit的别名可以在其他的Unit定义文件中使用,但是要求一个Unit的别名必须拥有相同的后缀。并且如果一个Unit拥有多个别名,那么在执行systemctl enable命令的时候,将会创建多个符号链接。WantedBy,RequiredBy,会在.wants和.requires命名的Target目录中创建当前Unit的符号链接,使当前Unit可以被指定的Target所包含。Also,设置与当前Unit一同被安装和卸载的Unit。
常见问题
设置的服务在启动的时候报Default-Start contains no runlevels
这种错误在比较旧的Linux系统中很少出现,反而在比较新的Linux系统中更容易出现。一旦出现了这种错误,服务将不会启动。但其实这种错误也十分容易解决,这种错误一般表示服务的启动脚本中没有定义Default-Start所可以使用的runlevel。
要解决这个错误,只需要在服务的启动脚本开头的位置加入以下代码即可。
|
|