轻松SRE-使用云监控实现自动化运维

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
云监控,每月短信1000条
简介: 本文将根据SRE中关于监控Action的定义,来讲述如何使用云监控来完成自动化运维的能力。

SRE中关于监控Action的定义

监控系统是 SRE 团队监控服务质量和可用性的一个主要手段。所以监控系统的设计和策略值得着重讨论。最普遍和传统的报警策略是针对某个特定的情况或者监控值,一旦出现情况或者监控值超过阈值就触发 E-mail 报警。但是这样的报警并不是非常有效:一个需要人工阅读邮件和分析报警来决定目前是否需要采取某种行动的系统从本质上是错误的。监控系统不应该依赖人来分析信息进行报警,而是应该由系统自动分析,仅仅当需要用户执行某种操作时,才需要通知用户。

监控不做任何事情是不可能的,有三种有效的监控输出:
警报

意味着收到警报的用户需要立即执行某种操作,目标是解决某种已经发生的问题,或者是避免即将要发生的问题。

工单

意味着接受工单的用户应该执行某种操作,但是并非立即执行。系统并不能自动解决目前的情况,但是如果一个用户在几天内执行这项操作,系统不会受到任何影响。

日志

平时没有人需要关注日志信息,但是日志信息依然被收集起来以备调试和事后分析时使用。正确的做法是平时没有人主动阅读日志,除非处理其他请求的时候被要求这么做。

如何使用云监控实现

默认报警

云监控报警服务提供第一个报警能力就是发送报警信息,为此我们作了多种报警配置

  • 支持多种渠道的通知方式
  • 多种报警抑制策略:通道沉默/报警条件次数/生效时间
  • 支持全部资源/应用分组/单个实例等多不同level的报警设置

具体使用方法参考

通过云监控控制台/OpenAPI/SDK三种方式,可以在云监控设置报警。

新的问题

从SRE的实践来看,设置报警规则不会是一锤定音的事情,需要长期维护,当前的某一个常见的自动化困难的警报,可能很快就会变成一个经常触发的问题,这时最好能一个临时的自动化处理的脚本来应对。
实际上SRE也是这么定义的

没有不需要采取行动的警报。如果您遇到一个自己认为不需要执行操作的警报,您需要采用自动化的手段来修复该警报。

那么问题来了,在云监控中,如何实现自动化修复/生成工单/记录日志这几个Action呢。
云监控提供了报警回调(webhook)的能力,可以用来打通云监控报警服务与你的业务系统,完成更多的运维管理可能,如何报警回调,下面进行详细说明,并且提供了回调服务的demo供你选择。

使用云监控控制台使用报警回调

设置报警回调

从以下入口创建报警规则时都可以设置报警回调:报警服务/主机监控/日志监控/云服务监控
image.png | center | 704x374

注意事项:

  • 仅支持http post,不支持的http服务会报错
  • 不要设置常见网站地址,会出现未知错误
  • 该http服务需要公网可访问

定义回调的HTTP服务

基本调用方式

云监控回调时会将报警相关参数以content-type:application/json的格式post到你的http服务,因此你的服务需要使用json格式来解析,回调传入的参数,请参考文档使用报警回调

JSON代码示例

#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

VERSION = '0.1'

import argparse
import BaseHTTPServer
import cgi
import logging
import os
import sys
import json


def make_request_handler_class(opts):
    '''
    Factory to make the request handler and add arguments to it.

    It exists to allow the handler to access the opts.path variable
    locally.
    '''
    class MyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
        '''
        Factory generated request handler class that contain
        additional class variables.
        '''
        m_opts = opts

        def do_POST(self):
            '''
            Handle POST requests.
            '''
            logging.debug('POST %s' % (self.path))

            ctype, pdict = cgi.parse_header(self.headers['content-type'])
            result = {}
            if ctype == 'application/json':
                length = int(self.headers['content-length'])
                result = json.loads(self.rfile.read(length))
                logging.info('post json: %s'% result)

            # Get the "Back" link.
            back = self.path if self.path.find('?') < 0 else self.path[:self.path.find('?')]

            # Print out logging information about the path and args.
            logging.debug('TYPE %s' % (ctype))
            logging.debug('PATH %s' % (self.path))
            logging.debug('ARGS %d' % (len(result)))
            if len(result):
                i = 0
                for key in sorted(result):
                    logging.debug('ARG[%d] %s=%s' % (i, key, result[key]))
                    i += 1

            # Tell the browser everything is okay and that there is
            # HTML to display.
            self.send_response(200)  # OK
            self.send_header('Content-type', ctype)
            self.end_headers()

            # Display the POST variables.
            self.wfile.write('{message:"called by cms success."}')
    return MyRequestHandler

def err(msg):
    '''
    Report an error message and exit.
    '''
    print('ERROR: %s' % (msg))
    sys.exit(1)


def getopts():
    '''
    Get the command line options.
    '''

    # Get the help from the module documentation.
    this = os.path.basename(sys.argv[0])
    description = ('description:%s' % '\n  '.join(__doc__.split('\n')))
    epilog = ' '
    rawd = argparse.RawDescriptionHelpFormatter
    parser = argparse.ArgumentParser(formatter_class=rawd,
                                     description=description,
                                     epilog=epilog)

    parser.add_argument('-d', '--daemonize',
                        action='store',
                        type=str,
                        default='.',
                        metavar='DIR',
                        help='daemonize this process, store the 3 run files (.log, .err, .pid) in DIR (default "%(default)s")')

    parser.add_argument('-H', '--host',
                        action='store',
                        type=str,
                        default='localhost',
                        help='hostname, default=%(default)s')

    parser.add_argument('-l', '--level',
                        action='store',
                        type=str,
                        default='info',
                        choices=['notset', 'debug', 'info', 'warning', 'error', 'critical',],
                        help='define the logging level, the default is %(default)s')

    parser.add_argument('--no-dirlist',
                        action='store_true',
                        help='disable directory listings')

    parser.add_argument('-p', '--port',
                        action='store',
                        type=int,
                        default=8080,
                        help='port, default=%(default)s')

    parser.add_argument('-r', '--rootdir',
                        action='store',
                        type=str,
                        default=os.path.abspath('.'),
                        help='web directory root that contains the HTML/CSS/JS files %(default)s')

    parser.add_argument('-v', '--verbose',
                        action='count',
                        help='level of verbosity')

    parser.add_argument('-V', '--version',
                        action='version',
                        version='%(prog)s - v' + VERSION)

    opts = parser.parse_args()
    opts.rootdir = os.path.abspath(opts.rootdir)
    if not os.path.isdir(opts.rootdir):
        err('Root directory does not exist: ' + opts.rootdir)
    if opts.port < 1 or opts.port > 65535:
        err('Port is out of range [1..65535]: %d' % (opts.port))
    return opts


def httpd(opts):
    '''
    HTTP server
    '''
    RequestHandlerClass = make_request_handler_class(opts)
    server = BaseHTTPServer.HTTPServer((opts.host, opts.port), RequestHandlerClass)
    logging.info('Server starting %s:%s (level=%s)' % (opts.host, opts.port, opts.level))
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    server.server_close()
    logging.info('Server stopping %s:%s' % (opts.host, opts.port))


def get_logging_level(opts):
    '''
    Get the logging levels specified on the command line.
    The level can only be set once.
    '''
    if opts.level == 'notset':
        return logging.NOTSET
    elif opts.level == 'debug':
        return logging.DEBUG
    elif opts.level == 'info':
        return logging.INFO
    elif opts.level == 'warning':
        return logging.WARNING
    elif opts.level == 'error':
        return logging.ERROR
    elif opts.level == 'critical':
        return logging.CRITICAL


def daemonize(opts):
    '''
    Daemonize this process.

    '''
    if os.path.exists(opts.daemonize) is False:
        err('directory does not exist: ' + opts.daemonize)

    if os.path.isdir(opts.daemonize) is False:
        err('not a directory: ' + opts.daemonize)

    bname = 'webserver-%s-%d' % (opts.host, opts.port)
    outfile = os.path.abspath(os.path.join(opts.daemonize, bname + '.log'))
    errfile = os.path.abspath(os.path.join(opts.daemonize, bname + '.err'))
    pidfile = os.path.abspath(os.path.join(opts.daemonize, bname + '.pid'))

    if os.path.exists(pidfile):
        err('pid file exists, cannot continue: ' + pidfile)
    if os.path.exists(outfile):
        os.unlink(outfile)
    if os.path.exists(errfile):
        os.unlink(errfile)

    if os.fork():
        sys.exit(0)  # exit the parent

    os.umask(0)
    os.setsid()
    if os.fork():
        sys.exit(0)  # exit the parent

    print('daemon pid %d' % (os.getpid()))

    sys.stdout.flush()
    sys.stderr.flush()

    stdin = file('/dev/null', 'r')
    stdout = file(outfile, 'a+')
    stderr = file(errfile, 'a+', 0)

    os.dup2(stdin.fileno(), sys.stdin.fileno())
    os.dup2(stdout.fileno(), sys.stdout.fileno())
    os.dup2(stderr.fileno(), sys.stderr.fileno())

    with open(pidfile, 'w') as ofp:
        ofp.write('%i' % (os.getpid()))


def main():
    ''' main entry '''
    opts = getopts()
    if opts.daemonize:
        daemonize(opts)
    logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', level=get_logging_level(opts))
    httpd(opts)


if __name__ == '__main__':
    main()  # this allows library functionality

HTTP服务回调校验

在使用云监控控制台和SDK创建带有报警回调的报警规则时,会触发一次回调校验,因此需要注意两点

  1. 在创建报警规则之前,被回调HTTP服务必须可正常使用
  2. 创建的这个报警规则时,云监控Mock了一组数据来校验HTTP服务是否可以正常接收请求,你要确保接收到这样的请求不做任何业务处理。你可以判断userId为test-userId就不处理
## MOCK的数据
{"alertName":"test-alertName","alertState":"-1","curValue":"4","dimensions":"[{}]","expression":"$Maximum>=85","metricName":"test-metricName","metricProject":"test-metricProject","timestamp":"1507618020731","userId":"test-userId"}

如何使用SDK定义报警回调

sdk的使用方式请参考JavaSDK使用手册
在sdk5.0.6以上版本,开始支持报警回调

<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>aliyun-java-sdk-cms</artifactId>
  <version>5.0.6</version>
</dependency>

创建方式

import com.aliyuncs.cms.model.v20170301.CreateAlarmRequest;
import com.aliyuncs.cms.model.v20170301.CreateAlarmResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
public class WebhookTest{
    public void init() throws ClientException {
        IClientProfile profile = DefaultProfile.getProfile("<RegionId>", "<AccessKey>", "<AccessKeySecret>");
        client = new DefaultAcsClient(profile);
    }
    public void createAlarm() throws Exception{
        CreateAlarmRequest request = new CreateAlarmRequest();
        request.setName("test_3");
        request.setNamespace("acs_ocs");
        request.setMetricName("UsedQps");
        request.setDimensions("[{userId:*****,instanceId:\"****\"}]");
        request.setPeriod(60);
        request.setStatistics("Average");
        request.setComparisonOperator(">=");
        request.setThreshold("0");
        request.setEvaluationCount(1);
        request.setContactGroups("[\"云账号报警联系人\"]");
        request.setWebhook("{url:\"http://*****\"}");
        request.setNotifyType(0);

        CreateAlarmResponse response = client.getAcsResponse(request);
    }
}

报警回调最佳实践

报警规则中关于报警抑制的设置对于报警回调同样有效,为了使报警回调更好的服务,需要一些不同于普通报警规则的最佳实践。

报警沉默期时间尽量调低

报警沉默期间,报警回调同样会不触发,所以这个报警规则的沉默期要尽量短
undefined | center

至少连续3次超过阈值才触发

这么做避免监控信息一次偶然的抖动,对你的业务系统造成影响
undefined | center

生效期要00:00-23:59

报警规则不在生效期,报警回调同样会不触发,而报警回调可以让你在熟睡中把故障自动处理掉,所以还是保持默认设置吧

报警回调可以实现的能力

  1. 弹性计算,目前阿里云多个产品均使用报警回调的方式实现弹性伸缩,如ESS/容器服务
  2. 工单流程处理系统,如SRE的规则所述,对于不能短期定位的问题,你可以根据不同的实例分配工单给不同的处理的人,可以方便的进行问题跟踪
  3. 事件分析,每次报警都可以在你自己的系统中记录事件,以便进行整体故障定位分析/故障复盘,关于事事件,云监控同样提供了整体方案,请参考使用事件监控
  4. 自动故障处理,如SRE所述,应该尽可能的使用自动化手段处理报警。比如硬盘空间使用监控,你可以在接收到回调之后,调用一个自动清理日志的脚本。

报警回调推荐使用的监控项

  1. 云服务器ECS操作系统级别监控项:cpu_user/memory_usedutilization/load_5m/diskusage_utilization/
  2. 云数据库RDS监控项:ConnectionUsage
  3. 使用日志监控定义的可用性监控,比如统计nginx日志中的RT,如果平均RT超过阈值就进行新实例部署
相关实践学习
基于云监控实现的监控系统
通过阿里云云监控功能给非阿里云主机安装监控插件,从而实现对非阿里云主机的各项指标进行监控和管理,在配置报警规则和报警人的情况下,能对特定的场景做出报警反应通知到报警人的手机上。
目录
相关文章
|
2月前
|
机器学习/深度学习 人工智能 运维
构建高效运维体系:从自动化到智能化的演进
本文探讨了如何通过自动化和智能化手段,提升IT运维效率与质量。首先介绍了自动化在简化操作、减少错误中的作用;然后阐述了智能化技术如AI在预测故障、优化资源中的应用;最后讨论了如何构建一个既自动化又智能的运维体系,以实现高效、稳定和安全的IT环境。
81 4
|
2月前
|
运维 Linux Apache
,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具
【10月更文挑战第7天】随着云计算和容器化技术的发展,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具,通过定义资源状态和关系,确保系统始终处于期望配置状态。本文介绍Puppet的基本概念、安装配置及使用示例,帮助读者快速掌握Puppet,实现高效自动化运维。
67 4
|
27天前
|
机器学习/深度学习 运维 监控
智能化运维:从自动化到AIOps的演进之路####
本文深入探讨了IT运维领域如何由传统手工操作逐步迈向高度自动化,并进一步向智能化运维(AIOps)转型的过程。不同于常规摘要仅概述内容要点,本摘要将直接引入一个核心观点:随着云计算、大数据及人工智能技术的飞速发展,智能化运维已成为提升企业IT系统稳定性与效率的关键驱动力。文章详细阐述了自动化工具的应用现状、面临的挑战以及AIOps如何通过预测性分析和智能决策支持,实现运维工作的质变,引领读者思考未来运维模式的发展趋势。 ####
|
27天前
|
机器学习/深度学习 数据采集 人工智能
智能化运维:从自动化到AIOps的演进与实践####
本文探讨了智能运维(AIOps)的崛起背景,深入分析了其核心概念、关键技术、应用场景及面临的挑战,并对比了传统IT运维模式,揭示了AIOps如何引领运维管理向更高效、智能的方向迈进。通过实际案例分析,展示了AIOps在不同行业中的应用成效,为读者提供了对未来智能运维趋势的洞察与思考。 ####
67 1
|
1月前
|
机器学习/深度学习 数据采集 人工智能
智能运维:从自动化到AIOps的演进与实践####
本文探讨了智能运维(AIOps)的兴起背景、核心组件及其在现代IT运维中的应用。通过对比传统运维模式,阐述了AIOps如何利用机器学习、大数据分析等技术,实现故障预测、根因分析、自动化修复等功能,从而提升系统稳定性和运维效率。文章还深入分析了实施AIOps面临的挑战与解决方案,并展望了其未来发展趋势。 ####
|
1月前
|
机器学习/深度学习 数据采集 运维
智能化运维:机器学习在故障预测和自动化响应中的应用
智能化运维:机器学习在故障预测和自动化响应中的应用
61 4
|
2月前
|
运维 jenkins 持续交付
自动化部署的魅力:如何用Jenkins和Docker简化运维工作
【10月更文挑战第7天】在现代软件开发周期中,快速且高效的部署是至关重要的。本文将引导你理解如何使用Jenkins和Docker实现自动化部署,从而简化运维流程。我们将从基础概念开始,逐步深入到实战操作,让你轻松掌握这一强大的工具组合。通过这篇文章,你将学会如何利用这些工具来提升你的工作效率,并减少人为错误的可能性。
|
2月前
|
运维 Prometheus 监控
运维中的自动化实践每月一次的系统维护曾经是许多企业的噩梦。不仅因为停机时间长,更因为手动操作容易出错。然而,随着自动化工具的引入,这一切正在悄然改变。本文将探讨自动化在IT运维中的重要性及其具体应用。
在当今信息技术飞速发展的时代,企业对系统的稳定性和效率要求越来越高。传统的手动运维方式已经无法满足现代企业的需求。自动化技术的引入不仅提高了运维效率,还显著降低了出错风险。本文通过几个实际案例,展示了自动化在IT运维中的具体应用,包括自动化部署、监控告警和故障排除等方面,旨在为读者提供一些实用的参考。
|
2月前
|
机器学习/深度学习 数据采集 运维
智能化运维:机器学习在故障预测和自动化响应中的应用
【10月更文挑战第1天】智能化运维:机器学习在故障预测和自动化响应中的应用
79 3
|
2月前
|
机器学习/深度学习 运维 监控
构建高效运维体系:从自动化到智能化的演进之路
在当今数字化时代,运维工作的重要性日益凸显。随着企业业务的不断扩展和技术的日新月异,传统的运维方式已难以满足现代企业的需求。因此,构建一个高效、智能的运维体系成为了企业发展的关键。本文将探讨如何从自动化逐步演进到智能化,以实现运维工作的高效化和智能化。