systemd 服务注册

发布时间 2023-09-16 21:05:36作者: Hukenis

systemd 服务项注册

前言

systemd 是所有进程之母,它负责使 Linux 主机启动到可以做生产性任务的状态。systemd 设定的一些功能比老的初始化程序要广泛得多,它要管理正在运行的 Linux 主机的许多方面,包括挂载文件系统、管理硬件、处理定时器以及启动和管理生产性主机所需的系统服务。

CentOS7引导顺序:

  1. UEFi或BIOS初始化,运行POST开机自检
  2. 选择启动设备
  3. 引导装载程序, centos7是grub2
  4. 加载装载程序的配置文件:(/etc/grub.d/ 、etc/default/grub为模板配置文件 )/boot/grub2/grub.cfg为真正的配置文件,不建议直接修改
  5. 加载initramfs驱动模块
  6. 加载内核选项
  7. 内核初始化,centos7使用systemd代替init
  8. 执行initrd.target所有单元,包括挂载/etc/fstab
  9. 从initramfs根文件系统切换到磁盘根目录
  10. systemd执行默认target配置,配置文件/etc/systemd/default.target /etc/systemd/system/
  11. systemd执行sysinit.target初始化系统及basic.target准备操作系统
  12. systemd启动multi-user.target下的本机与服务器服务
  13. systemd执行multi-user.target下的/etc/rc.d/rc.local
  14. Systemd执行multi-user.target下的getty.target及登入服务
  15. systemd执行graphical需要的服务

X.配置文件路径

/usr/lib/systemd/system/

XX.结构示例

[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

A.[unit] 编写

1.Description 描述

对于此启动单元的概述

[Unit]
Description=Daemon to start He.net IPv6

2.依赖关系

note: 依赖的项目一般是与本单元同时启动的、可以指定多个服务,以空格隔开

2-1.弱依赖

2-1-1.want

推荐使用。本单元启动了,它“想要”的单元也会被启动。但是启动不成功,对本单元没有影响。

大多数时候 Wants 的都是一个固定的系统状态而不是其它 systemd 服务

[Unit]
Description=Daemon to start He.net IPv6
Wants=network-online.target

2-2.强依赖

2-2-1.Requires

Systemd 不是先启动 Requires 再启动本单元,而是在本单元被激活时,并行启动两者。于是会产生争分夺秒的问题,如果 Requires 先启动成功,那么皆大欢喜; 如果 Requires 启动得慢,那本单元就会失败

[Unit]
Description=Daemon to start He.net IPv6
Requires=network-online.target
2-2-2. RequiresOverridable

跟 Requires 很像。但是如果这条服务是由用户手动启动的,那么 RequiresOverridable 后面的服务即使启动不成功也不报错;如果不由用户手动启动而是随系统开机启动,那么依然会有 Requires 面临的问题

[Unit]
Description=Daemon to start He.net IPv6
RequiresOverridable=network-online.target
2-2-3.Requisite

强势版本的 Requires。要是这里需要的服务启动不成功、本单元文件就会立即失败。

[Unit]
Description=Daemon to start He.net IPv6
Requisite=network-online.target

3.启动顺序

note:After和Before字段只涉及启动顺序,不涉及依赖关系。

3-1.Before

本服务会在预设的服务之前启动

[Unit]
Description=Daemon to start He.net IPv6
Wants=network-online.target
Before=mysqld.service

3-1.After

本服务会在预设的服务之后启动

[Unit]
Description=Daemon to start He.net IPv6
Wants=network-online.target
Before=mysqld.service
After=network.service

4.冲突检测

Conflicts

配置与当前服务互斥的服务。如果本unit启动了,则指定的服务就不能启动,相反亦是如此。

[Unit]
Description=Daemon to start He.net IPv6
Conflicts=network-online.target

B.[service] 编写

本服务的重点部分、指定启动行为。Type、EnvironmentFile、ExecStart、ExecReload、ExecStop、PrivateTmp。

1.声明服务类型

Type

Type指定服务的启动类型,必须为simple, exec, forking, oneshot, dbus, notify, idle 之一、常用simple和forking。

建议对长时间持续运行的服务尽可能使用Type=simple(这是最简单和速度最快的选择)。注意,因为simple类型的服务 无法报告启动失败、也无法在服务完成初始化后对其他单元进行排序,所以,当客户端需要通过仅由该服务本身创建的IPC通道(而非由systemd创建的套接字或D-bus之类)连接到该服务的时候,simple类型并不是最佳选择。在这种情况下, notify或dbus(该服务必须提供D-Bus接口)才是最佳选择, 因为这两种类型都允许服务进程精确的安排何时算是服务启动成功、何时可以继续启动后继单元。notify类型需要服务进程明确使用sd_notify()函数或类似的API,否则,可以使用forking作为替代(它支持传统的UNIX服务启动协议)。最后,如果能够确保服务进程调用成功、服务进程自身不做或只做很少的初始化工作(且不大可能初始化失败),那么exec将是最佳选择。注意,因为使用任何 simple 之外的类型都需要等待服务完成初始化,所以可能会减慢系统启动速度。 因此,应该尽可能避免使用 simple 之外的类型(除非必须)。另外,也不建议对长时间持续运行的服务使用 idle 或 oneshot 类型。

1-1.simple

默认类型,启动的程序就是主体程序,这个程序要是退出那么一切皆休。simple 类型不存在主进程退出的情况也就不存在有返回状态的情况,所以它一旦启动就认为是成功的,除非没起来。

[Service]
Type=simple

1-2.exec

与simple的不同之处在于,只有在该服务的主服务进程正常结束之后,systemd才会认为该服务启动完成。 其他后继单元必须一直阻塞到这个时间点之后才能继续启动。

[Service]
Type=exec

1-3.forking

forking 标准 Unix Daemon 使用的启动方式。启动程序后会调用 fork() 函数,把必要的通信频道都设置好之后父进程退出,留下守护精灵的子进程。你要是使用的这种方式,最好也指定下 PIDFILE= /pid路径/,不要让 Systemd 去猜,非要猜也可以,把 GuessMainPID 设为 yes。如果你的程序确实是 forking 类型,但就是没实现创建 PIDFILE 的功能,那么建议使用 ExecStartPost= 结合 shell 命令来手动抓取进程编号并写到 /var/run/xxx.pid。

[Service]
Type=forking
PIDFILE= /pid路径/

[Service]
Type=forking
GuessMainPID=yes

1-4.oneshot

字面意思,打一枪换一个地方。所以这种服务类型就是启动,完成就退出、没进程了。只有在该服务的主服务进程退出之后,systemd才会认为该服务启动完成,才会开始启动后继单元。 此种类型的服务通常会设定RemainAfterExit=yes;这条配置的意思是说,即使没进程了,我们也要 Systemd 认为该服务是存在并成功了的、状态一直都是running的;除非手动systemctl stop 掉,或者压根就没启动成功。

[Service]
Type=oneshot
RemainAfterExit=yes

1-5.dbus

dbus 这个程序启动时需要获取一块 DBus 空间,所以需要和 BusName= 一起用。只有它成功获得了 DBus 空间,依赖它的程序才会被启动。

BusName需要使用一个D-Bus的总线名称,作为该服务的可访问名称

[Service]
Type=dbus
BusName=org.freedesktop.NetworkManager   

1-6.notify

比较少用的类型;notify 这个程序会在启动结束后会发出通知信号,然后 Systemd 再启动其他服务。所以还需要配合 NotifyAccess 来让 Systemd 接收消息,后者有三个级别:none,所有消息都忽略掉; main,只接受我们程序的主进程发过去的消息; all,我们程序的所有进程发过去的消息都算。NotifyAccess 要是不写的话默认是 main。

[Service]
Type=notify
NotifyAccess=main

1-7.idle

比较少用的类型;这个程序要等它里面调度的全部其它东西都跑完才会跑它自己。比如你 ExecStart 的是个 shell 脚本,里面可能跑了一些别的东西,如果不这样的话,那很可能别的东西的控制台输出里会多一个“启动成功”这样的 Systemd 消息。

[Service]
Type=idle

2.环境变量

Environment

可以多次使用Environment赋值,也可以在""或'' 内定义好各个键值、并加以空格分割不同变量。

也可以通过导入外部预设变量文件的方式设定、所有传参必须使用 $ 。

eg:

[Service]
Environment=“ONE=one” ‘TWO=two two’
ExecStart=echo $ONE $TWO ${TWO}

# 这将给 /bin/echo 命令依次传递如下四个参数: “one”, “two”, “two”, “two two”

eg:

cat >>/opt/envfile/network_env<<EOF
AAA_IPV4_ANCHOR_0=X.X.X.X
BBB_IPV4_PRIVATE_0=X.X.X.X
CCC_HOSTNAME=test.example.com
EOF

···
[Service]
EnvironmentFile=/opt/envfile/network_env
ExecStart=/xxx --abc=xx${AAA_IPV4_ANCHOR_0}yy

# 导入之后就可以直接使用预定义文件中的变量咯

3.启动指令

ExecStart

就是实际执行此服务的程序。接受 "命令 参数 参数..." 的格式,不接受 <, >, >>, |, & 等特殊字符,很多 bash 语法也不支持。所以,要使用这些特殊的字符时,最好直接写入到脚本里面去!

可以接受一个命令,参数不限

如果非bash不行、可以尝试ExecStart=sh -c ‘dmesg | tac’ 这样的格式变通。

  • 如果你服务的类型不是 oneshot,那么它只可以接受一个命令,参数不限,比如你先 ip tunnel create 再 ip tunnel0 up,那是两个 ip 命令,如果你不是 oneshot 类型这样是不行的。
  • 如果有多条命令(oneshot 类型),命令之间以分号 ; 分隔,跨行可用反斜杠 \。
  • 除非你的服务类型是 forking,否则你在这里输入的命令都会被认为是主进程,不管它是不是。
[Service]
ExecStart=/usr/bin/redis-server    /etc/redis/conf/6379.conf

4.启动前额外执行

ExecStartPre

在输入的命令是服务启动前执行的命令,要求同ExecStart.

5.启动后额外执行

ExecStartPost

在输入的命令是服务启动后执行的命令,要求同ExecStart.

6.停止指令

ExecStop

在输入的命令是stop时候执行的命令,要求同ExecStart.

[Service]
ExecStop=/usr/bin/kill  -9  $MAINPID

7.重载指令

ExecReload

[Service]
ExecReload=/usr/bin/kill  -HUP  $MAINPID

8.自重启触发

Restart

当服务进程 正常退出、异常退出、被杀死、超时的时候, 是否重新启动该服务;;

所谓"服务进程" 是指 ExecStart=, ExecStartPre=, ExecStartPost=, ExecStop=, ExecStopPost=, ExecReload= 中设置的进程

取值范围:no(默认), on-success, on-failure, on-abnormal, on-watchdog, on-abort, always

no(默认值) 表示不会被重启

always 表示会被无条件的重启。

on-success 表示仅在服务进程正常退出时重启

on-failure 表示 仅在服务进程异常退出时重启; 推荐此项以增强可用性

on-abort 表示 服务被强制杀死时

[Service]
Restart=on-failure

9.自重启等待时间

RestartSec

一般与Restart同时出现、在服务终止多长时间之后才重新启动它。默认是 100ms

[Service]
RestartSec=60s

10.强制结束的耐心时间

TimeoutSec

无法顺利 "正常启动或正常结束" 的情况下,等多久才可以"强制结束"

[Service]
TimeoutSec=120s

11.stop 信号设定

KillMode

systemctl stop **.service 时发送的kill信号

四个类型选择

  1. control-group(默认):# 当前控制组里的所有子进程,都会被杀掉
  2. process: # 只杀主进程
  3. mixed: # 主进程将收到SIGTERM信号,子进程收到SIGKILL信号
  4. none: # 没有进程会被杀掉,只是执行服务的stop命令
[Service]
KillMode=process

C.[Install] 编写

主要说明如何安装这个配置文件,把该 unit 安装在哪个 target上,做到开机自启。其实就是在使用【systemctl enable】时,进行创建符号连接会识别到[Install]字段的内容进行安装,主要是创建软链到【/etc/systemd/system/】目录下

1.Target挂载选择

WantedBy

这个设置后面接的大部分是 *.target 。意思是,这个unit本身该附挂在哪个 target 下面

此项的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入/etc/systemd/system目录下面以 Target 名 + .wants后缀构成的子目录中

  1. multi-user.target: # 表示多用户命令行状态,这个设置很重要;一般写这个
  2. graphical.target: # 表示图形用户状体,它依赖于multi-user.target
[Install]
WantedBy=multi-user.target

2.触发其他unity自启

Also

当目前这个unit被设置开机自启时,Also 后面接的unit也要开机自启的意思

3.服务别名

Alias

当前 Unit 可用于启动的别名,systemctl enable相关的服务时,则此服务会进行链接文件的创建!默认开启!

D.关于Target

Target的含义是服务组,表示一组服务。WantedBy=multi-user.target指的是,sshd 所在的 Target 是multi-user.target;这个设置非常重要,因为执行systemctl enable sshd.service命令时,sshd.service的一个符号链接,就会放在/etc/systemd/system目录下面的multi-user.target.wants子目录之中。

启动计算机的时候,需要启动大量的 Unit。如果每一次启动,都要一一写明本次启动需要哪些 Unit,显然非常不方便。Systemd 的解决方案就是 Target。简单说,Target 就是一个 Unit 组,包含许多相关的 Unit 。启动某个 Target 的时候,Systemd 就会启动里面所有的 Unit。从这个意义上说,Target 这个概念类似于"状态点",启动某个 Target 就好比启动到某种状态。

note:默认Target 是 multi-user.target;是Systemd 默认的启动 Target,在这个组里的所有服务,都将开机自动启动

一般来说,常用的 Target 有两个:一个是multi-user.target,表示多用户命令行状态;另一个是graphical.target,表示图形用户状态,它依赖于multi-user.target

1.Target 编写

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

E.示范

1.simple

systemd 执行守护进程之后, 即认为该单元已经启动成功;本例中的sshd服务 必须在启动后持续运行到服务被停止。 如果该进程只是为了派生守护进程,那么应该使用 Type=forking

[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
Type=simple
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

2.forking

多数传统的守护进程(服务)在启动时会转入后台运行。 systemd 通过 Type=forking 来支持这种工作方式。 对于这种类型的服务,如果最初启动的进程尚未退出, 那么该单元将依然处于"启动中"(activating)状态。当最初的进程成功退出, 并且至少有一个进程仍然在运行(并且 RemainAfterExit=no), 该服务才会被视为处于"活动"(active)状态。

对于单进程的传统服务,当最初的进程成功退出后, 将会只剩单独一个进程仍然在持续运行, systemd 将会把这个唯一剩余的进程视为该服务的主进程。 仅在这种情况下,才将可以在 ExecReload=, ExecStop= … 之类的选项中使用 $MAINPID 变量。

当最初的进程成功退出后,将会剩余多个进程在持续运行, 因此,systemd 无法确定哪一个进程才是该服务的主进程。 在这种情况下,不可以使用 $MAINPID 变量。 然而,如果主进程会创建传统的PID文件, 那么应该将 PIDFile= 设为此PID文件的绝对路径, 以帮助 systemd 从该PID文件中读取主进程的PID,从而帮助确定该服务的主进程。 注意,守护进程必须在完成初始化之前写入PID文件, 否则可能会导致 systemd 读取失败 (读取时文件不存在)。

[Unit]   # 主要是服务说明
Description=test   # 简单描述服务
After=network.target    # 描述服务类别,表示本服务需要在network服务启动后在启动
Before=xxx.service      # 表示需要在某些服务启动之前启动,After和Before字段只涉及启动顺序,不涉及依赖关系。
 
[Service]  # 服务的关键
Type=forking     # 表示后台运行模式。
PIDFile=/usr/local/test/test.pid    # 存放PID的绝对路径
ExecStart=/usr/local/test/bin/startup.sh    # 服务启动命令,命令需要绝对路径
ExecReload=xxx  #为重启命令,
ExecStop=xxx   #为停止命令
PrivateTmp=true                               # 表示给服务分配独立的临时空间
   
[Install]   
WantedBy=multi-user.target  # 多用户

3.oneshot

用于那些只需要执行一次性动作而不需要持久运行的单元, 例如文件系统检查或者清理临时文件。 此类单元, 将会在启动后一直等待指定的动作完成, 然后再回到停止状态。 下面是一个执行清理动作的单元:

[Unit]
After=network.target
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/mount -t nfs -o defaults 192.168.1.120:/volume1/mydsm /mnt  
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target

4.dbus

对于需要在 D-Bus 系统总线上注册一个名字的服务, 应该使用 Type=dbus 并且设置相应的 BusName= 值。 该服务不可以派生任何子进程。 一旦从 D-Bus 系统总线成功获取所需的名字,该服务即被视为初始化成功。 下面是一个典型的 D-Bus 服务:

[Unit]
Description=一个简单的 DBus 服务

[Service]
Type=dbus
BusName=org.example.simple-dbus-service
ExecStart=/usr/sbin/simple-dbus-service

[Install]
WantedBy=multi-user.target

5.notify

Type=simple 类型的服务 非常容易编写, 但是, 无法向 systemd 及时通知 "启动成功"的消息, 是一个重大缺陷。 Type=notify 可以弥补该缺陷, 它支持将"启动成功"的消息及时通知给 systemd 。 下面是一个典型的例子;

该守护进程必须支持 systemd 通知协议, 否则 systemd 将会认为该服务一直处于"启动中"(activating)状态,并在超时后将其杀死。

[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target