Ubuntu 系统系列 | 第 12 天(重访·第二季):计划任务与自动化——进阶自动化

第 12/30 天(重访·第二季)

引言

在第一季的第 12 天,我们学会了用 cron 写定时任务、用 systemd timer 替代 crontab、用 at 安排一次性任务。但生产环境中的自动化远不止「写一行 crontab 让它每天跑一次」那么简单——如何防止定时任务重叠执行把数据库打挂?如何让 cron 任务在集群多台机器上不重复触发?时区与夏令时为何让 cron 静默失效?几十台服务器的 cron 任务如何统一纳管、可审计、可回滚? 这些才是进阶自动化要解决的真正问题。

本篇文章是第二季的重访,我们将从「会写定时任务」跃升到「构建可运维的自动化体系」:掌握 cron 的进阶防护(flock 互斥锁、随机延迟、失败重试)、深入 systemd timer 的时间语义(OnCalendar 表达式、单调时间、精度与随机化)、用 Ansible 把定时任务变成基础设施即代码、最后把定时任务接入 CI/CD 定时流水线,让「无人值守」真正可信。如果你已经会用 crontab,本文会告诉你如何让定时任务在生产环境「跑得稳、防得住、管得清」。


一、cron 进阶防护:从「能跑」到「跑得稳」

cron 最大的生产隐患不是不执行,而是重叠执行——上一轮还没跑完,下一轮又启动了,轻则资源浪费,重则并发写库造成数据损坏。第一季我们只讲了 crontab 的基本语法,这里补上三件「保命」武器。

1.1 flock 互斥锁:根治重叠执行

# 传统写法(有重叠风险)
*/5 * * * * /opt/scripts/sync_data.sh

# 进阶写法:加 flock 互斥锁,拿不到锁直接退出
*/5 * * * * /usr/bin/flock -n /var/lock/sync_data.lock /opt/scripts/sync_data.sh

# 若担心上一轮崩溃导致锁未释放,可设置锁超时(-w 等待上限 30 秒)
*/5 * * * * /usr/bin/flock -w 30 /var/lock/sync_data.lock /opt/scripts/sync_data.sh
  • -n(–nonblock):拿不到锁立即退出,绝不排队等待——适合「丢一次可以,绝不重叠」的采集类任务
  • -w 30:最多等待 30 秒,超时放弃——适合「可以稍等但不允许并发」的任务
  • 锁文件放在 /var/lock/,天然支持进程间互斥,比自写 PID 文件可靠得多

1.2 随机延迟:错峰降低资源峰值

凌晨的 cron 任务常常「一窝蜂」同时触发,把磁盘 IO 和 CPU 打满。进阶做法是给任务加随机抖动:

# 利用 $RANDOM 生成 0-300 秒的随机延迟(配合 bash 执行)
30 2 * * * /bin/bash -c 'sleep $((RANDOM % 300)); /opt/scripts/backup.sh'

1.3 失败重试与日志审计:让错误「看得见」

# 把标准输出与错误分别落盘,MAILTO 留空避免垃圾邮件
MAILTO=""
30 2 * * * /opt/scripts/backup.sh >> /var/log/cron/backup.log 2>&1

# 关键任务:失败时留下退出码标记
30 2 * * * /opt/scripts/backup.sh >> /var/log/cron/backup.log 2>&1 || echo "FAILED $(date)" >> /var/log/cron/backup.fail

进阶要点:cron 的日志默认只记录「执行了」,不记录「成功与否」。生产实践是让脚本自身写结构化结果,配合 grep FAILED /var/log/cron/ 做日报巡检,而不是盯着 crontab 发呆。

关键:cron 的 PATH 极简(通常只有 /usr/bin:/bin),脚本内务必使用绝对路径或显式 export PATH,否则 dockerpython3 等命令会「找不到」。这是 cron 任务静默失败的头号原因。


二、systemd timer 进阶:时间语义与控制力

第一季介绍了 timer 的基本用法,这里深入它比 cron 强在可精确控制、可持久化、可依赖

2.1 OnCalendar 表达式:比 5 段 cron 更强

# /etc/systemd/system/backup.timer
[Unit]
Description=Daily incremental backup

[Timer]
# 每天 02:30 触发(等价于 cron 的 "30 2 * * *")
OnCalendar=*-*-* 02:30:00

# 服务器正好关机则恢复后立即补跑(cron 做不到!)
Persistent=true

# 允许 ±15 分钟抖动,错峰(默认 AccuracySec=1min)
AccuracySec=15min
RandomizedDelaySec=10min

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers --all    # 查看所有 timer 与下次触发时间
systemctl cat backup.timer     # 查看定义

cron 与 systemd timer 的关键差异

能力 cron systemd timer
掉电/关机后补跑 Persistent=true
触发时间精确到秒
随机抖动错峰 手写 ✅ 内置 RandomizedDelaySec
依赖其他服务先启动 After=/Requires=
错过窗口补跑 ✅ 单调时间(OnUnitActiveSec
统一日志/资源控制 ✅ journald + cgroup

2.2 单调时间:不看日历看「距上次多久」

有些任务不该按「几点几分」跑,而该按「距上次执行 N 小时」跑——比如健康检查、心跳上报:

# /etc/systemd/system/healthcheck.timer
[Timer]
# 自上次触发起每 4 小时执行一次(重启机器后从启动时间算)
OnUnitActiveSec=4h
# 加上持久化:错过也不积压,只补最近一次
Persistent=true

这解决了 cron 的经典痛点:服务器在 02:30 恰好停机,cron 直接跳过这一天;而 timer 会在开机后补跑,保证「每天至少一次」的语义。


三、Ansible 自动化运维:定时任务即代码

当服务器超过十几台,逐台编辑 crontab 是灾难——改一处要登录 N 台机器、无法审计、无法回滚。进阶做法是用 Ansible 把定时任务变成声明式的「代码」。

3.1 用 Ansible 统一纳管 cron

# cron_manage.yml
---
- name: 统一管理所有服务器的定时任务
  hosts: all
  become: yes
  tasks:
    - name: 确保日志目录存在
      file:
        path: /var/log/cron
        state: directory

    - name: 部署数据同步任务(幂等:重复执行不产生重复条目)
      cron:
        name: "sync_data"
        minute: "*/5"
        job: "/usr/bin/flock -n /var/lock/sync_data.lock /opt/scripts/sync_data.sh >> /var/log/cron/sync.log 2>&1"
        user: root
        state: present

    - name: 移除废弃的旧任务(统一治理,防止僵尸 crontab)
      cron:
        name: "legacy_cleanup"
        state: absent
# 先 dry-run 看变更,再实际执行
ansible-playbook cron_manage.yml --check --diff
ansible-playbook cron_manage.yml

Ansible cron 模块的优势:每个任务以 name 为唯一标识,重复执行自动幂等;--check 模式可预览变更;配合 Git 管理 playbook,等于给定时任务上了「版本控制 + 变更审批」。

3.2 用 Ansible 管理 systemd timer

# timer_manage.yml
---
- name: 部署 systemd timer 服务
  hosts: web
  become: yes
  tasks:
    - name: 写入 timer 定义
      copy:
        dest: /etc/systemd/system/backup.timer
        content: |
          [Unit]
          Description=Daily backup

          [Timer]
          OnCalendar=*-*-* 02:30:00
          Persistent=true

          [Install]
          WantedBy=timers.target

    - name: 启用并启动 timer
      systemd:
        name: backup.timer
        enabled: yes
        state: started
        daemon_reload: yes

生产实践中,把 cron 与 timer 的定义全部纳入 Git 仓库,配合 Ansible Tower/AWX 或 GitHub Actions 做变更审批,就实现了「定时任务的 GitOps」。


四、CI/CD 定时流水线:让自动化进入工程体系

进阶自动化的最终形态,是把「定时执行」接入 CI/CD 平台,享受可视化、日志聚合、失败告警、依赖编排这些裸 cron 给不了的工程能力。

4.1 GitHub Actions 定时工作流

# .github/workflows/nightly-report.yml
name: nightly-report
on:
  schedule:
    - cron: "30 2 * * *"      # 每天 02:30 UTC
    - cron: "0 6 * * 1"       # 每周一 06:00 UTC
  workflow_dispatch:          # 手动触发兜底

jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: 运行夜间汇总脚本
        run: bash scripts/nightly_report.sh
      - name: 失败时通知钉钉
        if: failure()
        run: curl -s -X POST "${{ secrets.DINGTALK_WEBHOOK }}" -d '{"msgtype":"text","text":{"content":"夜间任务失败!"}}'

4.2 GitLab CI 定时流水线(pipeline schedule)

# .gitlab-ci.yml 片段——配合 GitLab 的 Schedule 功能
stages:
  - build
  - deploy

nightly-build:
  stage: build
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'   # 仅定时触发
  script:
    - make nightly
  artifacts:
    paths:
      - dist/

为什么值得把定时任务搬进 CI/CD:执行历史有界面可查、失败自动重试/告警、产物可追溯、权限可收敛(流水线用最小权限的 CI Token,而不是服务器 root)。对于「数据汇总、报告生成、镜像构建、测试巡检」这类任务,CI/CD 定时流水线几乎是更优解。


五、进阶自动化常见问题与踩坑

Q1:cron 任务到点没执行,从哪排查?

grep CRON /var/log/syslog | tail -20        # 看 cron 是否尝试执行
systemctl status cron                       # 确认 crond 存活
crontab -l                                  # 确认条目还在
/usr/sbin/cron -f -L 15                     # 前台调试模式(临时)

90% 的情况是:脚本权限不够(chmod +x 没加)脚本内命令路径不对服务器时区与你预期不同timedatectl 查看,cron 用的是系统时区)。

Q2:systemd timer 时间总是「差一点」触发?
AccuracySec 默认 1 分钟,是 timer 故意允许的「省电窗口」。如果必须准点,设 AccuracySec=1us;如果需要错峰,反而调大它并配合 RandomizedDelaySec

Q3:定时任务在容器/Docker 里怎么跑?
容器里通常没有 crond。方案有二:一是用宿主机的 cron 定时 docker exec 进容器执行;二是使用 systemd timer 配合 systemctl start 触发容器(需容器以 systemd 作为 PID 1)。切勿在容器内后台启动 cron 进程——容器退出后任务一并消失。

Q4:cron 任务环境变量和交互终端不一样?
cron 不加载 .bashrc/.profile。脚本首行建议:

#!/bin/bash
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export LANG=en_US.UTF-8

Q5:如何做定时任务的「演习」——只测不真跑?
用 systemd timer 的 dry-run 式验证,或临时把 OnCalendar 改成 1 分钟后观察 systemctl list-timers 倒计时,确认触发无误后再改回正式时间。


总结与下期预告

进阶自动化的核心是把定时任务从「一行 crontab」升级为「一套可运维的工程体系」:用 flock 互斥锁根治重叠执行用 systemd timer 的 Persistent/单调时间补上 cron 的关机丢任务短板用 Ansible 把定时任务变成可审计、可回滚的代码用 CI/CD 定时流水线获得可视化与告警。记住三个立即可用的要点:所有关键任务必须加 flock需要「每天至少一次」语义就选 timer 的 Persistent=true脚本里永远显式声明 PATH

下期预告:第 13 天(重访·第二季)将带来《备份与恢复策略——企业级备份方案》,深入 3-2-1 备份原则的落地、borg/restic 去重备份实战、备份的自动化验证与灾难恢复演练。敬请期待!


系列目录

天数 主题 状态
第 1 天 Ubuntu 简介与版本选择
第 2 天 手把手安装 Ubuntu
第 3 天 Ubuntu 桌面环境初探
第 4 天 Ubuntu 终端基础
第 5 天 用户与权限管理
第 6 天 软件包管理
第 7 天 文件与文本操作
第 8 天 系统服务管理
第 9 天 磁盘与文件系统管理
第 10 天 网络配置与管理
第 11 天 进程管理与监控
第 12 天 计划任务与自动化
第 13 天 备份与恢复策略
第 14 天 系统更新与升级管理
第 15 天 SSH 远程管理与安全加固
第 16 天 Web 服务器搭建
第 17 天 数据库服务器部署
第 18 天 Docker 容器化管理
第 19 天 文件共享服务
第 20 天 监控与告警系统
第 21 天 邮件服务器基础
第 22 天 系统安全加固
第 23 天 性能调优与内核参数
第 24 天 虚拟化技术
第 25 天 容器编排入门
第 26 天 高可用与负载均衡
第 27 天 Ubuntu 自动部署
第 28 天 故障排查实战
第 29 天 Ubuntu 社区与文档
第 30 天 30 天回顾总结
第 23 天(重访) 性能调优与内核参数——生产环境基准测试与踩坑实战
第 24 天(重访) KVM 虚拟化高级实战——生产级配置与性能调优
第 25 天(重访) 容器编排进阶——Docker Compose 生产实践与 Kubernetes 单节点集群实战
第 26 天(重访) 高可用与负载均衡生产实战——Keepalived + HAProxy 深度调优与故障排查
第 27 天(重访) 自动部署生产实践——PXE + Preseed + Cloud-init 深度进阶与企业级流水线
第 28 天(重访) 故障排查实战——深度内核调试、文件系统救援与生产级排障
第 29 天(重访) Ubuntu 社区与文档——从使用者到贡献者深度实践
第 30 天(重访) 30 天系列完结篇——学习路线图与成长行动指南
第 1 天(重访·第二季) Ubuntu 新篇章——2026 年生态全景与版本选择
第 2 天(重访·第二季) 手把手安装 Ubuntu——2026 年全新安装体验与自动化部署
第 3 天(重访·第二季) Ubuntu 桌面环境深度探索——GNOME 46 新特性与个性化定制
第 4 天(重访·第二季) Ubuntu 终端与 Shell 高级技巧——现代 CLI 工具链
第 5 天(重访·第二季) 用户与权限管理——从 ACL 到 PAM 企业级权限体系
第 6 天(重访·第二季) 软件包管理——apt、dpkg、snap、flatpak 深入对比
第 7 天(重访·第二季) 文件与文本操作——现代化 CLI 工具链
第 8 天(重访·第二季) 系统服务管理——systemd 深度剖析与企业级服务治理
第 9 天(重访·第二季) 磁盘与文件系统管理——LVM 与存储进阶
第 10 天(重访·第二季) 网络配置与管理——netplan 高级网络、策略路由与生产级防火墙
第 11 天(重访·第二季) 进程管理与监控——性能分析与调优
第 12 天(重访·第二季) 计划任务与自动化——进阶自动化 🟢 今日
第 13 天(重访·第二季) 备份与恢复策略——企业级备份方案
第 14 天(重访·第二季) 系统更新与升级管理——生产环境升级策略
第 15 天(重访·第二季) SSH 远程管理——安全加固进阶
第 16 天(重访·第二季) Web 服务器搭建——高性能 Nginx 调优
第 17 天(重访·第二季) 数据库服务器部署——生产级数据库运维
第 18 天(重访·第二季) Docker 容器化——生产级容器管理
第 19 天(重访·第二季) 文件共享服务——高性能存储方案
第 20 天(重访·第二季) 监控与告警系统——可观测性平台
第 21 天(重访·第二季) 邮件服务器基础——企业邮件方案
第 22 天(重访·第二季) 系统安全加固——深度安全策略
第 23 天(重访·第二季) 性能调优与内核参数——生产环境优化
第 24 天(重访·第二季) 虚拟化技术——KVM 高级虚拟化
第 25 天(重访·第二季) 容器编排入门——Docker Compose 与 K8s
第 26 天(重访·第二季) 高可用与负载均衡——集群方案
第 27 天(重访·第二季) Ubuntu 自动部署——自动化运维
第 28 天(重访·第二季) 故障排查实战——深度排障
第 29 天(重访·第二季) Ubuntu 社区与文档——开源贡献
第 30 天(重访·第二季) 30 天回顾总结——第二季完结篇
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容