通过Serverless架构实现监控告警功能
在实际生产中,经常需要编写一些监控脚本来监控网站服务或API服务的健康状况,包括是否可用、响应速度是否足够快等。传统的方法是直接使用网站监控平台(如DNSPod监控、360网站服务监控、阿里云监控等)提供的监控告警服务。这些监控平台的运行机制是先让用户自己设置要监控的网址和预期的时间阈值,再由监控平台部署在各地区的服务器定期发起请求对网站或服务的可用性进行判断。这些服务很多是大众化的,通用性强,但不能满足一些定制化需求。例如,现在要监控某网站的状态码和在不同区域访问该网站的延时,并设置一个延时阈值,当网站状态异常或者延时过大时,通过邮件等进行告警。对于这样一个定制化需求,目前大部分监控平台其实很难直接实现,所以定制开发一个网站状态监控工具就显得尤为必要。
Serverless架构有一个很重要的应用场景是运维、监控与告警,本章就来通过阿里云函数计算、腾讯云云函数等FaaS平台部署相关的监控脚本,对目标网站或者目标服务进行监控告警。
6.1.1 Web服务监控告警
1. 简单版监控告警
针对Web服务,先设计一个简单版监控告警功能的流程,如图6-1所示。
图6-1 简单版监控告警流程
在该流程中,仅对网站的状态码进行监控,若返回的状态码是200,则判定网站可正常访问,否则进行告警。简单的代码实现如下:
# -*- coding: utf8 -*- import ssl import smtplib import urllib.request from email.mime.text import MIMEText from email.header import Header #关闭SSL验证 ssl._create_default_https_context = ssl._create_unverified_context def sendEmail(content, to_user): ''' 发送邮件 :param content:邮件内容 :param to_user:接收人 :return: ''' sender = '[email protected]' #发送人邮箱地址 receivers = [to_user] #接收人邮箱地址 #邮件内容 mail_msg = content message = MIMEText(mail_msg, 'html', 'utf-8') message['From'] = Header("网站监控", 'utf-8') message['To'] = Header("站长", 'utf-8') subject = "网站监控告警" message['Subject'] = Header(subject, 'utf-8') try: smtpObj = smtplib.SMTP_SSL("smtp.exmail.qq.com", 465) smtpObj.login('发送人邮箱地址', '密码') smtpObj.sendmail(sender, receivers, message.as_string()) except Exception as e: print(e) def getStatusCode(url): ''' 获取网站的状态码 :param url:网址 :return:状态码 ''' return urllib.request.urlopen(url).getcode() def handler(event, context): #待监控的地址 url = "http://www.anycodes.cn" #如果状态码不是200,则进行告警 if getStatusCode(url) == 200: print("您的网站%s可以访问!" % (url)) else: sendEmail("您的网站%s不可以访问!" % (url), "接收人邮箱地址") return None
在传统的云主机中,需要将代码上传到云主机,并设置定时任务。例如在Ubuntu Server操作系统下,需要使用Cron工具,并完成配置和启动等操作。但是在Serverless架构下,只需要将代码部署到函数计算中,并配合设置定时触发器即可,无须安装或配置额外的软件、工具。
以阿里云函数计算为例,可以创建函数部署项目,如图6-2所示。
图6-2 阿里云函数计算部署结果
完成后,还可以配置定时触发器,如图6-3所示。
图6-3 阿里云函数计算配置定时触发器
完成以上配置之后,即完成了一个简单版网站监控告警功能的开发。该简单监控脚本每5分钟会执行一次,当所监控的网站不可访问时,所配置的邮箱会收到告警,如图6-4所示。
图6-4 邮箱告警示例
2. 优化版监控告警
上面实现的是极其简单的状态码监控与告警功能。在实际生产中,可能存在一定的不确定性,例如网站的某些接口功能已经不能提供正常服务,但是该网站的被监控页面依旧可以返回200状态码;或者网站的响应速度已经非常缓慢,但是被监控页面依旧返回200状态码。所以,对网站或者服务的监控不能只看其能不能返回200状态码,还要看链接耗时、下载耗时以及在不同地区、通过不同运营商访问网站或者服务时的延时信息等。针对上述需求,对代码进行额外的更新与优化,具体如下:
1)通过在线网速测试网站,通过抓包获取不同地区、不同运营商的请求特征;
2)编写爬虫程序和在线网速测试模块;
3)将编写的程序和模块集成到前面的项目中。
这里以站长工具网站中的国内网站测速工具为例。可以通过网页查到相关信息,对部分数据进行整理和封装,形成一个定制化的监控告警工具,如图6-5所示。
图6-5 站长工具网站监测页面
通过网络爬虫相关技术对网页进行分析,获取请求特征(包括URL、Form data、及Headers等相关信息)。可以发现该监控网站在使用不同监测点对网站发出请求时,是通过Form data中的guid参数实现的。部分监测点的guid如下:
广东佛山电信f403cdf2-27f8-4ccd-8f22-6f5a28a01309 江苏宿迁多线74cb6a5c-b044-49d0-abee-bf42beb6ae05 江苏常州移动5074fb13-4ab9-4f0a-87d9-f8ae51cb81c5 浙江嘉兴联通ddfeba9f-a432-4b9a-b0a9-ef76e9499558
此时,可以编写基本的爬虫代码来对响应结果进行解析。
以“62a55a0e-387e-4d87-bf69-5e0c9dd6b983江苏宿迁[电信]”为例,可以通过如下代码进行简单的测试:
# -*- coding: utf8 -*- import urllib.request import urllib.parse #待测速网站地址 url = "*某测速网站地址*" #请求的form_data form_data = { 'guid': '62a55a0e-387e-4d87-bf69-5e0c9dd6b983', 'host': 'anycodes.cn', 'ishost': '1', 'encode': 'ECvBP9vjbuXRi0CVhnXAbufDNPDryYzO', 'checktype': '1', } #请求头 headers = { 'Host': 'tool.chinaz.com', 'Origin': '*某测速网站地址*', 'Referer': '*某测速网站地址*', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36', 'X-Requested-With': 'XMLHttpRequest' } requestData = urllib.parse.urlencode(form_data).encode('utf-8') requestAttr = urllib.request.Request(url=url, data=requestData, headers=headers) responseAttr = urllib.request.urlopen(requestAttr) print(responseAttr.read().decode("utf-8"))
获得的结果如下:
({ state: 1, msg: '', result: { ip: '119.28.190.46', httpstate: 200, alltime: '212', dnstime: '18', conntime: '116', downtime: '78', filesize: '-', downspeed: '4.72', ipaddress: '新加坡新加坡', headers: 'HTTP/1.1 200 OK br>Server: ...', pagehtml: '' } })
在这个结果中,可以提取部分数据,例如“江苏宿迁[电信]”访问目标网站的基础数据如下:
总耗时:alltime:'212' 链接耗时:conntime:'116' 下载耗时:downtime:'78'
此时,可以改造代码对更多节点进行测试:
江苏宿迁[电信]总耗时:223 链接耗时:121 下载耗时:81 广东佛山[电信]总耗时:44 链接耗时:27 下载耗时:17 广东惠州[电信]总耗时:56 链接耗时:34 下载耗时:22 广东深圳[电信]总耗时:149 链接耗时:36 下载耗时:25 浙江湖州[电信]总耗时:3190 链接耗时:3115 下载耗时:75 辽宁大连[电信]总耗时:468 链接耗时:255 下载耗时:170 江苏泰州[电信]总耗时:180 链接耗时:104 下载耗时:69 安徽合肥[电信]总耗时:196 链接耗时:110 下载耗时:73 ……
测试完成后,可以获得各个节点的基本信息,进而对之前的监控告警功能进行升级:
# -*- coding: utf8 -*- import ssl import re import socket import smtplib import urllib.request from email.mime.text import MIMEText from email.header import Header #设置整体的请求超时时间 socket.setdefaulttimeout(2.5) #关闭SSL验证 ssl._create_default_https_context = ssl._create_unverified_context def getWebTime(): ''' 获取网站的延时信息 :return:每个节点的延时 ''' service_content = [] service_status = True node = [ ('62a55a0e-387e-4d87-bf69-5e0c9dd6b983', '江苏宿迁[电信]'), ('f403cdf2-27f8-4ccd-8f22-6f5a28a01309', '广东佛山[电信]'), ('5bea1430-f7c2-4146-88f4-17a7dc73a953', '河南新乡[多线]'), ('1f430ff0-eae9-413a-af2a-1c2a8986cff0', '河南新乡[多线]'), ('ea551b59-2609-4ab4-89bc-14b2080f501a', '河南新乡[多线]'), ('2805fa9f-05ea-46bc-8ac0-1769b782bf52', '黑龙江哈尔滨[联通]'), ('722e28ca-dd02-4ccd-a134-f9d4218505a5', '广东深圳[移动]'), ('8e7a403c-d998-4efa-b3d1-b67c0dfabc41', '广东深圳[移动]'), ] url = "*某测速网站地址*" for eve_node in node.split('\n'): node_name = eve_node[1] form_data = { 'guid': eve_node[0], 'host': 'anycodes.cn', 'ishost': '1', 'encode': 'ECvBP9vjbuXRi0CVhnXAbufDNPDryYzO', 'checktype': '1', } headers = { 'Host': '*某测速网站地址*', 'Origin': '*某测速网站地址*', 'Referer': '*某测速网站地址*', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) Apple WebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36', 'X-Requested-With': 'XMLHttpRequest' } try: service_info = urllib.request.urlopen( urllib.request.Request( url=url, data=urllib.parse.urlencode(form_data).encode('utf-8'), headers=headers ) ).read().decode("utf-8") try: alltime = re.findall("alltime:'(.*?)'", service_info)[0] conntime = re.findall("conntime:'(.*?)'", service_info)[0] downtime = re.findall("downtime:'(.*?)'", service_info)[0] temp_service_content = "%s\t总耗时:%s\t链接耗时:%s\t下载耗时:%s" % (node_name, alltime, conntime, downtime) except: temp_service_content = "%s链接异常!" % (node_name) service_status = False except: temp_service_content = "%s链接超时!" % (node_name) service_status = False service_content.append(temp_service_content) return (service_status, service_content) def sendEmail(content, to_user): ''' 发送邮件 :param content:邮件内容 :param to_user:发送人 :return: ''' sender = '[email protected]' #发送人邮箱地址 receivers = [to_user] #接收人邮箱地址 #邮件内容 mail_msg = content message = MIMEText(mail_msg, 'html', 'utf-8') message['From'] = Header("网站监控", 'utf-8') message['To'] = Header("站长", 'utf-8') subject = "网站监控告警" message['Subject'] = Header(subject, 'utf-8') try: smtpObj = smtplib.SMTP_SSL("smtp.exmail.qq.com", 465) smtpObj.login('发送人邮箱地址', '密码') smtpObj.sendmail(sender, receivers, message.as_string()) except Exception as e: print(e) def handler(event, context): #待监控的地址 url = "http://www.anycodes.cn" service_status, service_content = getWebTime() if not service_status: sendEmail("您的网站%s的状态:<br>%s" % (url, "<br>".join(service_content)), "[email protected]")
为了便于学习,上述代码对测试节点进行了缩减。通过部署,当网站出现超时情况时,可得到如图6-6所示的结果。
图6-6 邮箱告警示例
当然,在实际生产过程中,可以根据业务需求对告警的灵敏度和监控的频率进行调整。至此就完成了简单版网站监控告警功能的升级。
在实际生产中,还可以继续对上述的监控告警功能进行如下升级。
- 如果出现链接异常,可以重试几次,如果连续出现异常,再进行告警。
- 当请求出现问题时,可以根据成功率判断是否告警。例如在图6-6中,“河南新乡[多线]”有3个节点,其中两个节点的链接耗时是比较小的,而一个比较高,且高于平均值。此时我们可以认为该节点的请求存在异常,所以可以设定当某节点的异常率高于50%时进行告警。图6-6中的“河南新乡[多线]”处于正常范围内,不进行告警。
6.1.2 云服务监控告警
6.1.1节以阿里云函数计算为例,对网站状态及健康状况等信息进行了监控告警,在实际的生产运维中,还非常有必要对云服务进行监控告警。例如,在使用Hadoop、Spark的时候要对节点的健康状况进行监控,在使用Kubernetes的时候要对API Server、Etcd等多维度指标进行监控,在使用Kafka的时候要对数据积压量以及Topic、Consumer等指标进行监控。对这些服务的监控往往不能简单地通过URL及状态码来进行判断。在传统的运维中,通常会在其他机器上设置一个定时任务,对相关服务进行旁路监控。本节将会基于Serverless技术,以腾讯云CKafka产品为例建设相关的监控告警功能。
在使用云上的Kafka时,通常要看数据积压量,因为如果Consumer集群出现故障,或者消费能力突然降低导致数据积压,很可能会对服务产生不可预估的影响。下面将会以腾讯云云函数及腾讯云CKafka为例,通过组合多个云产品(包括云监控、云API及云短信等)来实现短信告警、邮件告警及企业微信告警功能。
首先,设计一个简单的服务监控告警流程图,如图6-7所示。
图6-7 服务监控告警流程示意图
在开始项目之前,要准备一些基础模块,具体如下。
1)Kafka数据积压量获取模块:
def GetSignature(param): #公共参数 param["SecretId"] = "" param["Timestamp"] = int(time.time()) param["Nonce"] = random.randint(1, sys.maxsize) param["Region"] = "ap-guangzhou" # param["SignatureMethod"] = "HmacSHA256" #生成待签名字符串 sign_str = "GETckafka.api.qcloud.com/v2/index.php?" sign_str += "&".join("%s=%s" % (k, param[k]) for k in sorted(param)) #生成签名 secret_key = "" if sys.version_info[0] > 2: sign_str = bytes(sign_str, "utf-8") secret_key = bytes(secret_key, "utf-8") hashed = hmac.new(secret_key, sign_str, hashlib.sha1) signature = binascii.b2a_base64(hashed.digest())[:-1] if sys.version_info[0] > 2: signature = signature.decode() signature = urllib.parse.quote(signature) return signature def GetGroupOffsets(max_lag, phoneList): param = {} param["Action"] = "GetGroupOffsets" param["instanceId"] = "" param["group"] = "" signature = GetSignature(param) param["Signature"] = signature url = "https://ckafka.api.qcloud.com/v2/index.php?Action=GetGroupOffsets&" url += "&".join("%s=%s" % (k, param[k]) for k in sorted(param)) req_attr = urllib.request.urlopen(url) res_data = req_attr.read().decode("utf-8") json_data = json.loads(res_data) for eve_topic in json_data['data']['topicList']: temp_lag = 0 result_list = [] for eve_partition in eve_topic["partitions"]: lag = eve_partition["lag"] temp_lag = temp_lag + lag if temp_lag > max_lag: result_list.append( { "topic": eve_topic["topic"], "lag": lag } ) print(result_list) if len(result_list) > 0: KafkaLagRobot(result_list) KafkaLagSMS(result_list, phoneList)
2)接入企业微信机器人模块:
def KafkaLagRobot(content): url = "" data = { "msgtype": "markdown", "markdown": { "content": content, } } data = json.dumps(data).encode("utf-8") req_attr = urllib.request.Request(url, data) resp_attr = urllib.request.urlopen(req_attr) resp_attr.read().decode("utf-8")
3)接入腾讯云短信服务模块:
def KafkaLagSMS(infor, phone_list): url = "" strMobile = phone_list strAppKey = "" strRand = str(random.randint(1, sys.maxsize)) strTime = int(time.time()) strSign = "appkey=%s&random=%s&time=%s&mobile=%s" % (strAppKey, strRand, strTime, ",".join(strMobile)) sig = hashlib.sha256() sig.update(strSign.encode("utf-8")) data = { "ext": "", "extend": "", "params": [ infor, ], "sig": sig.hexdigest(), "sign": "你的sign", "tel": phone_list, "time": strTime, "tpl_id": "你的模板id" } data = json.dumps(data).encode("utf-8") req_attr = urllib.request.Request(url=url, data=data) resp_attr = urllib.request.urlopen(req_attr) resp_attr.read().decode("utf-8")
4)发送邮件告警模块:
def sendEmail(content, to_user): sender = '[email protected]' message = MIMEText(content, 'html', 'utf-8') message['From'] = Header("监控", 'utf-8') message['To'] = Header("站长", 'utf-8') message['Subject'] = Header("告警", 'utf-8') try: smtpObj = smtplib.SMTP_SSL("smtp.exmail.qq.com", 465) smtpObj.login('[email protected]', '密码') smtpObj.sendmail(sender, [to_user], message.as_string()) except smtplib.SMTPException as e: logging.debug(e)
完成模块编写后,即可在入口方法中根据整体业务编排模块,并将其投入生产。
6.1.3 总结
通过上述场景实践,读者可以了解到Serverless相关产品在运维领域的基本应用,尤其是监控告警的基本使用方法和初步设计灵感。设计网站监控程序实际上是一个很初级的入门场景。希望大家可以将更多的监控告警功能与Serverless技术结合,例如监控自己的MySQL压力情况、监控自己已有服务器的数据指标等。通过对这些指标的监控告警,不仅可以让管理者及时发现服务的潜在风险,也可以通过一些自动化流程实现项目的自动化运维。
通过上述场景实践,还可以对项目进行额外优化并将其应用在不同的领域或场景中。例如,可以增加短信告警、微信告警、企业微信告警等多个维度(后续的应用场景会举例说明),以确保相关人员及时收到告警信息;也可以通过监控某个小说网站、视频网站等了解自己关注的小说或者视频的更新情况,以便于追更等。
其实,无论采用哪个云厂商的FaaS平台,其整体效果都是一致的。限于篇幅,本文只分别以阿里云函数计算和腾讯云云函数为例进行讲解,希望读者可以发挥自己的想象,在更多的FaaS平台上进行学习和实践。
标签:Serverless,utf,service,运维,网站,耗时,监控,告警 From: https://www.cnblogs.com/muzinan110/p/17067635.html