笔者在日常的开发和使用中,经常需要写大量的本地服务并且进行管理,例如定时执行,定时查错,异步排队以及数据库的备份归档工作。因此systemd作为一个服务管理神器就成了必备工具。今天就写一篇小工具文来介绍一下这东西的用法。
一、SYSTEMD基本工具
监控和控制systemd主要使用的指令是systemctl。主要是从来看系统状态、服务状态,以及管理系统和服务。
同时systemctl可以通过ssh连接远程控制其他主机,使用 systemctl -H <username>@<URL>
格式。
1、分析系统状态
显示系统状态: $ systemctl status
输出激活的单元列表: $systemctl
或者 $systemctl list-units
输出运行失败的单元: $ systemctl —failed
所有可用的单元都在 /etc/systemd/system/
(优先度高) 和 /usr/lib/systemd/system/
(优先度低)。
查看所有已安装的服务:$ systemctl list-unit-files
2、使用单元
一个单元配置文件可以描述如下内容之一:系统服务(.service)、挂载点(.mount)、sockets(.sockets) 、系统设备(.device)、交换分区(.swap)、文件路径(.path)、启动目标(.target)、由 systemd 管理的计时器(.timer)。
我们通常在用systemctl调用单元的时候一般要单元文件的全名。也就是带上述后缀的那些。
如果不带扩展名的话systemctl会默认成是.service
文件,所以为了不发生意外一般还是推荐把名字打全了。
挂载点和设备会自动转化为对应的后缀单元,比如/home就等价于home.mount
, /dev/sda等价于dev-sda.device
。
systemctl在enable、disable、mask子命令里面增加了--now
选项,可以激活同时启动服务,激活同时停止服务等。
立刻激活单元:$ systemctl start <unit>
立刻停止单元:$ systemctl stop <unit>
重启单元:$ systemctl restart <unit>
重新加载配置:$ systemctl reload <unit>
输出单元运行的状态:$ systemctl status <unit>
检测单元是否为自动启动:$ systemctl is-enabled <unit>
设置为开机自动激活单元:$ systemctl enable <unit>
设置为开机自动激活单元并现在立刻启动:$ systemctl enable --now <unit>
取消开机自动激活单元:$ systemctl disable <unit>
禁用一个单元:$ systemctl mask <unit>
取消禁用一个单元:$ systemctl unmask <unit>
显示单元的手册页(前提是由unit提供):$ systemctl help <unit>
重新载入整个systemd的系统配置并扫描unit文件的变动:$ systemctl daemon-reload
3、电源管理
注意:需要安装了polkit以后才能用普通用户身份进行电源管理。
重启:$ systemctl reboot
退出系统并关闭电源:$ systemctl poweroff
待机:$ systemctl suspend
休眠:$ systemctl hibernate
混合休眠模式:$ systemctl hybrid-sleep
二、编写unit文件
systemd的unit书写语法来源于XDG桌面配置文件,最初则来自于ini文件。
通过指令 $ systemctl show --property=UnitPath
可以按照优先级查看unit加载目录。
/usr/lib/systemd/system/
:软件包安装的单元
/etc/systemd/system/
:系统管理员安装的单元
1、处理依赖关系
主要有三类以外关系处理配置:
Requires=B
:启动必须依赖单元B
Wants=B
:启动依赖单元B(可选)
After=B
:本单元在B运行后再运行
注意的是Requires和Want并没有包含After属性,如果不声明After则默认同时启动。
2、服务类型
Type=simple
:(默认值) systemd认为该服务将立即启动。服务进程不会 fork 。
Type=forking
:systemd认为当该服务进程fork,且父进程退出后服务启动成功。使用此启动类型应同时指定 PIDFile=
,以便 systemd 能够跟踪服务的主进程。
Type=oneshot
:这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes
使 systemd 在服务进程退出之后仍然认为服务处于激活状态。
Type=notify
:与 Type=simple
相同,但约定服务会在就绪后向 systemd 发送一个信号。
Type=dbus
:若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。
Type=idle
:systemd会等待所有任务处理完成后,才开始执行 idle 类型的单元。其他行为与 Type=simple 类似。
3、修改现有的unit
直接修改文件并加载:$ systemctl daemon-reload
如果需要替换unit文件,则重新启用单元:$ systemctl reenable <unit>
或者运行:$ systemctl edit --full unit
附加配置片段:$ systemctl edit <unit>
这个操作会在编辑器里面打开/etc/systemd/system/<unit>.d/override.conf
重置到软件包版本:$ systemctl revert <unit>
三、目标 Target
目标是一个类似于运行界别的概念。一些目标能继承其他目标的服务并且启动新的服务,systemd默认提供了一部分类似于sysvinit运行级别的target。
获取当前的target:$ systemctl list-units --type=target
创建自定义target:我们可以新建一个/etc/systemd/system/<target>.target。然后再创建目录/etc/systemd/system/<target>.want,并且在里面加入需要启动的服务器连接(指向/usr/lib/systemd/system/)
SysV Level | Systemd 目标 | 注释 |
---|---|---|
0 | runlevel0.target, poweroff.target | 中断系统(halt) |
1, s, single | runlevel1.target, rescue.target | 单用户模式 |
2, 4 | runlevel2.target, runlevel4.target, multi-user.target | 用户自定义运行级别,通常识别为级别3。 |
3 | runlevel3.target, multi-user.target | 多用户,无图形界面。用户可以通过终端或网络登录。 |
5 | runlevel5.target, graphical.target | 多用户,图形界面。继承级别3的服务,并启动图形界面服务。 |
6 | runlevel6.target, reboot.target | 重启 |
emergency | emergency.target | 急救模式(Emergency shell) |
切换当前运行目标:$ systemctl isolate <target>.target
这个指令仅仅改变当前运行的target,不会对启动有影响
更改开机默认启动target:
检查当前的启动target:$ systemctl get-default
用systemctl修改default.target来变更开机启动target:$ systemctl set-default multi-user.target
四、临时文件
/usr/lib/tmpfiles.d/
和 /etc/tmpfiles.d/
中的文件描述了 systemd-tmpfiles 如何创建、清理、删除临时文件和目录,这些文件和目录通常存放在 /run
和 /tmp
中。配置文件名称为 /etc/tmpfiles.d/<program>.conf
。此处的配置能覆盖 /usr/lib/tmpfiles.d/
目录中的同名配置。
临时文件一般和服务文件同时提供,来生成守护进程需要的文件和目录。也可以在开机的时候往特定的文件写入一些内容。
五、定时器
1、服务单元
定时器是一个以.timer
为结尾的unit配置文件,包含了systemd控制和监督的信息。很多时候可以替代crontab并且有更强的功能。
每一个.timer
文件在同一个目录都有一个对应的.service
文件。.timer用来激活并控制.service
文件。.service
文件不需要有[Install]
,这部分由.timer
单元管理。
2、管理
使用.timer
的方法也和其他unit一样,enable或者start即可。
查看已有的定时器使用:$ systemctl list-timers
查看所有的定时器(包括非活动的):$ systemctl list-timers --all
3、示例
下面是两个timer单元的例子。
单调定时器:
/etc/systemd/system/example.timer
----------------------------------
[Unit]
Description=Run example weekly and on boot ## 文件描述
[Timer]
OnBootSec=15min ## 开机后多久启动
OnUnitActiveSec=1w ## 执行间隔t
[Install]
WantedBy=timers.target ## 目标service单元
实时定时器:
定义一个定时执行,且上次未执行就立刻执行的timer
/etc/systemd/system/foo.timer
-----------------------------------
[Unit]
Description=Run foo weekly
[Timer]
OnCalendar=weekly
Persistent=true
[Install]
WantedBy=timers.target
4、替代crontab探讨
优势主要在于,每个任务都有自己的systemd服务。从而:
- 任务可以独立于定时器,便于测试。
- 任务可以运行在特殊情况下。
- 任务可以使用cgroups的特性。
- 任务可以依赖于其他systemd unit。
- 任务记录在systemd日志里,便于调试排查。
同时也可以和crontab一样在unit失效的时候发送一个email。不过现在一般都用其他开源监控工具,更好用。
六、挂载
因为systemd也负责按 /etc/fstab 挂在目录,所以在系统重启或者重新加载系统管理器的时候,systemd-fstab-generator会把/etc/fstab里面的配置转化为systemd unit。这些设置具体来说由 x-systemd组件来配置。
七、日志
systemd提供了自己的日至系统。
读取日志:$ journalctl
默认情况下(当 Storage= 在文件 /etc/systemd/journald.conf
中被设置为 auto),日志记录将被写入 /var/log/journal/
。该目录是 systemd 软件包的一部分。
1、优先级
日志系统内有日志的优先级区分,下面为对应介绍:
优先级 | 介绍 |
---|---|
优先级0 | Emergency(emerg) |
优先级1 | Alert(alert) |
优先级2 | Critical(crit) |
优先级3 | Error(err) |
优先级4 | Warning(warning) |
优先级5 | Notice(notice) |
优先级6 | Informational(info) |
优先级7 | Debug(debug) |
2、功能
在日志系统中,各个日志都会用编码来指代一定的功能区域。下面为对应介绍:
编码 | 内容(缩写) |
---|---|
功能编码0 | kernel-message(kern) |
功能编码1 | user-level-message(user) |
功能编码2 | mail-system(mail) |
功能编码3 | system-daemon(daemon) |
功能编码4 | authorization-messages(auth) |
功能编码5 | syslog(syslog) |
功能编码6 | line-printer-subsystem(lpr) |
功能编码7 | network-news-subsystem(news) |
功能编码8 | uucp-subsystem(uucp) |
功能编码9 | clock-daemon |
功能编码10 | authpriv(authpri) |
功能编码11 | ftp-daemon(ftp) |
功能编码12 | tp-subsyst |
功能编码13 | log-audit |
功能编码14 | log-alert |
功能编码15 | crontab(cron) |
功能编码16-23 | local0-7 |
3、过滤输出
Journalctl可以过滤字段输出,下面为常用操作。
显示本次启动后所有日志:$ journalctl -b
或者 $ journalctl -b -0
显示上次启动后的日志:$ journalctl -b -1
显示上上次启动后的日志:$ journalctl -b -2
只显示错误冲突和重要告警信息:$ journalctl -p err..alert
或者用编号表示 $ journalctl -p 3..1
显示某个时间开始的消息:$ journalctl --since="2019-01-01 00:00:00"
显示最新的消息:$ journalctl -f
显示特定程序的所有消息:$ journalctl /usr/lib/systemd/systemd
显示某进程的所有信息:$ journalctl _PID=12345
显示指定unit的所有信息:$ journalctl -u mydumper-archive.service
指定内核缓存消息:$ journalctl -k
显示auth.log当前量:$ journalctl -f -1 SYSLOG_FACILITY=10
4、日志大小限制
默认情况下,systemd日志的最大限制是所在FS容量的10%。但是我们可以通过修改来改变最大限制:
/etc/systemd/journald.conf
--------------------------------
SystemMaxUse=50M
同时也可以通过配置片段而非全局来进行设置:
/etc/systemd/journald.conf.d/00-journal-size.conf
-----------------------------------
[Journal]
SystemMaxUse=50M
5、配合syslog
systemd提供了socket:/run/systemd/journal/syslog
,来兼容传统日志服务。如果要让传统的日志服务工作,则要用这个socket来替代/dev/log。如果是使用rsyslog,则不用更改。
设置开机启动syslog-ng:$ systemctl enable syslog-ng
6、清理日志
所有的日志都存放在/var/log/journal
,这个目录下其实rm也可以用来清理,但是不推荐。
下面的方法比较推荐。
清理日志到小于100MB:$ journalctl --vacuum-size=100M
清理最早两周前的日志:$ journalctl --vacuum-time=2weeks
八、疑难和差错
1、寻找错误
这个案例中我们以mydumper-ro.service为例
通过systemd寻找失败的服务:$ systemctl --state=failed
或者用systemd消息:$ journalctl -fp err
找到了错误unit后查看更多信息:$ systemctl status mydumper-ro
查到这个unit的PID是12345之后:$ journalctl -b _PID=12345
发现部分内核模块的配置文件有问题:$ ls -Al /etc/mydumper-ro.d
修复错误,最后重新启动服务即可。
2、诊断启动问题
在配置中使用内核参数引导:systemd.log_level=debug systemd.log_target=kmsg log_buf_len=1M
3、诊断一个服务
在服务的配置文件中加入诊断选项。
[Service]
Environment=SYSTEMD_LOG_LEVEL=debug
4、短运行时间进程无日志
有时候某些短时间进程没有任何输出,没有日志。这个时候应该使用PID来查询。
5、禁止程序崩溃是转储内存
需要使用旧的内存转储则在对应的服务的配置文件中加入下面的设置:
/etc/sysctl.d/49-coredump.conf
-----------------------------------
kernel.core_pattern = core
kernel.core_uses_pid = 0