DEVOPS 运维开发系列六:绿灯测试

绿灯测试,是一种比喻,在运维领域中一般是指对应用服务进行一个或一系列的验证性测试。当通过全部测试时,我们认为该应用服务处于一个正常运行的状态。否则,就要亮“红灯”,即意味着存在部分或整体性的应用服务故障。

绿灯测试的实现方式

早期我们大多是开发一些SHELL或Python脚本,来帮助做一些例如测试进程是否存活、服务端口是否有响应的工作。
随着技术系统越来越复杂,应用服务的数量倍增,传统上的绿灯测试工作也在向更加信息化、自动化和平台化的方向发展。
绿灯测试的内容与应用服务方式密切相关,下面介绍的是一个对相对常见的WEB应用服务的绿灯测试功能。

WEB应用服务的测试范围

通常可以包括以下几个方面:

  • 系统类,如进程是否存活;
  • 网络类,页面能否正常打开;
  • 功能类,端口是否有响应,HTTP状态码是否正确,页面内容是否正确;
  • 集成类,与其他存在调用关系依赖的应用接口间,是否可以正常调用;
    等等。
    其中,系统、功能和集成类的测试内容,大多需要在应用程序所在主机系统中进行测试验证,而网络类的测试则需要从系统和应用的外部进行访问测试。

绿灯测试功能的技术实现

在这里,我们主要是通过WEB框架Django+自动化运维工具SaltStack来实现的。其中Django提供一个应用系统信息化、平台化管理的服务,而SaltStack则作为底层工具,通过接口提供远程执行命令、配置同步、在本地执行命令等功能的支持。在WEB页面中,则通过应用jquery的异常操作来为用户提供更好的使用体验。

需要实现一个应用程序信息的管理功能

提供应用程序信息和维护方法的定义和管理。
DEVOPS 运维开发系列六:绿灯测试_第1张图片
如图所示,我们需要预先把一个应用运维、监控相关的信息尽可能完整和准确得输入到系统中。

提供一个应用的发布管理功能

为应用程序提供基本的程序启、停、配置同步、查看或下载日志的服务。当然,正像本文所描述的,我们还实现了一个绿灯测试功能。
DEVOPS 运维开发系列六:绿灯测试_第2张图片

实现绿灯测试功能的主函数

目前暂实现了进程存活检测、服务端口响应检测、WEB页面http状态码检测和http响应返回内容检测共4种验证测试办法,如下所示。

def app_green_check(request):
    """
    执行应用服务的绿灯测试
    """
    app_id = request.POST.get('app_id', '')
    app = get_object(App, id=app_id)
    hostname_asset = app.asset.hostname
    process_name = app.process_name
    monitor_ports = app.monitor_ports
    app_ip = app.ip
    web_url = app.web_url
    response_str = app.response_str

    print "%s,%s,%s,%s" % (process_name, monitor_ports, web_url, response_str)
    return_value = {}
    check_data = {}
    # 针对web类应用提供了4种测试方法,支持随意组合使用
    step1_result = True
    step2_result = True
    step3_result = True
    step4_result = True
    # step1
    if process_name:
        if app_is_running(hostname_asset, process_name):
            step1_value = u'应用进程检测:进程已经在运行!'
        else:
            step1_value = u'应用进程检测:没有找到该应用的进程!'
            step1_result = False
        check_data['process_name'] = step1_value
    # step2
    if monitor_ports:
        has_nc, msg = check_nc_command(hostname_asset)
        if has_nc:
            step2_result, error_ports = app_check_ports(hostname_asset, app_ip, monitor_ports)
            if step2_result:
                step2_value = u'服务端口[%s]的检测:端口响应正常!' % monitor_ports
            else:
                step2_value = u'服务端口[%s]的检测:%s 端口没有响应!' % (monitor_ports, error_ports)
        else:
            step2_value = u'服务端口的检测:' + msg
        check_data['monitor_ports'] = step2_value
    # step3
    if web_url:
        http_code = get_http_code(web_url)
        if http_code == 200:
            step3_value = u'web服务HTTP返回状态码检测: %d!' % http_code
        else:
            step3_value = u'web服务HTTP返回状态码检测: %d! web服务异常!' % http_code
            step3_result = False
        check_data['web_url'] = step3_value
    # step4
    if response_str:
        has_str = check_response_str(web_url, response_str)
        if has_str:
            step4_value = u'web服务的HTTP响应内容检测: 在响应内容中找到了指定的字符串【%s】' % response_str
        else:
            step4_value = u'web服务的HTTP响应内容检测: 在响应内容中没有找到指定的字符串【%s】' % response_str
            step4_result = False
        check_data['response_str'] = step4_value
    return_value['data'] = check_data
    if step1_result and step2_result and step3_result and step4_result:
        return_value['result'] = True
    else:
        return_value['result'] = False
    return_response = json.dumps(return_value)
    # print return_response
    return HttpResponse(return_response)

实现几个负责具体测试验证工作的子函数

检测进程是否存活

这里是通过调用salt.client模块接口来实现的,功能为在指定的远程业务主机上执行相关的命令并返回结果。

def app_is_running(hostname_asset, process_name):
    """
    查验应用是否已经在运行
    """
    local = salt.client.LocalClient()
    ps_command = "ps -ef|grep %s|grep -v grep|wc -l" % process_name
    salt_output = local.cmd(hostname_asset, 'cmd.run', [ps_command])
    if int(salt_output.get(hostname_asset, '0')) == 1:
        return True
    else:
        return False

服务端口检测

先对系统命令nc进行检查,因为我们是使用这个命令实现的端口响应测试。

def check_nc_command(hostname_asset):
    """
    查验应用主机的系统中是否安装了nc命令工具
    """
    local = salt.client.LocalClient()

    check_nc = r"command -v nc >/dev/null 2>&1 || { echo >&2 'I require nc but it is not installed.  Aborting.'; exit 1; }"
    salt_output = local.cmd(hostname_asset, 'cmd.run', [check_nc])
    if salt_output[hostname_asset]:
        return False, salt_output[hostname_asset]
    else:
        return True, ""

def app_check_ports(hostname_asset, app_ip, monitor_ports):
    """
    查验应用的监听端口是否有响应
    """
    local = salt.client.LocalClient()
    ports_list = monitor_ports.split(';')
    error_ports = []
    check_result = True

    for aport in ports_list:
        check_command = "nc -w 1 %s %s < /dev/null && echo Connecting to tcp port succeeded." % (app_ip, aport)
        salt_output = local.cmd(hostname_asset, 'cmd.run', [check_command])
        print salt_output
        if "succeeded" in salt_output[hostname_asset] or "Connected" in salt_output[hostname_asset]:
            pass
        else:
            error_ports.append(aport)
            check_result = False
    if check_result:
        return True, '0'
    else:
        return False, ",".join(error_ports)

检测web服务页面的http返回状态码

def get_http_code(web_url):
    """
    查验web服务的HTTP返回状态码
    """
    curl_command = 'curl -I -m 10 -o /dev/null -s -w \%{http_code} ' + web_url
    output = commands.getoutput(curl_command)
    if output:
        return int(output)
    else:
        return 0

检测web服务的http响应内容

检查页面内容中是否包含预定的字符串。
这里使用到了SaltStack的RunnerClient模块提供的在Salt Master本机执行管理命令的功能。在这里执行的是一个Salt http.query函数,功能为访问指定的URL并返回响应数据。

def check_response_str(web_url, response_str):
    """
    查验应用的web响应内容中是否包含指定的字符串
    """
    opts = salt.config.master_config('/etc/salt/master')
    runner = salt.runner.RunnerClient(opts)

    http_code = get_http_code(web_url)
    if http_code == 200:
        salt_output = runner.cmd('http.query', [web_url], print_event=False)
        check_result = True if response_str in salt_output['body'] else False
    else:
        check_result = False
    return check_result

前端页面的实现

HTML部分

<tr>
    <td class="text-navy">绿灯检测td>
    <td>
        <span id="greencheck"><input id="green_check" type="button" class="conn btn btn-xs btn-info" value="服务检测" onclick="start_check()"/>span>
        <span id="lightspan"><img src="../../../static/img/light-close.png" />span>
    td>
tr>
......
<div class="text-left">
    <div id="app_ops_output" class="text-warning">div>
div>

JavaScript部分

    function start_check()
    {
        $("#green_check").attr({"disabled":"disabled"});
        $("#app_ops_output").html("正在检测,请稍候...");
        $.ajax({
            type: "post",
            data: {app_id: {{ app.id | safe }}},
            url: "{% url 'app_green_check' %}",
            success: function(result){
                var json = eval("("+result+")");
                var output = "";
                for(var key in json.data){
                    output += json.data[key] + "

"; } $("#green_check").removeAttr("disabled"); $("#lightspan").empty(); if (json.result) { output = ""+output+""; $("#app_ops_output").html(output); $("#lightspan").append(""); } else { $("#app_ops_output").html(output); $("#lightspan").append(""); } } });

绿灯测试功能的演示

默认状态下的页面效果

DEVOPS 运维开发系列六:绿灯测试_第3张图片

正在执行测试

DEVOPS 运维开发系列六:绿灯测试_第4张图片

通过测试后的效果

DEVOPS 运维开发系列六:绿灯测试_第5张图片

未通过绿灯测试时亮起红灯

DEVOPS 运维开发系列六:绿灯测试_第6张图片

全文完!

你可能感兴趣的:(DEVOPS)