简介
[Unit]
Description=Resets System Activity Logs
[Service]
Type=oneshot
RemainAfterExit=yes
User=root
ExecStart=/usr/lib64/sa/sa1 --boot
[Install]
WantedBy=multi-user.target
Also=sysstat-collect.timer
Also=sysstat-summary.timer
- Unit: 主要用于配置整个服务的详情信息以及服务依赖,用于快速识别该服务的相关情况以及依赖项目
- Service: 主要用于配置整个服务生命周期的管理行为
- Install: 用于配置服务的安装级别,当我们设置服务在 Linux 的那种级别下启动或开机自启动时会加载该部分 (典型的当 chkconfig nginx on 或者 systemctl enable|disable nginx.service 时会读取该部分的配置)
Unit
Unit 块除了可以简单描述整个服务的相关详情外,还有最重要的一点可以用来管理该服务的启动顺序和启动依赖 (必须也得由 systemd 管理)。
比如通常我们有一些服务,必须依赖一些基础环境 (ntpd,rsyslog,network) 才能启动成功,或者必须在某个服务后启动,那我们就可以在这里进行配置。
注意: 在 Unit 快中,每个指令后都可以指定一个以空格分隔的列表
单元目录
当 systemd 以系统实例(--system)运行时,加载单元的先后顺序(较前的目录优先级较高):
当 systemd 以用户实例(--user)运行时,加载单元的先后顺序(较前的目录优先级较高):
服务描述
在 systemd 的 service 配置中,我们通常会使用如下几个配置项来描述该服务的基本信息:
- Description: 服务的简单描述
- Documentation: 指定服务的文档,便于管理员快速追溯,一般可以使用 "http://", "https://", "file:", "info:", "man:" 五种 URI 类型
服务依赖管理
- After: 用于指定该服务在那些服务之后启动 ,停止时正好相反
- Before: 用于指定该服务在那些服务之前启动,停止时正好相反
- Requires: 设置该服务必须依赖的其他服务,因此在该服务启动之前,指定的服务列表必须全部在线,否则服务将启动失败或掉线。但如果未设置 After 和 Before 指令时,该服务和依赖的服务将会并行的同时启动。注意:该指令依赖的服务不一定要在整个生命周期都保持一直在线,这取决于其他的检查条件
- Requisite: 和 Requires 类似,区别是在该服务启动时,该指令指定的依赖资源必须全部处于启动成功的状态,否则该服务立马失败,并且不会启动那些失败的依赖服务。因此一般建议 Requisite 和 After 一起联合使用会比较好。
- Wants: Requires 的弱化版,当该服务启动时,尽可能的启动该指令指定的服务,但不会影响该服务的启动
- BindsTo: 和 Requires 类似,但是依赖性更强,这里列出来的任何服务停止运行或崩溃,该服务将立即被停止。考虑服务的启动依赖,一般会和 After 一起使用
- PartOf: 和 Requires 类似,但仅作用于服务的停止和重启,表示该服务是所列服务的一部分,会随指定服务的启动而启动。注意:该指令是单向依赖,服务的主动的重启不会影响其他
实例
# 这里指定了内部的Kubelet进程必须在docker 和kube-proxy 之后启动,且需要强依赖,减少调度失败和pod转发失败的问题
[Unit]
Description=kubelet - k8s node lifecycle manager.
Documentation=https://kubernetes.io/zh/docs/reference/command-line-tools-reference/kubelet/
After=docker.service kube-proxy.service
Requisite=docker.service kube-proxy.service
Service
基本指令
Type
Type: 指定进程的启动类型,必须设为 simple, exec, forking, oneshot, dbus, notify, idle 之一。常用的几种如下:
- simple: 一般没有其他指令时,为默认值,表示 ExecStart 后的指令为主进程,主进程启动后服务即启动成功。如果进程需要为其他进程提供服务,需要通过 socket 来进行
- exec: 同 simple 类似,但是表示该服务的主服务进程执行完成之后,才真正启动成功。
- forking: 标识在使用 ExecStart 后的指令启动程序后,会进行 fork() 的系统调用。传统 Unix 中守护进程的经典做法,如果使用此类型,建议设置 PIDFile= 来指定该服务的主进程。比如 nginx 进程的 daemon 方式,主进程执行完成后,至少有 1 个进程在运行,此时服务状态会变成 active 状态
- oneshot: 同 simple 类似,但是表示只有在该服务的主服务进程退出之后,才真正执行成功。一般用于执行一次性任务 (配置 RemainAfterExit=yes 设置可停止的一次性服务)
注意: 建议对长时间持续运行的服务尽可能使用 Type=simple;如果有 master/slave 进程的建议使用 Type=forking
PIDFile
PIDFile: 指定该服务的 PID 文件路径。systemd 会在启动后读取主服务进程的 PID,并记录在 MAINPID 变量中,在停止服务后,会主动进行删除该文件
KillMode
KillMode: 指定停止服务时,杀死进程的方式,必须设为 control-group, process, mixed, none 之一。(process 表示仅杀死主进程;mixed 表示向主进程发 SIGTERM 信号,然后想服务内 cgroup 的其他进程发 SIGKILL 信号;none 表示仅执行 ExecStop 动作,而不杀死进程)
环境变量初始化
通常我们的服务主进程可能会有比较多的参数,建议的做法是使用环境变量方式进行维护对应的参数,而在 systemd 中可支持 K/V 和配置文件方式来进行设置。
- Environment: 指定 Key=Value 的环境配置
- EnvironmentFile: 指定包含 key=value 的配置文件
注意: 通常情况下,我们会看到 EnvironmentFile=-filename 和 EnvironmentFile=filename 的写法,前者表示可忽略,即当文件不存在或环境变量加载失败时也不影响后续的处理逻辑。(在 etcd 的高版本里,大部分参数采用了环境变量加载的方式,可见未来通过环境变量来控制服务的启动参数已经是趋势)
进程生命周期管理
整个进程的生命周期是从准备启动进程开始到整个进程结束的过程管理。通常情况下,可以包含为如下几个环节:
- ExecStartPre: 启动前, 该指令可用于在进程启动前加载一些列的基础环境以及初始化检查机制
- ExecStart: 启动逻辑, 该指令用于启动进程的核心逻辑
- ExecStartPost: 启动后, 该指令用于在启动进程后进行回调的相关操作
- ExecReload: 热加载, 该指令用于对进程进行热加载,通常情况用于 配置变更后的热重启 (/bin/kill -s HUP $MAINPID)
- ExecStop: 停止, 该指令用于对进程执行指定的停止操作,通常情况下用于进行进程的优雅停止 (/bin/kill -s TERM $MAINPID)
- ExecStopPost: 停止后, 该指令用于在进程停止后,进行一些列的资源释放和等待操作,因为通常情况当进程停止后,相关资源不能立即释放,因为服务不算真正呗停止,如果此时认为进程已停止而强制做一些操作,可能会影响到相关服务处理逻辑
- TimeoutStopSec: 停止超时时间, 指定服务停止后的超时时间,一般在通知服务是会使用 SIGTERM 信号,待超时时间内资源未释放,将使用 SIGKILL 进行强制杀死
重启策略
- Restart: 当服务进程 正常退出、异常退出、被杀死、超时的时候, 是否重新启动该服务。
- RestartSec: 多久后重启
- StartLimitBurst: 启动的最大次数限制,超过后停止继续重启
- StartLimitInterval: 启动时间的最大间隔
所谓 "服务进程" 是指 ExecStartPre=, ExecStartPost=, ExecStop=, ExecStopPost=, ExecReload= 中设置的进程。 当进程是由于 systemd 的正常操作 (例如 systemctl stop|restart) 而被停止时, 该服务不会被重新启动。 所谓 "超时" 可以是看门狗的 "keep-alive ping" 超时, 也可以是 systemctl start|reload|stop 操作超时.
可选值如下:
- no: 默认值,不会重启
- on-success: 服务进程正常退出 (退出码为 "0", 或者进程收到 SIGHUP, SIGINT, SIGTERM, SIGPIPE 信号之一, 并且 退出码符合 SuccessExitStatus= 的设置) 时进行重启
- on-failure: 仅在服务进程异常退出 (退出码不为 "0", 或者 进程被强制杀死 (包括 "core dump" 以及收到 SIGHUP, SIGINT, SIGTERM, SIGPIPE 之外的其他信号), 或者进程由于 看门狗超时 或者 systemd 的操作超时 而被杀死) 时重启
- always: 无条件重启
资源限制
通常情况下,我们会对目标服务或进程进行一定资源的限制,以防止服务 bug 导致的整个系统资源耗尽。
在 systemd 中,可以使用如下指令进行简单的资源限制:
- LimitCPU: 限制 CPU 使用时长 (秒),等同 ulimit -t
- LimitNOFILE: 限制进程使用的文件描述符数量,等同 ulimit -n
- LimitNPROC: 限制进程的数量,等同于 ulimit -u
注意: 一般为防止服务导致的系统过载,会对进程设置一些资源限制
计算资源调度
- CPUSchedulingPolicy: 设置 CPU 的调度策略可设为 other, batch, idle, fifo, rr 之一
- CPUSchedulingPriority: 设置 CPU 调度的优先级,取决于调度策略
- CPUAffinity: 设置 CPU 的亲和性,可以指定 cpu 的编号,比如 0,1
实例
[Service]
EnvironmentFile=/etc/calico/calico.env
ExecStartPre=-/usr/bin/docker pull ${CALICO_IMAGE}
ExecStart=/usr/bin/docker run --net=host --privileged ${CALICO_IMAGE}
ExecStop=-/usr/bin/docker stop calico-node
ExecStopPost=-/usr/bin/docker rm -f -v calico-node
Restart=on-failure
RestartSec=5
StartLimitBurst=3
StartLimitInterval=60s
CPUSchedulingPolicy=fifo
CPUAffinity=0,2
LimitNOFILE=1000
LimitNPROC=3000
LimitCORE=infinity
Install
在 systemd 的服务管理中,install 块主要包含了单元的启用信息。只有 systemctl 的 enable/disable 指令会调用该部分内容。
- Alias: 指定服务的别名信息
- WantedBy/RequiredBy: 设置启用服务的依赖服务
实例
[Install]
Alias=syslog.service
WantedBy=multi-user.target
使用 systemd 定时器代替 cron 作业
查看操作系统维护的计时器
# 查看所有定时器
systemctl status *timer
# 查看指定定时器
systemctl status sysstat-collect.timer
systemctl list-timers
实例
systemctl status sysstat-collect.timer
● sysstat-collect.timer - Run system activity accounting tool every 10 minutes
Loaded: loaded (/usr/lib/systemd/system/sysstat-collect.timer; enabled; vendor preset: disabled)
Active: active (waiting) since Wed 2021-09-29 14:11:44 CST; 1 weeks 1 days ago
Trigger: Thu 2021-10-07 16:50:00 CST; 6min left
Sep 29 14:11:44 worker1 systemd[1]: Started Run system activity accounting tool every 10 minutes.
systemctl list-timers
NEXT LEFT LAST PASSED UNIT ACTIVATES
Thu 2021-10-07 17:00:00 CST 2s left Thu 2021-10-07 16:50:00 CST 9min ago sysstat-collect.timer sysstat-collect.service
Thu 2021-10-07 18:26:40 CST 1h 26min left Thu 2021-10-07 16:50:50 CST 9min ago dnf-makecache.timer dnf-makecache.service
Fri 2021-10-08 00:00:00 CST 7h left Thu 2021-10-07 00:00:00 CST 16h ago unbound-anchor.timer unbound-anchor.service
Fri 2021-10-08 00:07:00 CST 7h left Thu 2021-10-07 00:07:00 CST 16h ago sysstat-summary.timer sysstat-summary.service
Fri 2021-10-08 14:27:00 CST 21h left Thu 2021-10-07 14:27:00 CST 2h 32min ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
5 timers listed.
Pass --all to see loaded but inactive timers, too.
每个定时器至少有六行相关信息:
- 定时器的第一行有定时器名字和定时器目的的简短介绍
- 第二行展示了定时器的状态,是否已加载,定时器单元文件的完整路径以及预设信息。
- 第三行指明了其活动状态,包括该定时器激活的日期和时间。
- 第四行包括了该定时器下次被触发的日期和时间和距离触发的大概时间。
- 第五行展示了被定时器触发的事件或服务名称。
部分(不是全部)systemd 单元文件有相关文档的指引。我虚拟机上输出中有三个定时器有文档指引。这是一个很好(但非必要)的信息。 - 最后一行是计时器最近触发的服务实例的日志条目。
创建一个定时器
首先,创建一个运行基础命令的简单的服务,例如 free 命令。举个例子,你可能想定时监控空余内存。在 /etc/systemd/system 目录下创建如下的 myMonitor.server 单元文件。它不需要是可执行文件:
[Unit]
Description=Logs system statistics to the systemd journal
Wants=myMonitor.timer
[Service]
Type=oneshot
ExecStart=/usr/bin/free
[Install]
WantedBy=multi-user.target
在 /etc/systemd/system 目录下创建 myMonitor.timer 定时器单元文件,添加如下代码:
[Unit]
Description=Logs some system statistics to the systemd journal
Requires=myMonitor.service
[Timer]
Unit=myMonitor.service
OnCalendar=*-*-* *:*:00
[Install]
WantedBy=timers.target
在 myMonitor.timer 文件中的 OnCalendar 时间格式,--* ::00,应该会每分钟触发一次定时器去执行 myMonitor.service 单元。
systemctl start myMonitor.service
journalctl -S today -f -u myMonitor.service
一条结果立即就显示出来了,下一条大概在一分钟后出来。
别忘了检查下计时器和服务的状态。
你在日志里大概至少注意到两件事。
- 第一,你不需要特地做什么来让 myMonitor.service 单元中 ExecStart 触发器产生的 STDOUT 存储到日志里。这都是用 systemd 来运行服务的一部分功能。然而,它确实意味着你需要小心对待服务单元里面执行的脚本和它们能产生多少 STDOUT。
- 第二,定时器并不是精确在每分钟的 :00 秒执行的,甚至每次执行的时间间隔都不是刚好一分钟。这是特意的设计,但是有必要的话可以改变这种行为(如果只是它挑战了你的系统管理员的敏感神经)。
这样设计的初衷是为了防止多个服务在完全相同的时刻被触发。举个例子,你可以用例如 Weekly,Daily 等时间格式。这些快捷写法都被定义为在某一天的 00:00:00 执行。当多个定时器都这样定义的话,有很大可能它们会同时执行。
systemd 定时器被故意设计成在规定时间附近随机波动的时间点触发,以避免同一时间触发。它们在一个时间窗口内半随机触发,时间窗口开始于预设的触发时间,结束于预设时间后一分钟。根据 systemd.timer 的手册页,这个触发时间相对于其他已经定义的定时器单元保持在稳定的位置。你可以在日志条目中看到,定时器在启动后立即触发,然后在每分钟后的 46 或 47 秒触发。
大部分情况下,这种概率抖动的定时器是没事的。当调度类似执行备份的任务,只需要它们在下班时间运行,这样是没问题的。系统管理员可以选择确定的开始时间来确保不和其他任务冲突,例如 01:05:00 这样典型的 cron 作业时间,但是有很大范围的时间值可以满足这一点。在开始时间上的一个分钟级别的随机往往是无关紧要的。
然而,对某些任务来说,精确的触发时间是个硬性要求。对于这类任务,你可以向单元文件的 Timer 块中添加如下声明来指定更高的触发时间跨度精确度(精确到微秒以内):
AccuracySec=1us
时间跨度可用于指定所需的精度,以及定义重复事件或一次性事件的时间跨度。它能识别以下单位:
usec,us,µs
msec,ms
seconds,second,sec,s
minutes,minute,min,m
hours,hour,hr,h
days,day,d
weeks,week,w
months,month,M(定义为 30.44 天)
years,year,y(定义为 365.25 天)
所有 /usr/lib/systemd/system 中的定时器都指定了一个更宽松的时间精度,因为精准时间没那么重要。看看这些系统创建的定时器的时间格式:
grep Accur /usr/lib/systemd/system/*timer
/usr/lib/systemd/system/fstrim.timer:AccuracySec=1h
/usr/lib/systemd/system/logrotate.timer:AccuracySec=1h
/usr/lib/systemd/system/logwatch.timer:AccuracySec=12h
/usr/lib/systemd/system/mlocate-updatedb.timer:AccuracySec=24h
/usr/lib/systemd/system/raid-check.timer:AccuracySec=24h
/usr/lib/systemd/system/unbound-anchor.timer:AccuracySec=24h
看下 /usr/lib/systemd/system 目录下部分定时器单元文件的完整内容,看看它们是如何构建的。
在本实验中不必让这个定时器在启动时激活,但下面这个命令可以设置开机自启:
systemctl enable myMonitor.timer
你创建的单元文件不需要是可执行的。你同样不需要启用服务,因为它是被定时器触发的。如果你需要的话,你仍然可以在命令行里手动触发该服务单元。尝试一下,然后观察日志。
关于定时器精度、事件时间规格和触发事件的详细信息,请参见 systemd.timer 和 systemd.time 的手册页。
定时器类型
systemd 定时器还有一些在 cron 中找不到的功能,cron 只在确定的、重复的、具体的日期和时间触发。systemd 定时器可以被配置成根据其他 systemd 单元状态发生改变时触发。举个例子,定时器可以配置成在系统开机、启动后,或是某个确定的服务单元激活之后的一段时间被触发。这些被称为单调计时器。“单调”指的是一个持续增长的计数器或序列。这些定时器不是持久的,因为它们在每次启动后都会重置。
表格 1 列出了一些单调定时器以及每个定时器的简短定义,同时有 OnCalendar 定时器,这些不是单调的,它们被用于指定未来有可能重复的某个确定时间。这个信息来自于 systemd.timer 的手册页,有一些不重要的修改。
单调计时器可使用同样的简写名作为它们的时间跨度,即我们之前提到的 AccuracySec 表达式,但是 systemd 将这些名字统一转换成了秒。举个例子,比如你想规定某个定时器在系统启动后五天触发一次事件;它可能看起来像 OnBootSec=5d。如果机器启动于 2020-06-15 09:45:27,这个定时器会在 2020-06-20 09:45:27 或在这之后的一分钟内触发。
日历事件格式
日历事件格式是定时器在所需的重复时间触发的关键。我们开始看下一些 OnCalendar 设置一起使用的格式。
与 crontab 中的格式相比,systemd 及其计时器使用的时间和日历格式风格不同。它比 crontab 更为灵活,而且可以使用类似 at 命令的方式允许模糊的日期和时间。它还应该足够熟悉使其易于理解。
systemd 定时器使用 OnCalendar= 的基础格式是 DOW YYYY-MM-DD HH:MM:SS。DOW(星期几)是选填的,其他字段可以用一个星号(*)来匹配此位置的任意值。所有的日历时间格式会被转换成标准格式。如果时间没有指定,它会被设置为 00:00:00。如果日期没有指定但是时间指定了,那么下次匹配的时间可能是今天或者明天,取决于当前的时间。月份和星期可以使用名称或数字。每个单元都可以使用逗号分隔的列表。单元范围可以在开始值和结束值之间用 .. 指定。
指定日期有一些有趣的选项,波浪号(~)可以指定月份的最后一天或者最后一天之前的某几天。/ 可以用来指定星期几作为修饰符。
这里有几个在 OnCalendar 表达式中使用的典型时间格式例子。
测试日历格式
systemd 提供了一个绝佳的工具用于检测和测试定时器中日历时间事件的格式。systemd-analyze calendar 工具解析一个时间事件格式,提供标准格式和其他有趣的信息,例如下次“经过”(即匹配)的日期和时间,以及距离下次触发之前大概时间。
首先,看看未来没有时间的日(注意 Next elapse 和 UTC 的时间会根据你当地时区改变):
systemd-analyze calendar 2030-06-17
Original form: 2030-06-17
Normalized form: 2030-06-17 00:00:00
Next elapse: Mon 2030-06-17 00:00:00 EDT
(in UTC): Mon 2030-06-17 04:00:00 UTC
From now: 10 years 0 months left
现在添加一个时间,在这个例子中,日期和时间是当作无关的部分分开解析的:
systemd-analyze calendar 2030-06-17 15:21:16
Original form: 2030-06-17
Normalized form: 2030-06-17 00:00:00
Next elapse: Mon 2030-06-17 00:00:00 EDT
(in UTC): Mon 2030-06-17 04:00:00 UTC
From now: 10 years 0 months left
Original form: 15:21:16
Normalized form: *-*-* 15:21:16
Next elapse: Mon 2020-06-15 15:21:16 EDT
(in UTC): Mon 2020-06-15 19:21:16 UTC
From now: 3h 55min left
为了把日期和时间当作一个单元来分析,可以把它们包在引号里。你在定时器单元里 OnCalendar= 时间格式中使用的时候记得把引号去掉,否则会报错:
systemd-analyze calendar "2030-06-17 15:21:16"
Normalized form: 2030-06-17 15:21:16
Next elapse: Mon 2030-06-17 15:21:16 EDT
(in UTC): Mon 2030-06-17 19:21:16 UTC
From now: 10 years 0 months left
这个例子里我们列出了时间表达式的五个经过时间。
systemd-analyze calendar --iterations=5 "Mon *-05~3"
Original form: Mon *-05~3
Normalized form: Mon *-05~03 00:00:00
Next elapse: Mon 2023-05-29 00:00:00 EDT
(in UTC): Mon 2023-05-29 04:00:00 UTC
From now: 2 years 11 months left
Iter. #2: Mon 2028-05-29 00:00:00 EDT
(in UTC): Mon 2028-05-29 04:00:00 UTC
From now: 7 years 11 months left
Iter. #3: Mon 2034-05-29 00:00:00 EDT
(in UTC): Mon 2034-05-29 04:00:00 UTC
From now: 13 years 11 months left
Iter. #4: Mon 2045-05-29 00:00:00 EDT
(in UTC): Mon 2045-05-29 04:00:00 UTC
From now: 24 years 11 months left
Iter. #5: Mon 2051-05-29 00:00:00 EDT
(in UTC): Mon 2051-05-29 04:00:00 UTC
From now: 30 years 11 months left
模板文件
单元文件可以通过一个"实例名"参数从"模板文件"构造出来(这个过程叫"实例化")。 "模板文件"(也称"模板单元"或"单元模板")是定义一系列同类型单元的基础。 模板文件的名称必须以 "@" 结尾(在类型后缀之前)。 通过实例化得到的单元,其完整的单元名称是在模板文件的类型后缀与 "@" 之间插入实例名称形成的。在通过实例化得到的单元内部, 可以使用 "%i" 以及其他说明符来引用实例参数。
对于例如 foo.service 这样的单元文件, 可以同时存在对应的 foo.service.wants/ 与 foo.service.requires/ 目录, 其中可以放置许多指向其他单元文件的软连接。 软连接所指向的单元将会被隐含的添加到 foo.service 相应的 Wants= 与 Requires= 依赖中。 这样就可以方便的为单元添加依赖关系,而无需修改单元文件本身。 向 .wants/ 与 .requires/ 目录中添加软连接的首选方法是使用 systemctl(1) 的 enable 命令, 它会读取单元文件的 [Install] 小节(详见后文)。
对于例如 foo.service 这样的单元文件,可以同时存在对应的 foo.service.d/ 目录, 当解析完主单元文件之后,目录中所有以 ".conf" 结尾的文件,都会被按照文件名的字典顺序,依次解析(相当于依次附加到主单元文件的末尾)。 这样就可以方便的修改单元的设置,或者为单元添加额外的设置,而无需修改单元文件本身。 注意,配置片段(".conf" 文件)必须包含明确的小节头(例如 "[Service]" 之类)。 对于从模板文件实例化而来的单元,会优先读取与此实例对应的 ".d/" 目录(例如 "foo@bar.service.d/")中的配置片段(".conf" 文件), 然后才会读取与模板对应的 ".d/" 目录(例如 "foo@.service.d/")中的配置片段(".conf" 文件)。 对于名称中包含连字符("-")的单元,将会按特定顺序依次在一组(而不是一个)目录中搜索单元配置片段。 例如对于 foo-bar-baz.service 单元来说,将会依次在 foo-.service.d/, foo-bar-.service.d/, foo-bar-baz.service.d/ 目录下搜索单元配置片段。这个机制可以方便的为一组相关单元(单元名称的前缀都相同)定义共同的单元配置片段, 特别适合应用于 mount, automount, slice 类型的单元, 因为这些单元的命名规则就是基于连字符构建的。 注意,在前缀层次结构的下层目录中的单元配置片段,会覆盖上层目录中的同名文件, 也就是 foo-bar-.service.d/10-override.conf 会覆盖(取代) foo-.service.d/10-override.conf 文件。
存放配置片段(".conf" 文件)的 ".d/" 目录, 除了可以放置在 /etc/systemd/{system,user} 目录中, 还可以放置在 /usr/lib/systemd/{system,user} 与 /run/systemd/{system,user} 目录中。 虽然在优先级上,/etc 中的配置片段优先级最高、/run 中的配置片段优先级居中、 /usr/lib 中的配置片段优先级最低。但是这仅对同名配置片段之间的覆盖关系有意义。 因为所有 ".d/" 目录中的配置片段,无论其位于哪个目录, 都会被按照文件名的字典顺序,依次覆盖单元文件中的设置(相当于依次附加到主单元文件的末尾)。
注意,虽然 systemd 为明确设置单元之间的依赖关系提供了灵活的方法, 但是我们反对使用这些方法,你应该仅在万不得已的时候才使用它。 我们鼓励你使用基于 D-Bus 或 socket 的启动机制, 以将单元之间的依赖关系隐含化, 从而得到一个更简单也更健壮的系统。
如上所述,单元可以从模板实例化而来。 这样就可以用同一个模板文件衍生出多个单元。 当 systemd 查找单元文件时,会首先查找与单元名称完全吻合的单元文件, 如果没有找到,并且单元名称中包含 "@" 字符, 那么 systemd 将会继续查找拥有相同前缀的模板文件, 如果找到,那么将从这个模板文件实例化一个单元来使用。 例如,对于 getty@tty3.service 单元来说, 其对应的模板文件是 getty@.service (也就是去掉 "@" 与后缀名之间的部分)。
可以在模板文件内部 通过 "%i" 引用实例字符串(也就是上例中的"tty3"), 详见后面的小节。
如果一个单元文件的大小为零字节或者是指向 /dev/null 的软连接, 那么它的所有相关配置都将被忽略。同时,该单元将被标记为 "masked" 状态,并且无法被启动。 这样就可以 彻底屏蔽一个单元(即使手动启动也不行)。
PXC 集群的 mysql@bootstrap.service
mysql@.service 是模板文件
传入参数 bootstrap 后,会启动
mysql@bootstrap.service 服务,如下配置使他会加载 /etc/sysconfig/mysql.bootstrap 配置文件
EnvironmentFile=/etc/sysconfig/mysql.%i
vim /etc/sysconfig/mysql.bootstrap
EXTRA_ARGS=" --wsrep-new-cluster "
自动依赖
隐含依赖
多依赖关系是根据单元的类型与设置自动隐含创建的。 这些隐含的依赖关系可以让单元文件的内容更加简洁清爽。
例如,带有 Type=dbus 的 service 单元 将会自动隐含 Requires=dbus.socket 与 After=dbus.socket 依赖。
默认依赖
默认依赖与隐含依赖类似,不同之处在于默认依赖可以使用 DefaultDependencies= 选项进行开关(默认值 yes 表示开启默认依赖,而设为 no 则表示关闭默认依赖), 而隐含依赖永远有效。
例如,除非明确设置了 DefaultDependencies=no ,否则 target 单元将会默认添加对通过 Wants= 或 Requires= 汇聚的单元 的 After= 依赖。
注意,可以通过设置 DefaultDependencies=no 来关闭默认行为。
target
简单理解
就是通过 target 的 want 和 require 构建一个需求链,启动的时候相当于启动一个群组服务,便于管理。
比如 cephadm 创建的 ceph 集群就是通过 ceph.target 控制相关服务的。
在 ceph.target 目录中存在 ceph.target.wants 文件夹
ll ceph.target.wants/
total 8
lrwxrwxrwx 1 root root 68 Sep 28 23:05 ceph-3d7455cc-205f-11ec-8dc1-525400d203e5.target -> /etc/systemd/system/ceph-3d7455cc-205f-11ec-8dc1-525400d203e5.target
lrwxrwxrwx 1 root root 68 Aug 1 14:47 ceph-aeef8b18-f27e-11eb-8817-525400d203e5.target -> /etc/systemd/system/ceph-aeef8b18-f27e-11eb-8817-525400d203e5.target
可以通过 systemctl list-dependencies 查看相关依赖
systemctl list-dependencies ceph.target
ceph.target
● ├─ceph-3d7455cc-205f-11ec-8dc1-525400d203e5.target
● └─ceph-aeef8b18-f27e-11eb-8817-525400d203e5.target
官方定义
接受一个空格分隔的单元列表, 表示在使用 systemctl enable 启用此单元时, 将会在每个列表单元的 .wants/ 或 .requires/ 目录中创建一个指向该单元文件的软连接。 这相当于为每个列表中的单元文件添加了 Wants=此单元 或 Requires=此单元 选项。 这样当列表中的任意一个单元启动时,该单元都会被启动。 如果多次使用此选项, 那么每个选项的单元列表都会合并在一起。
在普通的 bar.service 单元内设置 WantedBy=foo.service 选项 与设置 Alias=foo.service.wants/bar.service 选项基本上是等价的。 但是对于模板单元来说,情况则有所不同。 虽然必须使用实例名称调用 systemctl enable 命令, 但是实际上添加到 .wants/ 或 .requires/ 目录中的软连接, 指向的却是模板单元(因为并不存在真正的单元实例文件)。 假设 getty@.service 文件中存在 WantedBy=getty.target 选项,那么 systemctl enable getty@tty2.service 命令将会创建一个 getty.target.wants/getty@tty2.service 软连接(指向 getty@.service)
systemd中Target管理
# 查看当前系统的所有Target:
systemctl list-unit-files --type=target
# 查看一个Target包含的所有 Unit:
systemctl list-dependencies multi-user.target
# 查看启动时的默认Target:
systemctl get-default
# 设置启动时的默认 Target:
sudo systemctl set-default multi-user.target