Linux 生产环境应用监控的 Shell 脚本

前言

在 Linux 生产环境里,通过编写 Shell 脚本,实现根据程序允许运行的时间范围,后台启动或者关闭程序。该脚本支持单独执行,但一般需要配合 Linux 的 Crontab 定时任务一起使用。

应用监控的基础版

编写 Shell 脚本代码

  • monitor.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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#!/bin/bash

################################################################################################################################################################################################################################################
#### 脚本的用途:根据程序允许运行的时间范围,后台启动或者关闭程序;脚本可以单独执行,但在生产环境一般需要配合 Linux 的 Crontab 定时任务一起使用
#### 脚本的参数:
#### 1. 第一个参数:程序的名称
#### 2. 第二个参数:程序所在的目录
#### 3. 第三个参数:程序允许运行的时间范围,格式:09:00:00-10:15:00(多个时间范围必须使用英文逗号隔开,不能含有空格符),24h 表示允许程序 24 小时运行
#### 脚本的使用示例:
#### 1. 第一种方式:/bin/bash monitor.sh minerd /home/clay/minerd/bin/ 24h
#### 2. 第二种方式:/bin/bash monitor.sh minerd /home/clay/minerd/bin/ 09:00:00-10:15:00,10:30:00-11:30:00,13:30:00-15:00:00,21:00:00-02:30:00
#### 脚本的使用注意事项:
#### 1. 脚本可以在任意目录下执行,一般指定脚本的绝对路径即可
#### 2. 当执行脚本时,如果当前时间在程序允许运行的时间范围内,则会后台启动程序;如果当前时间不在程序允许运行的时间范围内,则会关闭程序
#### 3. 关于程序允许运行的时间范围:开始时间比结束时间小(只在同一天),例如 07:00:00-09:00:00(实际允许2个小时);开始时间比结束时间大(可到第二天),例如 08:00:00-07:00:00(实际允许23个小时);24h 表示允许程序 24 小时运行
################################################################################################################################################################################################################################################


# 后台启动程序,如果程序正在运行,则什么都不做,否则在后台启动程序
# 第一个函数参数:程序的名称
# 第二个函数参数:程序所在的目录
# 第三个函数参数:等待程序启动的时间(秒)
# 函数的返回值:程序正在运行则返回 2;程序启动成功则返回 1;程序启动失败则返回 0;
# 函数的调用示例:startProgramBackground minerd /home/clay/minerd/bin/ 10
startProgramBackground() {
# 程序的名称
local app_name="$1"

# 程序所在的目录
local app_path="$2"

# 等待程序启动的时间(秒)
local wait_seconds_for_start="$3"

# 程序进程的ID
local app_id=`ps -A |grep $app_name| awk '{print $1}'`

# 当前的系统时间
local now=`date "+%Y-%m-%d %H:%M:%S"`

# 判断程序是否正在运行
if [ ! $app_id ];
then
echo "$now $app_name is not running, start it now..."

# 进入程序所在的目录
cd $app_path

# 后台启动程序
nohup ./$app_name > $app_name.log 2>&1 &

# 程序启动后新的进程ID
local new_pid=`ps -A |grep $app_name| awk '{print $1}'`

# 等待程序启动(秒)
sleep $wait_seconds_for_start

# 检查程序是否启动成功
if [ ! $new_pid ];
then
echo "$now $app_name start failed!"
return 0
else
# 检查程序启动后的进程是否真实存在
if ps -p $new_pid > /dev/null;
then
echo "$now $app_name start successful, pid is $new_pid"
return 1
else
echo "$now $app_name start failed!"
return 0
fi
fi
else
echo "$now $app_name is already running, pid is $app_id"
return 2
fi
}


# 杀手程序进程
# 第一个函数参数:程序的名称
# 第二个函数参数:等待程序进程被杀死的时间(秒)
# 函数的返回值:成功杀死进程,则返回 1,否则返回 0
# 函数的调用示例:killProgram minerd 1
killProgram() {
# 程序的名称
local app_name="$1"

# 等待程序进程被杀死的时间(秒)
local wait_seconds_for_kill="$2"

# 当前的系统时间
local now=`date "+%Y-%m-%d %H:%M:%S"`

# 程序进程的ID
local app_id=`ps -A |grep $app_name| awk '{print $1}'`

if [ $app_id ];
then
echo "$now $app_name is running, kill it now..."

# 杀死进程
kill -15 $app_id

# 等待进程被杀死(秒)
sleep $wait_seconds_for_kill

# 检查进程是否被真正杀死
if ps -p $app_id > /dev/null;
then
echo "$now $app_name kill failed, pid is $app_id"
return 0
else
echo "$now $app_name kill successful, pid is $app_id"
return 1
fi
else
echo "$now $app_name not running, no need to kill the process"
return 1
fi
}


# 判断当前时间是否在指定的时间范围内
# 第一个函数参数:开始时间,格式为 13:05:00
# 第二个函数参数:结束时间,格式为 13:05:00
# 第三个函数参数:当前时间,格式为 13:05:00
# 函数的返回值:如果当前时间在指定的时间范围内,则返回 1,否则返回 0
# 函数的调用示例:checkBetweenTimes "09:00:00" "16:15:30" "13:05:10"
# 特别注意:开始时间比结束时间小(只在同一天),例如 07:00:00-09:00:00(实际允许2个小时);开始时间比结束时间大(可到第二天),例如 08:00:00-07:00:00(实际允许23个小时)
checkBetweenTimes() {
# 开始时间
local start_time="$1"
local start_time_full=$(date "+%Y-%m-%d $start_time")
local start_time_senconds=`date -d "$start_time_full" +%s`

# 结束时间
local stop_time="$2"
local stop_time_full=$(date "+%Y-%m-%d $stop_time")
# 如果开始时间大于结束时间(例如 13:00:00-02:16:00),则结束时间加上一天
if [[ "$start_time" > "$stop_time" ]];
then
stop_time_full=`date -d "$stop_time_full 1 days" "+%Y-%m-%d %H:%M:%S"`
fi
local stop_time_seconds=`date -d "$stop_time_full" +%s`

# 当前时间
local cur_time="$3"
local cur_time_full=$(date "+%Y-%m-%d $cur_time")
# 如果开始时间大于当前时间(例如 13:00:00-02:16:00),则当前时间加上一天
if [[ "$start_time" > "$cur_time" ]];
then
cur_time_full=`date -d "$cur_time_full 1 days" "+%Y-%m-%d %H:%M:%S"`
fi
local cur_time_seconds=`date -d "$cur_time_full" +%s`

# 判断时间范围
if [[ ($cur_time_seconds -gt $start_time_senconds || $cur_time_seconds -eq $start_time_senconds) && ($cur_time_seconds -lt $stop_time_seconds || $cur_time_seconds -eq $stop_time_seconds) ]];
then
return 1
else
return 0
fi
}


# 程序的名称
program_name="$1"

# 程序所在的目录
program_path="$2"

# 程序允许运行的时间范围字符串,格式是 "09:00:00-10:15:00,10:30:00-11:30:00,21:00:00-02:30:00"
time_area_str="$3"

# 程序是否允许运行
program_can_run=0

# 等待程序启动的时间(秒)
wait_start_seconds=5

# 等待程序进程被杀死的时间(秒)
wait_kill_seconds=1

# 当前的系统时间
nowTime=`date "+%H:%M:%S"`

# 判断是否允许24小时运行程序
if [[ "$time_area_str" == "24h" || "$time_area_str" == "24H" ]];
then
# 标记程序可以运行
program_can_run=1
else
# 解析时间范围字符串,判断程序是否允许运行
time_area_array=(${time_area_str//,/ })
for time_area in ${time_area_array[@]}
do
times=(${time_area//-/ })
startTime=${times[0]}
stopTime=${times[1]}
# echo "startTime = $startTime, stopTime = $stopTime, nowTime = $nowTime"

# 判断当前时间是否在指定的时间范围内
checkBetweenTimes $startTime $stopTime $nowTime
if [ $? -eq 1 ];
then
# 标记程序可以运行
program_can_run=1
break
fi
done
fi

# 后台启动或者关闭程序
if [ $program_can_run -eq 0 ];
then
# 关闭程序
killProgram $program_name $wait_kill_seconds
else
# 后台启动程序
startProgramBackground $program_name $program_path $wait_start_seconds
fi

Crontab 定时任务的使用

1
2
3
4
5
# 编辑系统配置文件,添加Cron定时任务
$ sudo vim /etc/crontab

# 周一到周五,每隔一分钟执行一次监控
* * * * 1-5 clay /bin/bash /home/clay/minerd/scripts/monitor.sh minerd /home/clay/minerd/bin/ 09:00:00-10:15:00,10:30:00-11:30:00,13:30:00-15:00:00,21:00:00-02:30:00 >> /home/clay/minerd/bin/minerd.log

应用监控的增强版

提示

在上面基础版的代码之上,新增只杀死程序进程(不考虑程序允许运行的时间范围)而其他事情都不做的功能。

编写 Shell 脚本代码

  • monitor.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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#!/bin/bash

################################################################################################################################################################################################################################################
#### 脚本的用途:根据程序允许运行的时间范围,后台启动或者关闭程序;脚本可以单独执行,但在生产环境一般需要配合 Linux 的 Crontab 定时任务一起使用
#### 脚本的参数:
#### 1. 第一个参数:程序的名称
#### 2. 第二个参数:程序所在的目录
#### 3. 第三个参数:程序允许运行的时间范围,格式:09:00:00-10:15:00(多个时间范围必须使用英文逗号隔开,不能含有空格符),24h 表示允许程序 24 小时运行
#### 4. 第四个参数:是否只杀死程序进程(不考虑程序允许运行的时间范围)而其他事情都不做,其中 1 表示是,0 表示否,该参数省略不写时表示否
#### 脚本的使用示例:
#### 1. 第一种方式:/bin/bash monitor.sh minerd /home/clay/minerd/bin/ 24h
#### 2. 第二种方式:/bin/bash monitor.sh minerd /home/clay/minerd/bin/ 09:00:00-10:15:00,10:30:00-11:30:00,13:30:00-15:00:00,21:00:00-02:30:00
#### 3. 第三种方式:/bin/bash monitor.sh minerd /home/clay/minerd/bin/ 09:00:00-10:15:00,10:30:00-11:30:00,13:30:00-15:00:00,21:00:00-02:30:00 1
#### 脚本的使用注意事项:
#### 1. 脚本可以在任意目录下执行,一般指定脚本的绝对路径即可
#### 2. 当执行脚本时,如果当前时间在程序允许运行的时间范围内,则会后台启动程序;如果当前时间不在程序允许运行的时间范围内,则会关闭程序
#### 3. 关于程序允许运行的时间范围:开始时间比结束时间小(只在同一天),例如 07:00:00-09:00:00(实际允许2个小时);开始时间比结束时间大(可到第二天),例如 08:00:00-07:00:00(实际允许23个小时);24h 表示允许程序 24 小时运行
################################################################################################################################################################################################################################################


# 后台启动程序,如果程序正在运行,则什么都不做,否则在后台启动程序
# 第一个函数参数:程序的名称
# 第二个函数参数:程序所在的目录
# 第三个函数参数:等待程序启动的时间(秒)
# 函数的返回值:程序正在运行则返回 2;程序启动成功则返回 1;程序启动失败则返回 0;
# 函数的调用示例:startProgramBackground minerd /home/clay/minerd/bin/ 10
startProgramBackground() {
# 程序的名称
local app_name="$1"

# 程序所在的目录
local app_path="$2"

# 等待程序启动的时间(秒)
local wait_seconds_for_start="$3"

# 程序进程的ID
local app_id=`ps -A |grep $app_name| awk '{print $1}'`

# 当前的系统时间
local now=`date "+%Y-%m-%d %H:%M:%S"`

# 判断程序是否正在运行
if [ ! $app_id ];
then
echo "$now $app_name is not running, start it now..."

# 进入程序所在的目录
cd $app_path

# 后台启动程序
nohup ./$app_name > $app_name.log 2>&1 &

# 程序启动后新的进程ID
local new_pid=`ps -A |grep $app_name| awk '{print $1}'`

# 等待程序启动(秒)
sleep $wait_seconds_for_start

# 检查程序是否启动成功
if [ ! $new_pid ];
then
echo "$now $app_name start failed!"
return 0
else
# 检查程序启动后的进程是否真实存在
if ps -p $new_pid > /dev/null;
then
echo "$now $app_name start successful, pid is $new_pid"
return 1
else
echo "$now $app_name start failed!"
return 0
fi
fi
else
echo "$now $app_name is already running, pid is $app_id"
return 2
fi
}


# 杀手程序进程
# 第一个函数参数:程序的名称
# 第二个函数参数:等待程序进程被杀死的时间(秒)
# 函数的返回值:成功杀死进程,则返回 1,否则返回 0
# 函数的调用示例:killProgram minerd 1
killProgram() {
# 程序的名称
local app_name="$1"

# 等待程序进程被杀死的时间(秒)
local wait_seconds_for_kill="$2"

# 当前的系统时间
local now=`date "+%Y-%m-%d %H:%M:%S"`

# 程序进程的ID
local app_id=`ps -A |grep $app_name| awk '{print $1}'`

if [ $app_id ];
then
echo "$now $app_name is running, kill it now..."

# 杀死进程
kill -15 $app_id

# 等待进程被杀死(秒)
sleep $wait_seconds_for_kill

# 检查进程是否被真正杀死
if ps -p $app_id > /dev/null;
then
echo "$now $app_name kill failed, pid is $app_id"
return 0
else
echo "$now $app_name kill successful, pid is $app_id"
return 1
fi
else
echo "$now $app_name not running, no need to kill the process"
return 1
fi
}


# 判断当前时间是否在指定的时间范围内
# 第一个函数参数:开始时间,格式为 13:05:00
# 第二个函数参数:结束时间,格式为 13:05:00
# 第三个函数参数:当前时间,格式为 13:05:00
# 函数的返回值:如果当前时间在指定的时间范围内,则返回 1,否则返回 0
# 函数的调用示例:checkBetweenTimes "09:00:00" "16:15:30" "13:05:10"
# 特别注意:开始时间比结束时间小(只在同一天),例如 07:00:00-09:00:00(实际允许2个小时);开始时间比结束时间大(可到第二天),例如 08:00:00-07:00:00(实际允许23个小时)
checkBetweenTimes() {
# 开始时间
local start_time="$1"
local start_time_full=$(date "+%Y-%m-%d $start_time")
local start_time_senconds=`date -d "$start_time_full" +%s`

# 结束时间
local stop_time="$2"
local stop_time_full=$(date "+%Y-%m-%d $stop_time")
# 如果开始时间大于结束时间(例如 13:00:00-02:16:00),则结束时间加上一天
if [[ "$start_time" > "$stop_time" ]];
then
stop_time_full=`date -d "$stop_time_full 1 days" "+%Y-%m-%d %H:%M:%S"`
fi
local stop_time_seconds=`date -d "$stop_time_full" +%s`

# 当前时间
local cur_time="$3"
local cur_time_full=$(date "+%Y-%m-%d $cur_time")
# 如果开始时间大于当前时间(例如 13:00:00-02:16:00),则当前时间加上一天
if [[ "$start_time" > "$cur_time" ]];
then
cur_time_full=`date -d "$cur_time_full 1 days" "+%Y-%m-%d %H:%M:%S"`
fi
local cur_time_seconds=`date -d "$cur_time_full" +%s`

# 判断时间范围
if [[ ($cur_time_seconds -gt $start_time_senconds || $cur_time_seconds -eq $start_time_senconds) && ($cur_time_seconds -lt $stop_time_seconds || $cur_time_seconds -eq $stop_time_seconds) ]];
then
return 1
else
return 0
fi
}


# 程序的名称
program_name="$1"

# 程序所在的目录
program_path="$2"

# 程序允许运行的时间范围字符串,格式是 "09:00:00-10:15:00,10:30:00-11:30:00,21:00:00-02:30:00"
time_area_str="$3"

# 是否只杀死程序进程(不考虑程序允许运行的时间范围),其中 1 表示是,0 表示否
only_kill_program="$4"

# 程序是否允许运行
program_can_run=0

# 等待程序启动的时间(秒)
wait_start_seconds=5

# 等待程序进程被杀死的时间(秒)
wait_kill_seconds=1

# 当前的系统时间
nowTime=`date "+%H:%M:%S"`

# 处理缺省参数,默认不只是杀死程序进程(不考虑程序允许运行的时间范围)
if [ ! $4 ];
then
only_kill_program=0
fi

# 若只杀死程序进程(不考虑程序允许运行的时间范围),则关闭程序,然后直接退出
if [ $only_kill_program -eq 1 ];
then
killProgram $program_name $wait_kill_seconds
exit 0
fi

# 判断是否允许24小时运行程序
if [[ "$time_area_str" == "24h" || "$time_area_str" == "24H" ]];
then
# 标记程序可以运行
program_can_run=1
else
# 解析时间范围字符串,判断程序是否允许运行
time_area_array=(${time_area_str//,/ })
for time_area in ${time_area_array[@]}
do
times=(${time_area//-/ })
startTime=${times[0]}
stopTime=${times[1]}
# echo "startTime = $startTime, stopTime = $stopTime, nowTime = $nowTime"

# 判断当前时间是否在指定的时间范围内
checkBetweenTimes $startTime $stopTime $nowTime
if [ $? -eq 1 ];
then
# 标记程序可以运行
program_can_run=1
break
fi
done
fi

# 后台启动或者关闭程序
if [ $program_can_run -eq 0 ];
then
# 关闭程序
killProgram $program_name $wait_kill_seconds
else
# 后台启动程序
startProgramBackground $program_name $program_path $wait_start_seconds
fi

Crontab 定时任务的使用

1
2
3
4
5
6
7
8
# 编辑系统配置文件,添加Cron定时任务
$ sudo vim /etc/crontab

# 周一到周五,每隔一分钟执行一次监控
* * * * 1-5 clay /bin/bash /home/clay/minerd/scripts/monitor.sh minerd /home/clay/minerd/bin/ 09:00:00-10:15:00,10:30:00-11:30:00,13:30:00-15:00:00,21:00:00-02:30:00 >> /home/clay/minerd/bin/minerd.log

# 周六周日的00:00执行关闭程序
0 0 * * 6,0 clay /bin/bash /home/clay/minerd/scripts/monitor.sh minerd /home/clay/minerd/bin/ 09:00:00-10:15:00,10:30:00-11:30:00,13:30:00-15:00:00,21:00:00-02:30:00 >> /home/clay/minerd/bin/minerd.log 1