--- title: systemctl命令的使用 tags: - Linux - Service - 命令 - systemd - systemctl categories: - - DevOps - 系统管理 keywords: 'Service,命令,systemd,systemctl,daemon,服务,管理' date: 2021-05-15 22:28:36 --- 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进行管理。 ### 命令格式 ```bash systemctl [OPTIONS] COMMAND [UNIT] ``` 正如systemctl命令格式中所描述的一样,systemctl命令的功能是通过不同的子命令来实现的。 ### 列举Unit 列举Unit可以采用以下命令格式: ```bash systemctl list-units [pattern] ``` 直接运行这个命令可以列出目前已经存在于内存中的Unit,如果需要继续列出其他的Unit,那么就需要增加`pattern`了。常用的`pattern`有以下这些。 - `--all`,所有的Unit,包括没有找到配置文件和启动失败的。 - `--failed`,仅列出加载失败的Unit。 - `--type`,仅列出指定类型的Unit。这里的`type`值可以使用以上表格中的Unit名称(不带Unit字样且使用小写)。 - `--state`,列出拥有指定状态的Unit。可以使用的状态有: - `active` - `reloading` - `inactive` - `failed` - `activating` - `deactivating` 其他相似的列举命令还有: - `list-sockets`,列举所有内存中的套接字Unit。 - `list-timers`,列举所有内存中的定时任务Unit。 - `list-unit-files`,列举目前系统中已经安装的Unit文件。 - `is-active`, 列举所有已经激活的Unit。 - `is-failed`,列举所有加载失败的Unit。 ### 查看Unit状态 查看Unit状态需要使用`status`命令,格式为: ```bash systemctl status [pattern|PID] ``` 这里的`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。每个区块都是以下形式的键值对。 ```ini [Section] Directive=value Directive=value ``` ### Unit区块 Unit区块通常是配置文件的第一个区块,主要用来定义Unit的元信息和与其他Unit的关系。例如会形成以下这样的配置项。 ```ini [Unit] Description=A Sample Service Requires=sshd.service ``` 以上示例表示这个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`。 要解决这个错误,只需要在服务的启动脚本开头的位置加入以下代码即可。 ```bash ### BEGIN INIT INFO # Provides: service-name # Required-Start: # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: # Description: ### END INIT INFO ```