Systemd 作为 Linux 的系统启动器,功能强大。
本文通过一个简单例子,介绍 Systemd 如何设置定时任务。这不仅实用,而且可以作为 Systemd 的上手教程。
一、定时任务
所谓定时任务,就是未来的某个或多个时点,预定要执行的任务,比如每五分钟收一次邮件、每天半夜两点分析一下日志等等。
Linux 系统通常都使用 cron 设置定时任务,但是 Systemd 也有这个功能,而且优点显著。
- 自动生成日志,配合 Systemd 的日志工具,很方便除错
- 可以设置内存和 CPU 的使用额度,比如最多使用50%的 CPU
- 任务可以拆分,依赖其他 Systemd 单元,完成非常复杂的任务
下面,我就来演示一个 Systemd 定时任务:每小时发送一封电子邮件。
二、邮件脚本
先写一个发邮件的脚本mail.sh
。
#!/usr/bin/env bash echo "This is the body" | /usr/bin/mail -s "Subject" [email protected]
上面代码的[email protected]
,请替换成你的邮箱地址。
然后,执行这个脚本。
$ bash mail.sh
执行后,你应该就会收到一封邮件,标题为Subject
。
如果你的 Linux 系统不能发邮件,建议安装 ssmtp 或者 msmtp。另外,mail
命令的用法,可以参考这里。
三、Systemd 单元
学习 Systemd 的第一步,就是搞懂"单元"(unit)是什么。
简单说,单元就是 Systemd 的最小功能单位,是单个进程的描述。一个个小的单元互相调用和依赖,组成一个庞大的任务管理系统,这就是 Systemd 的基本思想。
由于 Systemd 要做的事情太多,导致单元有很多不同的种类,大概一共有12种。举例来说,Service 单元负责后台服务,Timer 单元负责定时器,Slice 单元负责资源的分配。
每个单元都有一个单元描述文件,它们分散在三个目录。
/lib/systemd/system
:系统默认的单元文件/etc/systemd/system
:用户安装的软件的单元文件/usr/lib/systemd/system
:用户自己定义的单元文件
下面的命令可以查看所有的单元文件。
# 查看所有单元 $ systemctl list-unit-files # 查看所有 Service 单元 $ systemctl list-unit-files --type service # 查看所有 Timer 单元 $ systemctl list-unit-files --type timer
四、单元的管理命令
下面是常用的单元管理命令。
# 启动单元 $ systemctl start [UnitName] # 关闭单元 $ systemctl stop [UnitName] # 重启单元 $ systemctl restart [UnitName] # 杀死单元进程 $ systemctl kill [UnitName] # 查看单元状态 $ systemctl status [UnitName] # 开机自动执行该单元 $ systemctl enable [UnitName] # 关闭开机自动执行 $ systemctl disable [UnitName]
五、Service 单元
前面说过,Service 单元就是所要执行的任务,比如发送邮件就是一种 Service。
新建 Service 非常简单,就是在/usr/lib/systemd/system
目录里面新建一个文件,比如mytimer.service
文件,你可以写入下面的内容。
[Unit] Description=MyTimer [Service] ExecStart=/bin/bash /path/to/mail.sh
可以看到,这个 Service 单元文件分成两个部分。
[Unit]
部分介绍本单元的基本信息(即元数据),Description
字段给出这个单元的简单介绍(名字叫做MyTimer
)。
[Service]
部分用来定制行为,Systemd 提供许多字段。
ExecStart
:systemctl start
所要执行的命令ExecStop
:systemctl stop
所要执行的命令ExecReload
:systemctl reload
所要执行的命令ExecStartPre
:ExecStart
之前自动执行的命令ExecStartPost
:ExecStart
之后自动执行的命令ExecStopPost
:ExecStop
之后自动执行的命令
注意,定义的时候,所有路径都要写成绝对路径,比如bash
要写成/bin/bash
,否则 Systemd 会找不到。
现在,启动这个 Service。
$ sudo systemctl start mytimer.service
如果一切正常,你应该就会收到一封邮件。
六、Timer 单元
Service 单元只是定义了如何执行任务,要定时执行这个 Service,还必须定义 Timer 单元。
/usr/lib/systemd/system
目录里面,新建一个mytimer.timer
文件,写入下面的内容。
[Unit] Description=Runs mytimer every hour [Timer] OnUnitActiveSec=1h Unit=mytimer.service [Install] WantedBy=multi-user.target
这个 Timer 单元文件分成几个部分。
[Unit]
部分定义元数据。
[Timer]
部分定制定时器。Systemd 提供以下一些字段。
OnActiveSec
:定时器生效后,多少时间开始执行任务OnBootSec
:系统启动后,多少时间开始执行任务OnStartupSec
:Systemd 进程启动后,多少时间开始执行任务OnUnitActiveSec
:该单元上次执行后,等多少时间再次执行OnUnitInactiveSec
: 定时器上次关闭后多少时间,再次执行OnCalendar
:基于绝对时间,而不是相对时间执行AccuracySec
:如果因为各种原因,任务必须推迟执行,推迟的最大秒数,默认是60秒Unit
:真正要执行的任务,默认是同名的带有.service
后缀的单元Persistent
:如果设置了该字段,即使定时器到时没有启动,也会自动执行相应的单元WakeSystem
:如果系统休眠,是否自动唤醒系统
上面的脚本里面,OnUnitActiveSec=1h
表示一小时执行一次任务。其他的写法还有OnUnitActiveSec=*-*-* 02:00:00
表示每天凌晨两点执行,OnUnitActiveSec=Mon *-*-* 02:00:00
表示每周一凌晨两点执行,具体请参考官方文档。
七、[Install] 和 target
mytimer.timer
文件里面,还有一个[Install]
部分,定义开机自启动(systemctl enable
)和关闭开机自启动(systemctl disable
)这个单元时,所要执行的命令。
上面脚本中,[Install]
部分只写了一个字段,即WantedBy=multi-user.target
。它的意思是,如果执行了systemctl enable mytimer.timer
(只要开机,定时器自动生效),那么该定时器归属于multi-user.target
。
所谓 Target 指的是一组相关进程,有点像 init 进程模式下面的启动级别。启动某个Target 的时候,属于这个 Target 的所有进程都会全部启动。
multi-user.target
是一个最常用的 Target,意为多用户模式。也就是说,当系统以多用户模式启动时,就会一起启动mytimer.timer
。它背后的操作其实很简单,执行systemctl enable mytimer.timer
命令时,就会在multi-user.target.wants
目录里面创建一个符号链接,指向mytimer.timer
。
八、定时器的相关命令
下面,启动刚刚新建的这个定时器。
$ sudo systemctl start mytimer.timer
你应该立刻就会收到邮件,然后每个小时都会收到同样邮件。
查看这个定时器的状态。
$ systemctl status mytimer.timer
查看所有正在运行的定时器。
$ systemctl list-timers
关闭这个定时器。
$ sudo systemctl stop myscript.timer
下次开机,自动运行这个定时器。
$ sudo systemctl enable myscript.timer
关闭定时器的开机自启动。
$ sudo systemctl disable myscript.timer
九、日志相关命令
如果发生问题,就需要查看日志。Systemd 的日志功能很强,提供统一的命令。
# 查看整个日志 $ sudo journalctl # 查看 mytimer.timer 的日志 $ sudo journalctl -u mytimer.timer # 查看 mytimer.timer 和 mytimer.service 的日志 $ sudo journalctl -u mytimer # 从结尾开始查看最新日志 $ sudo journalctl -f # 从结尾开始查看 mytimer.timer 的日志 $ journalctl -f -u timer.timer
十、参考链接
- How to Use Systemd Timers, by Jason Graham
- Using systemd as a better cron, by luqmaan
- Getting started with systemd, by CoreOS
- systemd/Timers, by ArchWiki
- Understanding Systemd Units and Unit Files, by Justin Ellingwood
(完)
张俊凯 说:
总结一句话,用systemd在linux下完成定时任务,高级版的windows计划任务程序。日后有需求拿着教程去实现!
2018年3月30日 10:47 | # | 引用
example 说:
浪费生命,我选择继续用cron
2018年3月30日 14:26 | # | 引用
吴亮 说:
工业上其实都是用crontab,简单好用
2018年3月30日 14:43 | # | 引用
但丁 说:
是啊,比crontab好在哪?没看完感觉复杂的多
2018年3月30日 21:01 | # | 引用
ixx 说:
我感觉一大波 反对Systemd的人 即将到达现场
2018年4月 2日 10:44 | # | 引用
但丁 说:
是啊,比crontab好在哪?没看完感觉复杂的多
2018年4月 2日 20:14 | # | 引用
安全上网指南 说:
新东西大家也了解一下吧,不是必要都不会去动原来的 cron ,谁知道有没有坑呢。
2018年4月 3日 16:20 | # | 引用
藕丝空间 说:
在这里总是能学到精华,拜读,感谢阮一峰。
2018年4月 3日 17:28 | # | 引用
pencil 说:
Centos7 使用systemd。存在既有道理,虽然我现在还在用6
2018年4月 4日 09:25 | # | 引用
匿名 说:
有好几个journalctl都是不需要sudo的
2018年4月 4日 13:13 | # | 引用
zf 说:
太复杂了,最好说下相对于cron收益在哪
2018年4月 4日 16:00 | # | 引用
业余草 说:
Systemd 还是没没出来比 crontab 好在哪里啊。运维现在大部分都使用 crontab 啊!
2018年4月 5日 10:32 | # | 引用
匿名 说:
用centos7都是systemd的服务,官方文档很枯燥,目前中文资料也不多。几篇介绍systemd的文章很有帮助,谢谢~
2018年4月 6日 15:44 | # | 引用
hajimuz 说:
问比 cron 好在哪里的根本没仔细看文章吧?
总结起来最明显的一条就是,systemd 把一些以前要靠 Shell 脚本实现的功能,全部都变成配置文件实现了。
2018年4月 8日 10:55 | # | 引用
owenliang 说:
systemd常用来开机自启程序,编写一个简单的service并enable它,就可以实现开机自动启动。
2018年4月11日 12:53 | # | 引用
zhiyue 说:
cron 也可以使用配置文件的
2018年4月13日 14:38 | # | 引用
ferstar 说:
建议阮老师可以加这张图讲解下
https://lcom.static.linuxfound.org/images/stories/41373/Systemd-components.png
个人感觉很形象
2018年4月18日 11:52 | # | 引用
helloqu 说:
阮老师
在执行了你的邮件脚本后,每次打开终端就会出现 you have a mail 想把这个去掉怎么版
2018年4月26日 15:19 | # | 引用
hj 说:
对比crontab , timer 功能强大不少,灵活度更高。以下是个人愚见:
crontab 多个计划任务远程文件备份时,比如 0点1分钟开始第一个,scp|rsync|ftp 简单有效的方式,大文件的网络传输时间不确定,那么下个计划任务开始时间,几乎都是按照经验估计。长期时间下来总有问题,有时候上个任务网络传输还没完成,下个任务已经开始,同时占用网络,然后再下个任务又开始了,恶性循环。
timer 可以建立依赖的前后关系,比如:必须第一网络传输完成后,在启动第二个网络传输任务,还能控制系统资源的占用,避免备份引起的连锁问题。
更多好处,待你发现。
2018年6月 6日 11:04 | # | 引用
vennvenn 说:
哇,DR RUAN, thank you so much for the sharing.
2018年6月 6日 15:46 | # | 引用
jimmy 说:
在ubuntu18.04版本上设置不成功,以下是日志:
systemd[1]: /home/cmp/cmp-backend/pr.timer:6: Failed to parse timer value, ignoring: *-*-* 02:00:00
systemd[1]: pr.timer: Timer unit lacks value setting. Refusing.
systemd[1]: /home/cmp/cmp-backend/pr.timer:6: Failed to parse timer value, ignoring: "*-*-* 02:00:00"
systemd[1]: pr.timer: Timer unit lacks value setting. Refusing.
以下是内容:
root@cmp ~# cat /lib/systemd/system/pr.timer
[Unit]
Description="Runs the pr.service every moring"
[Timer]
#OnCalendar=Mon-Sun *-*-* 02-06:00/240
OnUnitActiveSec="*-*-* 02:00:00"
#Persistent=true
Unit=pr.service
[Install]
WantedBy=multi-user.target
WantedBy=timers.target
还望博主赐教
2018年6月11日 14:53 | # | 引用
leaveye 说:
cron 只是定时任务,systemd 的定位是优化版的 initfs ,只是捎带脚可以实现 cron 的任务而已。
2018年7月21日 15:32 | # | 引用
逐风随想 说:
我觉得还是cron比较灵活,一直都使用cron能快速解决业务需求才是好的工具。
2018年8月26日 01:00 | # | 引用
六月和饭 说:
其他的写法还有OnUnitActiveSec=*-*-* 02:00:00表示每天凌晨两点执行,OnUnitActiveSec=Mon *-*-* 02:00:00表示每周一凌晨两点执行,具体请参考官方文档。
这个是错误的,我试了好久都不行,还好你后面跟了官方文档的地址,官方文档上说这种情况要用OnCalendar
2019年2月11日 14:30 | # | 引用
卡卡 说:
我和你有同样的问题,快一年了,终于有一天忍不住了
https://apple.stackexchange.com/questions/28745/how-do-i-delete-all-terminal-mail
2019年6月 4日 14:22 | # | 引用
yanwuhen 说:
2020年4月 2日 11:33 | # | 引用
无名小卒 说:
OnUnitActiveSec=*-*-* 02:00:00
这个属性应该改成OnCalendar,每天两点执行
2020年6月15日 10:37 | # | 引用
sixg0000d 说:
阮老师你好,我察觉这篇文章可能存在一些错误:
1.
/lib/systemd/system:系统默认的单元文件
/etc/systemd/system:用户安装的软件的单元文件
/usr/lib/systemd/system:用户自己定义的单元文件
参见 https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Unit%20File%20Load%20Path
首先,其实在大多数 linux 发行版中 /lib 是 /usr/lib 的软链接,所以1和3其实是同一目录,作用自然应该是相同的。
其次,按官方文档描述,应该是
/etc/systemd/system:用户创建的单元文件(也可以说是用户自己定义的单元文件)
/usr/lib/systemd/system:包管理安装的单元文件(也可以说是用户安装的软件的单元文件)
2.
按上面的修正
新建 Service 非常简单,就是在/usr/lib/systemd/system目录里面新建一个文件,比如mytimer.service文件,你可以写入下面的内容。
是不对的。
如要自行新建单元文件,最好是在/etc/systemd/system下(也可以systemctl edit --force --full,这样可以免于保存后systemctl daemon-reload)。
因为/usr/lib/systemd/system作为包管理的单元文件目录,自行改动很容易混淆。其次/usr/lib/systemd/system的优先级也低于/etc/systemd/system,前者的配置会被后者覆盖。
2021年2月 6日 21:29 | # | 引用
Robin 说:
上面的脚本里面,OnUnitActiveSec=1h表示一小时执行一次任务。其他的写法还有OnUnitActiveSec=*-*-* 02:00:00表示每天凌晨两点执行,OnUnitActiveSec=Mon *-*-* 02:00:00表示每周一凌晨两点执行,具体请参考官方文档。
这段话有问题,只有OnCalendar可以使用‘Mon *-*-* 02:00:00’这种格式,OnUnitActiveSec=Mon *-*-* 02:00:00这种写法是错误的。具体请参考官方文档 :)
2023年12月20日 17:45 | # | 引用
li 说:
服务器环境不一样,cron和systemd有不和获取的作用;比如cron执行脚本但脚本中有多个启动命令并且打印日志,使用cron可能在执行是出现问题,比如只成功执行第一条命令,日志也卡住或者别的问题导致脚本中命令没有全部执行完毕,但是用systemd加systemd定时可以解决,如果你想一个命令写一个脚本中多写几个也可以,看自己想法了
2025年1月 2日 16:01 | # | 引用