Linux中Cron定时任务的踩坑
前言
博主现在有个需求,需要定时执行一个脚本,于是在Linux环境中自然想到了cron。简单描述一下cron:
什么是 cron 服务?
cron 是 Unix 和类 Unix 操作系统中的一个守护进程,用于根据预定的时间表自动执行任务。这个服务是操作系统的核心部分,允许系统管理员和用户计划周期性任务,比如备份、系统维护和定时脚本执行。
cron 表达式的语法和用法
cron 表达式最早出现在 Unix 系统中,作为 cron 守护进程的一部分,用于定期执行计划任务。cron 是由 Ken Thompson 在 1975 年为 Unix 操作系统编写的,其后在许多 Unix 和类 Unix 操作系统(如 Linux、BSD 等)中得到了广泛应用。
cron 表达式由五个字段组成,这些字段指定了任务的执行时间。每个字段之间用空格分隔,字段的顺序和含义如下:
1 | * * * * * command to be executed |
字段解释
- 分钟(Minute):0-59
- 小时(Hour):0-23
- 月份中的日期(Day of Month):1-31
- 月份(Month):1-12 或使用简写(如 Jan, Feb, Mar)
- 星期中的日期(Day of Week):0-7(0 和 7 都表示星期日)或使用简写(如 Sun, Mon, Tue)
特殊字符
*:表示任何值。,:用于指定多个值。-:用于指定值的范围。/:用于指定步长。
示例
每天凌晨 2:30 执行任务
1 | 30 2 * * * /path/to/your/command |
每周一凌晨 3:00 执行任务
1 | 0 3 * * 1 /path/to/your/command |
每月的第一天凌晨 4:00 执行任务
1 | 0 4 1 * * /path/to/your/command |
每隔 5 分钟执行一次任务
1 | */5 * * * * /path/to/your/command |
每个工作日的中午 12:00 执行任务
1 | 0 12 * * 1-5 /path/to/your/command |
工作原理
后台运行:
cron守护进程在后台运行,持续监视系统中的crontab文件和目录中的计划任务。定时检查:
cron每分钟检查一次crontab文件,查看是否有需要执行的任务。执行任务:如果当前时间匹配某个任务的时间表,
cron会执行该任务。
配置文件和目录
cron 服务通过几个关键的配置文件和目录来管理任务:
1. /etc/crontab
这是系统范围内的 crontab 文件,可以包含多个用户的任务。与用户的 crontab 文件不同,这个文件中每个任务需要指定用户。
格式示例:
1 | SHELL=/bin/bash |
2. /etc/cron.d/
这个目录包含独立的文件,每个文件可以包含一个或多个 cron 任务,适用于系统级别的任务。
3. /var/spool/cron/crontabs/
这个目录包含每个用户的 crontab 文件。这些文件是用户通过 crontab -e 命令创建和管理的。
4. /etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/, /etc/cron.monthly/
这些目录包含定期执行的脚本,分别在每小时、每天、每周和每月运行一次。将脚本放入相应的目录中,cron 将在对应的时间间隔内执行它们。
管理 cron 服务
启动和停止 cron 服务
在大多数 Linux 发行版中,cron 服务默认启动并在系统启动时自动运行。可以使用 systemctl 或 service 命令来手动启动、停止或重启 cron 服务。
使用 systemctl:
1 | sudo systemctl start cron |
使用 service:
1 | sudo service cron start |
查看 cron 日志
cron 服务的日志通常存储在系统日志文件中。查看日志可以帮助诊断任务是否正确执行。
例如,在基于 syslog 的系统中,可以通过以下命令查看 cron 日志:
1 | grep CRON /var/log/syslog |
在基于 journalctl 的系统中:
1 | journalctl -u cron |
安全性
cron 服务包含两个文件来控制用户的访问权限:
/etc/cron.allow:只有列在这个文件中的用户才能使用cron服务。/etc/cron.deny:列在这个文件中的用户不能使用cron服务。
如果这两个文件都不存在,通常默认允许所有用户使用 cron 服务。
优点和缺点
优点
- 自动化:减少了手动执行任务的需要。
- 灵活性:可以精确到分钟级别的任务调度。
- 可靠性:经过长期使用和测试,非常稳定。
缺点
- 复杂性:配置文件语法可能对新用户不太友好。
- 缺乏可视化管理工具:相比现代任务调度系统,
cron缺少图形界面的管理工具。
结论
cron 是 Linux 系统中一个强大且可靠的任务调度工具,通过它,可以轻松实现各种周期性任务的自动化。理解 cron 的基本工作原理和配置文件结构,是有效利用这一工具的关键。
问题描述
根据上面的描述,只要在cron服务对应的配置目录下增加自己的需要的脚本就行了,根据博主的需求,直接在/etc/cron.hourly/目录添加自己的脚本就好了。
值得注意的一点是,脚本必须要有可执行权限,不然会调度失败的。
按照上面的说法,添加自定义脚本,并注意赋予可执行权限应该就好了吧,博主的脚本大概是这样:
1 |
|
但是实际执行起来却没有效果,代码没有按照预期更新。
问题分析
首先看看自己的脚本是不是有问题,直接手动执行一下,确认是能正常执行的。
再者看看cron服务是不是正常运行,博主的是debian12,服务是由systemd接管的,直接systemctl status cron看看服务状态:
可见cron本身也是正常运行的。
但是在刚才的状态显示中,注意到一行:
根据这个提示信息搜索,发现了原因:
当使用
cron运行任务时,如果任务产生了输出(标准输出或标准错误),默认情况下cron会尝试通过邮件发送这些输出给用户。然而,如果系统上没有安装邮件传输代理(MTA,例如sendmail或postfix),就会出现“No MTA installed, discarding output”的提示。
也就是cron默认会以邮件的形式来发送任务输出信息,而我没有配置邮件服务,自然会有这个提示。要进一步排除问题肯定要看日志才行,那么如何解决呢?
重定向输出到文件
你可以将
cron任务的输出重定向到日志文件中。这样你可以查看这些文件来了解任务的执行情况。示例:
假设你有一个脚本
/path/to/your/script.sh,你可以在crontab中将其输出重定向到文件:1
* * * * * /path/to/your/script.sh >> /path/to/your/logfile.log 2>&1
这里
>> /path/to/your/logfile.log 2>&1表示将标准输出和标准错误都重定向到/path/to/your/logfile.log文件中。在脚本内部重定向输出
你也可以在脚本本身内部进行输出重定向。这样可以确保所有输出都被重定向,无论脚本从哪里运行。
示例:
在你的脚本
/path/to/your/script.sh的开头添加重定向:1
2
3
4
5
6
exec >> /path/to/your/logfile.log 2>&1
# Your script content here
echo "This is a log message"安装 MTA
如果你希望接收
cron任务的邮件通知,可以安装并配置一个邮件传输代理(MTA)。常见的 MTA 包括sendmail和postfix。
博主不是在crontab中配置的,自然只能采用第二种方法,将输出重定向到一个日志文件
1 |
|
再次等待任务被执行,查看日志,发现了报错:
很明显就是没有git拉取的权限啊。我是以ssh的形式拉取代码的,已经把本地的ssh公钥配置好了,并且手动执行脚本也没有啥问题啊,为什么还会有这个报错呢?
问题解决
只能继续看日志,就脚本执行的结果的日志能看到的东西有限,只能分析出脚本执行失败是没有git权限的问题。那就看看cron本身的日志吧。
cron 任务的执行日志通常可以通过以下几种方式查看:
在大多数 Linux 发行版中,cron 的执行日志记录在系统日志文件中。这些日志文件的位置和管理方式可能因发行版而异。
查看系统日志
在基于
syslog的系统中,cron的日志通常记录在/var/log/syslog或/var/log/cron文件中。示例命令:
1 grep CRON /var/log/syslog或:
1 cat /var/log/syslog | grep cron在一些系统中,可能有专门的
cron日志文件/var/log/cron:
1 cat /var/log/cron在基于
systemd的系统中,可以使用journalctl查看cron服务的日志:示例命令:
1 journalctl -u cron
查看日志:
注意到上面那一行: session closed for user root
会话由root关闭,那么回到最根本的问题,cron在执行的时候,是以谁的身份执行的?
cron 任务在执行时的身份取决于任务的定义位置和方式。以下是几种常见情况:
cron任务在执行时的身份取决于任务的定义位置和方式。以下是几种常见情况:1. 系统
cron任务
/etc/crontab系统级别的
crontab文件位于/etc/crontab。在这个文件中,任务以明确指定的用户身份运行。每个任务行都有一个用户字段。示例:
1
2
3
4
5
6
7
8 SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# m h dom mon dow user command
17 * * * * root run-parts /etc/cron.hourly
25 6 * * * root run-parts /etc/cron.daily
47 6 * * 7 root run-parts /etc/cron.weekly
52 6 1 * * root run-parts /etc/cron.monthly在这个例子中,
run-parts命令中的所有脚本都以root用户身份运行。
/etc/cron.d类似于
/etc/crontab,在/etc/cron.d目录下的文件中也有一个用户字段,用于指定执行任务的用户。示例:
1
2
3
4
5
6 # /etc/cron.d/example
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# m h dom mon dow user command
30 2 * * * root /path/to/your/script.sh2. 用户
cron任务用户的
crontab文件用户可以通过
crontab -e命令编辑他们自己的crontab文件。用户crontab文件中的任务以该用户的身份运行,不需要在任务行中指定用户。示例:
1
2
3 # 通过 `crontab -e` 编辑的用户 crontab 文件
# m h dom mon dow command
0 5 * * * /path/to/your/script.sh这个任务会以当前用户身份运行。
3. 特定目录下的系统
cron任务
/etc/cron.hourly,/etc/cron.daily,/etc/cron.weekly,/etc/cron.monthly这些目录中的脚本由系统
cron任务(通常在/etc/crontab中定义)以指定用户(通常是root)的身份运行。示例:
在
/etc/crontab文件中:
1
2 # m h dom mon dow user command
25 6 * * * root run-parts /etc/cron.daily在
/etc/cron.daily目录中的所有脚本都以root用户身份运行。总结
- 系统
crontab文件(/etc/crontab) 和/etc/cron.d目录 中的任务按照文件中指定的用户身份运行。- 用户
crontab文件 中的任务以相应用户的身份运行。/etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly、/etc/cron.monthly目录 中的任务根据系统crontab文件中的定义,通常以root用户身份运行。通过这些机制,
cron可以灵活地调度任务并确保它们以适当的用户权限执行。
根据第三点可知,我的任务实际是以root的身份被执行的,但是实际上我git所配置的公钥是另外一个用户!
那么解决其实也很好解决了,自己在crontab中加上自己要执行任务的脚本,配置好时间和身份,就解决问题了!
总结
本质上出问题还是自己对cron不够熟悉导致的,最关键的一点就是cron的运行环境和正常的shell中运行脚本有所区别:
1. 环境变量
当 cron 运行时,环境变量的设置与用户在交互式 shell 中的环境不同。常见的环境变量如 PATH、HOME、USER 等可能不会被正确设置。
解决方法
- 显式设置环境变量:在
cron作业中显式设置所需的环境变量。 - 使用绝对路径:在
cron作业中使用命令和文件的绝对路径。
示例:
1 | * * * * * /usr/bin/env PATH=/usr/local/bin:/usr/bin:/bin /path/to/your/script.sh |
或在脚本的开头添加环境变量设置:
1 |
|
2. Shell 类型
cron 使用的默认 shell 是 /bin/sh,而不是用户在交互式 shell 中可能使用的 /bin/bash 或其他 shell。
解决方法
- 显式指定 shell:在
crontab文件或脚本中指定要使用的 shell。
示例:
在 crontab 文件的顶部指定 shell:
1 | SHELL=/bin/bash |
在脚本的开头指定:
1 |
|
3. 当前工作目录
当 cron 运行脚本时,当前工作目录通常是用户的主目录($HOME),而不是脚本所在的目录。
解决方法
- 显式指定工作目录:在脚本中显式切换到脚本所在的目录或所需的工作目录。
示例:
1 |
|
4. 输出重定向
cron 作业的标准输出和标准错误默认会通过邮件发送给用户,但在许多系统上邮件服务未配置或不使用。
解决方法
- 重定向输出:在
cron作业中显式地将输出和错误重定向到日志文件。
示例:
1 | * * * * * /path/to/your/script.sh >> /path/to/your/logfile 2>&1 |
5. 限制和权限
cron 作业运行时的权限是提交作业的用户的权限。确保该用户有执行脚本所需的所有权限。
总结
由于 cron 运行时的环境与交互式 shell 不同,最好在 cron 作业或脚本中显式设置所需的环境变量、使用绝对路径、指定工作目录,并处理输出重定向。这些措施可以确保 cron 作业按预期运行。
拓展
其实在cron以外,之前也知道在systemd中,cron很多功能都被systemd timers替代了,顺便看看它怎么用吧。
systemd timers 的优势
- 更细粒度的控制:
systemdtimers 提供了更详细的时间和日期配置选项,支持更复杂的定时任务安排。 - 日志记录:
systemd与journald紧密集成,提供了详细的日志记录和分析功能。 - 依赖管理:
systemdtimers 可以与其他systemd服务和目标(targets)定义依赖关系,确保任务在适当的系统状态下运行。 - 集成性:所有系统服务和定时任务都可以通过统一的
systemd界面进行管理和监控。
使用 systemd timers 替代 cron
创建一个 systemd timer
假设我们有一个脚本 /path/to/your/script.sh,需要每天运行一次。我们可以创建一个 systemd timer 和一个相应的 systemd 服务。
1. 创建 systemd 服务单元文件
首先,创建一个服务单元文件 /etc/systemd/system/my-script.service:
1 | [Unit] |
2. 创建 systemd timer 单元文件
然后,创建一个定时器单元文件 /etc/systemd/system/my-script.timer:
1 | [Unit] |
OnCalendar=daily 表示这个定时器将每天运行一次。Persistent=true 确保即使系统在预定时间关机或重启,任务也会在下次启动时立即运行。
3. 启用并启动定时器
启用并启动定时器:
1 | sudo systemctl enable my-script.timer |
4. 检查定时器状态
你可以使用以下命令检查定时器的状态:
1 | systemctl status my-script.timer |
5. 查看日志
使用 journalctl 查看服务的日志:
1 | journalctl -u my-script.service |
其他 systemd timers 的配置示例
每小时运行一次
1 | [Timer] |
每周一凌晨 3 点运行一次
1 | [Timer] |
每个月的第一天运行一次
1 | [Timer] |
使用 AccuracySec 和 RandomizedDelaySec
为了减少同时启动的定时任务的负载,可以使用 AccuracySec 和 RandomizedDelaySec 选项:
1 | [Timer] |
总结
systemd timers 提供了一个强大且灵活的替代方案,可以完全取代传统的 cron 定时任务。通过创建相应的 systemd 服务和定时器单元文件,你可以轻松管理和监控定时任务,并享受 systemd 提供的高级功能和集成性。












