本文来自作者 Alinx 在 GitChat 上分享 「用 Webhook+Python+Shell 编写一套 Unix 类系统监控工具」
告警系统是对系统监控必须掌握的技能、不管是用 zabbix、cacti 等监控平台还是其他的监控工具,都需要有一个实时的监控与反馈机制,能让问题、故障实时的通知到工程师的手里,及时得到解决,以最大化的保障业务的正常。
本次编写部署监控是为了更好的学习、经验的总结、也希望能给给位带来一点帮助,在大家刚好需要的时候,这篇文章能帮助你解决你所需要的。
Shell(计算机壳层),在计算机科学中,shell 俗称壳,是提供使用者使用界面的软件(命令解析器)。它类似于 DOS 下的 command.com 和后来的 cmd.exe。它接收用户命令,然后调用相应的应用程序。
同时它又是一种程序设计语言。作为命令语言,它交互式解释和执行用户输入的命令,或者自动地解释和执行预先设定好的一连串的命令;
作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
基本上 shell 分两大类:
在 UNIX 中主要有:
在 Unix 类系统之中,一个可执行的程序是一个机器指令及其数据的序列,一个进程是程序运行时的内存空间和设置。
Unix 系统中的内存分为系统空间和用户空间,进程存在于用户空间,用户空间是存放运行的程序和它们数据的一部分内存空间。
建立一个进程时,内核要找到存放程序指令和数据的空闲内存页。内核还要建立数据结构来存放相应的内存分配情况和进程属性。
shell 是一个管理进程和运行程序的程序,Unix 系统有很多可用的 shell。
shell 对输入的命令的分析:
在 Linux 中,有一些命令,例如 cd 是包含在 shell 内部的命令,还有一些命令,例如 cp、mv 或 rm 是存在于文件系统中某个目录下的单独的程序。对于用户而言,没必要关心一个命令是在 shell 内部还是在 shell 外部。
shell 对于命令的分析过程如下:
首先,检查用户输入的命令是否是内部命令,如果不是再检查是否是一个应用程序;shell 在搜索路径或者环境变量中寻找这些应用程序;
如果键入命令不是一个内部命令并且没有在搜索路径中查找到可执行文件,那么将会显示一条错误信息;
如果能够成功找到可执行文件,那么该内部命令或者应用程序将会被分解为系统调用传给 Linux 内核,然后内核在完成相应的工作。
只要有操作系统的地方就有 shell、学习 shell 也可以说是学习它的系统,掌握 shell 也等同于熟练的掌握了系统的使用规则。
Shell、内核、硬件的关系如图所示:
常用的特殊字符表
利用通配符可以同时引用多个文件,常用的通配符有 * 和 ? ,* 号表示可以匹配任意长度的任何字符,? 号代表了任意一个字符。
例如:
ls *.png
ls b?
ls b???
注意:
例如:
ls [abc]d
ls [abc]*
ls [a-f]*
ls [!abc]*
键盘称为标准输入设备,显示器称为标准输出设备
在 Shell 中,不使用系统的标准输入、输出设备而重新指定其输入输出的方法称为输入输出重定向。
什么时候需要使用重定向?
重定向符号有:>、1>、2>、>>、<
语法格式:
命令
重定向符号
设备或文件
根据不同的符号实现的效果可以分为:
标准输出重定向(>)
将命令执行的结果不在标准输出设备上显示,而是保存到某一文件或者通过某一设备进行输出的操作
例如:ls -al >list
说明:
可以通过 vi test 来浏览执行的结果信息。
附加输出重定向(>>)
和标准输出重定向不同之处在于前者将输出的内容保存到文件的同时不覆盖文件原有的内容,而是追加到原有内容的后面;
例如:ls -al >>list
错误输出重定向(2>)
例如:find / -name newtxt 2> err.txt
说明:该命令将正确的结果信息显示在屏幕上,将错误的信息输出到 err.txt 文件中
错误输出重定向(2>)
将正确的信息和错误的信息分别输出到不同的文件
find / -name newtxt 1>right.txt 2>err.txt
将正确的信息和错误的信息都输出到同一个文件中
find / -name newtxt 1>result.txt 2>&1
将显示的数据中正确的信息输出到某个文件,错误的信息丢弃
find / -name newtxt 1>result.txt 2> /dev/null
说明:/dev/null 可以视为垃圾设备,专么收集垃圾信息,导入到这里的数据将被清理并消除,将多个命令前后连接起来形成一个管道流。
管道命令执行流程图 :
实现管道功能的符号为 |
例如: 要利用管道统计当前目录下所有文件和子目录的数目 ls -l | wc -l
注意:管道操作只能处理前一个命令执行的正确信息,即标准输出的内容,而对错误信息无法处理
1. Python 是什么
2. 安装 Python
所谓安装 Python,实际上主要是安装一个 Python 解释器(CPython,以便使用该解释器执行 Python 程序)和内置类库;除此之外,同时还会安装一个集成开发环境,这个集成开发环境叫做 IDLE。
3. Python 的代码库
Python 的代码库可以分为两类,一类是 Python 内置的代码库,提供了包括网络 / 文件 / GUI / 数据库 / 文本处理等大量的功能,内置代码库在安装 Python 的时候会同时安装;另一类是第三方代码库,在 Python 上有大量的第三方代码库可供使用。
4. 启动 Python
所谓启动 Python,实际上是启动 Python 解释器(CPython),通过在命令行中输入 Python 启动解释器。
Python 解释器有两种模式,一种是交互式模式,在这种模式下,输入的代码在回车后会立即执行,并显示代码执行结果,在命令行中通过输入 Python 进入交互式模式,输入 exit() 退出交互式模式;
另一种是命令行模式,即在命令行中输入 python [文件名. py],直接执行 Python 程序。
这里有一个问题,就是 Python 可以用来编写 web 应用程序,web 应用程序的基本功能是处理 http 请求,Python 程序是如何运行起来(或者说在上面的哪种模式下)处理 http 请求的呢?
这是由 web 服务器和框架来启动 Python 程序处理 http 请求的。所有用脚本语言编写的网站后台都是这种由框架启动的模式。
5. Python 语言的输入和输出
Python 用 variable = input() 函数将用户输入保存在变量 variable 中,用 print(‘hello’, variable) 函数产生输出。
6. Python 程序需要保存为 utf-8 编码
在 notepad++ 的 Encoding 菜单中选择 Encode in UTF-8-BOM。
1. 数据类型、变量
Python 中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。
在 Python 中,变量就是变量,它没有类型,我们所说的”类型”是变量所指的内存中对象的类型。
等号(=)用来给变量赋值。
等号(=)运算符左边是一个变量名,等号(=)运算符右边是存储在变量中的值。例如:
#!/usr/bin/python3
counter = 100 # 整型变量
miles = 1000.0 # 浮点型变量
name = "runoob" # 字符串
print (counter) print (miles) print (name)
标准数据类型
Python3 中有六个标准的数据类型:
2. 语句
Python 条件语句: Python 条件语句是通过一条或多条语句的执行结果(True 或者 False)来决定执行的代码块。
可以通过下图来简单了解条件语句的执行过程:
if 判断条件:
执行语句……
else:
执行语句……
Python 循环语句
本节将向大家介绍 Python 的循环语句,程序在一般情况下是按顺序执行的。
编程语言提供了各种控制结构,允许更复杂的执行路径。
循环语句允许我们执行一个语句或语句组多次,下面是在大多数编程语言中的循环语句的一般形式:
循环控制语句
循环控制语句可以更改语句执行的顺序。Python支持以下循环控制语句:
3. 常见运算符
函数
函数的定义:def 函数名( 逗号分隔的参数列表 ): 函数体
调用函数:
再谈函数参数:
高级特性
什么是可迭代对象? 如何迭代 list/dict/tuple/set/ 字符串 Python 的 for 循环非常特别,在for循环中可以引用多个变量,形如:for i, j, k in …,这是要求 in 后的可迭代对象中也要有分别对应 i,j,k 多个变量的内容。
函数式编程
所谓函数式编程,就是语言中的“函数”也是数据,可以被赋值给变量、作为另一个函数的参数等,总之是在使用数据的场合都可以使用“函数”。
高阶函数:就是可以将其他函数作为参数的函数。Python 内置的几个高阶函数有(这些高阶函数和C#中在集合上定义的扩展方法非常类似,它们的参数都是一个函数和一个可迭代对象,然后将函数作用于可迭代对象中的每个元素,产生结果):
函数作为返回值和闭包:大概了解是怎么回事,但是还是有些说不清楚,也很难应用起来。
定义一个参数为函数、返回值也是函数的函数,即装饰器函数,在返回的函数中调用传入的参数函数及添加其他功能,也就是返回的函数成为了参数函数的一个包装器; 在定义需要临时增加一些功能的函数时,在函数前使用“@装饰器函数名”语法修饰该函数,则在调用此函数时,会转为调用在装饰器函数中定义的包装函数,从而达到临时增加功能的目的; 实际上包装器函数和原函数的一些属性还是不同的,如name属性等,但 python 通过 functools 模块中的wraps函数可以将原函数的属性复制给包装器函数,所以在装饰器函数中要求这么一句@functools.wrap(原函数名); 总结起来,装饰器函数有这么几个特征: (1)其参数为一个函数; (2)返回值也是一个函数; (3)在返回值函数中调用参数函数并添加其他功能,达到为参数函数临时增加功能的目的; (4)通过“@装饰器函数名”的方式修饰其他函数,从而为该函数增加装饰器中增加的临时功能; (5)装饰器函数中要有这样一个语句:@functools.wrap(原函数名),用于将原函数属性复制给包装器函数;这也太笨拙了吧?
模块
模块是 Python 中组织源代码的一种机制,一个 .py 文件就是一个模块,模块名是该 .py 文件所在的文件夹名与文件的组合,用 . 分隔,即“文件夹名 . 文件名”;
用文件夹来组织模块的方式,称为“包(Package)”。可以作为包的文件夹中必须包含一个 __init__.py 文件(这个 __init__.py 文件本身可以为空,也可以包含代码,它本身也是一个模块,但它的模块名不是 __init__,而是它所在文件夹的名称),否则该文件夹只是一个普通的文件夹而非“包”(“包”类似 C# 中命名空间的机制)。
使用内置模块
只要安装了 Python,内置模块就可以使用。使用模块的第一步是导入模块,语法为 import 模块名,如:
import sys
导入模块后,将相当于定义了一个与模块同名的变量 sys,使用该变量来引用该模块,如 sys.argv,就是引用模块 sys 中定义的变量 argv。
使用第三方模块
使用第三方模块之前,需要首先进行安装。在 Python 中,是通过包管理工具 pip 完成第三方模块管理的。
pip命令:
pip search param
pip install param
pip uninstall param
pip list
等
面向对象编程
面向对象编程的内容:类、对象、类和对象的成员、封装、继承、多态;类及其成员的可访问性 public/private/protected;构造函数
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
创建类的实例:
student = Student()
类和对象的属性:
类属性:
对象属性:
可以为对象动态添加属性,如 student1.score = 80;如果对象的属性与类的属性重名,则对象的属性覆盖类的属性;这个可能和 javascript 的属性访问机制一样,即从底层开始,找到后即不在继续向上层查找;
获取对象信息,使用以下函数:
1. 概述
Webhook 是一个 API 概念,并且变得越来越流行。我们能用事件描述的事物越多,Webhook 的作用范围也就越大。Webhook 作为一个轻量的事件处理应用,正变得越来越有用。
准确的说 Webhook 是一种 web 回调或者 http 的 push API,是向 APP 或者其他应用提供实时信息的一种方式。
Webhook 在数据产生时立即发送数据,也就是你能实时收到数据。这一种不同于典型的 API,需要用了实时性需要足够快的轮询。
这无论是对生产还是对消费者都是高效的,唯一的缺点是初始建立困难。Webhook 有时也被称为反向 API,因为他提供了 API 规则,你需要设计要使用的 API。
Webhook 将向你的应用发起 http 请求,典型的是 post 请求,应用程序由请求驱动。
2. 使用 Webhook
消费一个 Webhook 是为 Webhook 准备一个 URL,用于 Webhook 发送请求。这些通常由后台页面和或者 API 完成。这就意味你的应用要设置一个通过公网可以访问的 URL。
多数 Webhook 以两种数据格式发布数据:JSON 或者 XML。另一种数据格式是 application/x-www-form-urlencoded or multipart/form-data。这两种方式都很容易解析,并且多数的 Web 应用架构都可以做这部分工作。
3. Webhook 调试
调试 Webhook 有时很复杂,因为 Webhook 原则来说是异步的。你首先要触发它,然后等待,接着检查是否有响应,枯燥并且相当低效。幸运的是还有其他方法:
4. Webhook 安全
因为 Webhook 发送数据到应用上公开的 URL,这就给其他人找到这个 URL 并且发送错误数据的机会。你可采用技术手段,防止这样的事情发生。最简单的方法是采用 https(TLS connection)。除了使用 https 外,还可以采用以下的方法进一步提高安全性:
5. 重要的问题
当作为 Webhook 的消费者时有两件事需要铭记于心:
使用 Python 来编写 Python 告警脚本,结合 Webhook 技术:
首先需要获取 Webhook 地址,本文使用钉钉的 Webhook 来结合脚本使用;
钉钉内部申请机器人:
(1)新建群聊
(2)创建群机器人
(3)选择通过 Webhook 接入的自定义服务
(4)获取 Webhook 地址
Webhook地址:
https://oapi.dingtalk.com/robot/send?access_token=713f8d422336b8131a4829fa752293f2dd9c963c8d185e31a1a1799cfc5ffa35
告警脚本:
代码如下:
import json
import urllib2
url = ""
header = {
"Content-Type":"application/json",
"Charset":"utf-8"
}
data = {
"msgtype":"text",
"text":{
"content":"系统故障了"
},
"at":{
"atMobiles":[]
#"isAtAll":True
}
}
sendData = json.dumps(data)
request = urllib2.Request(url,data = sendData,headers = header)
opener= urllib2.urlopen(request)
print(opener.read())
我们这里结合 Python 的高级脚本来调用 Shell 脚本来给服务器的服务做好监控服务,并输出信息。
监控程序正常与否(服务状态、使用状态、连接状态、安全……)
例子: 现在我们编写一个监控 Java 程序的脚本,并且让它能自启动。
思路:
监控程序(状态)>>> 程序正常 >>> 结束 ;
↓
程序异常(自启动/修复)>>> 程序正常 >>> 结束;
↓
程序异常发送告警(需要手动处理);
告警我们使用 Python 来回调 Webhook 的参数发送到钉钉的群消息当中。
监控脚本 1:
Apache 监控脚本:
vim check_apache.sh
#!/bin/bash
app=httpd
ps aux|grep -v grep | grep httpd > /opt/jar/sys.log && grep "$app" /opt/jar/sys.l
if [ $? -eq 0 ];then
echo "[ `date -d today +"%Y-%m-%d %H:%M:%S"` ]- [app $app is runing!]" >> /opt/jar/app.log
else
echo "[ `date -d today +"%Y-%m-%d %H:%M:%S"` ]- [app $app is error!]" >> /opt/jar/app.log
python /jenkins/python/sys1.py
systemctl start httpd &
`sleep 5`
if [ $? -eq 0 ];then
python /jenkins/python/sys.py
echo "[ `date -d today +"%Y-%m-%d %H:%M:%S"` ]- [app $app is runing!]" >> /opt/jar/app.log
else
echo "[ `date -d today +"%Y-%m-%d %H:%M:%S"` ]- [app $app is error!]" >> /opt/jar/app.log
fi
将写好的脚本放到我们的计划任务当中,让它每分钟去执行这监控脚本;
计划任务:
crontab –e
* * * * * /bin/sh /jenkins/app.sh &>/dev/null
杀掉程序后,将会自动重启程序,并发送重启状态告警通知!
或者因为其他的原因【程序故障(攻击、bug、模块编译失败、)、服务器、数据库、接口异常、网络问题…… 】发送的告警通知!
结合 webhook/shell 脚本,基于第五章的 Python 脚本优化,去调用 shell 监控脚本,并发送 shell 程序的返回信息到钉钉告警。
脚本如下:
编写好 shell 脚本后对接到 Python 脚本:
vim check.sh
#!/bin/bash
#检测服务器所有服务状态,使用函数来涵盖
serverOne(){
echo "检测中..."
#mysql
netstat -ntlp | grep "3306" &> /dev/null
if [ $? -eq 0 ];then
echo "Mysql 服务正常"
else
echo "Mysql 服务异常"
fi
#fastdfs
ps aux| grep -v grep |grep "/usr/local/bin/fdfs_trackerd /etc/fdfs/tracker.conf" &>/dev/null && ps aux| grep -v grep | grep "/usr/bin/fdfs_storaged /etc/fdfs/storage.conf start" &>/dev/null
if [ $? -eq 0 ];then
echo "FastDFS 服务正常"
else
echo "FastDFS 服务异常"
fi
#Redis
ps aux | grep -v grep | grep "0.0.0.0:65379" &>/dev/null
if [ $? -eq 0 ];then
echo "Redis 服务正常"
else
echo "Redis 服务异常"
fi
vim check.py
#coding:utf-8
import json
import urllib2
import subprocess
p = subprocess.Popen("/opt/check.sh",shell=True,stdout=subprocess.PIPE)
str = p.stdout.read()
url = "webhook地址"
header = {
"Content-Type":"application/json",
"Charset":"utf-8"
}
data = {
"msgtype":"text",
"text":{
"content":"%s"%(str)
},
"at":{
"atMobiles":[]
#"isAtAll":True
}
}
sendData = json.dumps(data)
request = urllib2.Request(url,data = sendData,headers = header)
opener= urllib2.urlopen(request)
print(opener.read())
# crontab –e
#####* * * * * /bin/sh /root/check.sh &>/dev/null
结果为:
可以结合告警来监控所有的程序。
从认识 shell、认识 python、认识 webhook、编写 shell 脚本、编写 python 程序、使用 webhook 的这一系列的学习都需要一个的过程,且众所周知学习是循序渐进的,如果有那个章节不清楚的,请大家加群一起来讨论发问。但是对于本章就算没有扎实的基础也可以写出一套强大的监控系统。
我曾经会非常急促的去学过很多东西,去追逐过很人,到头来,结果都显而易见,完成度非常低,效率非常低,回应都不是肯定的。
如果你喜欢 IT 技术,或者你需要它来支撑某些东西,那么请继续前进,或许唯有这样我们才能走的更远。送给正努力进步的我们!
常见服务监控脚本
在编写脚本之前,我们首先要知道的就是脚本存在的意义,为什么要编写脚本,否则那将毫无意义!
监控脚本编写思路:
就算不知道程序本身代码实现的所有的代码表达的意思,但是需要知道的就是代码实现的思路,这样的对整个监控的理解将会有不一样的感觉。
不管是 Python 脚本还是 Shell 脚本都是可以实现我们的功能,但是本章节使用的是 Shell+Python 的形式去实现的。
那么问题来了,为什么不用一个语言去实现呢, 而是用两个结合起来使用,对于 Linux 系统而言,Shell 本身的还是非常优秀的,是 Python 无可替代的。
但是 Python 的语法简洁,清晰,简单的编写思路让我们能更好的去做好想要的功能、也刚好用来调用 Webhook 地址来发送信息;两者结合是很不错的搭档。
当具备了思路,如何去编写和实现就是一个很有趣的问题了,你可以用你熟悉的任何语言来编写你的脚本(本文推荐用shell+python)最佳。
注意填写 Webhook 的地址,与脚本回调的逻辑。
就算不知道如何编写或者调用,都是没有关系,在文中的脚本是现成的直接复制过去使用即可。