10月17, 2017

浅谈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

Systemd的主要特性:

  • 支持并行化任务(更快的启动速度)
  • 采用socket式与D-bus总线式激活服务
  • 按需启动守护进程(daemon)
  • 利用Linux的cgroups监控进程
  • 支持快照和系统恢复
  • 维护挂载点和自动挂载点
  • 各服务间基于依赖关系进行精密控制

下面挑几个特性介绍下。

更快的启动速度

systemd提供了比UpStart更激进的并行启动能力,采用了socket/D-Bus activation等技术服务。使得系统的启动速度更快。为了提高这方便的性能,Systemd主要做了两件事:

  • 尽可能启动更少的进程
  • 尽可能将更多的进程并行启动

下面的图演示了Systemd相对于Upstart和SysVInit在并发启动速度方面的改进:

systemd

假设有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状态。

显示系统状态
$ systemctl status 

显示单个 Unit 的状态
$ systemctl status kube-apiserver
Unit 管理

对于用户最常用的就是使用systemctl进行startstop服务。

立即启动一个服务
$ sudo systemctl start kube-apiserver.service

立即停止一个服务
$ sudo systemctl stop kube-apiserver.service

重启一个服务
$ sudo systemctl restart kube-apiserver.service

杀死一个服务的所有子进程
$ sudo systemctl kill kube-apiserver.service

重新加载一个服务的配置文件
$ sudo systemctl reload kube-apiserver.service

重载所有修改过的配置文件
$ sudo systemctl daemon-reload
Unit 依赖关系

Unit之间存在依赖关系,A依赖B,就意味着Systemd在启动A的时候,同时获回去启动B。 systemctl list-dependencies 命令列出一个Unit的所有依赖关系。

就像上面的简单例子,启动kube-apiserver.servie服务之前,systemd会去启动etcd.service

$ systemctl list-dependencies kube-apiserver.service

Unit的配置文件

每一个Unit都有一个配置文件,告诉Systemd怎么启动这个Unit.

Systemd默认从/etc/systemd/system目录读取配置文件。但是里面存放的大部分是符号链接,指向/etc/lib/systemd/system目录。正真的配置文件存放在这里。

systemctl enable 命令用于在上面两个目录之间,建立符号链接关系。

$ systemctl enable  kube-apiserver.service
等同于
$ sudo ln -s '/usr/lib/systemd/system/kube-apiserver.service' '/etc/systemd/system/multi-user.target.wants/kube-apiserver.service'

如果在配置文件中设置了开机启动,systemctl enable命令相当于激活开机启动。

与之对应的,systemctl disable命令用于在两个目录之间,撤销符号链接关系,相当于撤销开机启动。

$ systemctl disable kube-apiserver.service
配置文件的格式

下面是一个简单的Unit配置文件:

systemd

[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中的PostStartPreStop熟悉的话,应该就是借鉴了Systemd中ExecStartPostExecStartPre的思想。我猜的啊:)

There are two hooks that are exposed to Containers:
PostStart
This hook executes immediately after a container is created. However, there is no guarantee that the hook will execute before the container ENTRYPOINT. No parameters are passed to the handler.
PreStop
This hook is called immediately before a container is terminated. It is blocking, meaning it is synchronous, so it must complete before the call to delete the container can be sent. No parameters are passed to the handler.

[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)。读取日志的命令:

$ journalctl

默认情况下(当 Storage= 在文件 /etc/systemd/journald.conf 中被设置为 auto),日志记录将被写入 /var/log/journal/。该目录是 systemd 软件包的一部分。若被删除,systemd 不会自动创建它,直到下次升级软件包时重建该目录。如果该目录缺失,systemd 会将日志记录写入 /run/systemd/journal。这意味着,系统重启后日志将丢失。

过滤输出

journalctl可以根据特定字段过滤输出。如果过滤的字段比较多,需要较长时间才能显示出来。

查看系统本次启动的日志
$ 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
日志大小限制

可以修改配置文件指定最大限制。如限制日志最大 50MiB:

/etc/systemd/journald.conf
SystemMaxUse=50M

还可以通过配置片段而不是全局配置文件进行设置:

/etc/systemd/journald.conf.d/00-journal-size.conf
[Journal]
SystemMaxUse=50M

本文链接:https://www.opsdev.cn/post/systemd.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。