
1. 例行性工作Linux 运维里最容易被低估的基础功1.1 定时任务在真实环境里到底承担什么角色我接触 Linux 系统管理这些年最大的感受是很多人对定时任务例行性工作的理解停留在会用 crontab 写个备份脚本这个层面但真正到了线上环境定时任务几乎是无处不在的底层设施。日志切割、监控数据采集、证书续期、数据库备份、临时缓存清理、报表定时生成这些都是靠定时任务在背后默默跑着的。举个最直观的例子某公司内部有一个业务库每天凌晨两点要做全量备份备份完成之后还要把备份文件同步到远程存储再保留最近 7 天的备份、清理更早的文件。这一整条链路如果全靠人肉盯着不仅累而且还容易出纰漏。用定时任务把备份、同步、清理串成一套脚本每天自动执行才是正经的运维做法。RHCE 考试里定时任务属于必考的基础技能。它考的不是你会不会用 crontab -e而是你知不知道任务执行的底层逻辑、环境差异、日志排查以及不同场景下该选哪种工具。很多人在考试里能写对 crontab 语法但一遇到为什么我的脚本手动执行正常、定时执行就失败这种经典问题就懵了。这说明对定时任务的理解还停留在表面。这篇内容我想系统地把 Linux 例行性工作拆开讲清楚cron 系、at 系、systemd timer 这三套工具各自适合什么场景配置时有哪些坑出了问题怎么排查。不管你是备考 RHCE还是日常做系统维护这套东西都能直接用上。1.2 学习路线先分清一次性和周期性例行性工作按调度方式分两大类一类是周期性的比如每天、每周、每隔几分钟执行一次这类用 cron 或者 systemd timer另一类是一次性的比如五分钟后重启服务下午三点执行某个脚本这类用 at 命令最合适。很多人一开始会把这两类混在一起觉得反正都能定时执行。但实际使用中一次性任务如果用 cron 来做会留下一个永远不再需要执行的残留条目之后还得记得清理。而 at 任务执行完就自动消失不需要管理残渣。我建议的学习路径是先精通 cron这是最常用、考试必考的再掌握 systemd timer这是现代 Linux 发行版主推的替代方案最后熟悉 at一次性任务场景。下面我按这个顺序把每套工具的用法、原理、坑都过一遍。2. 工具选型crontab、systemd timer、at 怎么选2.1 三套工具的定位差异先做一张对比表方便大家直观感受三套工具的差异。维度croncrondsystemd timerat任务类型周期性执行周期性执行一次性执行最小粒度分钟级分钟级可到秒级分钟级配置方式crontab 文件 / 系统级 /etc/crontab.timer 单元 .service 单元at 命令交互式输入日志位置/var/log/cronjournalctl 统一日志/var/log/cron依赖管理不支持支持可定义服务依赖不支持错过补跑不补错过就错过Persistenttrue 可补跑不适用适合场景传统运维、脚本类任务需要更精细控制的服务型任务临时、一次性操作从这张表能看出cron 的优势是简单、普及率高、任何 Linux 发行版默认都有systemd timer 是面向服务化场景设计的能管理依赖、能在错过后补跑、日志统一at 则是专门为一次性任务设计的工具。三者不是竞争关系而是互补关系。2.2 实际场景下的选型建议我自己的选型习惯是这样的简单的周期脚本比如每天备份、每 5 分钟做一次健康检查直接用 crontab配置简单、排查问题也容易。如果任务需要保证这次没跑成下次开机要补上比如补课式的数据同步用 systemd timer 的 Persistent 特性。如果任务需要精细控制执行条件比如某个服务起来之后 10 分钟再执行systemd timer 的 After、OnUnitActiveSec 能实现cron 做不到。临时性的、只跑一次的任务比如一个小时后给我发个通知用 at 一行命令搞定不用污染 crontab。RHCE 考试对工具选型的要求其实就是要求你能根据需求描述选择合适的工具。比如题目说设置一个每 30 分钟执行一次的任务你写 cron说在晚上 11 点执行一次临时任务你用 at说要求任务在系统启动后延迟执行并允许错过补跑你就得想到 systemd timer。这些场景我在后面逐个展开。3. crontab 配置拆解与实战要点3.1 时间字段五分钟学会但要想清楚细节crontab 的语法核心是五个时间字段加一条命令顺序是分、时、日、月、周。对应取值范围是分0-59时0-23日1-31月1-12周0-7其中 0 和 7 都表示周日举个例子30 2 * * *表示每天凌晨 2 点 30 分执行*/5 * * * *表示每 5 分钟执行一次0 9-18 * * 1-5表示周一到周五的 9 点到 18 点整点执行。星号、逗号、减号、斜杠这四个通配符分别表示任意值、枚举多个值、连续范围、步进间隔组合起来非常灵活。但这里有个非常典型的坑当日和周字段同时被设置为非星号时cron 的处理逻辑是或而不是与。也就是说0 8 1 * 1的意思是每月 1 号的早上 8 点或者每周一的早上 8 点执行而不是每个月 1 号且恰好是周一才执行。这一点和很多人的直觉不符考试里也经常拿这个做文章。还有一个细节分钟字段一定要写很多人写* 2 * * *以为意思是凌晨 2 点执行一次实际上是凌晨 2 点的每一分钟都执行也就是会连跑 60 次。这是一个非常昂贵的误会之前真有同事在凌晨两点把服务器打挂了。想要每天两点执行一次必须写成0 2 * * *分钟位填 0。3.2 用户级与系统级权限边界要分清crontab 分两个层级用户级和系统级。用户用crontab -e编辑的是自己的任务表存放在 /var/spool/cron/ 目录下文件名就是用户名。系统级任务可以写在 /etc/crontab 文件里也可以在 /etc/cron.d/ 目录下新建文件。两者的区别在于用户级的 crontab 不用写执行用户因为默认就是当前用户而 /etc/crontab 和 /etc/cron.d/ 里的条目在时间字段后面必须写执行用户格式是分 时 日 月 周 用户 命令。比如系统级任务写备份应该是0 2 * * * root /opt/scripts/backup.sh。另外如果用户被列在 /etc/cron.deny 文件里就无法使用 crontab 命令如果存在 /etc/cron.allow 文件则只有该文件里列出的用户才能使用。默认情况下这两个文件可能都不存在所有用户都能配置在安全要求严格的机器上建议显式创建 /etc/cron.allow 来限制使用者范围。考试里经常考 /etc/crontab 和用户 crontab 的格式差异我见过不少人在用户 crontab 里写了用户名导致整行被当成命令执行任务根本不会按预期运行。记住用户 crontab 五段时间字段后面直接跟命令系统 crontab 六段后面跟用户再跟命令。3.3 环境变量定时任务失败的头号嫌疑人我排查过非常多的定时任务问题其中至少三分之一是环境变量导致的。手动在终端里执行脚本一切正常但 cron 一跑就报错最常见的报错就是command not found。原因很简单cron 执行命令时用的是精简环境PATH 一般只有 /usr/bin:/bin不包含 /usr/local/bin更不包含你自己加进去的自定义路径。手动登录终端时shell 会加载 /etc/profile 和 ~/.bash_profile把完整 PATH 配好但 cron 是非交互式、非登录式执行这些文件不会被加载。解决方案有两个思路。第一个是脚本内自包含在脚本开头硬编码 PATH比如export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin再在后面加一行export LANGen_US.UTF-8避免字符集导致的脚本异常。第二个是在 crontab 文件顶部追加环境变量配置PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin MAILTOadminexample.com另外要注意cron 的工作目录默认是用户家目录脚本里如果有相对路径引用一定要先cd到目标目录或者在脚本里用绝对路径定位文件。最稳妥的做法是脚本里所有路径全部写绝对路径包括日志文件的输出路径。3.4 输出日志与重定向别让邮件把系统塞满默认情况下crontab 任务执行时产生的标准输出和标准错误会通过邮件发送给用户。这里有两个隐患一是服务器上如果没配邮件服务输出会积压在本地邮件队列里时间长了占磁盘空间二是即使配了邮件也没人真的会去看邮件日志就相当于丢了。所以我的习惯是每个 cron 任务都显式重定向输出。标准写法是0 2 * * * /opt/scripts/backup.sh /var/log/backup.log 21这条命令把标准输出追加到备份日志把标准错误也重定向到同一个文件。如果你希望成功时安静、失败时通知可以用更精细的写法0 2 * * * /opt/scripts/backup.sh /var/log/backup.log 21 || echo backup failed | mail -s backup alert adminexample.com||左边的命令失败时会执行右边的告警命令这样既不丢日志又能在异常时主动报警。至于那种完全不需要输出的任务可以全部丢到 /dev/null但我不建议真的这么做因为之后排查问题时没有任何线索会很痛苦。3.5 百分号转义一个容易忽视的语法细节如果你在 crontab 命令里用到%符号比如 date 命令的格式化参数%Y%m%d会有一个坑在 crontab 文件中%会被解析成特殊字符表示换行之后的内容会被作为标准输入传给命令。所以想输出带格式的日期必须写成\%Y\%m\%d0 2 * * * /opt/scripts/backup.sh --prefix$(date \%Y\%m\%d) /var/log/backup.log 21这个细节很多人不知道写进去之后任务报错或者行为诡异还查不出原因。考试虽然不考这么细但实际工作中碰到了排查起来非常痛苦我特意提一下。4. at 一次性任务临时需求的最佳拍档4.1 at 命令的基本操作at 的用法比 cron 简单得多。进入交互模式直接用at 时间表达式比如at 14:30输入 at 后会进入一个类似 shell 的提示符你输入要执行的命令以 CtrlD 结束。也可以不用交互模式直接把命令从管道喂进去echo /opt/scripts/oneoff.sh | at 14:30时间表达式非常灵活支持绝对时间、相对时间、甚至模糊表达。比如at now 10 minutes表示 10 分钟之后at 17:00 tomorrow表示明天下午五点at noon表示今天中午十二点at teatime表示下午四点。相对时间的写法最常用因为临时任务通常就是再过多久跑一下。查看待执行的任务用atq输出结果里会带任务编号和执行时间。删除任务用atrm 编号比如atrm 5就把编号 5 的任务删掉。任务一旦被执行条目就从队列里消失不需要额外清理。4.2 at 的安全与权限控制at 的安全机制和 cron 类似受 /etc/at.allow 和 /etc/at.deny 控制。存在 at.allow 时只有列表内的用户能用 at否则所有用户都能用再看 at.deny 里有没有被排除的用户。默认系统一般只有 at.deny 且内容是空表示所有用户都能用。权限这块在企业环境里值得认真对待。如果一台机器是多人共管的临时任务又涉及重启服务、删文件这类高风险操作最好限制只有 root 或指定运维组成员能使用 at避免有人随手提交一个危险任务。还要注意at 任务执行时同样会继承精简环境所以脚本里该写的绝对路径和 PATH 声明一样不能少。另外 at 任务执行输出默认也是发邮件同样建议重定向到日志文件。5. systemd timer面向服务器时代的进阶方案5.1 一次完整的 timer 配置拆解systemd timer 由一对单元文件组成一个 .timer 描述触发条件一个 .service 描述要执行的内容。先用 timer 管理一个每小时执行一次健康检查的任务整个过程分三步。第一步创建服务单元 /etc/systemd/system/healthcheck.service[Unit] DescriptionRun health check script [Service] Typeoneshot ExecStart/opt/scripts/healthcheck.shTypeoneshot 表示执行完就退出不是长驻服务。第二步创建配套的定时单元 /etc/systemd/system/healthcheck.timer[Unit] DescriptionRun health check every hour [Timer] OnCalendarhourly Persistenttrue [Install] WantedBytimers.targetOnCalendar 的语法比 cron 更接近自然语言hourly 就是每小时还可以写Mon..Fri 09:00:00这种表达。Persistenttrue 的含义是如果到达执行时间时系统处于关机状态开机后会自动补跑错过的任务。这是 cron 完全没有的能力。第三步启用并启动定时器systemctl daemon-reload systemctl enable healthcheck.timer systemctl start healthcheck.timer注意这里 enable/start 的对象是 .timer不是 .service。查看当前系统上所有 timer 的状态用systemctl list-timers它会列出下一次执行时间、上一次执行时间以及对应的 service 单元。5.2 OnCalendar 语法与 cron 的对照OnCalendar 语法看起来和 cron 差异大但理解起来其实更直观。下面是几个常用对照需求crontab 写法OnCalendar 写法每天凌晨 2 点0 2 * * *02:00:00每 5 分钟*/5 * * * **:0/5每周一 9 点0 9 * * 1Mon 09:00:00每月 1 号和 15 号0 0 1,15 * **-*-01,15 00:00:00OnCalendar 还支持every 5 minutes这种纯粹自然语言的写法以及sec、min单位组合灵活度很高。systemd timer 还支持 OnBootSec 表示开机后多久执行、OnUnitActiveSec 表示服务上一次启动后多久再执行这是 cron 完全不支持的场景。如果用 systemd timer 管理任务查看日志直接走 journalctl比如journalctl -u healthcheck.service就能看到上一次执行的全部输出。这和 cron 需要单独看 /var/log/cron 加脚本日志的方式相比排查链路更统一。5.3 什么场景值得从 cron 迁移到 timer我的建议是不要为了用新工具而用新工具但以下几类场景建议优先考虑 timer任务需要错过补跑保证任务之间有明确的先后依赖关系任务对应的就是某个服务单元的定时启动你希望任务的日志纳入 journald 统一管理。比如某公司的数据同步任务要求每天凌晨同步一次但机器经常在凌晨关机。用 cron 的话关机期间的任务直接就丢了用 timer 加 Persistenttrue下次开机就能自动补跑。这种场景用 timer 的价值就非常明显cron 无法可靠实现。6. 常见问题与排查技巧实录6.1 任务没执行从哪一步开始查我总结了一套排查定时任务的固定思路按顺序走基本能定位大部分问题。第一步确认任务是否真的被加载了。crontab -l能看到用户级任务把输出和时间字段再核对一遍。系统级的去看 /etc/crontab 和 /etc/cron.d/ 目录。第二步确认 crond 服务是运行状态。systemctl status crond看主进程是否 active服务挂了任务必不可能执行。第三步看日志。cron 的日志在 /var/log/cron里面记录每条任务的执行时间和执行的命令比如Feb 16 02:00:01 hostname CROND[12345]: (root) CMD (/opt/scripts/backup.sh)确认日志里有这一行说明调度器确实触发了任务如果连日志都没有说明 crontab 配置根本没被识别。如果日志有但执行结果不对问题就出在脚本或环境上下一步检查脚本的手动执行结果和日志输出。第四步看任务输出日志。上一步如果确认脚本被执行但结果不对就看脚本自己的日志文件确认失败点是哪个命令。很多时候是脚本里某个程序依赖的路径没配置、依赖的数据库服务没启动这些在 cron 的干净环境里特别容易暴露。6.2 我踩过的几个经典坑位第一类问题是权限。某同事写了一个需要读 root 私有文件的备份脚本crontab 里用的用户是普通用户手动 su 到该用户测试看着正常一执行就 Permission denied。排查思路很简单crontab 里配置的执行用户是谁就用那个用户去手动执行不要在 root 终端下测试。第二类问题是时间格式。0 * * * *容易被误读成每 0 分钟执行也就是每小时执行一次实际上它确实表示每小时整点执行但很多人会把* * * * *当成每分钟执行把0 0 * * *写成0 0 * * 0导致周日才执行都是低级但真实的错误。第三类问题是脚本里用了相对路径。比如脚本开头cd backups在 cron 里默认工作目录是用户家目录如果家目录下没有 backups 目录整个脚本就崩了。所有路径改用绝对路径是解决这类问题最粗暴有效的办法。第四类问题是时区。系统时区改了之后cron 的任务时间会跟着变。如果服务器用的 UTC而业务方要求北京时间执行那么 cron 时间字段要按 UTC 算。排查时如果发现任务时间比预期差了几个小时优先怀疑时区配置。下面把这些坑整理成速查表方便大家作为自查清单。症状可能原因排查手段任务完全不执行crond 未运行 / crontab 格式错systemctl status crondcrontab -l 核对命令找不到PATH 精简未配置脚本或 crontab 顶部写完整 PATH手动执行正常、定时执行失败环境变量差异 / 相对路径用任务用户 su 过去手动执行复现输出看不到cron 默认发邮件未配置 MTA命令追加 日志 21执行时间不对时区配置 / 日、周字段用了与逻辑timedatectl 查看时区核对 cron 语义重复执行多次分钟位误写 *分钟位必须给具体值或用 */N6.3 调试技巧一个随时可用的测试方法论调试定时任务时有一个很实用的方法先用一个安全的测试命令替换掉真实脚本比如把真实任务临时改成0 * * * * /bin/echo test $(date) /tmp/test_cron.log等一分钟看 /tmp/test_cron.log 里有没有内容。这一步能快速确认调度链路是否通畅排除脚本本身的问题。确认链路通畅之后再逐步把真实脚本加回去。如果这个时候出问题就按 6.1 的第四步去看脚本自己的日志。这一套先测调度、再测脚本的思路能帮你快速缩小问题范围而不是抓瞎式地在多个可能性里瞎试。另外我还习惯给每个任务的脚本末尾加一行执行标记比如echo $(date %F %T) script executed /var/log/script_exec.log这样即使脚本中途失败至少能确认确实到这里了把失败范围进一步缩小。这种加埋点排查的习惯在大型脚本里尤其好用。写在最后的一点个人心得定时任务做久了我最大的体会是这类小功能平时不起眼但是真正出问题的时候往往都是大问题。备份丢了、监控断了、证书过期了这些事故的根源十有八九就是定时任务没跑起来或者跑错了。所以我现在对待定时任务的态度是每一条都要有日志、每一个脚本都要可排查绝不允许出现一个运行结果不可见的黑盒任务。另一个体会是不要迷恋某个工具。cron 简洁、普及systemd timer 强大、现代at 轻量、专一。真正的能力是能在面对需求时快速判断该用哪个并且能解释清楚为什么。RHCE 考试里那些看似琐碎的知识点其实都是在训练这种判断力。希望这篇内容能把你在定时任务这条路上的坑提前填平一些剩下的就靠你在真机上一遍遍地试了。