如何通過(guò)Serverless架構(gòu)實(shí)現(xiàn)監(jiān)控告警,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
我們提供的服務(wù)有:網(wǎng)站設(shè)計(jì)制作、網(wǎng)站制作、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、寶塔ssl等。為上千家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的寶塔網(wǎng)站制作公司
在實(shí)際生產(chǎn)中,我們經(jīng)常需要做一些監(jiān)控腳本來(lái)監(jiān)控網(wǎng)站服務(wù)或者 API 服務(wù)是否可用。傳統(tǒng)的方法是使用網(wǎng)站監(jiān)控平臺(tái)(例如 DNSPod 監(jiān)控、360 網(wǎng)站服務(wù)監(jiān)控,以及阿里云監(jiān)控等),它們的原理是通過(guò)用戶自己設(shè)置要監(jiān)控的服務(wù)地址和監(jiān)測(cè)的時(shí)間閾值,由監(jiān)控平臺(tái)定期發(fā)起請(qǐng)求對(duì)網(wǎng)站或服務(wù)的可用性進(jìn)行判斷。
這些方法很大眾化,通用性很強(qiáng),但也不是所有場(chǎng)景都適合。例如,如果我們的需求是監(jiān)控網(wǎng)站狀態(tài)碼,不同區(qū)域的延時(shí),并且通過(guò)監(jiān)控得到的數(shù)據(jù),設(shè)定一個(gè)閾值,一旦超過(guò)閾值就通過(guò)郵件等進(jìn)行統(tǒng)治告警,目前大部分的監(jiān)控平臺(tái)是很難滿足這些需求的,這時(shí)就需要定制開(kāi)發(fā)一個(gè)監(jiān)控工具。
Serverless 服務(wù)的一個(gè)重要應(yīng)用場(chǎng)景就是運(yùn)維、監(jiān)控與告警,所以本文將會(huì)通過(guò)現(xiàn)有的 Serverless 平臺(tái),部署一個(gè)網(wǎng)站狀態(tài)監(jiān)控腳本,對(duì)目標(biāo)網(wǎng)站的可用性進(jìn)行監(jiān)控告警。
針對(duì) Web 服務(wù),我們先設(shè)計(jì)一個(gè)簡(jiǎn)單的監(jiān)控告警功能的流程:
在這個(gè)流程中,我們僅對(duì)網(wǎng)站的狀態(tài)碼進(jìn)行監(jiān)控,即返回的狀態(tài)為 200,則判定網(wǎng)站可正常使用,否則進(jìn)行告警:
# -*- coding: utf8 -*- import ssl import json import smtplib import urllib.request from email.mime.text import MIMEText from email.header import Header ssl._create_default_https_context = ssl._create_unverified_context def sendEmail(content, to_user): sender = 'service@anycodes.cn' receivers = [to_user] mail_msg = content message = MIMEText(mail_msg, 'html', 'utf-8') message['From'] = Header(" 網(wǎng)站監(jiān)控 ", 'utf-8') message['To'] = Header(" 站長(zhǎng) ", 'utf-8') subject = " 網(wǎng)站監(jiān)控告警 " message['Subject'] = Header(subject, 'utf-8') try: smtpObj = smtplib.SMTP_SSL("smtp.exmail.qq.com", 465) smtpObj.login('發(fā)送郵件的郵箱地址', '密碼') smtpObj.sendmail(sender, receivers, message.as_string()) except smtplib.SMTPException as e: print(e) def getStatusCode(url): return urllib.request.urlopen(url).getcode() def main_handler(event, context): url = "http://www.anycodes.cn" if getStatusCode(url) == 200: print(" 您的網(wǎng)站 %s 可以訪問(wèn)!" % (url)) else: sendEmail(" 您的網(wǎng)站 %s 不可以訪問(wèn)!" % (url), " 接受人郵箱地址 ") return None
通過(guò) ServerlessFramework 可以部署,在部署的時(shí)候可以增加時(shí)間觸發(fā)器:
MyWebMonitor: component: "@serverless/tencent-scf" inputs: name: MyWebMonitor codeUri: ./code handler: index.main_handler runtime: Python3.6 region: ap-guangzhou description: 網(wǎng)站監(jiān)控 memorySize: 64 timeout: 20 events: - timer: name: timer parameters: cronExpression: '*/5 * * * *' enable: true
在這里,timer 表示時(shí)間觸發(fā)器,cronExpression
是表達(dá)式:
創(chuàng)建定時(shí)觸發(fā)器時(shí),用戶能夠使用標(biāo)準(zhǔn)的 Cron 表達(dá)式的形式自定義何時(shí)觸發(fā)。定時(shí)觸發(fā)器現(xiàn)已推出秒級(jí)觸發(fā)功能,為了兼容老的定時(shí)觸發(fā)器,因此 Cron 表達(dá)式有兩種寫(xiě)法。
Cron 表達(dá)式有七個(gè)必需字段,按空格分隔。 其中,每個(gè)字段都有相應(yīng)的取值范圍:
Cron 表達(dá)式有五個(gè)必需字段,按空格分隔。 其中,每個(gè)字段都有相應(yīng)的取值范圍:
在 Cron 表達(dá)式中的“日”和“星期”字段同時(shí)指定值時(shí),兩者為“或”關(guān)系,即兩者的條件分別均生效。
*/5 * * * * * *
表示每 5 秒觸發(fā)一次 0 0 2 1 * * *
表示在每月的 1 日的凌晨 2 點(diǎn)觸發(fā) 0 15 10 * * MON-FRI *
表示在周一到周五每天上午 10:15 觸發(fā) 0 0 10,14,16 * * * *
表示在每天上午 10 點(diǎn),下午 2 點(diǎn),4 點(diǎn)觸發(fā) 0 */30 9-17 * * * *
表示在每天上午 9 點(diǎn)到下午 5 點(diǎn)內(nèi)每半小時(shí)觸發(fā) 0 0 12 * * WED *
表示在每個(gè)星期三中午 12 點(diǎn)觸發(fā)
因此,我們上面的代碼可以認(rèn)為是每 5 秒觸發(fā)一次,當(dāng)然,也可以根據(jù)網(wǎng)站監(jiān)控密度,自定義設(shè)置觸發(fā)的間隔時(shí)間。當(dāng)我們網(wǎng)站服務(wù)不可用時(shí),就可以收到告警:
這種網(wǎng)站監(jiān)控方法比較簡(jiǎn)單,準(zhǔn)確度可能會(huì)有問(wèn)題,對(duì)于網(wǎng)站或服務(wù)的監(jiān)控不能簡(jiǎn)單的看返回值,還要看鏈接耗時(shí)、下載耗時(shí)以及不同區(qū)域、不同運(yùn)營(yíng)商訪問(wèn)網(wǎng)站或者服務(wù)的延時(shí)信息等。
所以,我們需要對(duì)這個(gè)代碼進(jìn)行額外的更新與優(yōu)化:
通過(guò)在線網(wǎng)速測(cè)試的網(wǎng)站,抓包獲取不同地區(qū)不同運(yùn)營(yíng)商的請(qǐng)求特征;
編寫(xiě)爬蟲(chóng)程序,進(jìn)行在線網(wǎng)速測(cè)試模塊的編寫(xiě);
集成到剛剛的項(xiàng)目中;
下面以站長(zhǎng)工具網(wǎng)站中國(guó)內(nèi)網(wǎng)站測(cè)速工具 為例,通過(guò)網(wǎng)頁(yè)查閱相關(guān)信息。
對(duì)網(wǎng)站測(cè)速工具進(jìn)行封裝,例如:
通過(guò)對(duì)網(wǎng)頁(yè)進(jìn)行分析,獲取請(qǐng)求特征,包括 Url,F(xiàn)orm data,以及 Headers 等相關(guān)信息,其中該網(wǎng)站在使用不同監(jiān)測(cè)點(diǎn)對(duì)網(wǎng)站進(jìn)行請(qǐng)求時(shí),是通過(guò) Form data 中的 guid 的參數(shù)實(shí)現(xiàn)的,例如部分監(jiān)測(cè)點(diǎn)的 guid:
廣東佛山 電信 f403cdf2-27f8-4ccd-8f22-6f5a28a01309 江蘇宿遷 多線 74cb6a5c-b044-49d0-abee-bf42beb6ae05 江蘇常州 移動(dòng) 5074fb13-4ab9-4f0a-87d9-f8ae51cb81c5 浙江嘉興 聯(lián)通 ddfeba9f-a432-4b9a-b0a9-ef76e9499558
此時(shí),我們可以編寫(xiě)基本的爬蟲(chóng)代碼,來(lái)對(duì) Response 進(jìn)行初步解析,以62a55a0e-387e-4d87-bf69-5e0c9dd6b983 江蘇宿遷 [電信]
為例,編寫(xiě)代碼:
import urllib.request import urllib.parse url = "* 某測(cè)速網(wǎng)站地址 *" form_data = { 'guid': '62a55a0e-387e-4d87-bf69-5e0c9dd6b983', 'host': 'anycodes.cn', 'ishost': '1', 'encode': 'ECvBP9vjbuXRi0CVhnXAbufDNPDryYzO', 'checktype': '1', } headers = { 'Host': 'tool.chinaz.com', 'Origin': '* 某測(cè)速網(wǎng)站地址 *', 'Referer': '* 某測(cè)速網(wǎng)站地址 *', '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' } print(urllib.request.urlopen( urllib.request.Request( url=url, data=urllib.parse.urlencode(form_data).encode('utf-8'), headers=headers ) ).read().decode("utf-8"))
獲得結(jié)果:
({ 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: '' } })
在這個(gè)結(jié)果中,我們可以提取部分?jǐn)?shù)據(jù),例如江蘇宿遷 [電信] 訪問(wèn)目標(biāo)網(wǎng)站的基礎(chǔ)數(shù)據(jù):
總耗時(shí):alltime:'212' 鏈接耗時(shí):conntime:'116' 下載耗時(shí):downtime:'78'
此時(shí),我們可以改造代碼對(duì)更多的節(jié)點(diǎn),進(jìn)行測(cè)試:
江蘇宿遷 [電信] 總耗時(shí):223 鏈接耗時(shí):121 下載耗時(shí):81 廣東佛山 [電信] 總耗時(shí):44 鏈接耗時(shí):27 下載耗時(shí):17 廣東惠州 [電信] 總耗時(shí):56 鏈接耗時(shí):34 下載耗時(shí):22 廣東深圳 [電信] 總耗時(shí):149 鏈接耗時(shí):36 下載耗時(shí):25 浙江湖州 [電信] 總耗時(shí):3190 鏈接耗時(shí):3115 下載耗時(shí):75 遼寧大連 [電信] 總耗時(shí):468 鏈接耗時(shí):255 下載耗時(shí):170 江蘇泰州 [電信] 總耗時(shí):180 鏈接耗時(shí):104 下載耗時(shí):69 安徽合肥 [電信] 總耗時(shí):196 鏈接耗時(shí):110 下載耗時(shí):73 ...
并對(duì)項(xiàng)目中的 index.py 進(jìn)行代碼修改:
# -*- coding: utf8 -*- import ssl import json 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._create_default_https_context = ssl._create_unverified_context def getWebTime(): final_list = [] final_status = True total_list = '''62a55a0e-387e-4d87-bf69-5e0c9dd6b983 江蘇宿遷 [電信] f403cdf2-27f8-4ccd-8f22-6f5a28a01309 廣東佛山 [電信] 5bea1430-f7c2-4146-88f4-17a7dc73a953 河南新鄉(xiāng) [多線] 1f430ff0-eae9-413a-af2a-1c2a8986cff0 河南新鄉(xiāng) [多線] ea551b59-2609-4ab4-89bc-14b2080f501a 河南新鄉(xiāng) [多線] 2805fa9f-05ea-46bc-8ac0-1769b782bf52 黑龍江哈爾濱 [聯(lián)通] 722e28ca-dd02-4ccd-a134-f9d4218505a5 廣東深圳 [移動(dòng)] 8e7a403c-d998-4efa-b3d1-b67c0dfabc41 廣東深圳 [移動(dòng)]''' url = "* 某測(cè)速網(wǎng)站地址 *" for eve in total_list.split('\n'): id_data, node_name = eve.strip().split(" ") form_data = { 'guid': id_data, 'host': 'anycodes.cn', 'ishost': '1', 'encode': 'ECvBP9vjbuXRi0CVhnXAbufDNPDryYzO', 'checktype': '1', } headers = { 'Host': '* 某測(cè)速網(wǎng)站地址 *', 'Origin': '* 某測(cè)速網(wǎng)站地址 *', 'Referer': '* 某測(cè)速網(wǎng)站地址 *', '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' } try: result_data = 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:'(.*?)'", result_data)[0] conntime = re.findall("conntime:'(.*?)'", result_data)[0] downtime = re.findall("downtime:'(.*?)'", result_data)[0] final_string = "%s\t 總耗時(shí):%s\t 鏈接耗時(shí):%s\t 下載耗時(shí):%s" % (node_name, alltime, conntime, downtime) except: final_string = "%s 鏈接異常!" % (node_name) final_status = False except: final_string = "%s 鏈接超時(shí)!" % (node_name) final_status = False final_list.append(final_string) print(final_string) return (final_status,final_list) def sendEmail(content, to_user): sender = 'service@anycodes.cn' receivers = [to_user] mail_msg = content message = MIMEText(mail_msg, 'html', 'utf-8') message['From'] = Header(" 網(wǎng)站監(jiān)控 ", 'utf-8') message['To'] = Header(" 站長(zhǎng) ", 'utf-8') subject = " 網(wǎng)站監(jiān)控告警 " message['Subject'] = Header(subject, 'utf-8') try: smtpObj = smtplib.SMTP_SSL("smtp.exmail.qq.com", 465) smtpObj.login('service@anycodes.cn', '密碼') smtpObj.sendmail(sender, receivers, message.as_string()) except smtplib.SMTPException: pass def getStatusCode(url): return urllib.request.urlopen(url).getcode() def main_handler(event, context): url = "http://www.anycodes.cn" final_status,final_list = getWebTime() if not final_status: sendEmail(" 您的網(wǎng)站 %s 的狀態(tài):
%s" % (url, "
".join(final_list)), "service@52exe.cn")
由于本文是以學(xué)習(xí)為主,所以我們將節(jié)點(diǎn)列表進(jìn)行縮減,只保留幾個(gè)。通過(guò)部署,可得到結(jié)果:
告警的靈敏度和監(jiān)控的頻率,在實(shí)際生產(chǎn)過(guò)程中可以根據(jù)自己的需求進(jìn)行調(diào)整。
前文,我們對(duì)網(wǎng)站狀態(tài)以及健康等信息進(jìn)行了監(jiān)控與告警,在實(shí)際的生產(chǎn)運(yùn)維中,還需要對(duì)服務(wù)進(jìn)行監(jiān)控,例如在使用 Hadoop、Spark 的時(shí)候?qū)?jié)點(diǎn)的健康進(jìn)行監(jiān)控,在使用 K8S 的時(shí)候?qū)?API 網(wǎng)關(guān)、ETCD 等多維度的指標(biāo)進(jìn)行監(jiān)控,在使用 Kafka 的時(shí)候,對(duì)數(shù)據(jù)積壓量,以及 Topic、Consumer 等進(jìn)行監(jiān)控…
而這些服務(wù)的監(jiān)控,往往不能通過(guò)簡(jiǎn)單的 URL 以及某些狀態(tài)來(lái)進(jìn)行判斷。傳統(tǒng)運(yùn)維的做法是在額外的機(jī)器上設(shè)置一個(gè)定時(shí)任務(wù),對(duì)相關(guān)的服務(wù)進(jìn)行旁路監(jiān)控。而在本文中,我們則通過(guò) Serverless 技術(shù),對(duì)云產(chǎn)品進(jìn)行相關(guān)的監(jiān)控與告警。
在使用云上的 Kafka 時(shí),我們通常要看數(shù)據(jù)積壓量,因?yàn)槿绻?Consumer 集群掛掉了,或者消費(fèi)能力突然降低導(dǎo)致數(shù)據(jù)積壓,很可能會(huì)對(duì)服務(wù)產(chǎn)生不可預(yù)估的影響,這個(gè)時(shí)候?qū)?Kafka 的數(shù)據(jù)積壓量進(jìn)行監(jiān)控告警,就顯得額外重要。
本文以監(jiān)控騰訊云的 Ckafka 為例進(jìn)行實(shí)踐,并通過(guò)多個(gè)云產(chǎn)品進(jìn)行組合(包括云監(jiān)控、Ckafka、云 API 以及云短信等)來(lái)實(shí)現(xiàn)短信告警、郵件告警以及企業(yè)微信告警功能。
首先,可以設(shè)計(jì)簡(jiǎn)單的流程圖:
在開(kāi)始項(xiàng)目之前,我們要準(zhǔn)備一些基礎(chǔ)的模塊:
Kafka 數(shù)據(jù)積壓量獲取模塊:
def GetSignature(param): # 公共參數(shù) 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) # 生成請(qǐng)求地址 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)
接入企業(yè)微信機(jī)器人模塊:
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) return_msg = resp_attr.read().decode("utf-8")
接入騰訊云短信服務(wù)模塊:
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")) phone_dict = [] for eve_phone in phone_list: phone_dict.append( { "mobile": eve_phone, "nationcode": "86" } ) data = { "ext": "", "extend": "", "params": [ infor, ], "sig": sig.hexdigest(), "sign": " 你的 sign", "tel": phone_dict, "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) return_msg = resp_attr.read().decode("utf-8")
發(fā)送郵件告警模塊:
def sendEmail(content, to_user): sender = 'service@anycodes.cn' message = MIMEText(content, 'html', 'utf-8') message['From'] = Header(" 監(jiān)控 ", 'utf-8') message['To'] = Header(" 站長(zhǎng) ", 'utf-8') message['Subject'] = Header(" 告警 ", 'utf-8') try: smtpObj = smtplib.SMTP_SSL("smtp.exmail.qq.com", 465) smtpObj.login('service@anycodes.cn', '密碼') smtpObj.sendmail(sender, [to_user], message.as_string()) except smtplib.SMTPException as e: logging.debug(e)
完成模塊編寫(xiě),和上面的方法一樣,進(jìn)行項(xiàng)目部署。部署成功之后進(jìn)行測(cè)試,測(cè)試可看到功能可用:
短信告警樣式:
企業(yè)微信告警樣式:
設(shè)計(jì)一個(gè)網(wǎng)站監(jiān)控程序?qū)嶋H上是一個(gè)很初級(jí)的入門(mén)場(chǎng)景,希望大家可以將更多的監(jiān)控告警功與 Serverless 技術(shù)進(jìn)行結(jié)合,例如監(jiān)控自己的 MySQL 壓力情況、監(jiān)控已有服務(wù)器的數(shù)據(jù)指標(biāo)等,通過(guò)對(duì)這些指標(biāo)的監(jiān)控告警,不僅僅可以讓管理者及時(shí)發(fā)現(xiàn)服務(wù)的潛在風(fēng)險(xiǎn),也可以通過(guò)一些自動(dòng)化流程實(shí)現(xiàn)項(xiàng)目的自動(dòng)化運(yùn)維。
關(guān)于如何通過(guò)Serverless架構(gòu)實(shí)現(xiàn)監(jiān)控告警問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。