如何创建一个systemd服务,使得可以用systemctl来管理它

发布时间 2023-08-16 15:35:44作者: ShineLe

学习自:systemd 服务配置文件编写_systemd服务编写_shiner_chen的博客-CSDN博客

1、前言

以NetworkManager为例,它可以被systemctl管理,可以进行start、stop、restart等操作。

现在有软件redis,每次启动都要输入指令redis-server redis.conf,关闭时要输入redis-cli shutdown或者kill -9 对应的进程号。如何把它纳入systemd服务,使得可以用systemctl来管理它的启停?

2、systemd简介

Systemd Service是systemd提供的用于管理服务启动、停止,它极大简化了服务管理的配置过程,用户只需要配置几项指令即可。相比SysV的服务管理脚本(service服务),用户不用去编写服务的启停、重启、状态检测等一系列复杂且重复的脚本代码。Systemd Service是面向所有用户的,即使对于新手用户来说,配置门槛也很低。

Systemd Service是Systemd Unit一种,除了service之外,systemd还有其他几种类型的unit,例如socket、slice、scope、target等等。

service:定义服务程序的启停、重启、状态与进程相关的属性

target:主要是对service(或其他Unit)进行分组、归类,可以包含一个或多个Service Unit

3、systemd配置文件(.service文件)

1)存放路径

Systemd Service配置文件的文件名均以.service为后缀,这些服务配置文件隐藏在/usr/lib/systemd/system目录下,同时会给用户暴露一个供用户存放服务配置文件的目录/etc/systemd/system。该目录下的配置文件可以是普通.service文件,也可以是链接至/usr/lib/systemd/system目录下服务配置文件的软链接

2).service文件写法

一个.service文件大概长这样:

[Unit]
Description = some descriptions
Documentation = man:xxx(8) man:xxx_config(5)
Requires = xxx1.target xxx2.target
After = yyy1.target yyy2.target

[Service]
Type = <TYPE>
ExecStart = <CMD_for_START>
ExecStop = <CMD_for_STOP>
ExecReload = <CMD_for_RELOAD>
Restart = <WHEN_TO_RESTART>
RestartSec = <TIME>

[Install]
WantedBy = xxx.target yy.target

一个.service配置文件大致分为3部分:

  • Unit:定义该服务作为Unit角色时相关属性
  • Service:定义本服务相关属性
  • Install:定义本服务在设置开机自启动时的相关属性。也就是说,只有在创建/移除服务配置文件的软链接时,Install段才会派上用场。这一配置端并非必须,当未配置Install时,设置/禁止开机自启动的操作将无任何效果。

UnitInstall段的配置指令都来自于man systemd.unit,这些指令都用于描述作为Unit时的属性,而Service段专属于.Service服务配置文件

1)Unit

Unit项

说明

Description 描述信息
Documentation 本Unit的man文档路径
After

本服务在哪些服务启动后启动。

只定义启动顺序,不定义依赖关系。

即使该项要求的服务启动失败,该服务也仍会启动

Before

本服务在哪些服务启动前启动。

只定义启动顺序,不定义依赖关系。

通常用于定义在关机前要关闭的服务,如Before=shutdown.target

Wants

[Unit段落]

本服务在哪些服务启动后启动。

定义依赖关系,不定义启动顺序。

启动本服务时,如果被依赖服务未启动,则也会启动被依赖服务。如果被依赖服务启动失败,本服务不会受之影响,因此本服务会继续启动。

如果未结合After使用,则本服务和被依赖服务将同时启动。

Wants

[Install段落]

systemctl enable操作会将本服务安装到对应的.wants目录下(在该目录下创建一个软链接),在开机自启动时,.wants目录中的服务会被隐式添加到目标Unit的Wants指令后。

Requires

[Unit]

本服务在哪些服务启动后启动。

定义强依赖关系,不定义启动顺序。

如果被依赖项未启动,则会启动被依赖服务。如果结合了After,当存在非active状态的被依赖服务时,本服务不会启动。当被依赖服务手动停止时,本服务也会被停止

如果要保证量服务状态一致,要用BindsTo指令

Requires

[Install]

systemctl enable操作会将本服务安装到对应的.requires目录下(在该目录下创建一个软链接),在开机自启动时,.requires目录中的服务会被隐式添加到目标Unit的Requires指令后
Requisite

本服务在哪些服务启动后启动。

定义依赖关系,不定义启动顺序。

启动本服务时,如果被依赖服务处于未启动状态,则不会主动启动这些服务,所以本服务直接启动失败

该指令一般结合After使用,以便保证启动顺序。

BindsTo

绑定两个服务,两个服务状态保持一致

如,服务1为active,则本服务也一定为active。

PartOf

本服务是其他服务的一部分

定义了单向依赖关系,且只对stop、restart有效。

被依赖服务执行stoprestart操作,本服务也会执行操作,但本服务执行这些操作,不会影响被依赖服务

一般用于组合target使用,比如a.service和b.service都配置了PartOf=c.target,那么stop c时,也会同时stop a和b

Conflicts

定义冲突的服务

本服务被冲突服务状态必须相反。

本服务要启动时,将会停止目标服务,当启动目标服务时,会停止本服务启停操作同时进行。

如果要让本服务目标服务启动前就处于停止状态,则必须定义After/Before

OnFailure

本服务处于failed时,将启动目标服务。

如果本服务配置了Restart重启指令,则在耗尽重启次数之后,本服务才会进入failed

一个典型用法是本服务失败时调用定义了邮件发送service来发送邮件。

可以结合systemd.timer定时任务实现cron的MAILTO功能

RefuseManualStart

RefuseManualStop

不服务不允许手动启停,只能用被依赖的方式启停,如果手动启停则会报错。

有些特殊服务很关键,或者某服务为某个大型服务的一部分,为了保证安全可以使用该特性。

例如,审计服务auditd.service中配置了不允许手动停止指令RefuseManualStop;网络服务network.target中配置了不允许手动启动指令RefuseManualStart

AllowIsolated

允许用systemctl isolate切换到本服务,只配置于target中。

一般来说,用户服务绝不可能用到这一项。

ConditionPathExists

AssertPathExists

要求给定的绝对路径文件已经存在,否则不做任何事(ConditionPath)进入failed状态(AssertPath)。

可在路径前用!表示条件取反,即不存在时才启动服务

ConditionPathIsDirectory

AssertPathIsDirectory

路径存在且为目录时启动,其余如上。

ConditionPathIsReadWrite

AssertPathIsReadWrite

路径存在且可读可写时启动,其余如上。

ConditionDirectoryNotEmpty

AssertDirectoryNotEmpty

路径存在且非空目录

ConditionFileNotEmpty

AssertFileNotEmpty

路径存在且非空文件

ConditionFileIsExecutable

AssertFileIsExecutable

路径存在且为可执行普通文件

对于自定义服务配置文件而言,需要定义的常见指令包括Description、After、Wants及可能需要的条件判断类指令。所以Unit段落是很简单的。

2)Install

Install段的指令只在systemctl enable/disable生效。如果期望服务开机期自动,一般只配置一个WantedBy指令,如果不期望服务开机自启动,则Install段通常省略:

Install项

说明

WantedBy

该项指明,当设置了开机自启动时,在被依赖目标的.wants目录下创建本服务的软链接

例如,WantedBy=multi-user.target,将在/etc/systemd/multi-user.target.wants目录下创建本服务的软链接

RequiredBy 类似WantedBy,但是在.requires目录下创建软链接
Alias

指定创建软链接链接至本服务配置文件的别名文件

例如,reboot.target配置了Alias=ctrl-alt-del.target,当执行enable时,将创建/etc/systemd/system/ctrl-alt-del.service 软链接并指向reboot.target

DefaultInstance

当是一个模板服务配置文件(文件名Service_Name@.service),该指令指定该模板的默认实例

例如 trojan@.service 中配置了 DefaultInstall=server 时,systemctl enable trojan@.service 时将创建名为 trojan@server.service 的软链接。

一个auditd.service的配置文件中的Unit和Instll段:

cat /usr/lib/systemd/system/auditd.service 
[Unit] Description
=Security Auditing Service DefaultDependencies=no After=local-fs.target systemd-tmpfiles-setup.service Before=sysinit.target shutdown.target Conflicts=shutdown.target RefuseManualStop=yes ConditionKernelCommandLine=!audit=0 Documentation=man:auditd(8) [Service] ...... [Install] WantedBy=multi-user.target

3)Service段

Service段有很多可配置的指令,这些指令来源有①man systemd.service;②man systemd.exec;③man systemd.kill;④man resource-control

man systemd.service

描述专属Service Unit的指令。

包括启停、重启等管理行为的指令。

例如ExecStart指定服务启动时执行的命令

man systemd.exec

描述启动环境、运行环境的指令。

包括指定工作目录、指定服务进程的运行用户、环境变量、服务OOM相关的属性。

man systemd.kill

定义服务终止的指令。

比如终止服务时发送什么信号、服务进程没杀死时如何处理

man resource-control

定义资源控制相关指令,即配置服务进程的CGroup

比如该服务进程最多允许多少内存、使用哪些CPU、最多占用多少百分比的CPU时间

目前来说,可以不用过多关注来自其他位置的指令,应该给予重点关注的是来自systemd.service的指令

  • Type:服务的管理类型
  • ExecStart:启动服务时执行的命令
  • ExecStop:停止服务时执行的命令
  • ExecReload:重载时执行的命令
  • Restart:systemd是否要自动重启服务进程、什么情况下重启

特别是Type指令,它直接影响Service段中多项配置方式。

根据man systemd.service,Type指令支持多种值:

  • simple
  • exec
  • forking
  • oneshot
  • dbus
  • notify
  • idle
如果配置的是服务进程Type的值很可能是forking或simple;如果是普通命令进程Type的值可能是simple、oneshot

 

Type=forking

Type=forking时,要求ExecStart启动的命令自身就是以daemon模式运行的。

nginx默认以daemon模式运行,所以可以将其配置为forking类型:

$ cat test.service 
[Unit]
Description = Test

[Service]
Type = forking
ExecStart = /usr/sbin/nginx

$ systemctl daemon-reload
$ systemctl start test
$ systemctl status test
● test.service - Test
   Loaded: loaded
   Active: active (running)
  Process: 7912 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
 Main PID: 7913 (nginx)
    Tasks: 5
   Memory: 4.6M
   CGroup: /system.slice/test.service
           ├─7913 nginx: master process /usr/sbin/nginx
           ├─7914 nginx: worker process
           ├─7915 nginx: worker process
           ├─7916 nginx: worker process
           └─7917 nginx: worker process

在上边status报告的信息中,ExecStart启动的nginx进程PID=7912,该进程状态是已退出,退出状态码为0,这个进程是daemon类进程创建过程中瞬间退出的中间父进程。在forking类型中,该进程称为初始化进程

同时还有一行Main PID:7913 (nginx),这是systemd真正监控的nginx服务主进程,其PID是7913,是PID=7912的子进程

Type=simple

Type=simple是一种最常见的通过systemd服务系统运行用户自定义命令的类型,也是省略Type指令时的默认类型

simple只适用于那些在shell下运行于前台的命令(如ls、sleep、非daemon运行的nginx进程、以前台调试模式运行的进程。当一个命令本身以daemon模式运行时,将不能用simple,而应该用Type=forking。

例如,编写一个/usr/lib/systemd/system/test.service运行sleep进程:

[Unit]
Description = test

[Service]
Type = simple
ExecStart = /usr/bin/sleep 10  # 命令必须使用绝对路径

使用daemon-reload重载该服务进程:

$ systemctl daemon-reload
$ systemctl start test
$ systemctl status test
● test.service - Test
   Loaded: loaded
   Active: active (running)
 Main PID: 6902 (sleep)
    Tasks: 1
   Memory: 96.0K
   CGroup: /system.slice/test.service
           └─6902 /usr/bin/sleep 10

10s内,sleep进程以daemon模式运行在后台,像一个服务进程一样。10s后,sleep退出,于是systemd将该进程从监控队列中踢出。再次查看进程状态将是inactive:

$ systemctl status test
● test.service - Test
   Loaded: loaded
   Active: inactive (dead)

 

在配置文件中,ExecStart指令指定启动本服务时执行的命令,即启动了一个本该前台运行的sleep进程作为服务进程在后台运行。

需注意,systemd service 的命令行中必须使用绝对路径,
且只能编写单条命令 (Type=oneshot 时除外),如果要命令续行,可在尾部使用反斜线符号 \ 等。 此外,命令行中支持部分类似 Shell 的特殊符号,但不支持重定向 > >> << <、管道 |、后台符号 &,
具体可参考 man systemd.service 中 command line 段落的解释说明。

 

对于 Type=simple 来说,systemd 系统在 fork 出子 systemd 进程后就认为服务已经启动完成了,所以 systemd 可以紧跟着启动排在该服务之后启动的服务。