浅谈Linux服务管理器Systemd
近年来,Linux系统的init进程经历了两次重大演进,传统的sysvinit已逐渐淡出了历史的舞台,新秀Upstart和Systemd陆续走上了舞台,鉴于现在越来越多的Linux发行版均采纳了systemd,并且对于软件开发人员和系统管理员经常用到,就抽时间学习了下systemd的基本原理及使用。仅仅了解systemd的一个表面和大家分享下:)
Systemd介绍
Systemd 是Linux系统中最新的初始化系统(init), 它的设计目的就是提高系统的启动速度。Systemd和Ubuntu的UpStart是竞争对手,但是现在已经被systemd干掉了,从ubuntu 15.04版本起已经切换到了systemd。
这样我们的Systemd,在Linux系统就不需要init
了。Systemd 取代了initd
,成为系统的第一个进程(PID=1),其它进程都是它的子进程。
Systemd特性
Systemd系统很复杂,Systemd的架构图:

Systemd的主要特性:
- 支持并行化任务(更快的启动速度)
- 采用socket式与D-bus总线式激活服务
- 按需启动守护进程(daemon)
- 利用Linux的cgroups监控进程
- 支持快照和系统恢复
- 维护挂载点和自动挂载点
- 各服务间基于依赖关系进行精密控制
下面挑几个特性介绍下。
更快的启动速度
systemd提供了比UpStart更激进的并行启动能力,采用了socket/D-Bus activation等技术服务。使得系统的启动速度更快。为了提高这方便的性能,Systemd主要做了两件事:
- 尽可能启动更少的进程
- 尽可能将更多的进程并行启动
下面的图演示了Systemd相对于Upstart和SysVInit在并发启动速度方面的改进:

假设有6个不同的启动项目,如job1,job2等等。
在SysVInit中,每个项目都是一个独立的脚本,他们由SysVInit顺序地,串行的调用执行,因此总的执行时间为T1+T2+T3+T4+T5+T6。
在Upstart能过并行的执行任务,但是总的启动时间减少为T1+T2+T3。虽然这样的启动速度比起SysVinit有很大的改进,但是对于有依赖的服务还是必须有先后的启动顺序。这样还是不能满足官大使用的欲望,systemd可以使所有的任务都同时的并发执行,总的启动时间进一步的降低为T1。
按需启动守护进程(daemon)
在SysVinit的时代,在系统初始化的时候会将所有的后台系统全部启动,这样的缺点是:
- 启动时间长
- 造成资源的浪费
Systemd则可以提供按需启动的能力,只有在某个服务被真正请求的时候才启动它。当该服务结束,Systemd可以关闭它,等待下次需要时再次启动它。
支持快照和系统恢复
人们无法准确地知道系统当前运行了哪些服务。Systemd 快照提供了一种将当前系统运行状态保存并恢复的能力。
比如系统当前正运行服务 A 和 B,可以用 systemd 命令行对当前系统运行状况创建快照。然后将进程 A 停止,或者做其他的任意的对系统的改变,比如启动新的进程 C。在这些改变之后,运行 systemd 的快照恢复命令,就可立即将系统恢复到快照时刻的状态,即只有服务 A,B 在运行。一个可能的应用场景是调试:比如服务器出现一些异常,为了调试用户将当前状态保存为快照,然后可以进行任意的操作,比如停止服务等等。等调试结束,恢复快照即可。
这个快照功能目前在 Systemd 中并不完善,似乎开发人员也没有特别关注它,因此有报告指出它还存在一些使用上的问题,使用时尚需慎重。
下面的内容是我们日常比较关注的。怎么使用systemd.
Unit
Systemd可以管理所有的系统资源,不同的资源被称为Unit
。
Unit一共12种:
- Service unit:系统服务
- Target unit:多个 Unit 构成的一个组
- Device Unit:硬件设备
- Mount Unit:文件系统的挂载点
- Automount Unit:自动挂载点
- Path Unit:文件或路径
- Scope Unit:不是由 Systemd 启动的外部进程
- Slice Unit:进程组
- Snapshot Unit:Systemd 快照,可以切回某个快照
- Socket Unit:进程间通信的 socket
- Swap Unit:swap 文件
- Timer Unit:定时器
Unit 状态
systemctl status
用于查看系统状态或单个Unit状态。
1 | 显示系统状态 |
Unit 管理
对于用户最常用的就是使用systemctl
进行start
或stop
服务。
1 | 立即启动一个服务 |
Unit 依赖关系
Unit之间存在依赖关系,A依赖B,就意味着Systemd在启动A的时候,同时获回去启动B。systemctl list-dependencies
命令列出一个Unit的所有依赖关系。
就像上面的简单例子,启动kube-apiserver.servie服务之前,systemd会去启动etcd.service
1 | $ systemctl list-dependencies kube-apiserver.service |
Unit的配置文件
每一个Unit都有一个配置文件,告诉Systemd怎么启动这个Unit.
Systemd默认从/etc/systemd/system
目录读取配置文件。但是里面存放的大部分是符号链接,指向/etc/lib/systemd/system
目录。正真的配置文件存放在这里。
systemctl enable
命令用于在上面两个目录之间,建立符号链接关系。
1 | systemctl enable kube-apiserver.service |
如果在配置文件中设置了开机启动,systemctl enable
命令相当于激活开机启动。
与之对应的,systemctl disable
命令用于在两个目录之间,撤销符号链接关系,相当于撤销开机启动。
1 | systemctl disable kube-apiserver.service |
配置文件的格式
下面是一个简单的Unit配置文件:

[Unit]
区块通常是配置文件的第一个区块,用来定义 Unit 的元数据,以及配置与其他 Unit 的关系。它的主要字段如下
- Description:简短描述
- Documentation:文档地址
- Requires:当前 Unit 依赖的其他 Unit,如果它们没有运行,当前 Unit 会启动失败
- Wants:与当前 Unit 配合的其他 Unit,如果它们没有运行,当前 Unit 不会启动失败
- BindsTo:与Requires类似,它指定的 Unit 如果退出,会导致当前 Unit 停止运行
- Before:如果该字段指定的 Unit 也要启动,那么必须在当前 Unit 之后启动
- After:如果该字段指定的 Unit 也要启动,那么必须在当前 Unit 之前启动
- Conflicts:这里指定的 Unit 不能与当前 Unit 同时运行
- Condition…:当前 Unit 运行必须满足的条件,否则不会运行
- Assert…:当前 Unit 运行必须满足的条件,否则会报启动失败
[Service]
区块用来 Service 的配置,只有 Service 类型的 Unit 才有这个区块。它的主要字段如下。
- Type:定义启动时的进程行为。它有以下几种值。
- Type=simple:默认值,执行ExecStart指定的命令,启动主进程
- Type=forking:以 fork 方式从父进程创建子进程,创建后父进程会立即退出
- Type=oneshot:一次性进程,Systemd 会等当前服务退出,再继续往下执行
- Type=dbus:当前服务通过D-Bus启动
- Type=notify:当前服务启动完毕,会通知Systemd,再继续往下执行
- Type=idle:若有其他任务执行完毕,当前服务才会运行
- ExecStart:启动当前服务的命令
- ExecStartPre:启动当前服务之前执行的命令
- ExecStartPost:启动当前服务之后执行的命令
- ExecReload:重启当前服务时执行的命令
- ExecStop:停止当前服务时执行的命令
- ExecStopPost:停止当其服务之后执行的命令
RestartSec:自动重启当前服务间隔的秒数
Restart:定义何种情况 Systemd 会自动重启当前服务,可能的值包括always(总是重启)、on-success、on-failure、on-abnormal、on-abort、on-watchdog
- TimeoutSec:定义 Systemd 停止当前服务之前等待的秒数
- Environment:指定环境变量
如果对kubernetes中Container Lifecycle Hooks
中的PostStart
和PreStop
熟悉的话,应该就是借鉴了Systemd中ExecStartPost
和ExecStartPre
的思想。我猜的啊:)
1 | There are two hooks that are exposed to Containers: |
[Install]
通常是配置文件的最后一个区块,用来定义如何启动,以及是否开机启动。它的主要字段如下。
- WantedBy:它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入/etc/systemd/system目录下面以 Target 名 + .wants后缀构成的子目录中
- RequiredBy:它的值是一个或多个 Target,当前 Unit 激活时,符号链接会放入/etc/systemd/system目录下面以 Target 名 + .required后缀构成的子目录中
- Alias:当前 Unit 可用于启动的别名
- Also:当前 Unit 激活(enable)时,会被同时激活的其他 Unit
日志管理
systemd 提供了自己的日志系统(logging system),称为 journal
。使用 systemd 日志,无需额外安装日志服务(syslog)。读取日志的命令:
1 | journalctl |
默认情况下(当 Storage= 在文件 /etc/systemd/journald.conf 中被设置为 auto),日志记录将被写入 /var/log/journal/。该目录是 systemd 软件包的一部分。若被删除,systemd 不会自动创建它,直到下次升级软件包时重建该目录。如果该目录缺失,systemd 会将日志记录写入 /run/systemd/journal。这意味着,系统重启后日志将丢失。
过滤输出
journalctl
可以根据特定字段过滤输出。如果过滤的字段比较多,需要较长时间才能显示出来。
1 | 查看系统本次启动的日志 |
日志大小限制
可以修改配置文件指定最大限制。如限制日志最大 50MiB:
1 | /etc/systemd/journald.conf |
还可以通过配置片段而不是全局配置文件进行设置:
1 | /etc/systemd/journald.conf.d/00-journal-size.conf |