线上常用Shell(持续更新中)

线上常用Shell

1. 监测Nginx访问日志 502 情况,并做相应动作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#场景:  
#1.访问日志文件的路径:/data/log/access.log
#2.脚本死循环,每10秒检测一次,10秒的日志条数为300条,出现502的比例不低于10%(30条)则需要重启php-fpm服务
#3.重启命令为:/etc/init.d/php-fpm restart
#!/bin/bash
###########################################################
#监测Nginx访问日志502情况,并做相应动作
###########################################################
log=/data/log/access.log
N=30 #设定阈值
while :do
#查看访问日志的最新300条,并统计502的次数
err=`tail -n 300 $log |grep -c '502" '`
if [ $err -ge $N ]
then
/etc/init.d/php-fpm restart 2> /dev/null
#设定60s延迟防止脚本bug导致无限重启php-fpm服务
sleep 60
fi
sleep 10
done

2. 扫描主机端口状态

1
2
3
4
5
6
7
8
9
10
#!/bin/bash  
HOST=$1
PORT="22 25 80 8080"
for PORT in $PORT; do
if echo &>/dev/null > /dev/tcp/$HOST/$PORT; then
echo "$PORT open"
else
echo "$PORT close"
fi
done

3. iptables 自动屏蔽访问网站频繁的IP

场景:恶意访问,安全防范

3.1. 屏蔽每分钟访问超过100的IP

  • 方法1:根据访问日志(Nginx为例)
1
2
3
4
5
6
7
8

#!/bin/bash
DATE=$(date +%d/%b/%Y:%H:%M)
ABNORMAL_IP=$(tail -n5000 access.log |grep $DATE |awk '{a[$1]++}END{for(i in a)if(a[i]>100)print i}')
#tail防止文件过大,读取慢,数字可调整每分钟最大的访问量。awk不能直接过滤日志,因为包含特殊字符。
for IP in $ABNORMAL_IP; do
if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then
iptables -I INPUT -s $IP -j DROP fidone
  • 方法2:通过TCP建立的连接
1
2
3
4
5
6
7
8
#!/bin/bash  
ABNORMAL_IP=$(netstat -an |awk '$4~/:80$/ && $6~/ESTABLISHED/{gsub(/:[0-9]+/,"",$5);{a[$5]++}}END{for(i in a)if(a[i]>100)print i}')
#gsub是将第五列(客户端IP)的冒号和端口去掉
for IP in $ABNORMAL_IP; do
if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then
iptables -I INPUT -s $IP -j DROP
fi
done

3.2. 屏蔽每分钟SSH尝试登录超过10次的IP

  • 方法1:通过lastb获取登录状态
1
2
3
4
5
#!/bin/bash  
DATE=$(date +"%a %b %e %H:%M") #星期月天时分 %e单数字时显示7,而%d显示07
ABNORMAL_IP=$(lastb |grep "$DATE" |awk '{a[$3]++}END{for(i in a)if(a[i]>10)print i}')for IP in $ABNORMAL_IP; do
if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then
iptables -I INPUT -s $IP -j DROP fidone
  • 方法2:通过日志获取登录状态
1
2
3
4
5
6
7
8
9
#!/bin/bash  
DATE=$(date +"%b %d %H")
ABNORMAL_IP="$(tail -n10000 /var/log/auth.log |grep "$DATE" |awk '/Failed/{a[$(NF-3)]++}END{for(i in a)if(a[i]>5)print i}')"
for IP in $ABNORMAL_IP; do
if [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; then
iptables -A INPUT -s $IP -j DROP
echo "$(date +"%F %T") - iptables -A INPUT -s $IP -j DROP" >>~/ssh-login-limit.log
fi
done

4. 根据Nginx访问日志,封禁请求量异常的IP

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
#!/bin/bash  
####################################################################################
#根据web访问日志,封禁请求量异常的IP,如IP在半小时后恢复正常,则解除封禁
####################################################################################
logfile=/data/log/access.log
#显示一分钟前的小时和分钟
d1=`date -d "-1 minute" +%H%M`
d2=`date +%M`
ipt=/sbin/iptables
ips=/tmp/ips.txt
block()
{
#将一分钟前的日志全部过滤出来并提取IP以及统计访问次数
grep '$d1:' $logfile|awk '{print $1}'|sort -n|uniq -c|sort -n > $ips
#利用for循环将次数超过100的IP依次遍历出来并予以封禁
for i in `awk '$1>100 {print $2}' $ips`
do
$ipt -I INPUT -p tcp --dport 80 -s $i -j REJECT
echo "`date +%F-%T` $i" >> /tmp/badip.log
done
}
unblock()
{
#将封禁后所产生的pkts数量小于10的IP依次遍历予以解封
for a in `$ipt -nvL INPUT --line-numbers |grep '0.0.0.0/0'|awk '$2<10 {print $1}'|sort -nr`
do
$ipt -D INPUT $a
done
$ipt -Z
}
#当时间在00分以及30分时执行解封函数
if [ $d2 -eq "00" ] || [ $d2 -eq "30" ]
then
#要先解再封,因为刚刚封禁时产生的pkts数量很少
unblock
block
else
block
fi

5. 启动jar包服务

5.1. 项目启动

其中的日志判断"Started DreamApplication in "需自行替换成自己的项目关键字

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
#!/bin/bash

startTime=$(date +'%Y-%m-%d %H:%M:%S')

# 设置当前目录为接口项目站点路径
APP_PATH=$(pwd)

# 设置 JAR 文件名称
APP_NAME=$APP_PATH/Dream-admin.jar

# 设置日志文件名称
LOG_FILE=$APP_PATH/dream_out.log

# 设置启动环境
APP_YML="--spring.profiles.active=prod"

# 删除之前的日志文件
rm -rf $LOG_FILE

echo "开始停止项目进程"
# 查询进程,并杀掉当前 jar/java 程序
pid=$(ps -ef | grep $APP_NAME | grep -v grep | awk '{print $2}')
if [ $pid ]; then
echo "pid: $pid"
kill -9 $pid
echo "项目进程终止成功"
fi

sleep 2

# 判断 JAR 包文件是否存在,如果存在启动 JAR 包,并实时查看启动日志
if test -e $APP_NAME; then
echo '文件存在,开始启动此程序...'

# 启动 JAR 包,指向日志文件,2>&1 & 表示打开或指向同一个日志文件
nohup java -jar $APP_NAME $APP_YML > dream_out.log 2>&1 &
echo "正在发布中,请稍后..."
sleep 10s

# 通过检测日志来判断
while [ -f $LOG_FILE ]; do
success=$(grep "Started DreamApplication in " $LOG_FILE)
if [[ "$success" != "" ]]; then
break
else
sleep 1s
fi

fail=$(grep "Fail" $LOG_FILE)
if [[ "$fail" != "" ]]; then
echo "项目启动失败"
tail -f $LOG_FILE
break
else
sleep 1s
fi
done
echo "Project Started Success"

endTime=$(date +'%Y-%m-%d %H:%M:%S')
startSecond=$(date --date="$startTime" +%s)
endSecond=$(date --date="$endTime" +%s)

total=$((endSecond - startSecond))
echo "本次运行时间: $total s"
echo "当前时间: $endTime"

else
echo '$APP_NAME 文件不存在,请检查。'
fi

# $APP_NAME 同级目录下运行 ./startAdmin.sh 命令即可启动项目。

5.2. 项目停止

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
#!/bin/bash

# 设置当前目录为接口项目站点路径
APP_PATH=$(pwd)

#jar包文件名称
APP_NAME=$APP_PATH/Dream-admin.jar

echo "开始停止 Dream-admin 项目进程"
#查询进程,并杀掉当前jar/java程序

pid=`ps -ef|grep $APP_NAME | grep -v grep | awk '{print $2}'`

echo "pid: $pid "

if [ $pid ];then
echo "pid: $pid"
kill -9 $pid
echo "Dream-admin 项目进程进程终止成功"
else
echo "未找到对应服务"
fi

# $APP_NAME 同级目录下运行 ./stopAdmin.sh 命令即可启动项目。

5.3. 启动+停止简化版

省去花里胡哨直接杀掉进程重启,直接nohup sh api.sh & 启动,再通过tail -f nohup.out查看控制台日志

1
2
3
4
#jar包文件名称
APP_NAME=Dream-admin.jar
kill -9 `jps -l | grep $APP_NAME | awk '{print $1}'`
nohup java -jar $APP_NAME --spring.profiles.active=prod&