摘要

简单记录CentOS7中的crontab使用,以及使用crontab实现Nginx服务器自动封禁ip

正文

简单记录CentOS7中的crontab使用,以及使用crontab实现Nginx服务器自动封禁ip

一、定时任务

1.1 一次性任务

使用bash脚本实现

sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/bin/bash
set -e

ENDTIME="2025-10-10 10:00:00"

# 动作
function action() {
  echo "Halo Wode">/root/action.txt
  log "执行完成"
}

# 日志打印函数
function log() {
  echo "$(date +"%Y-%m-%d %H:%M:%S"): $1"
}

# 定义一个函数,等待直到目标时间到达
function sleep_until_target() {
    local target_time="$1"

    # 获取当前时间的时间戳
    local current_timestamp=$(date +%s)

    # 将目标时间转换为时间戳
    local target_timestamp=$(date -d "$target_time" +%s)

    # 计算需要 sleep 的时间(秒)
    local delta=$((target_timestamp - current_timestamp))

    # 如果时间差大于0,则 sleep
    if [ "$delta" -gt 0 ]; then
        log "需要等待 $delta 秒"
        sleep "$delta"
    fi

    # 执行逻辑
    log "目标时间到达:$target_time"
    action
}



# 调用函数并传入目标时间
sleep_until_target "$ENDTIME"

1.2 crontab 周期任务

crontab,用来提交和管理用户的需要周期性执行的任务。与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具,并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。

1.2.1 语法

服务相关命令

sh
1
systemctl [status|start|stop|restart|enable|disable] crond

基本语法

crontab [选项] [参数]

  • 选项

    • -e 编辑该用户的定时任务

    • -l 列出该用户的定时任务

    • -r 删除该用户的定时任务

    • -u <user> 指定定时任务所属的用户

  • 参数,即包含待执行任务的crontab文件。

以编辑任务为例,输入crontab -e即可进入任务编辑,如图

image-20240714023759831.png

1.2.2 日志

以 CentOS 为例,cron 日志存储在 /var/log/cron 文件中

sh
1
cat /var/log/cron | tail -n 10

image-20251201005543648.png

二、实践

2.1 程序保活

我在运维时,有个HTTP反向代理的应用(不是我写的)有两个重大严重Bug

  1. JVM崩溃,会生成hs_err_pid.log日志。
  2. 大量close_wait导致程序假死。

由于我只负责生产环境运维,所以从运维角度解决该问题。实现程序保活脚本。

sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env bash

# 描述:自动重启脚本。
# 触发时机:程序宕机时、大量CLOSE_WAIT造成程序假死时

name="bug-test"

# 日志所在文件夹,需要手动创建
logDir="/var/log/$name"
# 日志存储的位置
logFile="$logDir/monitor.log"
# 日志触发归档的最大大小,单位字节
logMaxSize=1024
# 归档日志最多存储文件个数
logMaxFiles=5
# jar包名称
jar="bug-test.jar"
# jar包所在路径
jarPath="/root/bug-test"
# jar包后台启动命令
jarExecute="nohup java -jar $jar >/dev/null 2>&1 &"

log() {
  if [ ! -f "$logFile" ]; then
    touch $logFile
  fi
  echo "$(date +"%Y-%m-%d %H:%M:%S") -- $name: $1" >>$logFile
}

# 核心判定逻辑
judge() {
  log "=== judge start ==="
  existPort=$(netstat -nplt | grep tcp | grep 6666 | wc -l)
  count=$(netstat -ano | grep tcp | grep 6666 | grep CLOSE_WAIT | wc -l)
  log "existPort: $existPort, close_wait: $count"

  if [ "$existPort" -eq 0 ] || [ "$count" -gt 100 ]; then
    netstat -ano | grep tcp >"$jarPath/netstat$(date +%Y%m%d%H%M).log"
    pkill -9 -f $jar
    log "$jar is killed"
    cd $jarPath
    eval $jarExecute
    log "$jar started, pid is $!"
  else
    log "$jar is running normally"
  fi
  log "=== judge end ==="
}

# 日常任务。严格保证每天只执行一次
dailyTask() {
  # 设置每日执行的时间,如01:00表示凌晨1点
  TIMED="01:17"

  # 存储日常任务的日志文件。用于标识当前已执行
  FLAG_FILE="$logDir/flag_file"

  log "daily task condition, TIMED=$TIMED, FLAG_FILE=$FLAG_FILE"
  if [ ! -f "$FLAG_FILE" ]; then
    touch $FLAG_FILE
    log "created $FLAG_FILE"
  fi

  if [ -f "$FLAG_FILE" ]; then
    if [ "$TIMED" == "$(date +"%H:%M")" ]; then
      if [ "$(cat "$FLAG_FILE")" != "$(date +"%Y-%m-%d")" ]; then
        log "=== daily task start ==="
        pkill -9 -f $jar
        log "$jar is killed"
        cd $jarPath
        eval $jarExecute
        log "$jar started, pid is $!"
        echo "$(date +"%Y-%m-%d")" >$FLAG_FILE
        log "=== daily task end ==="
      fi
    fi
  fi


}

archiveLog() {
  if [ -f "$logFile" ]; then
    fileSize=$(stat -c%s "$logFile")
    if [ "$fileSize" -ge "$logMaxSize" ]; then
      for ((i = $logMaxFiles; i > 0; i--)); do
        if [ -f "$logFile.$i" ]; then
          mv "$logFile.$i" "$logFile.$((i + 1))"
        fi
      done
      mv "$logFile" "$logFile.1"
      touch $logFile
    fi
  fi
}

judge
dailyTask
archiveLog

2.2 Nginx封IP

以下内容,都是抄袭来的,经过自己简单修改。

参考地址在致谢参考里

nginx中在server下面,配置deny即可实现封禁ip

deny的配置比较灵活,既可以在server下面配置,也可以在server下具体到某个location配置。

Nginx实现流量控制,远没有这么麻烦。记录这种实现,只是为了学习crontab。

正式环境中对Nginx进行流控,参考该文章

2.2.1 实现封禁ip的脚本

先配置nginx.conf,添加配置

sh
1
include blockip.conf;

进入到目录/usr/local/nginx/logs/,创建bash脚本ip-block

sh
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
#!/bin/bash
# 配置变量
NGINX_CONF="/usr/local/nginx/conf/nginx.conf"
IP_BLACKLIST="/usr/local/nginx/conf/blockip.conf"
LOG_FILE="/usr/local/nginx/logs/access.log"
# 在指定时间内允许的最大请求数
THRESHOLD=500
# 时间间隔(秒)
TIME_INTERVAL=60
# 设置为 true 开启调试模式
DEBUG=false

# 检查是否以root权限运行
if [ "$(id -u)" != "0" ]; then
  #echo "此脚本必须以root权限运行" 1>&2
  exit 1
fi

# 确保IP黑名单文件存在
touch $IP_BLACKLIST

# 调试函数
debug() {
  if [ "$DEBUG" = true ]; then
    echo "[DEBUG] $1" >&2
  fi
}

clear_old_block() {
  #不能把别人IP一直封着吧,这里就清除掉了
  echo "" >"$IP_BLACKLIST"
}

# 分析日志并封锁IP
analyze_and_block() {
  debug "开始分析日志并封锁IP..."

  # 获取当前时间戳
  current_time=$(date +%s)
  debug "当前时间戳: $current_time"

  # 使用awk分析日志,找出在指定时间内请求次数超过阈值的IP
  suspicious_ips=$(awk -v interval="$TIME_INTERVAL" -v threshold="$THRESHOLD" -v current_time="$current_time" -v debug="$DEBUG" '
    function parse_time(time_string) {
        gsub(/[\[\]]/, "", time_string)
        split(time_string, a, "[/: ]")
        months["Jan"]=1; months["Feb"]=2; months["Mar"]=3; months["Apr"]=4; months["May"]=5; months["Jun"]=6;
        months["Jul"]=7; months["Aug"]=8; months["Sep"]=9; months["Oct"]=10; months["Nov"]=11; months["Dec"]=12;
        timestamp = mktime(a[3] " " months[a[2]] " " a[1] " " a[4] " " a[5] " " a[6])
        return timestamp
    }

    function debug_print(message) {
        if (debug == "true") {
            print "[DEBUG] " message > "/dev/stderr"
        }
    }

    {
        ip = $1
        log_time = parse_time($4)
        debug_print("$4: " $4 " IP: " ip ", Log Time: " log_time ", Current Time: " current_time ", Difference: " (current_time - log_time) " seconds")

        if (current_time - log_time <= interval) {
            count[ip]++
            debug_print("IP: " ip ", Count: " count[ip])
            if (count[ip] == threshold) {
                print ip
                debug_print("IP " ip " has reached the threshold")
            }
        }
    }' "$LOG_FILE")

  debug "可疑IP列表: $suspicious_ips"

  # 封锁可疑IP
  if [ -n "$suspicious_ips" ]; then
    echo "$suspicious_ips" | while IFS= read -r ip; do
      # debug "处理IP: $ip"
      if ! grep -q "deny $ip;" "$IP_BLACKLIST"; then
        echo "deny $ip;" >>"$IP_BLACKLIST"
        debug "已封锁IP: $ip"
      else
        debug "IP $ip 已在黑名单中"
      fi
    done
  else
    debug "没有发现需要封锁的IP"
  fi
}

# 重新加载Nginx配置
reload_nginx() {
  # 该操作会导致nginx先stop再start,如果是流式下载就会断开重新下载了
  # systemctl restart nginx
  # 使用reload可以保证只重载配置文件
  /usr/local/nginx/sbin/nginx -s reload
}

# 主函数
main() {
  clear_old_block
  analyze_and_block
  reload_nginx
}

# 运行主函数
main

2.2.2 定时任务每分钟执行封禁脚本

添加定时任务

sh
1
crontab -e

直接输入内容

sh
1
* * * * * sh /usr/local/nginx/logs/ip-block

crontab中的cron表达式可以使用在线模拟解析Crontab表达式执行时间 - ToolTT在线工具箱进行测试。

重启定时服务

sh
1
systemctl restart crond

三、致谢参考

CentOS7必备技能下的定时任务 crontab的使用_佞臣888的博客-CSDN博客

服务又被攻击?Nginx + 简单脚本,轻松拦截