systemd 是一个 2010 年诞生的系统和服务管理器,广泛用于现代Linux发行版中,负责启动和管理系统的用户空间服务、挂载文件系统、启动并管理设备、以及其他系统初始化任务
作为取代 init 系统的系统项目,systemd 通过单一的控制进程(systemd进程)来管理系统的生命周期,其采用 ini 配置文件来描述和管理服务
现代大部分 Linux 系统都由 systemd 启动和管理
1. systemd
1.1. 例子
以 sshd 服务为例,控制其启动、关闭、和重启:
sudo systemctl stop sshd
sudo systemctl start sshd
sudo systemctl restart sshd
# 检查 sshd 的服务状态
sudo systemctl status sshd
sshd 服务的开启、关闭、重启都由 /etc/systemd/system/sshd.service
定义,其内容如下:
[Unit]
# 描述该服务的功能
Description=OpenBSD Secure Shell server
# 提供该服务的文档参考,包括手册页
Documentation=man:sshd(8) man:sshd_config(5)
# 指定该服务在网络服务和审计服务启动后启动
After=network.target auditd.service
# 确保在指定路径下不存在文件时,该服务才会运行
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
[Service]
# 从指定文件加载环境变量,如果文件不存在则忽略
EnvironmentFile=-/etc/default/ssh
# 在服务启动之前检查 sshd 配置文件的有效性
ExecStartPre=/usr/sbin/sshd -t
# 启动 sshd 服务,使用 -D 参数使其在前台运行并使用环境变量中的选项
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
# 当服务重新加载时,首先检查配置文件的有效性
ExecReload=/usr/sbin/sshd -t
# 向主进程发送 HUP 信号以重新加载配置
ExecReload=/bin/kill -HUP $MAINPID
# 设置进程终止模式为仅终止该进程,而不是其子进程
KillMode=process
# 服务失败时自动重启
Restart=on-failure
# 避免在特定情况下(退出状态为255)重启服务
RestartPreventExitStatus=255
# 指定服务类型为 notify,表示服务会通过 sd_notify 通知其状态
Type=notify
# 为 sshd 服务创建运行时目录,以存储临时文件
RuntimeDirectory=sshd
# 设置运行时目录的权限为 0755
RuntimeDirectoryMode=0755
[Install]
# 指定在 multi-user.target 启动时自动启用该服务
WantedBy=multi-user.target
# 为该服务创建一个别名,方便管理
Alias=sshd.service
接下来,将简单快速拆解一下这种 ini 文件的构成
1.2. 配置文件
systemd 采用 ini 配置文件来描述和管理服务,其主要构成有三部分: Unit、Service 以及 Install 三大部分
Unit 用于定义程序的基本属性和元数据,常见选项:
- Description:单元的简短描述
- Documentation:相关文档的链接
- After:定义单元启动的顺序,表示当前单元在指定单元之后启动
- Before:定义单元启动的顺序,表示当前单元在指定单元之前启动
- Requires:表示当前单元依赖于其它单元,如果依赖的单元未能启动,当前单元也不会启动
- Wants:类似于 Requires,但不那么严格,依赖的单元如果未能启动,当前单元仍然会尝试启动
Service 用于定义服务的行为(如执行命令、环境变量、运行用户),常见选项:
- ExecStart:指定启动该服务时执行的命令
- ExecStop:指定停止该服务时执行的命令
- Restart:服务崩溃后的重启策略(如 always、on-failure 等)
- RestartSec:服务重启的延迟时间(单位秒)
- User:以哪个用户身份运行该服务
- StandardOutput:输出日志方式
- Environment:设置环境变量
- WorkingDirectory:指定工作目录
- Type:服务的类型(如 simple、forking、oneshot、notify、dbus)
Install 用于控制服务的启用和禁用策略,常见选项:
- WantedBy:指定该服务单位希望在启动时被哪些目标所激活
- RequiredBy:指定该服务单位是哪些单位所依赖的
2. 编写指南
2.1. Unit
在 Unit 部分中,After 和 Before 是较为常用的,都是用于描述启动顺序,即程序在什么情况下启动
这两者的值是一样的,常用值有:
- basic.target:基础的系统服务启动
- multi-user.target:多用户模式,适用于无图形界面的系统
- graphical.target:图形用户界面模式,依赖于 multi-user.target
- network-online.target:网络连接完全建立
- remote-fs.target:远程文件系统挂载
- local-fs.target:本地文件系统挂载
- shutdown.target:系统正在关闭
- reboot.target:系统正在重启
- network.service:网络服务启动完成
- dbus.service:D-Bus 服务启动
- systemd-journald.service:日志记录服务启动
- systemd-logind.service:用户登录管理服务
After = 在指定情况后启动
Before = 在指定情况前启动
2.2. Service
在 Service 中, Restart 和 Environment 是比较常用的,一个用于控制程序的重启逻辑,一个用于控制程序运行时的环境变量
Restart 常见值:
- no:默认值,不采取任何操作
- always:重启服务
- on-success:正常退出(状态码为0)则重启
- on-failure:非正常退出(状态码不为0)则重启
- on-abort:服务未捕捉到任何信号(如SIGKILL 或 SIGQUIT)而退出时重启
- on-watchdog:仅在服务因未响应 watchdog 超时而退出时重启
Environment 用于设置程序的环境变量
Type 用于帮助 systemd 判定服务是否启动成功,常见值:
- simple:启动后不需要任何信号即认为启动成功
- forking:当服务进程fork出一个主进程并退出,适用于传统的UNIX守护进程,它们会fork并在后台运行
- oneshot:服务执行一次后即退出,适合一次性脚本
- notidy:服务启动后会通过
sd_notidy
通知 systemd,适合与 systemd 进行复杂交互的程序 - idle:服务在其他任务执行完毕后再执行,低优先级的任务
如果环境变量过多,可以使用 EnvironmentFile 选项用于指定一个环境变量的文件路径
2.3. Install
常见的 WantedBy 和 RequiredBy 值包括:
- multi-user.target:这是最常用的目标,表示系统已进入多用户模式,但没有图形界面,通常用于服务器或没有图形界面的系统
- graphical.target:表示用户进入了图形界面
- basic.target:表示系统的基本初始化已完成,但没有进入多用户模式
- default.target:这是系统启动时的默认目标,通常是 graphical.target 或 multi-user.target 的别名
- sysinit.target:表示系统初始化阶段的目标,通常在系统启动的早期阶段使用
- network.target:在网络已初始化后使用,适用于需要网络连接的服务
- remote-fs.target:用于启动远程文件系统的挂载
RequiredBy 的用法与 WantedBy 类似,但它表示服务是目标的必须依赖项,如果目标被激活,则这个服务也必须被激活RequiredBy 一般用于更严格的依赖关系
这两者与 Unit 中的 Before 和 After 非常相似,但区别很大:
- After 和 Before:用于控制启动顺序,确保单元在特定的时间点启动
- RequiredBy 和 WantedBy:用于定义单元的依赖关系,主要影响单元的启用和禁用行为
3. 例子
3.1. 长期服务
接下来,以配置一个 easydns 程序服务为例
easydns 是一个简易 DNS 分流程序
编辑: /etc/systemd/system/easydns.service
[Unit]
# 服务描述
Description=easydns service
# 启动顺序
After=network.target
[Service]
# 执行命令
ExecStart=/root/easydns/easydns -m=127.0.0.1:1080 -p=114.114.114.114:53 -d=/root/easydns/domain.txt
# 退出机制
Restart=always
# 内存限制
MemoryMax=64M
# 日志输出 %N表示服务名称,所以日志输出位置是 /var/log/easydns.log
StandardOutput=append:/var/log/%N.log
# 重定向错误日志到标准流
StandardError=inherit
# 运行用户
User=root
[Install]
# 启动类型
WantedBy=multi-user.target
让 systemd 读入该配置单并设置开机自启
sudo systemctl daemon-reload
sudo systemctl enable easydns --now
查看 easydns 的运行服务状态
sudo systemctl status easydns
3.2. 定时服务
与 crontab 相比,systemd 的定时服务提供了状态监控、日志管理、重启策略控制等功能模块,能适应更复杂的运行需求
例如我们要定时执行域名 DDNS 解析,程序执行方法如下
/root/NameSilo-DDNS/namesilo-ddns --domain=chancel.me --name=demo --type=AAAA --record=$inet6_address --key=123456
创建一个 Service 描述文件,编辑: /etc/systemd/system/namesilo-ddns.service
[Unit]
Description=Register NameSilo dns record
[Service]
Type=oneshot
ExecStart=/root/NameSilo-DDNS/namesilo-ddns --domain=chancel.me --name=demo --type=AAAA --record=$inet6_address --key=123456
StandardOutput=append:/var/log/%N.log
StandardError=inherit
User=root
再创建一个定时器,编辑: /etc/systemd/system/namesilo-ddns.timer
[Unit]
Description=Register a DDNS domain every 30 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=30min
Unit=namesilo-ddns.service
[Install]
WantedBy=timers.target
OnBootSec
设置了程序在系统启动后1分钟运行程序,此后 OnUnitActiveSec
规定了每30分钟执行一次
接下来启动这个定时器即可
sudo systemctl daemon-reload
sudo systemctl enable namesilo-ddns.timer --now
查看所有定时器列表
sudo systemctl list-timers
列表如下
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2024-09-04 15:15:01 CST 29min left Wed 2024-09-04 14:45:01 CST 12s ago namesilo-ddns.timer namesilo-ddns.service
...
Mon 2024-09-09 01:38:12 CST 4 days left Mon 2024-09-02 01:39:50 CST 2 days ago fstrim.timer fstrim.service
9 timers listed.
Pass --all to see loaded but inactive timers, too.
4. 日志配置
systemd 使用 journald 进行日志管理,你可以通过以下命令查看日志:
# 查看所有日志
journalctl
# 查看特定服务日志
journalctl -u <service_name>
日志切割的话,通常会借助 logrotate 来实现
4.1. logrotate
logrotate 是一个常用的日志管理工具,可以定期轮换、压缩和删除旧日志文件
以配置 easydns 为例
编辑:/etc/logrotate.d/easydns
/var/log/easydns.log {
size 10M # 设置每个日志文件的最大大小为10MB
rotate 5 # 保留5个轮换的日志文件
compress # 压缩旧日志文件
missingok # 如果日志文件丢失,不报错
notifempty # 仅在日志非空时轮换
copytruncate # 保证在日志被写入时也可以实现轮换压缩日志
create 0640 root root # 创建新的日志文件时的权限
}
配置完成后,可以试运行(不会实际执行任何操作)
logrotate -d /etc/logrotate.d/easydns
通常而言, logrotate 在 crontab 或 systemd 中被预先定义为1天执行1次,可以通过检查 systemd 的定时任务来查看:
$ systemctl list-timers
NEXT LEFT LAST PASSED UNIT ACTIVATES
Mon 2024-09-02 00:00:00 CST 56min left Sun 2024-09-01 00:00:01 CST 23h ago dpkg-db-backup.timer dpkg-db-backup.service
Mon 2024-09-02 00:00:00 CST 56min left Sun 2024-09-01 00:00:01 CST 23h ago logrotate.timer logrotate.service
Mon 2024-09-02 01:10:26 CST 2h 7min left Sun 2024-09-01 04:48:01 CST 18h ago man-db.timer man-db.service
Mon 2024-09-02 01:39:40 CST 2h 36min left Mon 2024-08-26 01:39:19 CST 6 days ago fstrim.timer fstrim.service
Mon 2024-09-02 06:10:27 CST 7h left Sun 2024-09-01 06:45:01 CST 16h ago apt-daily-upgrade.timer apt-daily-upgrade.service
Mon 2024-09-02 10:53:56 CST 11h left Sun 2024-09-01 10:53:56 CST 12h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2024-09-02 14:55:51 CST 15h left Sun 2024-09-01 21:53:03 CST 1h 9min ago apt-daily.timer apt-daily.service
Sun 2024-09-08 03:10:20 CST 6 days left Sun 2024-09-01 03:11:06 CST 19h ago e2scrub_all.timer e2scrub_all.service
8 timers listed.
Pass --all to see loaded but inactive timers, too.