喵♂呜 的博客

一个刚毕业就当爹的程序猿 正在迷雾中寻找道路...

如何使用Shell和Nginx搭建一个简易的网页监控

有时候我们只是想简单的监控一下一个网站是否可用 但是目前大多数监控系统都非常复杂 这里通过一个特别的方案完成了

效果展示

图片

原理解释

  • 这里分为三部分
  • 第一部分 Shell 脚本 监控网站 生成数据JSON
  • 第二部分 Nginx 启动网页和发送数据
  • 第三部分 HTML 展示数据

Shell 脚本

原理 使用 CURL 读取网页信息 然后组装为 JSON 供网页调用 话不多说 直接上代码

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
# 发送钉钉通知
send_dingding() {
local title="${1}"
local body=$(echo -e "${2}" | sed s@\n@\\n@g -)
curl -qsk -o /dev/null -H 'Content-Type: application/json;charset=UTF-8' -d "$(printf "${DDPAYLOAD}" "${title}" "${title}" "${body}")" ${DDURL}
}

# 发送方糖通知
send_pushbear() {
local title="${1}"
local body=$(echo -e "${2}" | sed s@\n@%0A@g -)
curl -qsk -o /dev/null -d "$(printf "${FTPAYLOAD}" "${title}" "${body}")" ${FTURL}
}

send() {
local title="${1}"
local body="${2}"
case ${NOTIFY_TYPE} in
dingding)
send_dingding "${title}" "${body}"
;;
pushbear)
send_pushbear "${title}" "${body}"
;;
all)
send_dingding "${title}" "${body}"
send_pushbear "${title}" "${body}"
;;
esac
}

# 状态管理
getstatus() {
echo $(cat status/${app}-svc.json)
}

setstatus() {
local svc=${1}
local status=${2}
lastStatus=${status}
echo "${status:UP}">status/${svc}-svc.json
}

# 宕机时间
getdowntime() {
local svc=${1}
echo $(cat downtime/${svc}-svc.json)
}

setdowntime() {
local svc=${1}
echo "$(date +%s)">downtime/${svc}-svc.json
}

# 检查次数
getchecktime() {
local svc=${1}
echo $(cat checktime/${svc}-svc.json)
}

setchecktime() {
local svc=${1}
local times=${2}
checkTime=${times}
echo "${times}">checktime/${svc}-svc.json
}

# 宕机处理
down_check(){
local app=${1}
local code=${2}
if [[ "$(echo ${UPSTATUS} | grep ${code})" == "" ]]; then
if [[ "${checkTime}" == "1" ]]; then
setdowntime ${app}
fi
setchecktime ${app} $(( ${checkTime} + 1 ))
if [[ "${lastStatus}" == "UP" ]] && [[ ${checkTime} > ${CHECKTIME} ]]; then
setstatus ${app} "DOWN"
send "${app} 宕机" "- 服务地址: [点击访问](${url})\n- 当前状态: ${code}\n- 请检查相关服务是否运行正常!\n- 时间: `date`"
fi
else
setchecktime ${app} 1
fi
}

# 恢复处理
up_check() {
local app=${1}
local code=${2}
if [[ "$(echo ${UPSTATUS} | grep ${code})" != "" ]] && [[ "${lastStatus}" == "DOWN" ]]; then
setstatus ${app} "UP"
downtime=$(( $(date +%s) - $(getdowntime ${app}) ))
send "${app} 恢复运行" "- 服务地址: [点击访问](${url})\n- 当前状态: ${code}\n- 宕机时长: ${downtime}秒\n- 时间: `date`"
fi
}

# 检查状态
check_app() {
local app=${1:?No Space APP}
local url=${2:?No Space URL}
local start=$(date +%s%N)
local code=$(curl --request GET -Iqsw %{http_code} --connect-timeout 1 -m${TIMEOUT} -o/dev/null $url)
delayTime=$(( ( $(date +%s%N) - ${start} ) / 1000000 ))
checkTime=$(getchecktime ${app})
lastStatus=$(getstatus ${app})
down_check ${app} ${code}
up_check ${app} ${code}
echo "${app} STATUS: ${lastStatus} CODE: ${code} TIME: ${delayTime}ms"
echo "{'name':'${app}','status':'${lastStatus}','code':'${code}','time':'${delayTime}','url':'${url}'},">temp/${app}-svc.json
}

# 合并统计结果
merge_result() {
echo "">temp.json
for svc in $(ls temp | grep svc.json); do
cat temp/${svc}>>temp.json
done
echo "{'status':'200','data':[$(echo $(cat temp.json) | sed 's/.$//' -)],'msg':'OK'}">result.json
sed -i s/\'/\"/g result.json
}

main() {
for app in ${!apps[*]}; do
#这里可以丢到后台执行 但是可能会造成延时变大
check_app "${app}" "${apps[${app}]}" # &
done
wait
merge_result
echo '{"status":"200", "data":"'$(date +%s%N)'", "msg":"OK"}'>lastupdate.json
echo "========== Check Time: $(date) =========="
}

init_config() {
# 方糖自定义配置 https://pushbear.ftqq.com/admin
FTURL=https://pushbear.ftqq.com/sub
# 发送的Payload
FTPAYLOAD="sendkey=${FT_KEY:?please config FT_KEY if you want use pushbear}&text=%s&desp=%s"
# 钉钉自定义配置
DDURL=https://oapi.dingtalk.com/robot/send?access_token=${DD_KEY:?please config DD_KEY if you want use pushbear}
# 发送的Payload
DDPAYLOAD="{ \"msgtype\": \"markdown\", \"markdown\": { \"title\":\"%s\", \"text\":\"## %s\n%s\n- @${DD_MOBILE}\" }, \"at\": { \"atMobiles\": [ \"${DD_MOBILE}\" ], \"isAtAll\": false } }"
}

init() {
init_config
# 清理缓存
rm -rf temp

# 初始化缓存目录
mkdir -p temp
mkdir -p status
mkdir -p downtime
mkdir -p checktime

# 初始化状态MAP
for app in ${!apps[*]}; do
if [[ ! -f status/${app}-svc.json ]]; then
echo "UP">status/${app}-svc.json
fi
done

# 初始化状态MAP
for app in ${!apps[*]}; do
if [[ ! -f checktime/${app}-svc.json ]]; then
echo "0">checktime/${app}-svc.json
fi
done

# 循环检查服务状态
while true; do
main
sleep 5
done
}

# 发送通知类型(可选 dingding[钉钉] pushbear[方糖] all[全部通知])
NOTIFY_TYPE='none'
# 钉钉的发送Token
DD_KEY=''
# 需要@的手机号
DD_MOBILE=''
# 方糖的Key 参考 https://pushbear.ftqq.com/admin
FT_KEY=''
# 定义UP状态的状态码
UPSTATUS='200 404'
# 检查次数(异常连续超过这个次数则判定为DOWN)
CHECKTIME=5
# 超时时间
TIMEOUT=3

# 定义服务MAP
declare -A apps=(
["百度"]="https://www.baidu.com"
["Google"]="https://www.google.com"
["腾讯"]="https://www.qq.com"
)

Nginx 配置脚本目录

这里就是常规配置了

1
2
3
4
5
6
7
8
9
10
server
{
listen 80;
server_name m.i.sixi.com m.sixi.com;

root /home/app/monitor;
index index.html index.htm;

access_log /home/wwwlogs/m.sixi.com.log;
}

HTML 网页部分

用了 Avalon 简单的渲染了一下

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
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>四喜开放平台</title>
<script src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<script src="//unpkg.com/avalon2@2.2.8/dist/avalon.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container bs-docs-container ms-controller" :controller="i">
<div class="row">
<div class="container">
<div class="jumbotron text-center" >
<h2>四喜服务状态检测 .</h2>
<h4>最后更新时间: {{time}}.</h4>
<br>
<table class="table">
<thead>
<tr>
<th>服务名称</th>
<th>服务地址</th>
<th>服务状态</th>
<th>HTTP状态码</th>
<th>响应时间</th>
</tr>
</thead>
<tbody>
<!-- DOWN 状态显示 红色 || 非200状态或者延时大于300 显示黄色 || 其他情形显示绿色 -->
<tr :for='el in @result' :class="( el.status == 'UP' ? ( ( el.code == '200' && el.time < 300 ) ? 'success' : 'warning' ) : 'danger' )">
<th>{{el.name}}</th>
<th><a :attr="{href:el.url,target:'_blank'}">{{el.url}}</a></th>
<th>{{el.status}}</th>
<th>{{el.code}}({{@cn(el.code)}})</th>
<th>{{el.time}}ms</th>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script type="text/javascript">
var main = avalon.define({
$id : 'i',
lastupdate: "",
result: [],
time: "None",
cn: function(code){
switch(code){
case '200':
return '正常'
case '302':
return '重定向'
case '404':
return '网页丢失'
case '500':
return '服务器错误'
case '502':
return '网关超时'
case '000':
return '网络异常'
default:
return '未知状态'
}
}
});
function check() {
main.time = "刷新中..."
$.getJSON('/lastupdate.json', function(result){
if (result.data != main.lastupdate) {
$.getJSON('/result.json', function(result){
main.result = result.data;
main.time = new Date().toLocaleString();
})
main.lastupdate = result.data
setTimeout(check, 3000)
}else{
setTimeout(check, 1000)
}
})
}
check()
</script>
</body>
</html>

执行之后的目录结构

1
2
3
4
5
6
7
8
9
10
11
[root@2-230 monitor]# ll
总用量 44
drwxr-xr-x 2 root root 4096 11月 24 18:19 checktime # 检查数据目录
drwxr-xr-x 2 root root 4096 11月 26 11:46 downtime # 宕机时间数据
-rw-r--r-- 1 root root 6511 11月 24 18:35 index.html # 主页
-rw-r--r-- 1 root root 59 11月 26 14:40 lastupdate.json # 最后更新时间
-rwxr-xr-x. 1 root root 5437 11月 26 11:06 prod.sh # 执行脚本
-rw-r--r-- 1 root root 2536 11月 26 14:40 result.json # 最终合并的结果 返回给前端
drwxr-xr-x 2 root root 4096 11月 24 18:19 status # 状态数据目录
drwxr-xr-x 2 root root 4096 11月 26 11:07 temp # 临时目录 用于合并数据
-rw-r--r-- 1 root root 1 11月 26 14:40 temp.json # 临时json结果

欢迎关注我的其它发布渠道