systemd介绍与使用详细教程

时间:2018-11-21
本文章向大家介绍systemd介绍与使用详细教程,主要包括用户空间的启动顺序、SystemV、运行级别、Systemd、systemd启动步骤、单元和单元类型、systemd相关指令、电源管理、分析系统状态、单元的管理、单元的依赖列表、systemd配置、Service文件、target文件、systemd日志服务、在systemd中添加单元、systemd 的按需和资源并行启动等等,需要的朋友可以参考一下

1.1. 用户空间的启动顺序

用户的空间的大致启动顺序如下:

  • init
  • 基础底层服务,如udevd(设备管理器),syslogd(日志管理)
  • 网络配置
  • 中高层服务,如cron(定时器)
  • 登录提示符(getty)、GUI、mysql(如果设置开机启动的话)

init是内核启动的第一个用户空间进程,主要负责启动、终止系统中的基础服务进程。

Linux下,init主要有三个实现版本:

  • System V,传统的init
  • Upstart,Ubuntu后期针对sys-v的一个改进实现版本
  • systemd,是一套中央化系统及设置管理程序(init),包括有守护进程、程序库以及应用软件,兼容sys-v。现代大部分桌面版都使用此实现。也是本文主要介绍的一个...emmmm...框架。是的,systemd更像一个服务管理框架。

1.2. SystemV

先说说传统的SystemV,他其实就是利用一系列脚本来启动服务,之后的事就撒手不管了。

SystemV init依赖一个特定的启动顺序每次只能启动执行一个启动任务。

这些都是通过一个核心配置文件tab(/etc/init)和一组启动脚本以及符号链接集执行的,本质上为系统提供了合理的启动顺序,
支持不同的运行级别。

他的好处是依赖关系简单,任务之间泾渭分明的一个一个启动,即使某个基础服务出了错也便于排查。但也正因为如此,他的启动性能很不好。
服务无法并行启动不说,而且只能按照预先规定的顺序启动服务。如果你安装了新的硬件或者新服务,他不提供及时支持的标准方法。

time
图1

我们把用户空间init的服务分别叫做Job AJob B...图1可以看到,在SysV init之下,服务必须一个接一个的顺序启动,前面的服务初始化完毕,后面才可以开始。因此,启动时间就是所有服务启动时间之和。

他的改进版Upstart在此基础上就做了优化——互不相关的服务可以并行启动,这样启动总时间就等于时间消耗最大的一组服务,而不是所有服务之和。systemd在并行启动上采取了比Upstart更加激进的方案

systemd-time
图2

图2是systemd的并行启动方式,他让配置所有的服务同时启动。如果Job Aing依赖Job B怎么办呢?首先两个Job是同时启动的,A如果先启动,就向B发送请求服务,B会先将请求缓存起来,等到B初始化完毕之后,再处理缓存的请求。
相比SysV init,这也带来了不确定性,即你不知道此时到底哪些服务起了,哪些没起,全依赖系统管理

1.2.1. 运行级别

运行级别的概念最早应该也是来自于SysV init.

简单地说,运行级别定义了系统的特定状态,这种状态可以看成一系列服务状态的集合。

不同的发行版有不同的运行级别,但比较公认的如下:

  • 0,关机
  • 1,单用户模式(修复模式),如果你的系统凉凉了,这将是你的救命稻草
  • 6,重启

以我个人的deepin15.7为例,如图

runlevel
default

其中runlevel2/3/4都属于同一个运行等级(multi-user),而系统的默认的运行等级为5——graphical。我们平时所用的桌面环境就是这个等级了。其实,现代大部分采用systemd的发行版都和这个大同小异。

我们使用systemctl cat graphical.target打开graphical.target文件,可以看到下面内容:

[Unit]
Description=Graphical Interface
Documentation=man:systemd.special(7)
Requires=multi-user.target
Wants=display-manager.service
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target display-manager.service
AllowIsolate=yes

其中的Requires=multi-user.target表示,如果想启动graphical.target(即运行等级5)就必须先启动multi-user.target(运行等级3).由此可见,在systemd中,运行等级5就是在等级3基础上,同时启动一个display-manager服务。display-manage顾名思义,肯定是和图像显示有关的咯。

如果你对.target文件,和他的定义语法很迷惑,没有关系,后面还会详细解释。我举这个例子,只是想让你了解systemd是兼容systemV的运行等级概念的。所以,你关于SystemV的认识也是可以继续沿用的

1.3. Systemd

在Linux中以d结尾的,表示这是一个守护程序,systemd就是这个系统的守护程序

相比于之前的版本,systemd最关键的特性是:

  • 延迟启动某些服务和系统功能,等到需要他们的时候才开启
  • 完全并行启动

systemd architecture

systemd 架构图

1.3.1. systemd启动步骤

systemd的特性复杂,下面给出大致的启动步骤,使我们有个总体观:

  1. systemd加载配置信息
  2. 判定启动目标,一般是default.target
  3. 判定启动目标的依赖关系
  4. 激活依赖服务,启动目标
  5. 响应系统消息,激活其他组件

1.3.2. 单元和单元类型

systemd不光负责处理进程和服务,同时还能挂载文件系统、监控网络套接字等等。在systemd中
所有服务和功能都被抽象成一个个单元(Unit),根据功能不同,单元类型也不同。systemd正是通过配置这些单元
来开关、管理服务的。

1.3.2.1. 单元类型

比较常用的几种:

  • 服务单元,传统的守护进程(XXX.service文件表示)
  • 挂载单元,控制文件系统挂载(XXX.mount文件表示)
  • 目标单元,将服务单元、挂载单元等单元组织在一起的单元,一般对应Sys-V的运行等级(XXX.target文件表示)

上面的尤其是服务单元我们会经常打交道,而且必要时也可以自定义服务单元等。比如我们的蓝牙功能就抽象成
blueteeth.service,管理磁盘的udev系统对应systemd-udevd.service文件。如果你安装了mysql,
还可以找到一个mysql.service文件。

使用deepin15.7的过程中,遇到过一个bug,就是在系统长期休眠之后再重启,蓝牙模块莫名其妙的关闭了,进入[设置]面板也
无法找到蓝牙配置选项了。这时执行systemctl restart blueteeth.service重启蓝牙模块,大概率就会修复了

除了以上几种,还有其他类型,比如
socket单元(.socket)、系统设备单元(.device)、交换单元(.swap)、路径单元(.path)、定时单元(.time),
不一而足

1.3.3. systemd相关指令

1.3.3.1. 电源管理

主要涉及开关、系统重启等,如果你是当前唯一用户的话则不需要提权,否则需要root密码

systemctl reboot #重启
systemctl poweroff #关机
systemctl suspend #待机
systemctl hibernate #休眠
systemctl rescue #进入单用户模式

1.3.3.2. 分析系统状态

主要是查看系统中纳入systemd管理的服务的状态

systemctl status #系统状态
systemctl list-units #所有激活单元列表
systemctl --failed #运行失败单元列表

# 列出所有配置文件
$ systemctl list-unit-files

# 列出指定类型的配置文件
$ systemctl list-unit-files --type=service

1.3.3.3. 单元的管理

使用systemd操作单元的激活与关闭

systemctl start <unit> #立即激活单元
systemctl stop <unit> #立即关闭单元
sudo systemctl kill <unit> #前面的stop不好使了,就强行杀死这个单元
systemctl restart <unit> #重启单元
systemctl status <unit> #单元状态,这是和好用的指令,能够看到服务单元的几乎所有信息

systemctl is-enabled <unit> #单元是否配置自动启动
systemctl enable <unit># 配置自动启动单元
systemctl disable <unit>#关闭单元自动启动

systemctl help <unit>#单元帮助手册,一般是服务单元

systemctl daemon-reload <unit>#扫描单元配置文件变动,重新载入

systemctl mask <unit> #禁用单元
systemctl unmask <unit>#取消禁用

下面是我本人计算机上mysql的状态信息:

systemctl status mysql.service
● mysql.service - MySQL Community Server
   Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
   Active: active (running)
  Process: 2666 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid (code=exited, status=0/SUCCESS)
  Process: 2602 ExecStartPre=/usr/share/mysql/mysql-systemd-start pre (code=exited, status=0/SUCCESS)
 Main PID: 2668 (mysqld)
    Tasks: 27 (limit: 4915)
   Memory: 218.1M
   CGroup: /system.slice/mysql.service
           └─2668 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid
  • loaded,单元配置文件地址
  • active:激活状态
  • process:开启服务时执行的指令
  • main Pid:主进程ID
  • memory:占用内存
  • CGroup:systemd通过CGroup控制进程,这里展示该服务的所有子进程

1.3.3.4. 单元的依赖列表

systemctl list-depandencies <xxx.service> #列出xxx.service的依赖单元

在systemd中的单元的依赖关系

1.3.3.5. 其他

一些杂七杂八的指令

systemd-analyze #系统启动时间统计
systemd-analyze blame #查看所有服务启动时间列表,blame就能看出,这是要等一个背锅位
localectl #本地化信息
timedatectl #时区信息
loginctl list-user #列出当前登录用户

systemd的指令非常丰富,可以通过查询文档获取全部指令

1.4. systemd配置

systemd的配置文件主要分布在两个地方:
系统单元目录(全局配置,我的是/lib/systemd/system)和系统配置目录(局部配置,我的是/etc/systemd/system)

你可以通过下面的指令查询配置目录:

pkg-config systemd --variable=systemdsystemunitdir #单元目录
pkg-config systemd --variable=systemdsystemconfdir #配置目录

其实配置目录的很多文件都是指向单元目录的软链接。

单元配置文件就像一个蓝图,定义了一个单元的依赖关系、启动顺序、开启关闭指令或者挂载点等,
systemd就是读取这些信息来管理单元的。

1.4.1. Service文件

在systemd中一个.service就是一个服务类型的配置单元,同时也代表了一个服务功能。

我们使用sysctemctl cat ssh.service来查看ssh.service文件内容,该文件就在/lib/systemd/system下.

注:这个Service只有在你安装openssh-server之后才会有.

[Unit]
Description=OpenBSD Secure Shell server
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
RuntimeDirectory=sshd
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target
Alias=sshd.service

可以看到service文件分为Unit/Service/Install三个区块,我们分开解释

1.4.1.1. [Unit]

主要描述启动顺序与依赖关系

[Unit]
Description=OpenBSD Secure Shell server
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

Description,一段描述Service的信息

After,表示ssh.servicenetwork.target auditd.service单元之后启动。另外还有一个属性Before
表示当前单元在列出的单元之前启动。比如Before=bar.service,说明当前单元在bar.service之前启动。
AfterBefore定义了单元之间启动的顺序

ConditionPathExists,表示在后面的路径存在时返回true,这里使用了!非运算符,应该是取反的意思。
同样还有其他几个路径判断条件——ConditionPathIsDirectoryConditionFileNotEmpty,顾名思义,他们的
意义应该不难猜吧。这些条件必须返回为true,否则该单元不会运行

1.4.1.2. [Service]

这个区块定义如何启动当前服务

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify

EnvironmentFile,指定当前服务环境参数文件,内部使用键值对定义,可以使用$key读取值,比如后面的$SSHD_OPTS

ExecStartPre,定义启动服务前执行的指令

ExecStart,定义启动程序执行的指令

ExecReload,表示重启服务时执行的命令。其他的诸如ExecStop等等,望文生义即可

KillMode,定义 Systemd 如何停止 sshd 服务,process表示当kill sshd服务的时候,仅杀死主进程,子进程还是留着的。
其他的kill模式还有:

  • control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
  • mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号
  • none:没有进程会被杀掉,只是执行服务的 stop 命令

Restart字段,定义了 sshd 退出后,Systemd 的重启方式。on-failure,表示任何意外的失败,就将重启sshd。
另外还有其他重启模式定义:

  • no(默认值):退出后不会重启
  • on-success:只有正常退出时(退出状态码为0),才会重启
  • on-abnormal:只有被信号终止和超时,才会重启
  • on-abort:只有在收到没有捕捉到的信号终止时,才会重启
  • on-watchdog:超时退出,才会重启
  • always:不管是什么退出原因,总是重启

最后一个比较重要的是Type字段,定义启动类型。notify,表示启动结束后会发出通知信号,然后 Systemd 再启动其他服务。
其他的类型如下:

  • simple(默认值):ExecStart字段启动的进程为主进程
  • forking:ExecStart字段将以fork()方式启动,此时父进程将会退出,子进程将成为主进程
  • oneshot:类似于simple,但只执行一次,Systemd 会等它执行完,才启动其他服务
  • dbus:类似于simple,但会等待 D-Bus 信号后启动

1.4.1.3. [Install]

定义如何安装这个配置文件,即怎样做到开机启动

WantedBy字段:表示该服务所在的Target。
Target的含义是服务组,表示一组服务。WantedBy=multi-user.target指的是,sshd 所在的 Target 是multi-user.target。

systemctl enable sshd.service其实就是将sshd服务的链接放在multi-user.target.wants目录下。
同时multi-user.target是系统的默认target,在启动该target的时候,他下面的服务都会开机启动。
这也就是只要挂上multi-user.target就能开机启动的原因

1.4.2. target文件

执行systemctl cat multi-user.target,可得:

[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target
AllowIsolate=yes

target文件只是组织一批服务,因此他没有[service]、[mount]等定义启动或者挂载的区块

Requires,表示强依赖关系,即必须要求basic.target启动,否则multi-user启动失败。
其他的依赖关系如下:

  • Wants,只用于激活依赖,没有强依赖关系,该服务没起来也不影响当前服务
  • Conflicts,冲突关系,有我没他,否则不能运行
  • Requisite,前置依赖,当前单元激活前,必须激活它,否则失败,属于强依赖

Wants是比较重要的依赖关系,他不会将启动错误扩散给其他单元。systemd文档鼓励我们多用Wants关系

AllowIsolate,表示允许使用systemctl isolate命令切换到multi-user.target

1.5. systemd日志服务

systemd 自带日志服务 journald,该日志服务的设计初衷是克服现有的 syslog 服务的缺点。

  • syslog 不安全,消息的内容无法验证
  • 数据没有严格的格式,非常随意

Systemd Journal 用二进制格式保存所有日志信息,用户使用 journalctl 命令来查看日志信息。无需自己编写复杂脆弱的字符串分析处理程序。

常见的指令如下:

# 查看所有日志(默认情况下 ,只保存本次启动的日志)
$ sudo journalctl

# 查看内核日志(不显示应用日志)
$ sudo journalctl -k

# 查看系统本次启动的日志
$ sudo journalctl -b
$ sudo journalctl -b -0

# 查看上一次启动的日志(需更改设置)
$ sudo journalctl -b -1

# 查看指定时间的日志
$ sudo journalctl --since="2012-10-30 18:17:16"
$ sudo journalctl --since "20 min ago"
$ sudo journalctl --since yesterday
$ sudo journalctl --since "2015-01-10" --until "2015-01-11 03:00"
$ sudo journalctl --since 09:00 --until "1 hour ago"

# 显示尾部的最新10行日志
$ sudo journalctl -n

# 显示尾部指定行数的日志
$ sudo journalctl -n 20

# 实时滚动显示最新日志
$ sudo journalctl -f

# 查看指定服务的日志
$ sudo journalctl /usr/lib/systemd/systemd

# 查看指定进程的日志
$ sudo journalctl _PID=1

# 查看某个路径的脚本的日志
$ sudo journalctl /usr/bin/bash

# 查看指定用户的日志
$ sudo journalctl _UID=33 --since today

# 查看某个 Unit 的日志
$ sudo journalctl -u nginx.service
$ sudo journalctl -u nginx.service --since today

# 实时滚动显示某个 Unit 的最新日志
$ sudo journalctl -u nginx.service -f

# 合并显示多个 Unit 的日志
$ journalctl -u nginx.service -u php-fpm.service --since today

# 查看指定优先级(及其以上级别)的日志,共有8级
# 0: emerg
# 1: alert
# 2: crit
# 3: err
# 4: warning
# 5: notice
# 6: info
# 7: debug
$ sudo journalctl -p err -b

# 日志默认分页输出,--no-pager 改为正常的标准输出
$ sudo journalctl --no-pager

# 以 JSON 格式(单行)输出
$ sudo journalctl -b -u nginx.service -o json

# 以 JSON 格式(多行)输出,可读性更好
$ sudo journalctl -b -u nginx.serviceqq
 -o json-pretty

# 显示日志占据的硬盘空间
$ sudo journalctl --disk-usage

# 指定日志文件占据的最大空间
$ sudo journalctl --vacuum-size=1G

# 指定日志文件保存多久
$ sudo journalctl --vacuum-time=1years

1.6. 在systemd中添加单元

关于自定义单元的首要一点建议:不要更改/lib/systemd/system(系统单元目录),他由系统维护。
我们一般在/etc/systemd/system下自定义启动单元。

1.6.1. 写一个小栗子

  • 创建一个名为test1.target的单元
[Unit]
Description=test 1
  • 创建test2.target,依赖与test1
[Unit]
Description=test 2
Wants=test1.target
  • 激活test2.target,test1作为依赖也会被激活

systemctl start test2.target

  • 验证两个是否都被激活

systemctl status test1.target test2.target

注:如果单元内包含[Install]模块,需要在start前enable他.
systemctl enable <unit>

  • 删除单元
systemctl stop <unit> #首先停止单元
systemctl disable <unit> #如果有[Install]模块,则删除连接符号
#最后删除单元文件即可

1.7. systemd 的按需和资源并行启动

这是一个很复杂的概念,最好单独讨论