前言

博主现在有个需求,需要定时执行一个脚本,于是在Linux环境中自然想到了cron。简单描述一下cron:

什么是 cron 服务?

cron 是 Unix 和类 Unix 操作系统中的一个守护进程,用于根据预定的时间表自动执行任务。这个服务是操作系统的核心部分,允许系统管理员和用户计划周期性任务,比如备份、系统维护和定时脚本执行。

cron 表达式的语法和用法

cron 表达式最早出现在 Unix 系统中,作为 cron 守护进程的一部分,用于定期执行计划任务。cron 是由 Ken Thompson 在 1975 年为 Unix 操作系统编写的,其后在许多 Unix 和类 Unix 操作系统(如 Linux、BSD 等)中得到了广泛应用。

cron 表达式由五个字段组成,这些字段指定了任务的执行时间。每个字段之间用空格分隔,字段的顺序和含义如下:

1
2
3
4
5
6
7
8
* * * * * command to be executed
- - - - -
| | | | |
| | | | +----- Day of the week (0 - 7) (Sunday=0 or 7)
| | | +------- Month (1 - 12)
| | +--------- Day of the month (1 - 31)
| +----------- Hour (0 - 23)
+------------- Minute (0 - 59)

字段解释

  1. 分钟(Minute):0-59
  2. 小时(Hour):0-23
  3. 月份中的日期(Day of Month):1-31
  4. 月份(Month):1-12 或使用简写(如 Jan, Feb, Mar)
  5. 星期中的日期(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

工作原理

  1. 后台运行cron 守护进程在后台运行,持续监视系统中的 crontab 文件和目录中的计划任务。

  2. 定时检查cron 每分钟检查一次 crontab 文件,查看是否有需要执行的任务。

  3. 执行任务:如果当前时间匹配某个任务的时间表,cron 会执行该任务。

配置文件和目录

cron 服务通过几个关键的配置文件和目录来管理任务:

1. /etc/crontab

这是系统范围内的 crontab 文件,可以包含多个用户的任务。与用户的 crontab 文件不同,这个文件中每个任务需要指定用户。

格式示例:

1
2
3
4
5
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user command
0 5 * * * root /usr/bin/backup

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 服务默认启动并在系统启动时自动运行。可以使用 systemctlservice 命令来手动启动、停止或重启 cron 服务。

使用 systemctl

1
2
3
4
5
sudo systemctl start cron
sudo systemctl stop cron
sudo systemctl restart cron
sudo systemctl enable cron # 在系统启动时自动启动
sudo systemctl disable cron # 禁止在系统启动时自动启动

使用 service

1
2
3
sudo service cron start
sudo service cron stop
sudo service cron restart

查看 cron 日志

cron 服务的日志通常存储在系统日志文件中。查看日志可以帮助诊断任务是否正确执行。

例如,在基于 syslog 的系统中,可以通过以下命令查看 cron 日志:

1
grep CRON /var/log/syslog

在基于 journalctl 的系统中:

1
journalctl -u cron

安全性

cron 服务包含两个文件来控制用户的访问权限:

  1. /etc/cron.allow:只有列在这个文件中的用户才能使用 cron 服务。
  2. /etc/cron.deny:列在这个文件中的用户不能使用 cron 服务。

如果这两个文件都不存在,通常默认允许所有用户使用 cron 服务。

优点和缺点

优点

  • 自动化:减少了手动执行任务的需要。
  • 灵活性:可以精确到分钟级别的任务调度。
  • 可靠性:经过长期使用和测试,非常稳定。

缺点

  • 复杂性:配置文件语法可能对新用户不太友好。
  • 缺乏可视化管理工具:相比现代任务调度系统,cron 缺少图形界面的管理工具。

结论

cron 是 Linux 系统中一个强大且可靠的任务调度工具,通过它,可以轻松实现各种周期性任务的自动化。理解 cron 的基本工作原理和配置文件结构,是有效利用这一工具的关键。

问题描述

根据上面的描述,只要在cron服务对应的配置目录下增加自己的需要的脚本就行了,根据博主的需求,直接在/etc/cron.hourly/目录添加自己的脚本就好了。

v

值得注意的一点是,脚本必须要有可执行权限,不然会调度失败的。

按照上面的说法,添加自定义脚本,并注意赋予可执行权限应该就好了吧,博主的脚本大概是这样:

1
2
3
4
5
6
7
8
9
10
11
#! /bin/bash

# 更新代码前执行一些操作

# 更新最新代码
git pull

# 写入一些新的东西

# 上传代码
git push

但是实际执行起来却没有效果,代码没有按照预期更新。

问题分析

首先看看自己的脚本是不是有问题,直接手动执行一下,确认是能正常执行的。

再者看看cron服务是不是正常运行,博主的是debian12,服务是由systemd接管的,直接systemctl status cron看看服务状态:

image-20240728181223658

可见cron本身也是正常运行的。

但是在刚才的状态显示中,注意到一行:

image-20240728181257563

根据这个提示信息搜索,发现了原因:

当使用 cron 运行任务时,如果任务产生了输出(标准输出或标准错误),默认情况下 cron 会尝试通过邮件发送这些输出给用户。然而,如果系统上没有安装邮件传输代理(MTA,例如 sendmailpostfix),就会出现“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
    #!/bin/bash

    exec >> /path/to/your/logfile.log 2>&1

    # Your script content here
    echo "This is a log message"
  • 安装 MTA

    如果你希望接收 cron 任务的邮件通知,可以安装并配置一个邮件传输代理(MTA)。常见的 MTA 包括 sendmailpostfix

博主不是在crontab中配置的,自然只能采用第二种方法,将输出重定向到一个日志文件

1
2
3
4
5
6
7
8
9
10
11
12
13
#! /bin/bash
# 日志重定向
exec >> /home/xiao/logfile.log 2>&1

# 更新代码前执行一些操作

# 更新最新代码
git pull

# 写入一些新的东西

# 上传代码
git push

再次等待任务被执行,查看日志,发现了报错:

image-20240728182657468

很明显就是没有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

查看日志:

image-20240728183511779

注意到上面那一行: 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.sh

2. 用户 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 中的环境不同。常见的环境变量如 PATHHOMEUSER 等可能不会被正确设置。

解决方法

  • 显式设置环境变量:在 cron 作业中显式设置所需的环境变量。
  • 使用绝对路径:在 cron 作业中使用命令和文件的绝对路径。

示例:

1
* * * * * /usr/bin/env PATH=/usr/local/bin:/usr/bin:/bin /path/to/your/script.sh

或在脚本的开头添加环境变量设置:

1
2
3
4
#!/bin/bash
export PATH=/usr/local/bin:/usr/bin:/bin
# 其他环境变量
/path/to/your/command

2. Shell 类型

cron 使用的默认 shell 是 /bin/sh,而不是用户在交互式 shell 中可能使用的 /bin/bash 或其他 shell。

解决方法

  • 显式指定 shell:在 crontab 文件或脚本中指定要使用的 shell。

示例:

crontab 文件的顶部指定 shell:

1
2
SHELL=/bin/bash
* * * * * /path/to/your/script.sh

在脚本的开头指定:

1
2
#!/bin/bash
# 脚本内容

3. 当前工作目录

cron 运行脚本时,当前工作目录通常是用户的主目录($HOME),而不是脚本所在的目录。

解决方法

  • 显式指定工作目录:在脚本中显式切换到脚本所在的目录或所需的工作目录。

示例:

1
2
3
4
#!/bin/bash
cd /path/to/your/directory
# 运行命令
/path/to/your/command

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 的优势

  1. 更细粒度的控制systemd timers 提供了更详细的时间和日期配置选项,支持更复杂的定时任务安排。
  2. 日志记录systemdjournald 紧密集成,提供了详细的日志记录和分析功能。
  3. 依赖管理systemd timers 可以与其他 systemd 服务和目标(targets)定义依赖关系,确保任务在适当的系统状态下运行。
  4. 集成性:所有系统服务和定时任务都可以通过统一的 systemd 界面进行管理和监控。

使用 systemd timers 替代 cron

创建一个 systemd timer

假设我们有一个脚本 /path/to/your/script.sh,需要每天运行一次。我们可以创建一个 systemd timer 和一个相应的 systemd 服务。

1. 创建 systemd 服务单元文件

首先,创建一个服务单元文件 /etc/systemd/system/my-script.service

1
2
3
4
5
[Unit]
Description=Run my script

[Service]
ExecStart=/path/to/your/script.sh

2. 创建 systemd timer 单元文件

然后,创建一个定时器单元文件 /etc/systemd/system/my-script.timer

1
2
3
4
5
6
7
8
9
[Unit]
Description=Run my script daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

OnCalendar=daily 表示这个定时器将每天运行一次。Persistent=true 确保即使系统在预定时间关机或重启,任务也会在下次启动时立即运行。

3. 启用并启动定时器

启用并启动定时器:

1
2
sudo systemctl enable my-script.timer
sudo systemctl start my-script.timer

4. 检查定时器状态

你可以使用以下命令检查定时器的状态:

1
systemctl status my-script.timer

5. 查看日志

使用 journalctl 查看服务的日志:

1
journalctl -u my-script.service

其他 systemd timers 的配置示例

每小时运行一次

1
2
[Timer]
OnCalendar=hourly

每周一凌晨 3 点运行一次

1
2
[Timer]
OnCalendar=Mon *-*-* 03:00:00

每个月的第一天运行一次

1
2
[Timer]
OnCalendar=monthly

使用 AccuracySecRandomizedDelaySec

为了减少同时启动的定时任务的负载,可以使用 AccuracySecRandomizedDelaySec 选项:

1
2
3
4
[Timer]
OnCalendar=daily
AccuracySec=1h
RandomizedDelaySec=30m

总结

systemd timers 提供了一个强大且灵活的替代方案,可以完全取代传统的 cron 定时任务。通过创建相应的 systemd 服务和定时器单元文件,你可以轻松管理和监控定时任务,并享受 systemd 提供的高级功能和集成性。