Linux Systemd的一些东西
有关 Linux Systemd 的一些东西
1. 什么是 Systemd
1.1 Systemd 简介
Systemd(全称:system daemon)是 Linux 系统中用于启动和管理服务的核心工具,也是其前身 init 的改良版本,
旨在解决传统 init 系统启动时间长、脚本复杂的问题。
在 Linux 发展初期,系统启动过程中,内核完成初始化以后,由内核第一个启动的程序便是 init 程序,路径为 /sbin/init(为一个软连接,链接到真实的 init 进程),其 PID 为1,它为系统里所有进程的“祖先”,Linux 中所有的进程都由 init 进程直接或间接进行创建并运行,init 进程以守护进程的方式存在,负责组织与运行系统的相关初始化工作,让系统进入定义好的运行模式,如命令行模式或图形界面模式。
原文链接:https://blog.csdn.net/qq_35995514/article/details/125582824
init 程序的发展,大体上可分为三个阶段:sysvinit->upstart->systemd
| 阶段 | 描述 |
|---|---|
| sysvinit | 本质上是一个脚本,通过串行执行shell脚本来启动系统服务,速度缓慢 |
| upstart | 在前者的基础上,把一些无关联的程序并行启动,提升速度,但是存在依赖关系的服务仍为串行。 |
| systemd | 这个使用了套接字激活的机制,不管程序有无互相依赖全部并行启动,并且仅按照系统启动的需要启动相应的服务,大幅优化开机速度。 |
1.2 什么是套接字激活机制
套接字激活(Socket Activation)是 systemd 引入的一种延迟启动机制,核心思想是:先创建监听套接字,等服务真正被请求时再启动对应的服务程序。
传统启动: 服务A → 启动完成 → 创建套接字 → 等待连接 ↓ 服务B (等待A完成才能启动,即使B不依赖A)
套接字激活: systemd 先创建所有套接字(socket单元) ↓ 服务并行启动,无需等待彼此 ↓ 当某个套接字收到请求时,才真正启动对应的服务用到才启动
**例如 sshd **:
[Socket]ListenStream=22 # systemd 先监听 22 端口Accept=yes
[Install]WantedBy=sockets.target
# /usr/lib/systemd/system/sshd@.service[Service]ExecStart=-/usr/sbin/sshd -i # 有连接时才启动sshd实例效果:
-
开机时 systemd 只占用 22 端口,sshd 进程并未运行
-
第一次有 SSH 连接进来时,systemd 才按需启动 sshd
-
如果没人连 SSH,sshd 永远不启动(如果 sshd 使用套接字激活且无人连接,它的状态会显示为 inactive),节省资源
查看初始状态
Terminal window # 查看 socket 状态(活跃,正在监听22端口)$ systemctl status sshd.socket● sshd.socket - OpenSSH server socketLoaded: loaded (/usr/lib/systemd/system/sshd.socket; enabled;)Active: active (listening) since Mon 2026-04-13 10:00:00 CST# 查看 service 状态(未启动,inactive)$ systemctl status sshd.service○ sshd.service - OpenSSH server daemonLoaded: loaded (/usr/lib/systemd/system/sshd.service; enabled;)Active: inactive (dead) since Mon 2026-04-13 10:00:00 CST查看连接后状态
Terminal window # 当第一个 SSH 连接进来后$ systemctl status sshd@0-192.168.1.100:22-192.168.1.5:54321.service● sshd@0-192.168.1.100:22-192.168.1.5:54321.serviceActive: active (running) since Mon 2026-04-13 10:05:30 CST单元 开机时状态 有人连接后 sshd.socketactive (listening) 保持 listening sshd.serviceinactive (dead) 可能仍保持 inactive(取决于配置) sshd@xxx.service不存在 active (running) 配置差异
# 传统方式(Type=simple/forking)- 开机就启动[Service]Type=simpleExecStart=/usr/sbin/sshd -D# 套接字激活方式(Type=notify/简单的exec)- 按需启动[Service]Type=simpleExecStart=-/usr/sbin/sshd -i # -i 表示从stdin接受连接StandardInput=socketStandardOutput=socket
1.3 用户态 和 系统态
目前主流的系统中,systemd 的守护进程主要分为系统态(system)与用户态(user),可以在 ps -ef中看到 systemd 的守护进程
原文链接:https://blog.csdn.net/qq_35995514/article/details/125582824
1.4 Systemd相关的系统进程
root@aliyun-worker-01:~# ps -ef | grep systemdroot 277 1 0 Apr01 ? 00:00:06 /usr/lib/systemd/systemd-journaldsystemd+ 305 1 0 Apr01 ? 00:00:13 /usr/lib/systemd/systemd-resolvedroot 333 1 0 Apr01 ? 00:00:01 /usr/lib/systemd/systemd-udevdsystemd+ 751 1 0 Apr01 ? 00:00:01 /usr/lib/systemd/systemd-networkdmessage+ 777 1 0 Apr01 ? 00:00:13 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-onlyroot 791 1 0 Apr01 ? 00:00:01 /usr/lib/systemd/systemd-logindroot 832660 1 0 19:47 ? 00:00:00 /usr/lib/systemd/systemd --userroot 832692 832677 0 19:48 pts/0 00:00:00 grep systemd1.4.1 核心系统组件
| 进程 | 作用 | 说明 |
|---|---|---|
systemd-journald | 日志服务 | 收集和管理系统日志,替代传统的 syslog |
systemd-resolved | DNS 解析 | 提供本地 DNS 缓存和解析服务 |
systemd-udevd | 设备管理 | 处理内核 uevent,管理 /dev 设备节点 |
systemd-networkd | 网络管理 | 管理网络配置(你的服务器在用这个而非 NetworkManager) |
systemd-logind | 登录管理 | 管理用户登录会话、电源管理(重启/关机等) |
1.4.2 其他相关进程
| 进程 | 作用 | 说明 |
|---|---|---|
dbus-daemon | 消息总线 | 系统服务间通信的”邮局”,--systemd-activation 表示支持 systemd 套接字激活 |
systemd --user | 用户级 systemd | 你当前 root 用户的 systemd 用户实例,管理用户级服务 |
1.5 Systemd 的特点
1.5.1 兼容性
systemd 兼容 sysvinit,系统中已经存在的服务无需进行修改,即可迁移到 systemd 。
1.5.2 启动速度和资源占用
systemd 依赖于 套接字激活机制,先监听进程的套接字,出现请求时再进行启动。这使其在启动初期只需拉起少量进程,并且允许并行操作,大大加快启动速度。这样不仅能加快启动速度,在一定程度上还可以节省资源。
1.5.3 采用 linux 的 cgroups 跟踪和管理进程的生命周期
一个服务可能依赖多个进程,systemd 利用了 linux 的内核特性 cgroups 来进行管理和跟踪。当服务停止时,systemd 可以通过查询 cgroups 来获取所有相关进程,从而干净地停止服务。
什么是cgroups:
cgroups 已经出现了很久,它主要用来实现系统资源配额管理。cgroups 提供了类似文件系统的接口,使用方便。当进程创建子进程时,子进程会继承父进程的 cgroups 。因此无论服务如何启动新的子进程,所有的这些相关进程都会属于同一个 cgroups ,systemd 只需要简单地遍历指定的 cgroups 即可正确地找到所有的相关进程,将它们一一停止即可。
原文链接:https://blog.csdn.net/qq_35995514/article/details/125582824
2. Systemd 管理的基本单元 — Unit
| 后缀 | 类型 | 作用 | 例子 |
|---|---|---|---|
.service | 服务 | 后台进程/应用程序 | nginx.service, sshd.service |
.socket | 套接字 | 监听端口/套接字(用于套接字激活) | sshd.socket |
.target | 目标 | 一组 unit 的集合(类似 runlevel) | multi-user.target, graphical.target |
.timer | 定时器 | 定时触发任务(替代 cron) | apt-daily.timer |
.mount | 挂载点 | 管理文件系统挂载 | home.mount |
.automount | 自动挂载 | 按需挂载 | home.automount |
.path | 路径 | 监控文件/目录变化 | backup.path |
.slice | 切片 | 资源控制组(cgroup) | system.slice |
.scope | 范围 | 外部创建的进程组 | session-1.scope |
2.1 .service(服务 - 最常用)
2.1.1 定义
后台运行的应用程序或系统服务,是 Systemd 中最常见的 Unit 类型。
2.1.2 配置模板
# ============================================# [Unit] 段:定义服务的元信息、描述和依赖关系# ============================================[Unit]# 服务的简短描述,会显示在 systemctl status 中Description=My Application
# 文档链接,可以是 man 手册、网页或本地文件路径# Documentation=man:myapp(8)# Documentation=https://example.com/docs
# 启动顺序:本服务在 network.target 之后启动# 注意:After 只控制顺序,不控制依赖After=network.target
# 强依赖:启动本服务前,必须先启动这些服务# 如果依赖服务启动失败,本服务也会失败# Requires=network.target mysql.service
# 弱依赖:尽可能启动这些服务,但不影响本服务启动# Wants=network.target
# 反向顺序:本服务应该在哪些服务之前启动# Before=nginx.service
# 冲突服务:不能与这些服务同时运行,会自动停止冲突服务# Conflicts=another-app.service
# 启动条件判断:条件不满足时跳过启动(软失败)# ConditionPathExists=/etc/myapp/config.conf# ConditionDirectoryNotEmpty=/var/lib/myapp# ConditionUser=root
# 断言判断:条件不满足时启动失败(硬错误)# AssertPathExists=/usr/local/bin/myapp# AssertUser=root
# ============================================# [Service] 段:定义服务的执行方式和生命周期管理# ============================================[Service]
# ---- 服务类型 ----# simple: 默认,ExecStart 启动的进程保持前台运行# forking: 服务通过 fork 创建守护进程后父进程退出(传统守护进程)# oneshot: 执行一次性任务,执行完即退出# notify: 服务启动后会发送 sd_notify 通知 systemd# dbus: 服务在 D-Bus 上注册后认为启动完成# idle: 延迟到所有其他任务处理完再启动Type=simple
# ---- 进程执行配置 ----# 启动命令(必须),必须使用绝对路径ExecStart=/usr/local/bin/myapp
# 启动前的准备命令(可多个,按顺序执行)# ExecStartPre=/usr/local/bin/myapp --check-config# ExecStartPre=/bin/mkdir -p /var/run/myapp
# 启动后的收尾命令# ExecStartPost=/bin/echo "MyApp started"
# 停止命令,默认发送 SIGTERM# ExecStop=/usr/local/bin/myapp --shutdown
# 停止后的收尾命令# ExecStopPost=/bin/rm -f /var/run/myapp.pid
# 重载配置命令(systemctl reload)ExecReload=/bin/kill -HUP $MAINPID
# ---- 进程管理 ----# 主进程 PID 变量,可用于 ExecReload 等# $MAINPID 由 systemd 自动设置为服务主进程 PID
# 服务运行用户User=www-data
# 服务运行组(默认与用户主组相同)# Group=www-data
# 工作目录# WorkingDirectory=/var/www/myapp
# 根目录(chroot),设置后服务无法访问指定目录外文件# RootDirectory=/var/chroot/myapp
# 环境变量(可多次指定)# Environment="NODE_ENV=production"# Environment="PORT=8080"
# 环境变量文件(每行一个 KEY=VALUE)# EnvironmentFile=/etc/default/myapp# EnvironmentFile=-/etc/default/myapp # 减号表示文件不存在也不报错
# ---- 重启策略 ----# no: 不自动重启(默认)# on-success: 退出码为 0 时重启# on-failure: 退出码非 0 时重启# on-abnormal: 被信号终止或超时重启# on-abort: 被信号终止时重启(不含 SIGHUP/SIGINT)# on-watchdog: 看门狗超时重启# always: 总是重启Restart=on-failure
# 重启间隔秒数RestartSec=5
# 启动超时时间(0 表示禁用超时检测)# TimeoutStartSec=30
# 停止超时时间,超时后发送 SIGKILL# TimeoutStopSec=30
# 同时设置启动和停止超时# TimeoutSec=30
# 启动频率限制:在指定时间内最多重启次数# StartLimitIntervalSec=60# StartLimitBurst=3
# ---- 标准输入输出 ----# 标准输出目标:journal/syslog/kern/tty/null/socket/fd/file# StandardOutput=journal
# 标准错误输出目标# StandardError=journal
# 标准输入来源:null/tty/tty-force/tty-fail/file/fd/socket# StandardInput=null
# ---- 资源限制(类似 ulimit) ----# 文件描述符数量限制# LimitNOFILE=65535
# 进程数量限制# LimitNPROC=4096
# core dump 文件大小限制# LimitCORE=infinity
# ---- 进程终止配置 ----# 终止模式:# control-group: 终止控制组内所有进程(默认)# process: 仅终止主进程# mixed: 主进程发 SIGTERM,其他进程发 SIGKILL# none: 不终止任何进程# KillMode=control-group
# 发送给主进程的终止信号# KillSignal=SIGTERM
# 最终强制终止信号# FinalKillSignal=SIGKILL
# 发送 SIGKILL 前的等待时间# TimeoutAbortSec=30
# ---- 看门狗配置 ----# 启用看门狗,服务需定期调用 sd_notify("WATCHDOG=1")# WatchdogSec=30
# ---- 其他配置 ----# 是否为 oneshot 类型保持激活状态(即使进程已退出)# RemainAfterExit=yes
# 服务私有的临时文件目录# RuntimeDirectory=myapp# RuntimeDirectoryMode=0755
# 状态目录# StateDirectory=myapp
# 缓存目录# CacheDirectory=myapp
# 日志目录# LogsDirectory=myapp
# 配置目录# ConfigurationDirectory=myapp
# 是否从 /dev、/proc、/sys 挂载私有副本# PrivateDevices=yes
# 是否启用私有网络命名空间# PrivateNetwork=yes
# 是否启用私有用户命名空间# PrivateUsers=yes
# 是否启用私有挂载命名空间# PrivateMounts=yes
# 能力限制(Linux capabilities)# CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_DAC_READ_SEARCH
# 安全相关选项# NoNewPrivileges=yes# ProtectSystem=strict# ProtectHome=yes# ReadWritePaths=/var/lib/myapp
# ============================================# [Install] 段:定义服务如何安装到系统中# ============================================[Install]
# 启用服务时,创建符号链接到该 target 的 .wants 目录# multi-user.target: 多用户命令行模式(类似运行级别3)# graphical.target: 图形界面模式(类似运行级别5)WantedBy=multi-user.target
# 强依赖版本,服务失败会影响 target# RequiredBy=multi-user.target
# 别名,可用 systemctl enable/disable 操作别名# Alias=myapp-daemon.service
# 启用/禁用本服务时,同时操作的其他服务# Also=myapp-socket.socket
# 启用时的默认目标(systemctl enable 时使用)# DefaultInstance=2.1.3 常用配置速查表
| 段 | 配置项 | 作用 |
|---|---|---|
[Unit] | Description | 服务描述 |
[Unit] | After/Before | 启动顺序 |
[Unit] | Requires/Wants | 依赖关系 |
[Unit] | ConditionPathExists | 启动条件判断 |
[Service] | Type | 服务类型 (simple/forking/oneshot/notify) |
[Service] | ExecStart | 启动命令 |
[Service] | ExecStop/ExecReload | 停止/重载命令 |
[Service] | Restart | 重启策略 |
[Service] | User/Group | 运行用户/组 |
[Service] | Environment/EnvironmentFile | 环境变量 |
[Service] | WorkingDirectory | 工作目录 |
[Install] | WantedBy | 启用目标 |
2.1.4 常用命令
# 重新加载配置sudo systemctl daemon-reload
# 启动/停止/重启/重载服务sudo systemctl start myappsudo systemctl stop myappsudo systemctl restart myappsudo systemctl reload myapp
# 设置开机自启/禁用sudo systemctl enable myappsudo systemctl disable myapp
# 查看状态sudo systemctl status myapp
# 查看日志sudo journalctl -u myapp -f2.2 .socket(套接字)
2.2.1 定义
监听网络端口或 Unix 套接字,实现按需启动服务。服务未运行时,systemd 先监听端口,有连接时再启动服务。
2.2.2 配置模板
# ============================================# [Unit] 段:定义套接字单元的元信息和依赖关系# ============================================[Unit]# 套接字单元的简短描述Description=My Application Socket
# 文档链接# Documentation=man:systemd.socket(5)
# 启动顺序:在 network.target 之后启动After=network.target
# 启动顺序:在 sockets.target 之前启动# sockets.target 是系统启动时统一启动所有套接字的目标Before=sockets.target
# 弱依赖:启动本套接字前尽可能启动这些服务# Wants=network.target
# 强依赖:启动本套接字前必须先启动这些服务# Requires=network.target
# 冲突服务:不能与这些服务同时运行# Conflicts=shutdown.target# Before=shutdown.target
# ============================================# [Socket] 段:定义套接字的监听配置# ============================================[Socket]
# ---- 监听地址与类型(核心配置)----
# 流式套接字(SOCK_STREAM)- 通常用于 TCP# 格式:IP:PORT 或 [IPv6]:PORT 或 /path/to/socketListenStream=0.0.0.0:8080# ListenStream=127.0.0.1:8080# ListenStream=/run/myapp.sock
# 数据报套接字(SOCK_DGRAM)- 通常用于 UDP# ListenDatagram=53
# 顺序包套接字(SOCK_SEQPACKET)- 用于可靠有序数据传输# ListenSequentialPacket=/run/myapp-seq.sock
# FIFO 命名管道# ListenFIFO=/run/myapp.fifo
# Netlink 套接字(用于内核通信)# ListenNetlink=audit 1
# POSIX 消息队列# ListenMessageQueue=/myapp-queue
# USB 功能端点(用于 USB gadget 模式)# ListenUSBFunction=ffs.myapp
# 特殊值:留空表示重置所有已定义的监听地址# ListenStream=
# ---- 连接处理模式 ----
# 连接处理方式:# no(默认):将监听套接字传递给服务,服务自己 accept() 连接# yes:为每个连接创建独立的 [name@.service] 实例(inetd 风格)Accept=no
# 当 Accept=yes 时,指定要实例化的服务模板# 例如:Accept=yes 且 Service=myapp@.service 时,# 每个连接会创建 myapp@<连接ID>.service 实例# Service=myapp.service
# ---- 套接字选项 ----
# 绑定 IPv6 专用模式:# default:根据系统默认(/proc/sys/net/ipv6/bindv6only)# yes:仅绑定 IPv6# no:同时绑定 IPv4 和 IPv6(双栈)BindIPv6Only=default
# 重用端口(允许多个进程绑定同一端口,用于负载均衡)# ReusePort=yes
# 监听队列长度(backlog)# Backlog=128
# 是否延迟激活(接收到数据时才激活服务,而非连接建立时)# DeferAcceptSec=5
# 是否禁用 Nagle 算法(TCP_NODELAY)# NoDelay=yes
# 是否启用 TCP keepalive# KeepAlive=yes
# Keepalive 时间设置(秒)# KeepAliveTimeSec=7200
# Keepalive 探测间隔(秒)# KeepAliveIntervalSec=75
# Keepalive 探测次数# KeepAliveProbes=9
# 标记套接字(用于 SO_MARK)# Mark=1234
# 最大连接数# MaxConnections=64
# 每个连接的超时时间(秒)# MaxConnectionsPerSource=256
# 消息队列最大大小(字节)# MessageQueueMaxMessages=10# MessageQueueMessageSize=8192
# ---- 文件系统与权限(用于 AF_UNIX/FIFO)----
# 套接字文件的所有者用户SocketUser=www-data
# 套接字文件的所属组SocketGroup=www-data
# 套接字文件的权限模式(八进制)SocketMode=0660
# 自动创建父目录时的权限模式DirectoryMode=0755
# 停止套接字单元时是否删除文件系统节点# 注意:通常不推荐使用,因为服务可能仍在使用# RemoveOnStop=no
# 创建指向套接字的符号链接(别名)# Symlinks=/run/myapp-alias.sock
# ---- 文件描述符传递 ----
# 为传递的文件描述符命名,便于服务识别# FileDescriptorName=myapp-socket
# 是否传递套接字的 credentials(SCM_CREDENTIALS)PassCredentials=yes
# 是否传递套接字的 security context(SELinux)PassSecurity=yes
# 接收缓冲区大小(字节)# ReceiveBuffer=8M
# 发送缓冲区大小(字节)# SendBuffer=8M
# 是否移除传递的套接字上的 close-on-exec 标志# PassEnvironment=yes
# ---- 触发限制(防雪崩保护)----
# 触发间隔时间(秒)# TriggerLimitIntervalSec=2
# 在触发间隔内允许的最大触发次数# TriggerLimitBurst=200
# ---- 执行命令(可选)----
# 启动套接字前的准备命令# ExecStartPre=/bin/mkdir -p /run/myapp
# 启动套接字后的命令# ExecStartPost=/bin/chown www-data:www-data /run/myapp.sock
# 停止套接字前的命令# ExecStopPre=
# 停止套接字后的命令# ExecStopPost=/bin/rm -f /run/myapp.sock
# ============================================# [Install] 段:定义套接字如何安装到系统# ============================================[Install]
# 启用时链接到 sockets.target# 这是所有套接字单元的标准目标WantedBy=sockets.target
# 强依赖版本# RequiredBy=sockets.target
# 别名# Alias=myapp-daemon.socket
# 启用/禁用本单元时同时操作的其他单元# Also=myapp.service2.2.3 配套的.service模板
# ============================================# Socket Activation 配套的服务单元# ============================================
[Unit]Description=My Application Service# 关键:确保服务在套接字之后启动,且套接字存在Requires=myapp.socketAfter=myapp.socket
[Service]Type=simpleUser=www-dataExecStart=/usr/local/bin/myapp
# 重启策略Restart=on-failureRestartSec=5
# 标准输入输出(如果使用 inetd 风格的 Accept=yes)# StandardInput=socket# StandardOutput=socket
[Install]# 注意:socket 激活的服务通常不需要 WantedBy=multi-user.target# 因为服务是由套接字按需启动的# 但如果需要开机启动,可以启用此配置WantedBy=multi-user.target2.2.4 两种工作模式对比
| 模式 | Accept=no(默认) | Accept=yes |
|---|---|---|
| 套接字传递 | 传递监听套接字 | 传递已连接的套接字 |
| 服务单元 | myapp.service | myapp@.service(模板) |
| 连接处理 | 服务自己 accept() | 每个连接一个独立进程 |
| 适用场景 | 高性能服务器(nginx/redis) | 简单服务、inetd 风格服务 |
| 代码要求 | 需支持 sd_listen_fds() | 可从 stdin 读取(StandardInput=socket) |
2.2.5 常用命令
# 重新加载配置sudo systemctl daemon-reload
# 启用并启动套接字(服务会在第一个连接时自动启动)sudo systemctl enable myapp.socketsudo systemctl start myapp.socket
# 查看套接字状态sudo systemctl status myapp.socket
# 查看监听中的套接字sudo systemctl list-sockets
# 手动启动服务(非 socket 激活方式)sudo systemctl start myapp.service
# 停止套接字(会同时停止关联的服务)sudo systemctl stop myapp.socket
# 查看日志sudo journalctl -u myapp.socket -u myapp.service -f2.2.6 典型应用场景
- 按需启动:服务平时不运行,有请求时才启动(节省资源)
- 并行启动:系统启动时并行创建所有套接字,无需等待服务启动完成
- 故障恢复:服务崩溃后,新请求会自动重启服务,连接不会丢失(systemd 保持套接字监听)
- 无缝升级:可以停止旧服务、启动新服务,而不中断客户端连接
2.2.7 套接字激活机制流程图
2.2.8 代码中对套接字激活机制的检测
// 服务代码中检测 socket activation#include <systemd/sd-daemon.h>
int main() { int n = sd_listen_fds(0); // 获取 systemd 传递的 FD 数量 if (n > 0) { // 使用 FD 3(SD_LISTEN_FDS_START)作为监听套接字 int sock = SD_LISTEN_FDS_START; accept(sock, ...); // 直接 accept,无需 bind/listen } else { // 普通启动,需要自行 bind/listen }}2.3 .target (目标 - 运行级别)
2.3.1 定义
一组 Unit 的集合,用于组织启动顺序和系统状态,替代传统的 SysV runlevel。
2.3.2 配置模板
# ============================================# [Unit] 段:定义 Target 的元信息和依赖关系# ============================================[Unit]# Target 的简短描述,显示在 systemctl status 中Description=My Application Target
# 文档链接,可以是 man 手册或网页Documentation=man:systemd.special(7)# Documentation=https://example.com/docs/myapp-target
# ---- 依赖关系配置 ----
# 强依赖:启动本 Target 前必须先启动这些 Unit# 如果依赖的 Unit 启动失败,本 Target 也会失败Requires=basic.target# Requires=network.target mysql.service
# 弱依赖:尽可能启动这些 Unit,但不影响本 Target 启动# Wants=myapp-database.service myapp-cache.service
# 启动顺序:本 Target 在这些 Unit 之后启动# 注意:After 只控制顺序,不控制依赖After=basic.target network.target# After=myapp-database.service
# 启动顺序:本 Target 在这些 Unit 之前启动# Before=multi-user.target
# 冲突 Unit:不能与这些 Unit 同时运行# 当切换到本 Target 时,会自动停止冲突的 UnitConflicts=rescue.service rescue.target# Conflicts=shutdown.target
# 反向依赖:哪些 Unit 依赖于本 Target(用于 systemctl enable)# 通常不需要手动设置,由其他 Unit 的 WantedBy/RequiredBy 决定# WantedBy=# RequiredBy=
# ---- 启动条件与断言 ----
# 条件判断:条件不满足时跳过启动(软失败)# ConditionPathExists=/etc/myapp/enabled# ConditionDirectoryNotEmpty=/var/lib/myapp
# 断言判断:条件不满足时启动失败(硬错误)# AssertPathExists=/usr/bin/myapp
# ---- 隔离模式配置 ----
# 允许使用 systemctl isolate 命令切换到此 Target# 为 yes 时,切换到本 Target 会停止不属于本 Target 的其他 Unit# 为 no(默认)时,isolate 命令会拒绝执行AllowIsolate=yes
# 忽略隔离请求(当其他 Target 执行 isolate 时,本 Target 保持运行)# IgnoreOnIsolate=no
# ---- 停止行为配置 ----
# 停止本 Target 时,是否同时停止依赖它的 Unit# StopWhenUnneeded=no
# 忽略停止信号(systemctl stop 时保持运行)# RefuseManualStop=no
# 忽略启动信号(systemctl start 时拒绝启动)# RefuseManualStart=no
# ---- 其他元信息 ----
# 启动失败时的默认依赖行为# DefaultDependencies=yes
# 任务超时时间# JobTimeoutSec=0# JobRunningTimeoutSec=0
# 超时后的动作:fail/replace/ignore/isolate# JobTimeoutAction=fail# JobTimeoutRebootArgument=
# ============================================# [Install] 段:定义 Target 如何安装到系统# ============================================[Install]
# 启用时创建符号链接到该 Target 的 .wants 目录# 通常不需要设置,因为 Target 一般直接由其他 Unit 引用# WantedBy=multi-user.target
# 强依赖版本# RequiredBy=
# 别名# Alias=myapp-group.target
# 启用/禁用本 Target 时同时操作的其他 Unit# Also=myapp.service myapp.socket2.3.3 常用命令
# 查看当前默认 Targetsystemctl get-default
# 设置默认启动 Targetsudo systemctl set-default multi-user.targetsudo systemctl set-default graphical.target
# 切换到指定 Target(会停止不属于该 Target 的 Unit)sudo systemctl isolate multi-user.target
# 查看 Target 包含的所有 Unitsystemctl list-dependencies multi-user.target
# 查看当前激活的所有 Targetsystemctl list-units --type=target
# 查看所有可用的 Targetsystemctl list-unit-files --type=target
# 启动/停止 Targetsudo systemctl start myapp.targetsudo systemctl stop myapp.target
# 启用/禁用 Target(通常不需要,因为 Target 由依赖关系驱动)sudo systemctl enable myapp.targetsudo systemctl disable myapp.target2.3.4 自定义应用示例
2.3.4.1 自定义应用组 Target
[Unit]Description=My Application StackDocumentation=https://docs.example.comRequires=basic.target network.targetAfter=basic.target network.targetWants=myapp-db.service myapp-redis.service myapp-api.service myapp-web.serviceAllowIsolate=yes
[Install]WantedBy=multi-user.target2.3.4.2 配合 Service 使用
[Unit]Description=My Application API ServerPartOf=myapp-stack.target # 当 myapp-stack.target 停止时,本服务也停止
[Service]ExecStart=/usr/bin/myapp-apiRestart=on-failure
[Install]WantedBy=myapp-stack.target # 启用时加入 myapp-stack.target 组2.3.5 Target 与 Service 的关键区别
| 特性 | Target | Service |
|---|---|---|
| 功能 | 组织和管理一组 Unit | 运行具体的守护进程 |
| 进程 | 不启动任何进程 | 启动并管理实际进程 |
| 配置段 | 只有 [Unit] 和 [Install] | 有 [Unit]、[Service]、[Install] |
| 启动命令 | 无 ExecStart 等执行命令 | 必须有 ExecStart |
| 状态 | 激活表示依赖的 Unit 已启动 | 激活表示进程正在运行 |
| 用途 | 系统状态/运行级别分组 | 具体服务管理 |
2.4 .timer(定时器 - 替代 cron)
2.4.1 定义
在特定时间或周期触发服务,功能比 cron 更强大,支持依赖管理和日志集中。
2.4.2 配置模板
# ============================================# [Unit] 段:定义 Timer 的元信息和依赖关系# ============================================[Unit]# Timer 单元的简短描述Description=My Application Timer
# 文档链接Documentation=man:systemd.timer(5)# Documentation=https://example.com/docs/myapp-timer
# 启动顺序:在这些 Unit 之后启动After=network.target
# 弱依赖:尽可能启动这些 Unit# Wants=network.target
# 强依赖:必须先启动这些 Unit# Requires=network.target
# 冲突 Unit:不能与这些同时运行# Conflicts=shutdown.target
# 启动条件# ConditionPathExists=/etc/myapp/enabled
# ============================================# [Timer] 段:定义定时触发规则(核心配置)# ============================================[Timer]
# ---- 基础定时触发(OnCalendar)----
# 日历时间触发(类似 cron,但语法更强大)# 格式:DayOfWeek Year-Month-Day Hour:Minute:Second# 支持通配符 * 和范围OnCalendar=*-*-* *:*:00 # 每分钟# OnCalendar=*-*-* 02:00:00 # 每天凌晨 2 点# OnCalendar=Mon *-*-* 09:00:00 # 每周一 9 点# OnCalendar=*-*-01 00:00:00 # 每月 1 号# OnCalendar=*-01-01 00:00:00 # 每年 1 月 1 号# OnCalendar=Mon..Fri 09:00:00 # 工作日 9 点# OnCalendar=*-*-* 09:00,15:00:00 # 每天 9 点和 15 点
# 使用具体日期# OnCalendar=2024-12-25 00:00:00
# 相对时间语法# OnCalendar=*:0/10 # 每 10 分钟# OnCalendar=*-01/3-01 00:00:00 # 每 3 个月的 1 号
# 时区指定(默认系统时区)# OnCalendar=*-*-* 09:00:00 Asia/Shanghai
# ---- 单调时钟触发(Monotonic Timers)----
# 系统启动后触发(开机后)# OnBootSec=5min # 开机 5 分钟后# OnBootSec=10s
# 系统首次激活后触发(systemd 第一次启动)# OnStartupSec=1min
# 本 Timer 单元激活后触发# OnActiveSec=30min # Timer 启动 30 分钟后
# 关联 Unit 激活后触发# OnUnitActiveSec=1h # 上次运行后 1 小时# OnUnitInactiveSec=30min # 上次停止后 30 分钟
# ---- 持久化配置 ----
# 持久化模式:如果系统关机时错过了触发,开机后补执行# 适用于定时备份、日志轮转等不能错过的任务Persistent=true
# 持久化精度(秒),控制补执行的精度范围# AccuracySec=1min
# ---- 随机延迟 ----
# 随机延迟时间,防止多个 Timer 同时触发造成负载高峰# 格式:秒、分钟、小时等RandomizedDelaySec=30 # 随机延迟 0-30 秒# RandomizedDelaySec=5min
# 是否固定随机种子(相同配置产生相同随机数)# FixedRandomDelay=no
# ---- 触发精度 ----
# 触发精度,用于合并相近时间的触发以节省资源# AccuracySec=1min # 默认 1 分钟# AccuracySec=1us # 微秒级(高精度但费电)
# ---- 触发关联配置 ----
# 要触发的 Unit(默认是同名的 .service)# 例如:myapp.timer 默认触发 myapp.serviceUnit=myapp.service
# 触发时传递给 Unit 的属性# 可以覆盖被触发 Unit 的某些设置# PropagatesReloadTo=myapp.service
# ---- 触发限制 ----
# 在日历事件触发后,延迟多久才执行# OnCalendarSec=5min
# 两次触发之间的最小间隔(防抖动)# OnClockChange=yes # 系统时间调整时是否触发
# ---- 其他配置 ----
# 是否保持 Timer 运行(即使关联的 Unit 失败)# RemainAfterElapse=yes
# 是否唤醒休眠的系统# WakeSystem=no # 需要硬件支持 RTC 唤醒
# 硬件时钟使用 UTC 还是本地时间# UTC=no
# ============================================# [Install] 段:定义 Timer 如何安装到系统# ============================================[Install]
# 启用时链接到 timers.target(所有 Timer 的默认目标)WantedBy=timers.target
# 强依赖版本# RequiredBy=timers.target
# 别名# Alias=myapp-cron.timer
# 启用/禁用时同时操作的其他 Unit# Also=myapp.service2.4.3 配套的 .service 文件
[Unit]Description=My Application Service# 不需要 [Install] 段,因为由 Timer 触发
[Service]Type=oneshot # 一次性任务,执行完即退出ExecStart=/usr/local/bin/myapp --run-task
# 如果任务可能运行时间较长,可使用 simple 类型# Type=simple# ExecStart=/usr/local/bin/myapp-daemon
# 环境变量Environment="TASK_TYPE=scheduled"
# 工作目录WorkingDirectory=/var/lib/myapp
[Install]# 通常不需要 WantedBy,因为由 Timer 管理# 但如果需要手动启动,可以保留# WantedBy=multi-user.target2.4.4 OnCalendar 语法详解
DayOfWeek Year-Month-Day Hour:Minute:Second │ │ │ │ │ └── 时:分:秒 │ └───────────────── 年-月-日 └─────────────────────────── 星期几(英文缩写或数字)
特殊值: * 匹配任意值 , 列表分隔(如 1,3,5) .. 范围(如 Mon..Fri) / 步长(如 */10 或 0/10)
示例: *-*-* 00:00:00 每天午夜 Mon *-*-* 09:00:00 每周一 9 点 *-01-01 00:00:00 每年元旦 *-*-01 03:00:00 每月 1 号 3 点 *-*-* 09:00..17:00:00 每天 9 点到 17 点每小时 *:0/5 每 5 分钟 *-01/3-01 00:00:00 每季度第一天 Mon..Fri 08:00:00 工作日早上 8 点 Sat,Sun 10:00:00 周末早上 10 点2.4.5 常用命令
# 查看所有 Timer 状态systemctl list-timers
# 查看所有 Timer(包括未激活的)systemctl list-timers --all
# 查看特定 Timer 状态systemctl status myapp.timer
# 启动/停止/重启 Timersudo systemctl start myapp.timersudo systemctl stop myapp.timersudo systemctl restart myapp.timer
# 启用/禁用开机启动sudo systemctl enable myapp.timersudo systemctl disable myapp.timer
# 查看下次触发时间systemctl list-timers myapp.timer
# 手动触发关联的 Servicesudo systemctl start myapp.service
# 查看 Timer 日志sudo journalctl -u myapp.timer -f
# 查看 Service 日志sudo journalctl -u myapp.service -f
# 验证 OnCalendar 语法(测试下次触发时间)systemd-analyze calendar "*-*-* 02:00:00"systemd-analyze calendar "Mon..Fri 09:00:00"
# 验证 Timer 配置systemd-analyze verify /etc/systemd/system/myapp.timer2.4.6 数据库备份示例
[Unit]Description=Daily Database Backup Timer
[Timer]OnCalendar=*-*-* 02:00:00 # 每天凌晨 2 点RandomizedDelaySec=10min # 随机延迟 0-10 分钟,避免所有服务器同时备份Persistent=true # 如果错过(如关机),开机后补执行
[Install]WantedBy=timers.target[Unit]Description=Daily Database Backup
[Service]Type=oneshotExecStart=/usr/local/bin/backup-db.shUser=backupGroup=backup2.5 .mount / .automount(挂载管理)
2.5.1 定义
管理文件系统挂载,替代 /etc/fstab 中的条目,支持依赖和自动挂载。
2.5.2 配置模板
# ============================================# [Unit] 段:定义 Mount 的元信息和依赖关系# ============================================[Unit]# Mount 单元的描述Description=Data Partition Mount
# 文档链接Documentation=man:systemd.mount(5)
# ---- 依赖关系 ----
# 强依赖:挂载前必须先启动这些 Unit# 通常需要 local-fs-pre.target 和块设备Requires=local-fs-pre.targetRequires=dev-disk-by\x2duuid-12345678\x2d1234\x2d1234\x2d1234\x2d123456789abc.device
# 弱依赖# Wants=
# 启动顺序:在这些 Unit 之后挂载After=local-fs-pre.targetAfter=dev-disk-by\x2duuid-12345678\x2d1234\x2d1234\x2d1234\x2d123456789abc.device# After=network.target # 网络文件系统需要
# 启动顺序:在这些 Unit 之前挂载Before=local-fs.target
# 冲突 UnitConflicts=umount.target# Conflicts=shutdown.target
# ---- 启动条件 ----
# 条件判断# ConditionPathExists=/dev/sdb1# ConditionDirectoryNotEmpty=/mnt/data
# ============================================# [Mount] 段:定义挂载参数(核心配置)# ============================================[Mount]
# ---- 挂载源和目标(必需)----
# 要挂载的设备或远程路径What=/dev/disk/by-uuid/12345678-1234-1234-1234-123456789abc# What=/dev/sdb1# What=192.168.1.100:/nfs/export # NFS 远程路径# What=//192.168.1.100/share # SMB/CIFS 路径
# 挂载点(必须是绝对路径,且单元文件名必须匹配)# 例如:mnt-data.mount 对应 /mnt/dataWhere=/mnt/data
# ---- 文件系统类型 ----
# 文件系统类型Type=ext4# Type=xfs# Type=btrfs# Type=nfs# Type=cifs# Type=vfat# Type=auto # 自动检测
# ---- 挂载选项 ----
# 挂载选项(逗号分隔,无空格)Options=defaults,noatime,discard# Options=rw,sync,noexec,nosuid,nodev# Options=username=user,password=pass,uid=1000 # CIFS 认证
# 额外的挂载标志# SloppyOptions=no # 是否忽略不支持的挂载选项
# ---- 挂载模式 ----
# 是否以只读方式挂载# ReadOnlyOnly=no # 只尝试只读挂载
# 是否强制作为绑定挂载# BindPaths=# BindReadOnlyPaths=
# 是否作为移动挂载(mount --move)# MoveDirectory=no
# ---- 挂载前准备 ----
# 挂载前执行的命令# ExecMount=/bin/mount ...
# 挂载后执行的命令# ExecUnmount=/bin/umount ...
# 挂载前创建挂载点目录# DirectoryMode=0755
# ---- 超时与重试 ----
# 挂载超时时间TimeoutSec=30# TimeoutSec=0 # 0 表示无超时
# 挂载失败时的重试次数# Retry=0
# ============================================# [Install] 段:定义 Mount 如何安装到系统# ============================================[Install]
# 启用时链接到 local-fs.target(本地文件系统目标)WantedBy=local-fs.target
# 网络文件系统应使用 remote-fs.target# WantedBy=remote-fs.target
# 强依赖版本# RequiredBy=local-fs.target
# 别名# Alias=data.mount2.5.3 命名规则(重要)
| 单元文件路径 | 挂载点路径 |
|---|---|
mnt-data.mount | /mnt/data |
home-user-data.mount | /home/user/data |
mnt-usb\x2dbackup.mount | /mnt/usb-backup(- 用 \x2d 转义) |
dev-sdb1.mount | /dev/sdb1(不推荐直接挂载设备) |
转义规则:
-
-→\x2d -
/→-(路径分隔符) -
其他特殊字符使用
\xHH十六进制编码
2.5.4 Mount vs Automount 对比
| 特性 | .mount | .automount |
|---|---|---|
| 触发时机 | 系统启动时立即挂载 | 首次访问挂载点时挂载 |
| 卸载行为 | 系统关机时才卸载 | 空闲超时时自动卸载 |
| 资源占用 | 始终占用资源 | 按需占用,节省资源 |
| 适用场景 | 系统必需分区 | 移动设备、网络共享、备份盘 |
| 依赖关系 | 需等待设备就绪 | 设备就绪后仍延迟挂载 |
| 用户体验 | 启动时可能变慢 | 首次访问可能有延迟 |
2.5.5 常用命令
# 查看所有挂载点systemctl list-units --type=mount
# 查看所有自动挂载点systemctl list-units --type=automount
# 启动/停止挂载sudo systemctl start mnt-data.mountsudo systemctl stop mnt-data.mount
# 重新挂载sudo systemctl restart mnt-data.mount
# 启用/禁用开机挂载sudo systemctl enable mnt-data.mountsudo systemctl disable mnt-data.mount
# 启用/禁用自动挂载sudo systemctl enable mnt-data.automountsudo systemctl disable mnt-data.automount
# 查看挂载状态systemctl status mnt-data.mountsystemctl status mnt-data.automount
# 查看日志sudo journalctl -u mnt-data.mount -f
# 手动触发自动挂载(通过访问挂载点)ls /mnt/data
# 查看当前挂载的文件系统findmntmount | grep mnt-data
# 验证配置systemd-analyze verify /etc/systemd/system/mnt-data.mount2.5.6 自动挂在本地硬盘示例
[Unit]Description=Data Partition Automount
[Automount]Where=/mnt/dataTimeoutIdleSec=300 # 5分钟无访问自动卸载
[Install]WantedBy=local-fs.target[Unit]Description=Data Partition Mount
[Mount]What=/dev/disk/by-uuid/a1b2c3d4-e5f6-7890-abcd-ef1234567890Where=/mnt/dataType=ext4Options=defaults,noatime
[Install]WantedBy=local-fs.target2.5.7 挂载NFS示例
[Unit]Description=NFS Share MountRequires=network-online.targetAfter=network-online.targetBefore=remote-fs.target
[Mount]What=192.168.1.100:/export/shareWhere=/mnt/nfs-shareType=nfsOptions=defaults,_netdev,soft,intr
[Install]WantedBy=remote-fs.target2.5.8 USB 移动设备自动挂载示例
[Unit]Description=USB Drive AutomountAfter=local-fs-pre.target
[Automount]Where=/mnt/usbTimeoutIdleSec=60 # 1分钟无访问自动卸载
[Install]WantedBy=local-fs.target[Unit]Description=USB Drive Mount
[Mount]What=/dev/disk/by-label/MYUSBWhere=/mnt/usbType=autoOptions=defaults,noatime,uid=1000,gid=1000
[Install]WantedBy=local-fs.target2.6 .path(路径监控)
2.6.1 定义
监控文件或目录变化,触发对应的服务执行。适用于文件上传处理、日志轮转等场景。
2.6.2 配置模板
# ============================================# [Unit] 段:定义 Path 单元的元信息和依赖关系# ============================================[Unit]# Path 单元的描述Description=Monitor /var/lib/myapp for changes
# 文档链接Documentation=man:systemd.path(5)
# ---- 依赖关系 ----
# 启动顺序:在这些 Unit 之后启动After=basic.target
# 弱依赖# Wants=
# 强依赖# Requires=
# 冲突 UnitConflicts=shutdown.target
# ============================================# [Path] 段:定义路径监控规则(核心配置)# ============================================[Path]
# ---- 监控路径类型(四种触发条件)----
# 1. 监控目录存在性:当指定目录存在时触发PathExists=/var/lib/myapp/ready
# 2. 监控目录非空:当指定目录存在且非空时触发# PathExistsGlob=/var/lib/myapp/uploads/*
# 3. 监控目录内容变化:当目录内文件被创建、修改、删除时触发PathChanged=/var/lib/myapp/data/
# 4. 监控目录修改时间变化:当目录本身被修改(权限、时间戳等)时触发# PathModified=/var/lib/myapp/config/
# ---- 触发关联配置 ----
# 要触发的 Unit(默认是同名的 .service)# 例如:myapp-monitor.path 默认触发 myapp-monitor.serviceUnit=myapp-processor.service
# 触发时传递给 Unit 的属性# MakeDirectory=yes # 如果路径不存在,是否创建目录# DirectoryMode=0755 # 创建目录时的权限
# ---- 触发行为配置 ----
# 是否触发一次后停止监控(默认 no,持续监控)# TriggerLimitIntervalSec=0# TriggerLimitBurst=0
# 触发间隔限制(防雪崩)# TriggerLimitIntervalSec=2# TriggerLimitBurst=200
# ============================================# [Install] 段:定义 Path 单元如何安装到系统# ============================================[Install]
# 启用时链接到默认目标WantedBy=multi-user.target
# 或者使用 paths.target(所有 path 单元的标准目标)# WantedBy=paths.target2.6.3 配套的 .service 文件
[Unit]Description=Process myapp data changes# 不需要 [Install] 段,由 Path 单元触发
[Service]Type=oneshot # 一次性任务,执行完即退出ExecStart=/usr/local/bin/myapp-processor
# 如果处理可能并发,使用 simple 类型并处理并发# Type=simple# ExecStart=/usr/local/bin/myapp-daemon --process
# 环境变量Environment="WATCH_PATH=/var/lib/myapp/data"
# 工作目录WorkingDirectory=/var/lib/myapp2.6.4 种 Path 触发类型详解
| 类型 | 语法 | 触发条件 | 适用场景 |
|---|---|---|---|
| PathExists | PathExists=/path/to/file | 路径存在时触发一次 | 等待配置文件就绪、设备就绪 |
| PathExistsGlob | PathExistsGlob=/path/* | 匹配 glob 模式的文件存在时触发 | 等待特定类型文件出现 |
| PathChanged | PathChanged=/path/to/dir/ | 目录内容变化时触发(创建/删除/修改文件) | 监控上传目录、数据目录 |
| PathModified | PathModified=/path/to/file | 文件/目录元数据变化时触发(权限、时间戳、内容) | 监控配置文件修改 |
2.6.5 触发行为对比
PathExists: 文件/目录出现 ──→ 触发(只触发一次,除非删除后重新出现) 文件/目录删除 ──→ 无操作
PathExistsGlob: 匹配的文件出现 ──→ 触发 匹配的文件全部删除 ──→ 无操作
PathChanged: 目录内文件创建 ──→ 触发 目录内文件删除 ──→ 触发 目录内文件修改 ──→ 触发 子目录创建/删除 ──→ 触发
PathModified: 文件内容修改 ──→ 触发 文件权限修改 ──→ 触发 文件时间戳修改 ──→ 触发 目录属性修改 ──→ 触发2.6.6 常用命令
# 查看所有 Path 单元状态systemctl list-units --type=path
# 查看所有 Path 单元(包括未激活的)systemctl list-units --type=path --all
# 启动/停止 Path 监控sudo systemctl start myapp-monitor.pathsudo systemctl stop myapp-monitor.path
# 启用/禁用开机监控sudo systemctl enable myapp-monitor.pathsudo systemctl disable myapp-monitor.path
# 查看 Path 单元状态systemctl status myapp-monitor.path
# 查看日志sudo journalctl -u myapp-monitor.path -fsudo journalctl -u myapp-processor.service -f
# 手动触发关联的 Servicesudo systemctl start myapp-processor.service
# 验证配置systemd-analyze verify /etc/systemd/system/myapp-monitor.path2.6.7 配置文件热重载
[Unit]Description=Monitor myapp config changes
[Path]PathModified=/etc/myapp/config.yamlUnit=myapp-reload.service
[Install]WantedBy=multi-user.target[Unit]Description=Reload myapp configuration
[Service]Type=oneshotExecStart=/bin/kill -HUP $(cat /run/myapp.pid)2.7 .slice(资源控制 - cgroup)
2.7.1 定义
组织进程树,进行资源限制(CPU、内存、IO)。是 Linux cgroup 的 systemd 封装。
2.7.2 配置模板
# ============================================# [Unit] 段:定义 Slice 的元信息# ============================================[Unit]# Slice 的描述Description=My Application Resource Slice
# 文档链接Documentation=man:systemd.slice(5)
# ---- 依赖关系 ----
# 启动顺序After=system.slice
# 弱依赖# Wants=
# 强依赖# Requires=system.slice
# 冲突Conflicts=umount.target
# 停止时是否同时停止子单元# StopWhenUnneeded=no
# ============================================# [Slice] 段:定义资源限制(核心配置)# ============================================[Slice]
# ---- CPU 资源控制 ----
# CPU 权重(相对份额),用于 CPU 调度# 范围:1-10000,默认 100# 与其他 Slice 竞争 CPU 时按权重分配CPUWeight=500# CPUShares=500 # 旧版 cgroup v1 语法(已弃用)
# CPU 配额:绝对限制,百分比形式# 100% = 1 个核心,200% = 2 个核心CPUQuota=80%# CPUQuotaPeriodSec=100ms # 配额计算周期
# CPU 亲和性:绑定到特定 CPU 核心# AllowedCPUs=0-3 # 允许使用 0-3 号核心# AllowedCPUs=0,2,4-6 # 混合指定
# NUMA 节点亲和性# AllowedMemoryNodes=0-1
# ---- 内存资源控制 ----
# 内存限制(硬限制,超过会被 OOM killer 终止)MemoryMax=512M# MemoryMax=1G# MemoryMax=infinity # 无限制(默认)
# 内存高水位(软限制,超过时内核优先回收该组内存)MemoryHigh=400M
# 内存交换限制# MemorySwapMax=1G # 最大交换使用量# MemorySwapMax=0 # 禁用交换
# 内存最小保证(预留内存,不被其他组借用)# MemoryMin=256M# MemoryLow=256M # 尽力保证的内存量
# 内存回收压力控制# MemoryPressureWatch=auto# MemoryPressureThresholdSec=100ms
# OOM 控制# OOMPolicy=continue # OOM 时继续运行(默认)# OOMPolicy=stop # OOM 时停止# OOMPolicy=kill # OOM 时杀死进程
# ---- IO 资源控制 ----
# IO 权重(相对份额),用于块设备调度# 范围:1-10000,默认 100IOWeight=500# BlockIOWeight=500 # 旧版语法
# 特定设备的 IO 限制# IOReadBandwidthMax=/dev/sda 10M# IOWriteBandwidthMax=/dev/sda 10M# IOReadIOPSMax=/dev/sda 100# IOWriteIOPSMax=/dev/sda 100
# IO 延迟目标(控制 IO 延迟)# IODeviceLatencyTargetSec=/dev/sda 10ms
# ---- 网络资源控制 ----
# 网络带宽限制(需要内核支持,通常通过 tc 实现)# NetworkIngressMax=100M# NetworkEgressMax=100M
# IP 防火墙规则(通过 BPF/cgroup 实现)# IPAddressAllow=192.168.0.0/16# IPAddressDeny=any
# ---- 任务/进程数量控制 ----
# 最大任务数(线程/进程)TasksMax=1000# TasksMax=infinity
# 最大进程数# TasksAccounting=yes # 启用任务统计
# ---- 设备访问控制 ----
# 允许访问的设备DeviceAllow=/dev/null rwDeviceAllow=/dev/zero rwDeviceAllow=/dev/random rDeviceAllow=/dev/urandom r# DeviceAllow=char-pts rw # 允许伪终端
# 拒绝访问的设备(默认策略)DevicePolicy=closed# DevicePolicy=auto # 自动(根据类型)# DevicePolicy=strict # 严格(只允许明确允许的)# DevicePolicy=closed # 关闭(只允许白名单)
# ---- 控制组属性 ----
# 是否委托控制组给子进程管理# Delegate=yes# Delegate=cpu cpuset io memory # 委托特定控制器
# 控制组内存回收策略# MemoryAccounting=yes # 启用内存统计(默认启用)
# 控制组 CPU 统计# CPUAccounting=yes # 启用 CPU 统计(默认启用)
# IO 统计# IOAccounting=yes # 启用 IO 统计(默认启用)
# IP 统计# IPAccounting=yes # 启用网络统计
# 控制组层级中的权重# Slice=machine.slice # 指定父 Slice
# ---- 其他资源控制 ----
# 文件描述符限制(继承到子单元)# LimitNOFILE=65535
# 进程数限制(继承到子单元)# LimitNPROC=1024
# 核心转储限制# LimitCORE=0
# 文件大小限制# LimitFSIZE=1G
# 虚拟内存限制# LimitAS=2G
# 栈大小限制# LimitSTACK=8M
# ============================================# [Install] 段:通常不需要,Slice 由依赖关系驱动# ============================================[Install]
# Slice 通常不需要 [Install] 段# 因为它们被其他单元引用而非直接启用# WantedBy=2.7.3 Slice 层级结构
system.slice (系统默认 Slice)├── user.slice (用户会话)│ ├── user-1000.slice (UID 1000 用户)│ │ ├── session-1.scope (会话 1)│ │ └── session-2.scope (会话 2)│ └── user-1001.slice (UID 1001 用户)├── machine.slice (容器/虚拟机)│ ├── libvirt-lxc\x2dvm1.scope│ └── docker-abc123.scope└── myapp.slice (自定义应用组) ├── myapp-web.service ├── myapp-api.service └── myapp-db.service2.7.4 命名规则
| 单元文件路径 | 层级位置 |
|---|---|
myapp.slice | 直接位于根层级(与 system.slice 同级) |
myapp-frontend.slice | myapp.slice 的子 Slice |
myapp-backend.slice | myapp.slice 的子 Slice |
system-myapp.slice | 位于 system.slice 下 |
命名约定:
-
使用
-连接父子层级 -
顶层 Slice 建议放在 system.slice 下(
system-xxx.slice) -
用户相关放在 user.slice 下
2.7.5 常用命令
# 查看所有 Slicesystemctl list-units --type=slice
# 查看 Slice 树状结构systemd-cgls
# 查看资源使用情况systemd-cgtop
# 查看特定 Slice 状态systemctl status myapp.slice
# 启动/停止 Slicesudo systemctl start myapp.slicesudo systemctl stop myapp.slice
# 查看 cgroup 文件系统ls /sys/fs/cgroup/system.slice/myapp.slice/
# 实时查看资源使用cat /sys/fs/cgroup/system.slice/myapp.slice/memory.currentcat /sys/fs/cgroup/system.slice/myapp.slice/cpu.stat
# 查看资源限制cat /sys/fs/cgroup/system.slice/myapp.slice/memory.maxcat /sys/fs/cgroup/system.slice/myapp.slice/cpu.max2.7.6 数据库资源隔离示例
[Unit]Description=MyApp Database Tier Resources
[Slice]# 数据库需要更多 CPUCPUWeight=800
# 内存:2GB 限制,1.5GB 高水位MemoryHigh=1536MMemoryMax=2G
# 禁用交换(数据库性能考虑)MemorySwapMax=0
# IO:最高优先级IOWeight=1000
# 允许访问块设备(用于原始设备访问)DeviceAllow=/dev/sda rwDeviceAllow=block-blkext rw2.8 .scope(外部进程组)
2.8.1 定义
由外部程序创建的进程组,systemd 只负责管理生命周期,不启动进程。主要用于用户会话和容器。
2.8.2 与 Service 的区别
| 特性 | Service | Scope |
|---|---|---|
| 启动方式 | systemd 启动 | 外部程序创建 |
| 主进程 | systemd 管理 | 外部管理 |
| 使用场景 | 守护进程 | 用户会话、容器 |
| 配置 | 完整配置 | 主要是资源限制 |
2.8.3 配置模板
# 通过 systemd-run 创建的临时 scope 示例# ============================================# [Unit] 段:定义 Scope 的元信息# ============================================[Unit]# Scope 的描述Description=User Session 123
# 文档链接Documentation=man:systemd.scope(5)
# ---- 依赖关系 ----
# 启动顺序After=systemd-logind.service
# 弱依赖# Wants=
# 强依赖# Requires=
# 冲突Conflicts=shutdown.target
# 停止时是否同时停止# StopWhenUnneeded=no
# 忽略停止信号# RefuseManualStop=no
# ============================================# [Scope] 段:定义进程组管理参数(核心配置)# ============================================[Scope]
# ---- 进程管理 ----
# 要管理的初始 PID(创建时指定)# PIDs=1234 # 主进程 PID# PIDs=1234 1235 1236 # 多个初始 PID
# 运行时目录# RuntimeDirectory=session-123
# 运行时目录权限# RuntimeDirectoryMode=0755
# 状态目录# StateDirectory=
# 缓存目录# CacheDirectory=
# 日志目录# LogsDirectory=
# 配置目录# ConfigurationDirectory=
# ---- 资源控制(与 Slice 相同)----
# CPU 权重CPUWeight=100
# CPU 配额# CPUQuota=
# 内存限制MemoryMax=1G# MemoryHigh=800M
# 交换限制# MemorySwapMax=
# IO 权重IOWeight=100
# 任务数限制TasksMax=1000
# 设备访问控制DevicePolicy=auto# DeviceAllow=
# ---- 控制组委托 ----
# 是否委托 cgroup 给子进程# Delegate=yes # 委托所有控制器# Delegate=cpu memory io # 委托特定控制器
# 委托属性# DelegateSubgroup=
# ---- _KILL 模式_ ----
# 终止模式KillMode=mixed # mixed: 主进程 SIGTERM,其他 SIGKILL# KillMode=control-group # 终止整个控制组# KillMode=process # 仅终止主进程# KillMode=none # 不终止任何进程
# 终止信号KillSignal=SIGTERM
# 最终终止信号FinalKillSignal=SIGKILL
# 发送终止信号前的等待时间TimeoutStopSec=30
# 是否发送 SIGHUPSendSIGHUP=yes
# 是否发送 SIGKILLSendSIGKILL=yes
# ---- 标准输入输出 ----
# 标准输入StandardInput=null# StandardInput=tty# StandardInput=tty-force
# 标准输出StandardOutput=journal# StandardOutput=inherit # 继承调用者的输出# StandardOutput=null# StandardOutput=tty
# 标准错误StandardError=journal# StandardError=inherit# StandardError=null# StandardError=tty
# 终端设置# TTYPath=/dev/tty1# TTYReset=yes# TTYVHangup=yes# TTYVTDisallocate=yes
# ---- 环境变量 ----
# 环境变量# Environment=VAR=value
# 环境变量文件# EnvironmentFile=
# 是否传递环境变量给子进程# PassEnvironment=
# ---- 其他控制组设置 ----
# 控制组属性# Slice=session.slice
# 内存压力监控# MemoryPressureWatch=auto# MemoryPressureThresholdSec=100ms
# OOM 策略OOMPolicy=continue# OOMPolicy=stop# OOMPolicy=kill
# ============================================# [Install] 段:通常不需要,Scope 是动态的# ============================================[Install]
# Scope 通常是动态创建的,不需要 [Install]# WantedBy=2.8.4 创建 Scope 的方式
2.8.4.1 使用 systemd-run 🌟
# 创建临时 scope 运行命令systemd-run --scope --unit=myjob --slice=myapp.slice --property=MemoryMax=512M ./my-script.sh
# 创建交互式 scopesystemd-run --scope --unit=interactive-session --uid=$(id -u) --gid=$(id -g) /bin/bash
# 指定更多资源限制systemd-run --scope \ --unit=heavy-job \ --property=CPUWeight=200 \ --property=MemoryMax=2G \ --property=IOWeight=50 \ ./heavy-processing.sh2.8.4.2 使用 D-Bus API(程序化)
package main
import ( "context" "fmt" "os" "os/exec" "syscall"
"github.com/godbus/dbus/v5")
// SystemdManager 封装 systemd D-Bus 调用type SystemdManager struct { conn *dbus.Conn object dbus.BusObject}
// NewSystemdManager 连接到 systemd D-Busfunc NewSystemdManager() (*SystemdManager, error) { conn, err := dbus.SystemBus() if err != nil { // 尝试用户 bus(用于用户级 systemd) conn, err = dbus.UserBus() if err != nil { return nil, fmt.Errorf("连接 D-Bus 失败: %w", err) } }
object := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
return &SystemdManager{ conn: conn, object: object, }, nil}
// RunInScope 在指定 scope 中运行命令func (m *SystemdManager) RunInScope( ctx context.Context, unitName string, slice string, properties map[string]interface{}, cmd string, args ...string,) error {
// 准备 transient unit 的属性 props := []dbus.Property{ { Name: "Description", Value: dbus.MakeVariant(fmt.Sprintf("Scope for %s", unitName)), }, { Name: "PIDs", Value: dbus.MakeVariant([]uint32{uint32(os.Getpid())}), }, }
// 指定 slice if slice != "" { props = append(props, dbus.Property{ Name: "Slice", Value: dbus.MakeVariant(slice), }) }
// 添加自定义属性(资源限制等) for name, value := range properties { props = append(props, dbus.Property{ Name: name, Value: dbus.MakeVariant(value), }) }
// 辅助单元(无) aux := []dbus.UnitAux{}
// 调用 StartTransientUnit var jobPath dbus.ObjectPath err := m.object.CallWithContext( ctx, "org.freedesktop.systemd1.Manager.StartTransientUnit", 0, unitName, "fail", // 模式: fail/replace/ignore-dependencies/isolate props, aux, ).Store(&jobPath)
if err != nil { return fmt.Errorf("创建 scope 失败: %w", err) }
fmt.Printf("Scope %s created, job: %s\n", unitName, jobPath)
// 执行实际命令 command := exec.CommandContext(ctx, cmd, args...) command.Stdin = os.Stdin command.Stdout = os.Stdout command.Stderr = os.Stderr
// 设置进程组,确保信号正确传递 command.SysProcAttr = &syscall.SysProcAttr{ Setpgid: true, }
return command.Run()}
func main() { manager, err := NewSystemdManager() if err != nil { fmt.Fprintf(os.Stderr, "错误: %v\n", err) os.Exit(1) }
// 资源限制属性 properties := map[string]interface{}{ "MemoryMax": uint64(512 * 1024 * 1024), // 512M }
// 运行命令 err = manager.RunInScope( context.Background(), "myjob.scope", "myapp.slice", properties, "./my-script.sh", )
if err != nil { fmt.Fprintf(os.Stderr, "执行失败: %v\n", err) os.Exit(1) }}2.8.5 常用命令
# 查看所有 Scopesystemctl list-units --type=scope
# 查看特定 Scope 状态systemctl status session-1.scope
# 停止 Scope(会终止其中所有进程)sudo systemctl stop myscope.scope
# 查看 Scope 中的进程systemd-cgls /user.slice/user-1000.slice/session-1.scope
# 查看资源使用systemd-cgtop --batch | grep scope
# 查看 cgroup 文件ls /sys/fs/cgroup/user.slice/user-1000.slice/session-1.scope/
# 将现有进程移入 Scope(通过 busctl)busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager AttachProcessesToUnit 'sau' myscope.scope 0 1234 1235
# 创建临时 scope 并立即执行systemd-run --scope --unit=temporary --collect /path/to/command2.8.6 容器/沙箱环境示例
# 通过 systemd-nspawn 创建的容器 scope[Unit]Description=Container web
[Scope]Slice=machine.sliceCPUWeight=200MemoryMax=4GMemorySwapMax=0TasksMax=10000DevicePolicy=closedDeviceAllow=/dev/null rwDeviceAllow=/dev/zero rwDeviceAllow=/dev/random rDeviceAllow=/dev/urandom rDeviceAllow=eth0 rw
# 网络命名空间PrivateNetwork=no
# 用户命名空间PrivateUsers=yes
# 挂载命名空间PrivateMounts=yes3. 结尾
没了喵,谢谢欣赏

文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!