跳转至

systemd

  • 以下文档来源: https://zhuanlan.zhihu.com/p/472379780

Systemd 是 Linux 的系统和服务的管理器,兼容 SysV 和 LSB 初始化脚本,Systemd 有以下特性:

  • 积极的并行化能力
  • 使用套接字和 D-Bus 激活来启动服务
  • 提供按需启动守护进程,使用 Linux cgroups 跟踪进程
  • 支持系统状态的快照和恢复
  • 维护挂载和自动挂载点
  • 实现精心设计的基于事务依赖的服务控制逻辑

systemctl 命令是管理 systemd 的主要工具,它将 SysVinit service 和 chkconfig 命令的功能结合到一个工具中,您可以使用它来永久启用和禁用服务或仅针对当前会话启用和禁用服务

1 unit 类型

Systemd 管理 unit,它们是系统资源和服务的表示,以下列表显示了 systemd 可以管理的 unit 类型:

  • service 系统上的一项服务,包括启动、重新启动和停止服务
  • socket 与服务关联的网络套接字
  • device 使用 systemd 专门管理的设备
  • mount 由 systemd 管理的挂载点
  • automount 启动时自动挂载的挂载点
  • swap 交换系统上的空间
  • target 其它 unit 的同步点,通常用于在启动时启动启用的服务
  • path 基于路径激活的路径。例如,您可以根据某条路径的状态(例如是否存在)来启动服务
  • timer 用于安排激活另一个单元的计时器。
  • snapshot 当前 systemd 状态的快照。通常用于在对 systemd 进行临时更改后回滚
  • slice 通过 Linux 控制组节点 (cgroups) 限制资源
  • scope 来自 systemd 总线接口的信息。通常用于管理外部系统进程

2 systemctl 常用命令

systemctl start chronyd #启动
systemctl stop chronyd #停止
systemctl restart chronyd #重启
systemctl status chronyd #查看 unit 状态

systemctl enable chronyd #设置 unit 开启启动
systemctl disable chronyd #取消 unit 开机启动
systemctl is-enabled chronyd #查看 unit 是否开机启动
systemctl list-dependencies rpcbind #查看依赖关系
# 重新加载 unit 的配置文件,每次修改了 unit 的配置文件后,需要执行以下命令重新加载 unit 的配置文件
systemctl daemon-reload

systemctl mask chronyd #屏蔽 unit,屏蔽后 unit 无法启动
systemctl unmask chronyd #取消屏蔽

# 更多命令可通过 systemctl --help 或 man systemctl 来查看

3 unit 配置文件

配置文件放在 /usr/lib/systemd/system目录

sshd 服务的 unit 配置文件如下所示,主要分为三部分:[Unit][Service][Install]

[root@vm03 ~]# cat /usr/lib/systemd/system/sshd.service
[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

3.1 [Unit] 部分详解

此部分中使用的参数不仅限于 service 类型的 unit,对其它类型 unit 也是通用的,有关这些参数及其说明的完整列表,可运行命令 man systemd.unit 或访问 systemd.unit 中文手册

注意:在 [unit] 块中的每个参数后都可以指定一个以空格分隔的列表

  • Description:当前 unit 的描述
  • Documentation:文档地址,仅接受类型为:http://、https://、file:、info:、man: 的URI
  • Requires:表示本 unit 与其它 unit 之间存在强依赖关系,如果本 unit 被激活,此处列出的 unit 也会被激活,如果其中一个依赖的 unit 无法激活,systemd 都不会启动本 unit
  • Wants:与 Requires 类似,区别在于如果依赖的 unit 启动失败,不影响本 unit 的继续运行
  • After:表示本 unit 应该在某服务之后启动,选项可参考 systemd.special 中文手册
  • Before:表示本 unit 应该在某服务之前启动,After 和 Before 字段只涉及启动顺序,不涉及依赖关系
  • BindsTo:与 Requires 类似,当指定的 unit 停止时,也会导致本 unit 停止
  • PartOf:与 Requires 类似,当指定的 unit 停止或重启时,也会导致本 unit 停止或重启
  • Conflicts:如果指定的 unit 正在运行,将导致本 unit 无法运行
  • OnFailure:当本 unit 进入故障状态时,激活指定的 unit

4 [Service] 部分详解

4.1 service 专有参数

只有 service 类型的 unit 才有这些参数,参数完整列表请访问 systemd.service 中文手册 或 systemd.service - type - simple(默认值):服务为主进程启动,systemd 认为该服务将立即启动,服务进程不会 fork ,如果该服务要启动其他服务,则不要使用此类型启动,除非该服务是 socket 激活型。 - forking:服务将以 fork 分叉的方式启动,此时父进程将会退出,子进程将成为主进程。systemd 认为当该服务进程 fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon)除非你确定此启动方式无法满足需求,否则使用此类型启动即可。使用此启动类型应同时指定 PIDFile= 以便 systemd 能够跟踪服务的主进程 - oneshot:类似于 simple,但只执行一次,Systemd 会等它执行完,才启动其它服务。这一选项适用于只执行一项任务,随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态 - dbus:类似于 simple,但会等待 D-Bus 信号后启动。当指定的 BusName 出现在 DBus 系统总线上时,systemd 认为服务就绪。 - notify:类似于 simple,启动完毕后会通知 Systemd ,然后继续往下执行 - idle:类似于 simple,但是要等到其它所有任务都执行完,才会启动该服务 - RemainAfterExit:一个布尔值,它指定即使服务的所有进程都已退出,也应将服务视为活动的,默认为 no - GuessMainPID:一个布尔值,指定 systemd 在无法可靠确定服务的主 PID 时是否应该猜测它。除非设置了 Type=forking 并且未设置 PIDFile,否则此选项将被忽略,默认为 yes - PIDFile:指向此守护进程的 PID 文件的绝对文件名。对于 Type=forking 的服务,建议使用此选项。Systemd 在服务启动后读取守护进程的主进程的 PID。Systemd 不会写入此处配置的文件,但它会在服务关闭后删除该文件 - BusName:到达此服务的 D-Bus 总线名称。对于 Type=dbus 的服务,此选项是必需的 - ExecStart:服务启动时执行的命令和参数 - ExecStartPre: 服务启动之前执行的命令 - ExecStartPost: 服务启动之后执行的命令 - ExecReload:重启服务时执行的命令 - ExecStop:服务停止时执行的命令和参数 - ExecStopPost:服务停止之后执行的命令 - RestartSec:重新启动服务前的睡眠时间(以秒为单位) - TimeoutStartSec:等待服务启动的时间(以秒为单位) - TimeoutStopSec:等待服务停止的时间(以秒为单位) - TimeoutSec:同时配置 TimeoutStartSec 和 TimeOutshopSec 的缩写 - Restart:配置当服务的进程退出、被杀死或达到超时时是否重新启动服务,可以设置的值如下: - no 服务将不会重新启动,这是默认设置 - on-success 仅当服务进程完全退出时重新启动(退出代码 0) - on-failure 仅在服务进程异常退出时重启,所谓“异常退出”是指:退出码不为 "0" - on-abnormal 如果进程因信号或超时而终止时重新启动 - on-watchdog 当看门狗超时而重新启动 - on-abort 如果进程由于未指定为干净退出状态的未捕获信号而退出,则重新启动 - always 总是重新启动 - Environment:指定环境变量, 多个环境变量空格分开。 - KillMode:定义 systemd 如何停止服务,可以设置的值如下: - control-group(默认值):当前控制组里面的所有子进程,都会被杀掉 - process:只杀主进程 - mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号 - none:没有进程会被杀掉,只是执行服务的 stop 命令

4.2 进程执行环境

下面列出了 service, socket, mount, swap 单元所共有的、用于定义进程执行环境的配置选项,完整参数说明可运行命令 man systemd.exec 或访问 systemd.exec 中文手册

  • WorkingDirectory:设置进程的工作目录
  • User=, Group:设置进程在执行时使用的用户与组,既可以设为一个数字形式的 UID/GID 也可以设为一个字符串形式的名称
  • NoNewPrivileges:接收一个布尔值。设为 yes 表示该服务的所有进程与子进程都不能通过 execve() 调用获得任何新权限。该选项是最简单也是最有效的防止进程提升权限的方法
  • SELinuxContext:设置进程的 SELinux 安全上下文
  • 设置进程的各种软/硬资源限制:LimitCPU=, LimitFSIZE=, LimitDATA=, LimitSTACK=, LimitCORE=, LimitRSS=, LimitNOFILE=, LimitAS=, LimitNPROC=, LimitMEMLOCK=, LimitLOCKS=, LimitSIGPENDING=, LimitMSGQUEUE=, LimitNICE=, LimitRTPRIO=, LimitRTTIME= 这些指令的值有两种表示法,一个单独的 value 值表示将软硬两种限制设为同一个值。 而冒号分隔的 soft:hard 值表示分别设置软限制与硬限制 (例如 LimitAS=4G:16G),特殊值 infinity 表示没有限制。对于以字节为单位的选项,可以使用以 1024 为基数的 K, M, G, T, P, E 后缀 (例如 LimitAS=16G)。对于时间限制,可以加上 "ms"(毫秒), "s"(秒), "min"(分钟), "h"(小时), "d"(天), "w"(周) 等明确的时间单位后缀

  • UMask:设置文件创建掩码,默认值为 0022
  • OOMScoreAdjust:设置进程因内存不足而被杀死的优先级。可设为 -1000(禁止被杀死) 到 1000(最先被杀死) 之间的整数值
  • Environment:设置进程的环境变量,接受一个空格分隔的 VAR=VALUE 列表。可以多次使用此选项以增加新的变量或者修改已有的变量 (同一个变量以最后一次设置为准)。设为空表示清空先前所有已设置的变量。注意:(1) 不会在字符串内部进行变量展开 (也就是 $ 没有特殊含义);(2) 如果值中包含空格或者等号,那么必须在字符串两边使用双引号 (") 界定,例如:Environment="VAR1=word1 word2" VAR2=word3 "VAR3= $word 5 6" 表示设置了 "VAR1", "VAR2", "VAR3" 三个变量,其值分别为 "word1 word2", "word3", "$word 5 6"
  • EnvironmentFile:与 Environment= 类似,不同之处在于此选项是从文本文件中读取环境变量的设置。文件中的空行以及以分号 (;) 或井号 (#) 开头的行会被忽略, 其他行的格式必须符合 VAR=VALUE 的 shell 变量赋值语法。 行尾的反斜杠 () 将被视为续行符,这与 shell 语法类似。若想在变量值中包含空格, 则必须在值的两端加上双引号 (") 界定。文件必须用绝对路径表示 (可以包含通配符)。但可在路径前加上 "-" 前缀表示忽略不存在的文件。可以多次使用此选项,以从多个不同的文件中读取设置。若设为空,则表示清空所有先前已经从文件中读取的环境变量。这里列出的文件将在进程启动前的瞬间被读取,因此可以由前一个单元生成配置文件, 再由后一个单元去读取它。从文件中读取的环境变量会覆盖 Environment= 中设置的同名变量。文件的读取顺序就是它们出现在单元文件中的顺序,并且对于同一个变量,以最后读取的文件中的设置为准
  • UnsetEnvironment:明确撤销该单元的特定环境变量
  • StandardInput:设置进程的标准输入 (STDIN),可设为 null, tty, tty-force, tty-fail, data, file:path, socket, fd: name 之一,此选项的默认值是 null
  • StandardOutput:设置进程的标准输出 (STDOUT),可设为 inherit, null, tty, journal, syslog, kmsg, journal+console, syslog+console, kmsg+console, file:path, append:path, socket, fd:name 之一
  • StandardError:设置进程的标准错误 (STDERR),取值范围及含义与 StandardOutput= 相同
  • StandardInputText, StandardInputData:设置通过标准输入 (STDIN) 传递给进程的任意文本或二进制数据,这些选项仅在 StandardInput=data 时有意义
  • LogLevelMax:按此日志级别过滤该单元生成的日志消息,可设为一个 syslog 日志级别,也就是 emerg(最低日志级别,仅显示最致命的消息), alert, crit, err, warning, notice, info, debug(最高日志级别,显示最细致的调试消息) 之一

4.3 进程环境变量

详细说明访问 systemd.exec 中文手册

  • $PATH :可执行文件的目录列表 (冒号分隔的绝对路径),此值固定为 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  • $LANG :本地化设置。可以通过 locale.conf (5) 文件设置
  • $USER, $LOGNAME, $HOME, $SHELL :用户名, 用户名, 家目录, 登录 shell
  • $MAINPID :单元主进程的 PID (如果能确定的话)

4.4 如何杀死进程

service, socket, mount, swap, scope 单元都拥有一组如何杀死进程的配置选项,完整参数说明可运行命令 man systemd.kill 或访问 systemd.kill 中文手册

KillMode:设置在单元停止时,杀死进程的方法。取值范围如:control-group, process, mixed, none 默认值是 control-group 表示杀死该单元的 cgroup 内的所有进程 (对于 service 单元,还要先执行 ExecStop= 动作)

KillSignal:设置杀死进程的第一步使用什么信号,所有可用的信号详见 signal(7) 手册。 默认值为 SIGTERM 信号。注意, systemd 会无条件的紧跟此信号之后再发送一个 SIGCONT 信号, 以确保干净的杀死已挂起 (suspended) 的进程

SendSIGHUP:是否在第一步发送 KillSignal= 信号后,立即紧跟着向该单元的所有进程再发送一个 SIGHUP 信号。这主要用于通知 shell 之类的进程,它们的连接已中断。默认为 "no"

SendSIGKILL:是否在超过 TimeoutStopSec= 时间后,使用 SIGKILL 或 FinalKillSignal= 信号杀死依然残存的进程。默认值为 "yes"

FinalKillSignal:当发生了超时并且已开启 SendSIGKILL= 时, 发送哪个信号给剩余的进程。应当设置为不能被服务捕获和处理的信号 (SIGTERM 就不合适)。对于开发人员来说,可以使用它来生成 coredump 以了解为什么服务在接收到最初的 SIGTERM 信号时没有正确终止。具体做法是设置 LimitCORE= 并且将 FinalKillSignal= 设为 SIGQUIT 或 SIGABRT 之一。此选项的默认值为 SIGKILL

WatchdogSignal:在看门狗 (WatchdogSec=) 发生超时的情况下,使用哪个信号终止服务。此选项的默认值为 SIGABRT

4.5 进程资源控制

slice, scope, service, socket, mount, swap 单元共享一组用于限制进程资源占用的配置选项,本质上,这些选项依赖于 Linux 内核的 cgroups 功能,将一组进程组织成树形层次结构,并对其允许占用的各种资源进行限制,有关这些参数及其说明的完整列表,可运行命令 man systemd.resource-control 或访问 systemd.resource-control 中文手册

5 [Install] 部分详解

[install] 定义了 unit 的安装信息,此部分配置仅在 systemctl enable 或 systemctl disable 时使用,在 unit 运行时不解释此部分,相当于是配置如何开机启动

此部分中使用的参数不仅限于 service 类型的 unit,对其它类型的 unit 也是通用的,有关这些参数及其说明的完整列表,可运行命令 man systemd.unit 或访问 systemd.unit 中文手册

  • Alias:当前 unit 可用于启动的别名,此处列出的名称必须与服务文件名具有相同的后缀(即类型),在执行 systemctl enable 时将创建从这些名称到 unit 文件名的符号链接
  • RequiredBy:表示该服务所在的 Target,它的值是一个或多个 Target,当 systemctl enable 时 unit 符号链接会放入 /etc/systemd/system 目录下面以 Target 名 + .required 后缀构成的子目录中
  • WantedBy:表示该服务所在的 Target,它的值是一个或多个 Target,当前 systemctl enable 时 unit 符号链接会放入 /etc/systemd/system 目录下面以 Target 名 + .wants 后缀构成的子目录中
  • Also:当 systemctl enable 或 systemctl disable 时会同时 enable 和 disable 的其它 unit 列表

5.1 Target 含义

Target 的含义是服务组,表示一组服务 WantedBy=multi-user.target 指的是,unit 所在的 Target 是 multi-user.target(多用户模式)

这个设置非常重要,因为执行 systemctl enable 是会将 unit 链接到 /etc/systemd/system/multi-user.target.wants 目录之中,实现 开机启动的功能

6 常见 unit 配置文件

# nginx的unit 配置文件
cat /usr/lib/systemd/system/nginx.service
[Unit]
Description=nginx - high performance web server
Documentation=http://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/usr/local/nginx/run/nginx.pid
ExecStart=/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
ExecReload=/bin/sh -c "/bin/kill -s HUP $(/bin/cat /usr/local/nginx/run/nginx.pid)"
ExecStop=/bin/sh -c "/bin/kill -s TERM $(/bin/cat /usr/local/nginx/run/nginx.pid)"

[Install]
WantedBy=multi-user.target

# redis的unit 配置文件
[Unit]
Description=Redis data structure server
Documentation=https://redis.io/documentation
Wants=network-online.target
After=network-online.target

[Service]
ExecStart=/usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf
LimitNOFILE=10032
NoNewPrivileges=yes
OOMScoreAdjust=-900
Type=notify
User=redis
Group=redis

[Install]
WantedBy=multi-user.target

# mysql的unit 配置文件
[Unit]
Description=mysql server
Documentation=https://dev.mysql.com/doc/refman/5.7/en/using-systemd.html
After=network.target
After=syslog.target

[Service]
User=mysql
Group=mysql
Type=forking
PIDFile=/usr/local/mysql/mysqld.pid
ExecStart=/usr/local/mysql/bin/mysqld --daemonize --pid-file=/usr/local/mysql/mysqld.pid $MYSQLD_OPTS
LimitNOFILE=5000
Restart=on-failure

[Install]
WantedBy=multi-user.target

# prometheus的unit 配置文件
[Unit]
Description=prometheus service
Documentation=https://prometheus.io/docs/prometheus/latest/management_api
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/prometheus/prometheus \
    --config.file=/usr/local/prometheus/prometheus.yml \
    --storage.tsdb.retention.time=92d \
    --storage.tsdb.path=/usr/local/prometheus/data \
    --web.listen-address=0.0.0.0:9090 \
    --web.console.templates=/usr/local/prometheus/consoles \
    --web.console.libraries=/usr/local/prometheus/console_libraries
#如果想要输出日志重定向到文件,可以按如下方式,注意此时就不能使用续行符了
#ExecStart=/bin/bash -ce "/usr/local/prometheus/prometheus --config.file=/usr/local/prometheus/prometheus.yml >> /var/log/prometheus.log  2>&1"
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target

# alertmanager的unit 配置文件
[Unit]
Description=alertmanager service
Documentation=https://prometheus.io/docs/alerting/latest/management_api
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/alertmanager/alertmanager \
    --config.file=/usr/local/alertmanager/alertmanager.yml \
    --storage.path=/usr/local/alertmanager/data \
    --data.retention=120h \
    --web.listen-address=0.0.0.0:9093
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target

# node_exporter的unit配置文件
[Unit]
Description=node_exporter
Documentation=https://github.com/prometheus/node_exporter
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/prometheus_exporter/node_exporter-1.1.2.linux-amd64/node_exporter \
    --web.listen-address=0.0.0.0:9100 \
    --web.telemetry-path=/metrics
ExecStop=/bin/kill -s TERM $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target

# redis_exporter的unit配置文件
[Unit]
Description=redis_exporter
Documentation=https://github.com/oliver006/redis_exporter
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/prometheus_exporter/redis_exporter-v1.25.0.linux-amd64/redis_exporter \
    -redis.addr=localhost:6379 \
    -redis.password=123456 \
    -web.listen-address=:9121 \
    -web.telemetry-path=/metrics
Restart=on-failure

[Install]
WantedBy=multi-user.target

# blackbox_exporter的unit配置文件
[Unit]
Description=blackbox_exporter
Documentation=https://github.com/prometheus/blackbox_exporter
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/prometheus_exporter/blackbox_exporter-0.19.0.linux-amd64/blackbox_exporter \
    --web.listen-address=0.0.0.0:9115 \
    --config.file=/usr/local/prometheus_exporter/blackbox_exporter-0.19.0.linux-amd64/blackbox.yml
ExecStop=/bin/kill -s TERM $MAINPID
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target

# mysqld_exporter的unit配置文件
[Unit]
Description=mysql_exporter
Documentation=https://github.com/prometheus/mysqld_exporter
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/prometheus_exporter/mysqld_exporter-0.13.0.linux-amd64/mysqld_exporter \
    --config.my-cnf=/usr/local/prometheus_exporter/mysqld_exporter-0.13.0.linux-amd64/.my.cnf \
    --web.listen-address=:9104 \
    --web.telemetry-path=/metrics
Restart=on-failure

[Install]
WantedBy=multi-user.target

# systemd管理java进程
[Unit]
Description=question_api
Documentation=https://docs.oracle.com/javase/tutorial/deployment/jar/basicsindex.html
After=network.target

[Service]
Type=forking
SuccessExitStatus=143
ExecStart=/bin/sh -c "/usr/local/java/jdk1.8.0_201/bin/java -Xms2048m -Xmx2048m -jar /app/question_api/question_api_beta.jar --server.port=8080 >> /var/log/app/question_api.log 2>&1 &"
ExecStop=/bin/kill -s TERM $MAINPID

Restart=on-failure

[Install]
WantedBy=multi-user.target

# 配置项 SuccessExitStatus=143 是为了抑制stop时报错 Main process exited, code=exited, status=143/n/a
# 这是因为 java 程序在响应 SIGTERM 时不会并不会发回预期的退出状态
# 所以需要将退出代码 SuccessExitStatus=143 添加到 systemd 服务文件作为成功退出状态来抑制这种报错

7 常见问题

7.1 Control process exited, code=exited, status=203/EXEC

测试中使用了启动的是一个脚本,脚本文件开头是有指定 #!/bin/bash 的 - 原 service

[Unit]
Description= test service

[Service]
Type=simple
ExecStart=/test/test-start.sh

[Install]
WantedBy=multi-user.target
  • 错误信息
[root@web20 test]# systemctl status  test
× test.service - test service
     Loaded: loaded (/usr/lib/systemd/system/test.service; disabled; vendor preset: disabled)
     Active: failed (Result: exit-code) since Sun 2023-06-11 11:39:46 CST; 15s ago
   Main PID: 2089 (code=exited, status=203/EXEC)

Jun 11 11:39:46 web20 systemd[1]: Started test service.
Jun 11 11:39:46 web20 systemd[1]: test.service: Main process exited, code=exited, status=203/EXEC
Jun 11 11:39:46 web20 systemd[1]: test.service: Failed with result 'exit-code'.
  • 命令前增加了 /bin/bash
[Unit]
Description= test service

[Service]
Type=simple
ExecStart=/bin/bash /test/test-start.sh

[Install]
WantedBy=multi-user.target
  • 执行结果
[root@web20 test]# systemctl status  test test.service - test service
     Loaded: loaded (/usr/lib/systemd/system/test.service; disabled; vendor preset: disabled)
     Active: active (running) since Sun 2023-06-11 11:42:37 CST; 3s ago
   Main PID: 2180 (bash)
      Tasks: 2 (limit: 98816)
     Memory: 860.0K
     CGroup: /system.slice/test.service
             ├─ 2180 /bin/bash /test/test-start.sh
             └─ 2188 sleep 1

Jun 11 11:42:37 web20 systemd[1]: Started test service.
Jun 11 11:42:37 web20 bash[2180]: 20230611-114237
Jun 11 11:42:38 web20 bash[2180]: 20230611-114238
Jun 11 11:42:39 web20 bash[2180]: 20230611-114239
Jun 11 11:42:40 web20 bash[2180]: 20230611-114240

8 参考文档

https://docs.fedoraproject.org/en-US/quick-docs/understanding-and-administering-systemd/index.html