menu Chancel's blog
rss_feed
Chancel's blog
有善始者实繁,能克终者盖寡。

systemd配置指南

作者:Chancel Yang, 创建:2024-08-30, 字数:8230, 已阅:136, 最后更新:2024-08-30

systemd 是一个 2010 年诞生的系统和服务管理器,广泛用于现代Linux发行版中,负责启动和管理系统的用户空间服务、挂载文件系统、启动并管理设备、以及其他系统初始化任务

作为取代 init 系统的系统项目,systemd 通过单一的控制进程(systemd进程)来管理系统的生命周期,其采用 ini 配置文件来描述和管理服务

现代大部分 Linux 系统都由 systemd 启动和管理

1. systemd

1.1. 例子

以 sshd 服务为例,控制其启动、关闭、和重启:

Bash
sudo systemctl stop sshd
sudo systemctl start sshd
sudo systemctl restart sshd

# 检查 sshd 的服务状态
sudo systemctl status sshd

sshd 服务的开启、关闭、重启都由 /etc/systemd/system/sshd.service 定义,其内容如下:

INI
[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 配置文件来描述和管理服务,其主要构成有三部分: UnitService 以及 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 部分中,AfterBefore 是较为常用的,都是用于描述启动顺序,即程序在什么情况下启动

这两者的值是一样的,常用值有:

  • 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

TEXT
[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 读入该配置单并设置开机自启

Bash
sudo systemctl daemon-reload
sudo systemctl enable easydns --now

查看 easydns 的运行服务状态

Bash
sudo systemctl status easydns

3.2. 定时服务

与 crontab 相比,systemd 的定时服务提供了状态监控、日志管理、重启策略控制等功能模块,能适应更复杂的运行需求

例如我们要定时执行域名 DDNS 解析,程序执行方法如下

Bash
/root/NameSilo-DDNS/namesilo-ddns --domain=chancel.me --name=demo --type=AAAA --record=$inet6_address --key=123456

创建一个 Service 描述文件,编辑: /etc/systemd/system/namesilo-ddns.service

INI
[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

INI
[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分钟执行一次

接下来启动这个定时器即可

Bash
sudo systemctl daemon-reload
sudo systemctl enable namesilo-ddns.timer --now

查看所有定时器列表

Bash
sudo systemctl list-timers

列表如下

Bash
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 进行日志管理,你可以通过以下命令查看日志:

Bash
# 查看所有日志
journalctl

# 查看特定服务日志
journalctl -u <service_name>

日志切割的话,通常会借助 logrotate 来实现

4.1. logrotate

logrotate 是一个常用的日志管理工具,可以定期轮换、压缩和删除旧日志文件

以配置 easydns 为例

编辑:/etc/logrotate.d/easydns

Text only
/var/log/easydns.log {
    size 10M            # 设置每个日志文件的最大大小为10MB
    rotate 5            # 保留5个轮换的日志文件
    compress           # 压缩旧日志文件
    missingok           # 如果日志文件丢失,不报错
    notifempty          # 仅在日志非空时轮换
    copytruncate        # 保证在日志被写入时也可以实现轮换压缩日志
    create 0640 root root  # 创建新的日志文件时的权限
}

配置完成后,可以试运行(不会实际执行任何操作)

Bash
logrotate -d /etc/logrotate.d/easydns

通常而言, logrotate 在 crontab 或 systemd 中被预先定义为1天执行1次,可以通过检查 systemd 的定时任务来查看:

Bash
$ 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.

[[replyMessage== null?"发表评论":"发表评论 @ " + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageResponse.total]])

还没有可以显示的留言...
gravatar
[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[getEnviron(messageItem.m_environ)]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[getEnviron(messageItem.m_environ)]]