Systemd的Unit文件

在 Systemd 的生态圈中(除了 CoreOS 外,目前的主流 Linux 系统,如 Arch、SUSE、Fedora、RedHat/CentOS 也都已经使用了 Systemd,此外 Ubuntu 也将最快于15.04版本启用 Systemd 作为默认的系统管理工具),Unit 文件统一了过去各种不同的系统资源配置格式,例如服务的启/停、定时任务、设备自动挂载、网络配置、设备配置、虚拟内存配置等。而 Systemd 通过不同的通过文件的后缀名来区分这些配置文件,之前我们写的 .service 文件便是其中的一种。

每一个服务以.service结尾,一般会分为3部分:[Unit]、[Service]和[Install],我写的这个服务用于开机运行Node.js项目,简化内容如下:

#[Unit]部分主要是对这个服务的说明,内容包括Description和After,Description
#用于描述服务,After用于描述服务启动依赖
[Unit]
Description=xiyoulibapi
After=network.target remote-fs.target nss-lookup.target
#[Service]部分是服务的关键,是服务的一些具体运行参数的设置,这里Type=forking
#是后台运行的形式,PIDFile为存放PID的文件路径,ExecStart为服务的具体运行命令,
#ExecReload为重启命令,ExecStop为停止命令,PrivateTmp=True表示给服务分配独
#立的临时空间,注意:[Service]部分的启动、重启、停止命令全部要求使用绝对路径,使
#用相对路径则会报错!
[Service]
Type=forking
PIDFile=/node.js/pid
ExecStart=/usr/local/bin/forever start /node.js/xiyoulib/bin/www
ExecReload=/usr/local/bin/forever restart /node.js/xiyoulib/bin/www
ExecStop=/usr/local/bin/forever stop /node.js/xiyoulib/bin/www
PrivateTmp=true
#[Install]部分是服务安装的相关设置,可设置为多用户的
[Install]
WantedBy=multi-user.target

服务脚本按照上面编写完成后,以754的权限保存在/usr/lib/systemd/system目录下,这时就可以利用systemctl进行配置了

下面是 Systemd 所支持的12种 Unit 文件类型。

后缀名

作用

.automount

用于控制自动挂载文件系统。自动挂载即当某一目录被访问时系统自动挂载该目录,这类 unit 取代了传统 Linux 系统的 autofs 相应功能

.device

对应 /dev 目录下设备,主要用于定义设备之间的依赖关系

.mount

定义系统结构层次中的一个挂载点,可以替代过去的 /etc/fstab 配置文件

.path

用于监控指定目录变化,并触发其他 unit 运行

.scope

这类 unit 文件不是用户创建的,而是 Systemd 运行时自己产生的,描述一些系统服务的分组信息

.service

封装守护进程的启动、停止、重启和重载操作,是最常见的一种 unit 类型

.slice

用于描述 cgroup 的一些信息,极少使用到,一般用户就忽略它吧

.snapshot

这种 unit 其实是 systemctl snapshot 命令创建的一个描述 Systemd unit 运行状态的快照

.socket

监控系统或互联网中的 socket 消息,用于实现基于网络数据自动触发服务启动

.swap

定义一个用于做虚拟内存的交换分区

.target

用于对 unit 进行逻辑分组,引导其他 unit 的执行。它替代了 SysV 中运行级别的作用,并提供更灵活的基于特定设备事件的启动方式。例如 multi-user.target 相当于过去的运行级别5,而 bluetooth.target 在有蓝牙设备接入时就会被触发

.timer

封装由system的里面由时间触发的动作, 替代了 crontab 的功能

这些琳琅满目的种类,几乎囊括了系统管理的大部分的日常工作内容,一致的配置格式和操作方法使得即便普通的 Linux 系统使用者和软件开发者也能够很快的上手修改系统的配置,妈妈再也不用担心我们把系统弄挂了。其实这些配置文件类型中,真正经常需要修改的并不多,并且这篇文章只打算对其中最常用的,也是之前一直在写的 .service 类型展开说明。主要出于篇幅考虑,不过,既然格式都统一了,只要将一种配置类型用熟了,其他的配置学习来还不是分分钟的事啦 😄
需要再次强调的是,Unit 文件按照 Systemd 约定,应该被放置在指定的3个系统目录之一。这3个目录是有优先级的,依照下面表格,越靠上的优先级越高,因此在几个目录中有同名文件的时候,只有优先级最高的目录里的那个会被使用。

路径

说明

/etc/systemd/system

系统或用户提供的配置文件

/run/systemd/system

软件运行时生成的配置文件

/usr/lib/systemd/system

系统或第三方软件安装时添加的配置文件

由于这里的最后一个目录在 CoreOS 中是属于系统的只读分区,因此在 CoreOS 中,第三方软件如果安装时可能需要特别处理,将配置的 Unit 文件放到 /run/systemd/system目录中。索性 CoreOS 本来也不推荐直接在系统上安装软件,人家特意把系统分区做成只读这个意思就已经很明确了。一些确实需要安装在 CoreOS 上的软件比如 Deis,它的 .service 服务配置都是放到 /run/systemd/system目录里面的。
Service文件
开门见山,直接来看两个实际的服务配置文件吧。

第一个配置是 CoreOS 系统中 Docker 服务的 Unit 文件,路径是 /usr/lib/systemd/system/docker.service,可以看到其中的内容相当精简易读。

[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.com
After=docker.socket early-docker.target network.target
Requires=docker.socket early-docker.target
[Service]
Environment=TMPDIR=/var/tmp
Environment=DOCKER_OPTS=’–insecure-registry=“0.0.0.0/0”’
EnvironmentFile=-/run/docker_opts.env
LimitNOFILE=1048576
LimitNPROC=1048576
ExecStart=/usr/lib/coreos/dockerd --daemon --host=fd:// $DOCKER_OPTS
[Install]
WantedBy=multi-user.target
第二个配置的写法风格与前一个有所差异,但同样的内容清晰,条理明确。这个配置来自 CoreOS 的一篇文档,作用是启动一个 Apache 服务容器然后将服务的运行信息注册到 Etcd 中。

(注意,这篇文档原文中的示例中似乎有一个错误,在启动 docker 时,ExecStart 中的命令参数 -p 80:80 应当为 -p 8081:80,下面代码已修正)

[Unit]
Description=My Advanced Service
After=etcd.service
After=docker.service
[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill apache1
ExecStartPre=-/usr/bin/docker rm apache1
ExecStartPre=/usr/bin/docker pull coreos/apache
ExecStart=/usr/bin/docker run --name apache1 -p 8081:80 coreos/apache /usr/sbin/apache2ctl -D FOREGROUND
ExecStartPost=/usr/bin/etcdctl set /domains/example.com/10.10.10.123:8081 running
ExecStop=/usr/bin/docker stop apache1
ExecStopPost=/usr/bin/etcdctl rm /domains/example.com/10.10.10.123:8081
[Install]
WantedBy=multi-user.target
仔细观察着两个服务配置,其中有一些很明显的共同点。我们接下来就以这两个 Unit 文件为例,一步步的分析一下 Systemd 服务配置的写法。

Service 的 Unit 文件可以分为3个配置区段,其中 Unit 和 Install 段是所有 Unit 文件通用的,用于配置服务(或其他系统资源)的描述、依赖和随系统启动方式。而 Service 段则是服务类型的 Unit 文件(后缀.service)特有的,用于定义服务的具体管理和操作方法。其他的每种配置文件也都会有一个特有的配置段,这就是几种不同 Unit 配置文件最明显的区别。

来看看每个配置段常用的参数有哪些。

一、Unit 段
Description
一段描述这个 Unit 文件的文字,通常只是简短的一句话。

Documentation
指定服务的文档,可以是一个或多个文档的URL路径。

Requires
依赖的其他 Unit 列表,列在其中的 Unit 模块会在这个服务启动的同时被启动,并且如果其中有任意一个服务启动失败,这个服务也会被终止。

Wants
与 Requires 相似,但只是在被配置的这个 Unit 启动时,触发启动列出的每个 Unit 模块,而不去考虑这些模块启动是否成功。

After
与 Requires 相似,但会在后面列出的所有模块全部启动完成以后,才会启动当前的服务。

Before
与 After 相反,在启动指定的任一个模块之前,都会首先确保当前服务已经运行。

BindsTo
与 Requires 相似,但是一种更强的关联。启动这个服务时会同时启动列出的所有模块,当有模块启动失败时终止当前服务。反之,只要列出的模块全部启动以后,也会自动启动当前服务。并且这些模块中有任意一个出现意外结束或重启,这个服务会跟着终止或重启。

PartOf
这是一个 BindTo 作用的子集,仅在列出的任何模块失败或重启时,终止或重启当前服务,而不会随列出模块的启动而启动。

OnFailure
当这个模块启动失败时,就自动启动列出的每个模块。

Conflicts
与这个模块有冲突的模块,如果列出模块中有已经在运行的,这个服务就不能启动,反之亦然。

上面这些配置中,除了 Description 外,都能够被添加多次。比如前面第一个例子中的After参数在一行中使用空格分隔指定所有值,也可以像第二个例子中那样使用多个After参数,在每行参数中指定一个值。

二、Install 段
这个段中的配置与 Unit 有几分相似,但是这部分配置需要通过 systemctl enable 命令来激活,并且可以通过 systemctl disable 命令禁用。另外这部分配置的目标模块通常是特定启动级别的 .target 文件,用来使得服务在系统启动时自动运行。

WantedBy
和前面的 Wants 作用相似,只是后面列出的不是服务所依赖的模块,而是依赖当前服务的模块。

RequiredBy
和前面的 Requires 作用相似,同样后面列出的不是服务所依赖的模块,而是依赖当前服务的模块。

Also
当这个服务被 enable/disable 时,将自动 enable/disable 后面列出的每个模块。

上面的两个例子中使用的都是 “WantedBy=multi-user.target” 表明当系统以多用户方式(默认的运行级别)启动时,这个服务需要被自动运行。当然还需要 systemctl enable 激活这个服务以后自动运行才会生效。关于 Linux 系统启动时的运行级别,可以参看这篇文章。

三、Service 段
这个段是 .service 文件独有的,也是对于服务配置最重要的部分。这部分的配置选项非常多,主要分为服务生命周期控制和服务上下文配置两个方面,下面是比较常用到的一些参数。

服务生命周期控制相关的参数:

Type
服务的类型,常用的有 simple(默认类型) 和 forking。默认的 simple 类型可以适应于绝大多数的场景,因此一般可以忽略这个参数的配置。而如果服务程序启动后会通过 fork 系统调用创建子进程,然后关闭应用程序本身进程的情况,则应该将 Type 的值设置为 forking,否则 systemd 将不会跟踪子进程的行为,而认为服务已经退出。

RemainAfterExit
值为 true 或 false(也可以写 yes 或 no),默认为 false。当配置值为 true 时,systemd 只会负责启动服务进程,之后即便服务进程退出了,systemd 仍然会认为这个服务是在运行中的。这个配置主要是提供给一些并非常驻内存,而是启动注册后立即退出然后等待消息按需启动的特殊类型服务使用

ExecStart
这个参数是几乎每个 .service 文件都会有的,指定服务启动的主要命令,在每个配置文件中只能使用一次。

ExecStartPre
指定在启动执行 ExecStart 的命令前的准备工作,可以有多个,如前面第二个例子中所示,所有命令会按照文件中书写的顺序依次被执行。

ExecStartPost
指定在启动执行 ExecStart 的命令后的收尾工作,也可以有多个。

TimeoutStartSec
启动服务时的等待的秒数,如果超过这个时间服务任然没有执行完所有的启动命令,则 systemd 会认为服务自动失败。这一配置对于使用 Docker 容器托管的应用十分重要,由于 Docker 第一次运行时可以能会需要从网络下载服务的镜像文件,因此造成比较严重的延时,容易被 systemd 误判为启动失败而杀死。通常对于这种服务,需要将 TimeoutStartSec 的值指定为 0,从而关闭超时检测,如前面的第二个例子。

ExecStop
停止服务所需要执行的主要命令。

ExecStopPost
指定在 ExecStop 命令执行后的收尾工作,也可以有多个。

TimeoutStopSec
停止服务时的等待的秒数,如果超过这个时间服务仍然没有停止,systemd 会使用 SIGKILL 信号强行杀死服务的进程。

Restart
这个值用于指定在什么情况下需要重启服务进程。常用的值有 no,on-success,on-failure,on-abnormal,on-abort 和 always。默认值为 no,即不会自动重启服务。这些不同的值分别表示了在哪些情况下,服务会被重新启动,参见下表。

服务退出原因

no

always

on-failure

on-abnormal

on-abort

no-success

正常退出

异常退出

启动/停止超时

被异常KILL

RestartSec
如果服务需要被重启,这个参数的值为服务被重启前的等待秒数。

ExecReload
重新加载服务所需执行的主要命令。

服务上下文配置相关的参数:

Environment
为服务添加环境变量,如前面的第一个例子中所示。

EnvironmentFile
指定加载一个包含服务所需的环境变量列表的文件,文件中的每一行都是一个环境变量的定义。

Nice
服务的进程优先级,值越小优先级越高,默认为0。-20为最高优先级,19为最低优先级。

WorkingDirectory
指定服务的工作目录。

RootDirectory
指定服务进程的根目录( / 目录),如果配置了这个参数后,服务将无法访问指定目录以外的任何文件。

User
指定运行服务的用户,会影响服务对本地文件系统的访问权限。

Group
指定运行服务的用户组,会影响服务对本地文件系统的访问权限。

LimitCPU / LimitSTACK / LimitNOFILE / LimitNPROC 等
限制特定服务可用的系统资源量,例如 CPU,程序堆栈,文件句柄数量,子进程数量… 不再展开说明了,值的含义可参考 Linux 文档资源配额部分中 RLIMIT_ 开头的那些参数们。