Linux: 任务自动化——cron / systemd / journalctl
服务器上的一切操作都应该自动化——定时备份、日志清理、健康检查。Linux 提供了从简单(cron)到现代(systemd timer)的各种自动化方案。
📋 前置知识:需要先掌握以下内容
- 第21课:Bash 脚本进阶
1. 你将学到
- crontab 定时任务语法
- systemd service 单元编写
- systemd timer 替代 cron
- journalctl 日志查询
- logrotate 日志轮转
2. 一个 "再也不用凌晨起来跑脚本" 的故事
(1) 痛点:每天手动备份
小明的备份脚本每天需要手动执行。他设了一个闹钟——每天凌晨 2 点起来跑 ./backup.sh。坚持了一周他就崩溃了。
(2) cron 解放了他
Bob 教他用 crontab:
crontab -e
# 添加:
0 2 * * * /home/alice/scripts/backup.sh > /dev/null 2>&1
从此每天凌晨 2 点,系统自动执行备份。小明只需要偶尔检查日志。
(3) 收益:机器该做的事,让机器做
小明学会了 cron 后,一口气自动化了:日备份、周报告、日志清理、健康检查 ping。他每天花在运维上的时间从 2 小时降到了 10 分钟。
3. 知识点讲解
(1) crontab——定时任务
cron 语法: 分 时 日 月 周 命令
分 时 日 月 周 命令
(0-59)(0-23)(1-31)(1-12)(0-7)
(0=周日 7=周日)
# 特殊的语法
@reboot # 重启时执行
@daily # 每天 0:00(同 0 0 * * *)
@hourly # 每小时(同 0 * * * *)
@weekly # 每周一 0:00
@monthly # 每月 1 日 0:00
常见示例:
# 查看当前用户的 cron
crontab -l
# 编辑 crontab
crontab -e
# 删除全部 cron
crontab -r
# 常用例子
0 2 * * * /scripts/backup.sh # 每天 2:00
*/5 * * * * /scripts/health-check.sh # 每 5 分钟
0 0 * * 0 /scripts/weekly-report.sh # 每周日 0:00
0 0 1 * * /scripts/monthly-cleanup.sh # 每月 1 日
0 9-17 * * 1-5 /scripts/business-hours.sh # 工作日 9-17 点每小时
crontab -r 会直接删除当前用户的所有 cron 任务,且没有确认提示!误删后只能靠备份恢复。建议先用 crontab -l 备份再删除,或养成使用 crontab -e 编辑而非 crontab -r 删除的习惯。
~/.bashrc、~/.profile 等配置文件,PATH 通常只有 /usr/bin:/bin。如果脚本依赖自定义环境变量,需在脚本开头手动 source 配置文件,或在 crontab 中使用命令的绝对路径。
(2) systemd service
# service 单元路径
/etc/systemd/system/myapp.service
# 基本结构
[Unit]
Description=My Web Application
After=network.target
[Service]
Type=simple
User=alice
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
# 使用
sudo systemctl daemon-reload # 重载配置
sudo systemctl start myapp # 启动
sudo systemctl enable myapp # 开机启动
sudo systemctl status myapp # 查看状态
sudo journalctl -u myapp -f # 查看日志
(3) systemd timer
Persistent=true)。如果只是简单的周期性脚本且不需要上述特性,cron 更轻量直观。新项目推荐优先尝试 systemd timer。
# timer 单元(替代 cron)
/etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
# 对应的 service
/etc/systemd/system/backup.service
[Unit]
Description=Daily Backup Service
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
# 使用
sudo systemctl enable --now backup.timer
sudo systemctl list-timers --all
OnCalendar 语法:
OnCalendar=daily # 每天 0:00
OnCalendar=hourly # 每小时
OnCalendar=*-*-* 02:00:00 # 每天 2:00
OnCalendar=Mon *-*-* 03:00:00 # 每周一 3:00
OnCalendar=*-*-1 00:00:00 # 每月 1 日
OnCalendar=*:0/5 # 每 5 分钟
(4) journalctl——日志查看
# 基本
journalctl # 所有日志
journalctl -u nginx # 特定服务
journalctl -u nginx -f # 实时追踪
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --until "2026-07-07 10:00"
journalctl -p err # 只显示错误级别以上
journalctl -p err -u nginx # nginx 的错误日志
# 管理
journalctl --disk-usage # 查看日志占用
sudo journalctl --vacuum-size=500M # 限制日志大小
sudo journalctl --vacuum-time=7d # 保留 7 天
(5) logrotate——日志轮转
/etc/cron.daily/logrotate),你只需在 /etc/logrotate.d/ 下编写配置即可。修改配置后,用 logrotate -d /etc/logrotate.d/你的配置 调试模式预览效果,确认无误再让它自动生效。
# 配置位置
/etc/logrotate.conf # 全局配置
/etc/logrotate.d/ # 各应用配置
# 示例:Nginx 日志轮转
/var/log/nginx/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 640 www-data adm
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}
常用指令:
| 指令 | 含义 |
|---|---|
daily/weekly/monthly |
轮转频率 |
rotate N |
保留 N 个归档 |
compress |
压缩归档(gzip) |
delaycompress |
延迟一个周期再压缩 |
missingok |
文件不存在时不报错 |
notifempty |
空文件不轮转 |
size 100M |
文件达到指定大小才轮转 |
maxage 30 |
超过 30 天的归档删除 |
▶ 示例:crontab 配置
# 编辑 crontab
crontab -e
# 写入以下内容
# ┌───── 分钟 (0-59)
# │ ┌───── 小时 (0-23)
# │ │ ┌───── 日期 (1-31)
# │ │ │ ┌───── 月份 (1-12)
# │ │ │ │ ┌───── 星期 (0-7, 0=周日)
# * * * * * 命令
# 每天 2:00 自动备份
0 2 * * * /home/alice/scripts/backup.sh
# 每 5 分钟检查服务健康
*/5 * * * * /home/alice/scripts/health-check.sh
# 每周一 3:00 生成周报
0 3 * * 1 /home/alice/scripts/weekly-report.sh
# 每月 1 日清理归档
0 0 1 * * /home/alice/scripts/cleanup.sh
▶ 示例:创建 systemd service
# 创建一个 Node.js 应用的 systemd service
sudo tee /etc/systemd/system/webapp.service << 'EOF'
[Unit]
Description=Node.js Web Application
Documentation=https://example.com/docs
After=network.target
[Service]
Type=simple
User=alice
Group=alice
WorkingDirectory=/opt/webapp
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node /opt/webapp/server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
# 安全选项
ProtectSystem=full
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now webapp
sudo systemctl status webapp
输出:
TEXT 📖 仅展示● webapp.service - Node.js Web Application Loaded: loaded (/etc/systemd/system/webapp.service; enabled) Active: active (running) since Tue 2026-07-07 10:23:45 UTC Main PID: 5678 (node) Tasks: 12 (limit: 4915) Memory: 45.2M CPU: 1.234s
▶ 示例:systemd timer 替代 cron
# 备份 service
sudo tee /etc/systemd/system/backup.service << 'EOF'
[Unit]
Description=Daily Backup
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
User=alice
EOF
# 备份 timer
sudo tee /etc/systemd/system/backup.timer << 'EOF'
[Unit]
Description=Run backup daily at 2am
Requires=backup.service
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
sudo systemctl list-timers --all
输出:
TEXT 📖 仅展示NEXT LEFT LAST PASSED Tue 2026-07-07 02:00:00 UTC 15h left Mon 2026-07-06 02:00:00 UTC 9h ago n/a n/a n/a n/a 1 timers listed.
▶ 示例:journalctl 日志查询
# 查看 nginx 最近 1 小时的错误
journalctl -u nginx -p err --since "1 hour ago"
# 查看昨天的全部日志
journalctl --since yesterday --until today
# 查看特定 PID 的日志
journalctl _PID=1234
# 实时追踪多个服务
journalctl -u nginx -u webapp -f
# 导出日志到文件
journalctl -u webapp --since "2026-07-06" > webapp-logs.txt
# 查看日志占用
journalctl --disk-usage
# 归档和当前日志占用 512.0M
# 清理
sudo journalctl --vacuum-size=200M
输出:
TEXT 📖 仅展示Jul 07 10:23:45 server nginx[1234]: GET /api/users 200 1234 Jul 07 10:23:50 server nginx[1234]: POST /api/data 500 0 Jul 07 10:24:01 server webapp[5678]: Error: ECONNREFUSED -- Logs begin at Mon 2026-07-06 00:00:01 -- Jul 07 09:00:00 server sshd[890]: Accepted publickey for alice 归档和当前日志占用 512.0M Deleted archived journals: 23 Vacuuming done, freed 312.0M of archived journals
▶ 示例:logrotate 配置
# 为自定义应用配置日志轮转
sudo tee /etc/logrotate.d/custom-app << 'EOF'
/var/log/custom-app/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 640 www-data www-data
sharedscripts
postrotate
systemctl reload custom-app > /dev/null 2>&1 || true
endscript
}
EOF
# 测试配置
sudo logrotate -d /etc/logrotate.d/custom-app
# 手动强制执行
sudo logrotate -f /etc/logrotate.d/custom-app
输出:
TEXT 📖 仅展示reading config file /etc/logrotate.d/custom-app Handling 1 logs rotating pattern: /var/log/custom-app/*.log rotating 14 kept versions considering log /var/log/custom-app/app.log log needs rotating renaming /var/log/custom-app/app.log to /var/log/custom-app/app.log.1 creating new /var/log/custom-app/app.log mode=0640 user=www-data group=www-data
▶ 综合示例:自动化运维体系
#!/bin/bash
# setup-automation.sh - 配置自动化运维体系
set -euo pipefail
echo "=== 搭建自动化运维体系 ==="
# 1. 创建脚本目录
mkdir -p /opt/scripts
# 2. 备份脚本
cat > /opt/scripts/backup.sh << 'BACKUPEOF'
#!/bin/bash
# 自动备份 - 每天凌晨 2 点由 cron 或 systemd timer 触发
SOURCE="/var/www"
DEST="/backup"
RETENTION=30
mkdir -p "$DEST"
BACKUP_FILE="${DEST}/backup_$(date +%Y%m%d).tar.gz"
tar -czf "$BACKUP_FILE" \
--exclude=node_modules \
--exclude=.git \
"$SOURCE" 2>/dev/null
# 清理过期备份
find "$DEST" -name "backup_*.tar.gz" -mtime +$RETENTION -delete
# 日志
echo "[$(date)] 备份: $BACKUP_FILE ($(du -h "$BACKUP_FILE" | cut -f1))" >> /var/log/backup.log
BACKUPEOF
chmod +x /opt/scripts/backup.sh
# 3. 健康检查脚本
cat > /opt/scripts/health-check.sh << 'HEALTHEOF'
#!/bin/bash
# 健康检查 - 每 5 分钟执行
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
SERVICES=("nginx" "postgresql" "webapp")
for svc in "${SERVICES[@]}"; do
if ! systemctl is-active --quiet "$svc"; then
MESSAGE="❌ $svc 服务异常 $(date)"
echo "$MESSAGE" | mail -s "服务告警" admin@example.com
# curl -X POST -d "text=$MESSAGE" "$WEBHOOK_URL"
fi
done
# 磁盘检查
DISK_USAGE=$(df -h / | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt 85 ]; then
echo "⚠️ 磁盘使用率: ${DISK_USAGE}%" >> /var/log/disk-alert.log
fi
HEALTHEOF
chmod +x /opt/scripts/health-check.sh
# 4. 配置 cron
(crontab -l 2>/dev/null; echo "") | crontab -
(crontab -l 2>/dev/null; echo "# 自动备份 - 每天 2:00") | crontab -
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/scripts/backup.sh > /dev/null 2>&1") | crontab -
(crontab -l 2>/dev/null; echo "# 健康检查 - 每 5 分钟") | crontab -
(crontab -l 2>/dev/null; echo "*/5 * * * * /opt/scripts/health-check.sh > /dev/null 2>&1") | crontab -
(crontab -l 2>/dev/null; echo "# 日志清理 - 每周日") | crontab -
(crontab -l 2>/dev/null; echo "0 0 * * 0 journalctl --vacuum-size=200M > /dev/null 2>&1") | crontab -
echo ""
echo "=== 自动化运维体系搭建完成 ==="
echo "已配置的任务:"
crontab -l | grep -v "^#"
echo ""
echo "手动验证: crontab -l"
❓ 常见问题
Q: crontab 的环境变量和终端一样吗? A: 不一样!cron 执行命令时的 PATH 非常精简(通常是
/usr/bin:/bin)。如果你的脚本依赖~/.bashrc中定义的 PATH,要么在脚本开头设置 PATH,要么在 cron 命令中写绝对路径。
Q: systemd timer 比 cron 好在哪? A: 1)依赖管理:timer 可以依赖其他 service。2)日志集成:自动 journalctl 记录。3)错过执行:
Persistent=true在下次开机时补执行。4)更精确:支持毫秒级别。5)统一管理:systemctl list-timers。
Q: journalctl 日志会占满硬盘吗? A: 默认日志最大为磁盘的 10%(但不超过 4GB)。可以用
journalctl --vacuum-size=500M或配置文件/etc/systemd/journald.conf中的SystemMaxUse=500M限制。
Q: logrotate 是自动执行还是要配 cron? A: logrotate 由系统自带的 cron job 每天执行一次(在
/etc/cron.daily/logrotate)。你只需要在/etc/logrotate.d/下配置规则,不需要额外配置 cron。
Q: 怎么调试 crontab 不执行的问题? A: 三步排查:1)检查 cron 服务是否在运行
systemctl status cron。2)查看 cron 日志grep CRON /var/log/syslog。3)手动执行脚本确认没问题bash /path/to/script.sh。
📖 小节
- crontab:
分 时 日 月 周 命令,crontab -e编辑 - systemd service:
[Service] ExecStart=,Restart=on-failure - systemd timer:
OnCalendar=,Persistent=true - journalctl:
-u service按服务查,-p err按级别,-f实时 - logrotate:
daily / rotate 7 / compress自动轮转日志
📝 作业
- 基础题(难度⭐):配置 crontab 每小时执行一次
date到/tmp/cron-test.log,然后用 journalctl 查看 nginx 或 sshd 的启动日志 - 进阶题(难度⭐⭐):创建一个简单的
hello.service并启动(Type=simple),然后创建一个 logrotate 配置,对/var/log/test-app/*.log按天轮转,保留 7 天 - 挑战题(难度⭐⭐⭐):写一个 systemd timer,每天 3:00 清理
/tmp中的过期文件,配合对应的 service 文件,并配置 journalctl 查看其执行日志