Systemed守护进程
介绍
systemd是linux系统中的一个初始化系统和服务管理器,它负责在系统启动时启动服务、监听系统事件以及管理系统的运行等。它的主要优点包括并行启动服务、按需启动进程、减少系统资源消耗等。
自定义一个systemd服务
创建服务单元文件
如果我们想自定义一个systemd服务,需要创建一个服务单元文件,该文件定义了服务的属性和行为。
服务单元文件通常存储在两个位置:
/etc/systemd/system/
:用于系统级别的服务~/.config/systemd/user/
:用于用户级别的服务,这个目录在用户主目录中。
我们也可以在其他目录创建服务单元文件,然后在/etc/systemd/system/
目录中创建该服务单元文件的符号链接,比如,/home/abc/Service/a.service
:
1 |
|
用户级别的服务单元通常在以下情况下进行服务:
用户登录时:当用户登录到系统时,systemd会自动启动该用户的所有已启用的用户级别服务
手动启动:拥有root权限的用户可以手动启动其他用户级别的服务,使用命令:
1
sudo systemctl start user@service_name
通过其他服务或目标触发:用户级别的服务也可以配置为在特定目标(target)启动后自动运行,或者由其他服务或事件触发
创建的服务单元的名字后缀通常是.service
其他常见的单元文件类型包括:
.socket
:定义套接字单元,用于监听网络套接字或UNIX域套接字.device
:定义设备单元,用于管理设备.mount
:定义挂载单元,用于管理文件系统挂载点.automount
:定义自动挂载单元,用于自动挂载文件系统.swap
:定义交换分区单元.target
:定义目标单元,用于组织系统状态.path
:定义路径单元,用于监事文件系统路径.timer
:定义定时器单元,用于定时执行任务
.service
文件的几个部分
Unit
用法
1 |
|
Description
:对该服务的描述Documention
:说明文档Before
:在a.service
启动之前,启动本服务After
:在b.target
启动之后,启动本服务Wants
:弱依赖于c.service
,即使被依赖服务启动失败或停止,本服务仍然运行Requires
:强依赖于d.service
,如果被依赖服务启动失败或停止,本服务也会停止
配置项可选可不选
启动顺序以依赖关系无关,被依赖的服务与本服务可以同时启动
Wants
与Requires
区别
Wants
:Wants
表示一种弱依赖关系,意味着如果列出的单元启动了,那么当前单元也会被启动,但如果没有启动,也不会影响当前单元的启动。- 当系统启动或某个目标(target)被激活时,systemd会尝试启动所有被该目标
Wants
的单元。因此,Wants
可以确保在特定条件下,某些服务会尝试启动,从而间接影响启动顺序。
Requires
:Requires
表示一种强依赖关系,意味着如果列出的单元没有启动,那么当前单元也会失败。- 由于
Requires
定义了强依赖,systemd在启动当前单元时,会尝试启动所有被Requires
的单元。如果这些依赖单元无法启动,当前单元也不会启动。
示例:
假设有两个服务单元:serviceA.service和serviceB.service。
- 如果serviceA.service在Unit部分有
Requires=serviceB.service
,那么在启动serviceA.service时,systemd会同时启动serviceB.service。如果serviceB.service启动失败,serviceA.service也不会启动。如果停止serviceB.service,serviceA.service也会停止 - 如果serviceA.service在Unit部分有
Wants=serviceB.service
,那么在系统启动或相关目标被激活时,systemd会尝试启动serviceB.service,但serviceA.service的启动不会因为serviceB.service的失败而受阻。如果停止serviceB.service,serviceA.service并不会停止
Wants
、Requires
和After
、Before
区别
Before
和After
直接指定了单元之间的启动顺序,即一个单元应该在另一个单元之前或之后启动。这些指令明确告诉systemd如何排序单元的启动顺序。Before
和After
并不会在该服务启动之前或之后去启动其他服务。而是在几个服务需要同时启动时,会根据启动顺序先后启动这几个服务。比如,a.service
中有After=b.service
,在启动a.service
时,systemd并不会尝试先去启动b.service
,而是直接启动a.service
。当系统开机时,a.service
与b.service
均需要启动,那么systemd则会优先启动b.service
,然后再去启动a.service
,并且b.service
无论是否启动成功都不影响a.service
的启动Wants
和Requires
并不规定启动顺序,只是强调依赖关系,并且会在启动该服务时根据依赖关系去启动其他服务。- 当
a.service
中有Requires=b.service
时,当我们启动a.service
,systemd会同时启动b.service
,如果b.service
启动失败,a.service
也不会启动。当a.service
和b.service
均启动后,如果停止b.service
,根据依赖关系,a.service
也会自动停止 - 当
a.service
中有Wants=b.service
时,当我们启动a.service
,systemd会同时启动b.service
,如果b.service
启动失败,a.service
会继续启动。当a.service
和b.service
均启动后,如果停止b.service
,因为a.service
与b.service
是弱依赖关系,a.service
并不会被停止
比如:
a.service
:1
2
3
4
5
6
7
8
9
10
11
12[Unit]
Description=App a
After=b.service
[Service]
User=root
ExecStart=python /root/a.py
Restart=on-failure
RestartSec=60s
[Install]
WantedBy=multi-user.targetb.service
:1
2
3
4
5
6
7
8
9
10
11[Unit]
Description=App b
[Service]
User=root
ExecStart=python /root/b.py
Restart=on-failure
RestartSec=60s
[Install]
WantedBy=multi-user.target其中,
a.py
:1
2
3
4
5import time
while True:
with open("/root/test.txt", "a+") as f:
f.write("a success\n")
time.sleep(600)b.py
:1
2
3
4
5import time
while True:
with open("/root/test.txt", "a+") as f:
f.write("b success\n")
time.sleep(600)运行
a.service
,test.txt
中的结果只有a success
a.service
:1
2
3
4
5
6
7
8
9
10
11
12[Unit]
Description=App a
Requires=b.service
[Service]
User=root
ExecStart=python /root/a.py
Restart=on-failure
RestartSec=60s
[Install]
WantedBy=multi-user.targetb.service
:1
2
3
4
5
6
7
8
9
10
11[Unit]
Description=App b
[Service]
User=root
ExecStart=python /root/b.py
Restart=on-failure
RestartSec=60s
[Install]
WantedBy=multi-user.target运行
a.service
,test.txt
中的结果:1
2a success
b successa.service
:1
2
3
4
5
6
7
8
9
10
11
12
13[Unit]
Description=App a
Requires=b.service
After=b.service
[Service]
User=root
ExecStart=python /root/a.py
Restart=on-failure
RestartSec=60s
[Install]
WantedBy=multi-user.targetb.service
:1
2
3
4
5
6
7
8
9
10
11[Unit]
Description=App b
[Service]
User=root
ExecStart=python /root/b.py
Restart=on-failure
RestartSec=60s
[Install]
WantedBy=multi-user.target运行
a.service
,test.txt
中的结果:1
2b success
a success
Service
服务具体执行的方式
1 |
|
User
:指定服务运行的用户Group
:指定服务运行的用户组EnvironmentFile
:指定环境变量文件ExecStart
:指定服务的启动命令(服务启动时执行的命令)ExecStop
:服务器停止时执行的命令ExecReload
:重启当前服务时执行的命令ExecStartPost
:启动当前服务之后执行的命令ExecStopPost
:停止当前服务之后执行的命令ExecStartPre
:启动当前服务之前执行的命令Type
:服务启动类型simple
:默认类型,表示ExecStart
为主进程notify
:类似于simple
,启动结束后会发出通知信号forking
:以fock方式从父进程创建子进程,创建后父进程会立即退出oneshot
:一次性进程,Systemd会等当前服务退出,再往下执行dbus
:当前服务只通过D-Bus启动idle
:若有其他任务,等其他任务执行完毕,当前服务才会运行
KillMode
:定义服务停止时如何杀死服务进程control-group
:默认,停止时杀死所有子进程process
:只杀死主进程none
:只停止服务,不杀死进程
Restart
:服务在失败时重启的方式no
:默认,不重启on-success
:正常退出时重启on-failure
:非正常退出时重启always
:不论什么原因退出,均重启
RestartSec
:指定重启服务前等待的时间WorkingDirectory
:指定服务的工作目录TimeoutStartSec
:指定服务启动超时时间,超过对应时间没有启动成功,则视为失败TimeoutStopSec
:指定服务停止超时时间,超过对应时间服务还没有停止,则强制杀死进程SuccessExitStatus
:定义哪些退出状态码被视为成功,比如SuccessExitStatus=0 2
表示退出状态码0和2都视为成功RestartPreventExitStatus
:定义哪些退出状态码会阻止服务重启,比如RestartPreventExitStatus=255
表示退出状态码255时不会重启NonBlocking
:指定ExecStart
执行的非阻塞模式,比如NonBlocking=true
Nice
:用于调整进程的优先级,比如Nice=10
MemoryLimit
:设置服务的内存使用限制,比如MemoryLimit=500M
LimitNPROC
:限制服务可以创建的最大进程数,比如LimitNPROC=10000
LimitNOFILE
:限制服务进程可以打开的最大文件描述符数,防止服务进程打开过多的文件,比如LimitNOFILE=1000000
Install
用法
1 |
|
WantedBy
:指定服务应该被哪些目标(target)所依赖。当这些目标启动时,该服务也会被启动。比如:WantedBy=multi-user.target
表示服务在多用户目标启动时自动启动RequiredBy
:与WantedBy
类似,但表示强依赖关系。如果该服务失败,依赖该服务的目标(target)也会失败。比如:RequiredBy=graphical.target
表示图形界面目标强依赖于该服务Alias
:为服务定义别名,可以通过别名来管理服务。比如:Alias=myapp.service
表示可以通过myapp.service
来管理该服务Also
:指定在启用当前服务时,还应该启用其他服务或单元。比如:Also=myotherapp.service
表示在启用当前服务时,也会启用myotherapp.service
DefaultInstance
:用于定义模板服务的默认实例。模板服务可以生成多个实例,DefaultInstance
指定默认的实例名称
在systemd中,模板服务是一种特殊的服务,它可以生成多个实例,每个实例都有自己独立的配置和运行环境。DefaultInstance
选项用于指定在启用模板服务时,如果没有明确指出实例名称,应该使用哪个默认的实例
模板服务的配置文件通常包含占位符,这些占位符在生成实例时会被具体的值替换。
通常模板服务的文件名会包含一个@
符号,如myapp@.service
,其中@
后面的部分会被实例名替换。
比如创建myapp@.service
1 |
|
在这个配置中,%i
是一个占位符,会被实例名称所替换。DefaultInstance=default
表示:如果用户启用myapp@.service
,而没有指定实例名称,系统会自动使用default
作为实例名称
指定一个特定实例:
1 |
|
这条命令会创建一个指向my@instance1.service
的符号链接,表示系统启动时自动启用名为instance1
的实例
与Unit的区别
[Unit]
中的Wants
和Requires
定义的是当前单元对其他单元的依赖
[Install]
中的WantedBy
和RequiredBy
定义的是其他单元或目标对当前单元的依赖
对a.service
有WantedBy=b.service
,在systemctl enable a.service
后,等价于b.service
中有Wants=a.servcie
对a.service
有RequiredBy=b.service
,在systemctl enable a.service
后,等价于b.service
中有Requires=a.service
WantedBy
和RequiredBy
需要对该服务使用systemctl enable
命令后才会起作用,比如:
对于a.service
:
1 |
|
对于b.service
:
1 |
|
对a.service
使用命令:
1 |
|
系统输出:Created symlink /etc/systemd/system/b.service.wants/a.service → /etc/systemd/system/a.service.
systemd在/etc/systemd/system/b.service.wants/
目录下创建了一个指向a.service
的符号链接。这样,当b.service
启动时,systemd
会同时启动a.service
,但即使a.service
启动失败,也不会影响b.service
的启动。当停止b.service
时,a.service
也会停止
对b.service
使用命令:
1 |
|
系统输出:Created symlink /etc/systemd/system/multi-user.target.wants/b.service → /etc/systemd/system/b.service.
同理,如果是RequiredBy=b.service
,则会在/etc/systemd/system/b.service.requires/
目录下创建一个指向a.service
的符号链接
设置开机自启动
如果我们希望该服务能够在系统开机的时候自动启动,一般的做法是设置该服务的[Install]
部分:
1 |
|
multi-user.target
是systemd中的一个目标(target),它代表了一个系统运行级别,类似于传统的
UNIX 系统中的运行级别
3。这个目标意味着系统已经启动了所有的基本服务,并且可以支持多用户登录,但没有启动图形界面。
如果我们希望该服务开机自动自动,但是需要在图形界面启动之后再启动,可以设置为:
1 |
|
graphical.target
是 systemd
中的一个目标,它代表了一个系统运行级别,类似于传统的 UNIX
系统中的运行级别 5,即完整的图形用户界面(GUI)模式。
占位符
systemd支持多个占位符:
%i
:表示实例名称,具体用法参照上面举例%n
:表示单元名称%p
:表示服务的_prefix_
,即单元名称中@
之前的部分%t
:表示当前的时间戳%u
:表示当前用户的用户名%U
:表示当前用户的UID%h
:表示主机名%H
:表示主机的硬件地址%m
:表示主机的机器ID%b
:表示boot ID%s
:表示服务的状态
这些占位符可以在服务文件的不同部分使用,以实现更灵活的配置和管理。例如,我们可以在服务文件的ExecStart
路径中使用%i
来指定不同的配置文件或日志文件路径,以便每个实例都有自己的配置或日志
举例:
%i
:假设有一个模板服务文件myapp@.service
,我们可以为不同的用户创建实例,如myapp@alice.service
和myapp@bob.service
,在服务文件中,我们可以使用%i
来引用实例名称:1
2[Service]
ExecStart=/usr/bin/myapp --user=%i当启动
myapp@alice.service
时,%i
会被替换为alice
,命令变为/usr/bin/myapp --user=alice
%n
:如果服务单元名为myapp.service
,在服务文件中可以使用%n
来引用单元名称:1
2[Service]
ExecStart=/usr/bin/myapp --service-name=%n这将导致命令变为
/usr/bin/myapp --service-name=myapp.service
%p
:对于模板服务myapp@.service
,%p
表示@
符号之前的部分:1
2[Service]
ExecStart=/usr/bin/myapp --prefix=%p对于实例
myapp@alice.service
,命令变为/usr/bin/myapp --prefix=myapp
%t
:在服务文件中,可以使用%t
来添加时间戳到日志文件名1
2[Service]
ExecStart=/usr/bin/myapp >> /var/log/myapp_%t.log这将创建一个包含时间戳的日志文件,如
/var/log/myapp_1626456789.log
%u
:如果服务以特定用户身份运行,可以使用%u
来引用用户名:1
2[Service]
ExecStart=/usr/bin/myapp --username=%u如果服务由用户
alice
启动,命令变为/usr/bin/myapp --username=alice
systemd对服务的控制命令
启动服务:
1
systemctl start <服务名>
停止服务:
1
systemctl stop <服务名>
重启服务:
1
systemctl restart <服务名>
检查服务状态:
1
systemctl status <服务名>
重新加载systemd管理器配置:
1
systemctl daemon-reload
当我们修改了systemd单元文件(例如服务、目标、挂载点等)或者创建、删除了单元文件时,需要使用这个命令来让systemd重新识别这些更改。当systemd的单元文件被修改后,
daemon-reload
会重新加载这些文件,但不影响当前正在运行的单元。daemon-reload
不会重启任何服务或改变任何正在运行的服务状态。它只是让systemd知道配置文件发生了变化。重新加载服务配置:
1
systemctl reload <服务名>
例如:
systemctl reload nginx
将重新加载Nginx服务的配置文件。重新加载服务配置并重启服务:
1
systemctl reload-or-restart <服务名>
如果服务支持重新加载配置,则重新加载;否则,重启服务。
启用服务:
1
systemctl enable <服务名>
systemctl enable <服务名>
命令的作用是创建一个符号链接,将服务的单元文件链接到相应的目标目录下。如果服务的单元文件中没有WantedBy
或RequiredBy
字段,systemctl enable
命令将无法创建这样的链接禁用服务:
1
systemctl disable <服务名>
与启用服务相反,删除对应符号链接
查看服务是否启用:
1
systemctl is-enabled <服务名>
查看服务的依赖关系:
1
systemctl list-dependencies <服务名>
杀死服务:
1
systemctl kill <服务名>
例如:
systemctl kill nginx
将发送信号杀死Nginx服务。显示服务主配置文件:
1
systemctl show <服务名> --property=FragmentPath
比如:
systemctl show a --property=FragmentPath
结果:FragmentPath=/etc/systemd/system/a.service
编辑服务配置文件:
1
systemctl edit <服务名>
重置服务到初始状态:
1
systemctl reset-failed <服务名>
例如:
systemctl reset-failed nginx
将重置Nginx服务的状态,清除任何失败标记。
流程
创建一个开机自启动,并能在服务停止后自动重启服务的流程:
到
/etc/systemd/system/
目录下创建服务文件,如a.service
编辑
a.service
执行命令:
1
sudo systemctl daemon-reload
1
sudo systemctl enable a
1
sudo systemctl start a
注意事项
- 设置开机自启动时,service的重启时间需要设置,否则会出现开机自启动失败的情况:
1 |
|
这种情况没有设置RestartSec
,会出现开机自启动失败的情况,用systemctl status ...
会显示重启速度过快的消息
- 正确配置:
1 |
|
RestartSec=60
,代表启动失败后60秒再重启
target
常见的target
- multi-user.target:
- 类似于传统的运行级别3,表示多用户命令行界面模式。
- 通常用于服务器或不需要图形界面的环境。
- graphical.target:
- 类似于传统的运行级别5,表示完整的图形用户界面模式。
- 用于桌面环境或需要图形界面的系统。
- default.target:
- 系统启动时的默认目标。
- 通常链接到
graphical.target
或multi-user.target
。
- rescue.target:
- 类似于单用户模式,提供一个救援 shell。运行级别1
- 用于系统修复或维护。
- emergency.target:
- 提供一个非常基本的系统,只有根文件系统和一个 emergency shell。
- 用于严重的系统问题修复。
- poweroff.target:
- 关闭系统。运行级别0
- reboot.target:
- 重启系统。运行级别6
- halt.target:
- 停止系统,但不关闭电源。
- suspend.target:
- 挂起系统。
- hibernate.target:
- 将系统休眠到硬盘。
- hybrid-sleep.target:
- 结合挂起和休眠的特性。
- runlevel0.target:
- 关闭系统,等同于
poweroff.target
。
- 关闭系统,等同于
- runlevel1.target:
- 单用户模式,等同于
rescue.target
。
- 单用户模式,等同于
- runlevel2.target:
- 不完全的多用户模式,没有网络服务。
- runlevel3.target:
- 多用户模式,等同于
multi-user.target
。
- 多用户模式,等同于
- runlevel4.target:
- 用户定义的运行级别,通常不使用。
- runlevel5.target:
- 图形界面模式,等同于
graphical.target
。
- 图形界面模式,等同于
- runlevel6.target:
- 重启系统,等同于
reboot.target
。
- 重启系统,等同于
- network-online.target:
- 表示网络已经在线,可以用于等待网络连接的服务。
- remote-fs.target:
- 远程文件系统挂载点。用于远程文件系统(如NFS)的挂载。这个目标确保所有配置的远程文件系统都已经挂载。
- timers.target:
- 用于定时器服务。
- emergency.target:
- 急救模式,提供紧急shell用于修复系统问题。
- network.target:
- 表示网络服务已启动。这个目标并不等待所有的网络接口都完全配置好,而是确保基本的网络栈已经启动。依赖于这个目标的服务可以在网络基本可用时开始运行。
- nss-lookup.target:
- 用于表示名称服务切换(NSS)查找服务已准备就绪的状态。这个目标确保在系统完全启动之前,NSS服务(如DNS解析)已经可用。
- sockets.target:
- 用于管理套接字服务。这个目标确保所有定义的套接字都已经创建,相关服务可以在需要时启动。
- timers.target:
- 用于定时器服务。这个目标确保所有定义的定时器都已经激活。
- paths.target:
- 用于路径监控服务。这个目标确保所有定义的路径监控都已经激活。
- swap.target:
- 用于交换分区。这个目标确保所有配置的交换分区都已经激活。
- devices.target:
- 用于设备管理。这个目标确保所有设备都已经被发现并配置。
这些目标可以根据我们的需求和服务特性进行选择。例如,如果我们有一个网络服务,我们希望它在网络准备好之后启动,那么可以将服务关联到
network-online.target
。如果我们有一个只在系统关闭时运行的服务,可以将其关联到
poweroff.target
。
使用Systemd Target进行系统管理
- 切换运行级别:使用
systemctl isolate
命令,可以立即切换到不同的Target。例如:sudo systemctl isolate graphical.target
将立即启动图形用户界面 - 查看当前激活的Target:使用
systemctl list-units --type=target
命令,我们可以查看当前激活的Target,以及它们的状态 - 修改默认Target:使用
systemctl set-default
命令,我们可以更改系统启动后默认进入的Target。例如:sudo systemctl set-default multi-user.target
将设置默认Target为多用户模式
完整的示例
1 |
|