功能概述

自定义监控的功能支持,可满足云平台已有用户、非云平台用户对基础资源的指标监控需求,可与 CloudSat 服务内的其他功能结合使用,形成完整的立体化监控告警服务。

如果您需要使用自定义相关功能,请按照下述步骤进行。

操作步骤

步骤一:创建自定义监控

  1. 通过 Web 浏览器登录{product_name}的 Console。

  2. 在顶部菜单栏中选择产品与服务 > 运维与监控 > 云监控 CloudSat > 自定义监控,进入自定义监控页面。

    20201102102144
  3. 点击创建自定义监控,弹出自定义监控设置对话框。

  4. 用户按照弹框内的提示进行信息填写,点击提交,完成操作。

步骤二:上报监控数据

延续前文步骤,按照初始页面的提示,进行数据上报工作,上报数据请参照自定义监控上报数据规范进行上传。

自定义页面监控列表中涉及到一些时间区间内的监控数据计算,最小统计周期为 5 分钟,因此需连续发送不小于 5 分钟时间段的数据,页面才有数据显示。

如持续发送超过 5 分钟仍未正常显示,请检查上传数据的 meter 字段是否和在自定义监控页面下该命名空间下创建的监控指标名称一致。

步骤三:管理自定义监控

  1. 延续前文步骤,进入自定义监控列表页面。该页面汇总用户自主创建的命名空间,各个命名空间之间相互独立。

    20201102102921
  2. 点击目标命名空间,进入具体的空间内,对该空间内的、自主上报的指标进行统一的管理。对异常的监控指标,进行直观的显示。

    20201210144621
  3. 若需对异常情况进行下钻与细查,可以点击指标所在列,进入该这指标的监控详情视图页面,如下图所示。

    20201210144823
  4. 平台也支持对自定义监控数据的配置修改,可按照实际业务需求灵活调整统计方式、命名等信息,如下图所示。

    20201210144822

步骤四:查看告警信息

延续前文步骤,进入命名空间详情页面后,点击监控列表 > 告警,可查看告警规则汇总。如下图所示。

20201210144923

自定义监控上报数据

目前上报自定义监控数据,是通过 CloudSat 中 UploadMonitorData 接口进行上报,然后进行监控数据分析和告警。

详细操作如下。

  • 步骤一:参数升序排列

    将所需参数进行按参数名进行升序排列,排序后的参数示例如下:

    {
      "access_key_id":"QYACCESSKEYIDEXAMPLE",
      "action":"DescribeUsers",
      "signature_method":"HmacSHA256",
      "signature_version":1,
      "time_stamp":"2013-08-27T14:30:10Z",
      "version":1,
      "zone":"sh1"
    }
  • 步骤二:对参数进行 URL 编码

    对参数名称和参数值进行 URL 编码,编码后的参数示例如下:

    {
      "access_key_id":"QYACCESSKEYIDEXAMPLE",
      "action":"DescribeUsers",
      "signature_method":"HmacSHA256",
      "signature_version":1,
      "time_stamp":"2013-08-27T14%3A30%3A10Z",
      "version":1,
      "zone":"sh1"
    }
    注意
    • 编码时,空格要转换成 %20,而不是 +

    • 转码部分的字符要用大写,如 : 应转成 %3A,而不是 %3a

  • 步骤三:构造 URL 请求

    参数名和参数值之间使用 = 号进行连接,参数和参数之间使用 号连接。构造后的 URL 请求示例如下。

    {
      access_key_id=QYACCESSKEYIDEXAMPLE&action=DescribeUsers&signature_method=HmacSHA256&signature_version=1&time_stamp=2013-08-27T14%3A30%3A10Z&version=1&zone=sh1
    }
  • 步骤四:构造被签名串

    被签名串的构造规则为: 被签名串 = HTTP 请求方式 + \n + URI + \n + 上述请求串

    注意

    \n 是换行符,不要将 \ 转义。

    假设 HTTP 请求方法为 GET,请求的 URI 路径为 /iaas/, 则构造的被签名串示例如下。

    {
      GET\n/iaas/\naccess_key_id=QYACCESSKEYIDEXAMPLE&action=DescribeUsers&signature_method=HmacSHA256&signature_version=1&time_stamp=2013-08-27T14%3A30%3A10Z&version=1&zone=sh1
    }
  • 步骤五:计算签名

    计算被签名串的签名。

    1. 将 API 密钥的私钥 secret_access_key 作为 Key,生成被签名串的 HMAC-SHA256 或者 HMAC-SHA1 签名,更多信息可参见 RFC2104 文档。

    2. 将签名进行 Base64 编码。

    3. 将 Base64 编码后的结果进行 URL 编码。

    4. 当 Base64 编码后存在空格时,不要对空格进行 URL 编码,而要直接将空格转为 +,以 Python 版本 2.7 代码为例,结果如下。

      {
          import base64
          import hmac
          import urllib
          from hashlib import sha256
      
          # 前面生成的被签名串
          string_to_sign = 'GET\n/iaas/\naccess_key_id=QYACCESSKEYIDEXAMPLE&action=DescribeUsers&signature_method=HmacSHA256&signature_version=1&time_stamp=2013-08-27T14%3A30%3A10Z&version=1&zone=sh1'
          h = hmac.new(secret_access_key, digestmod=sha256)
          h.update(string_to_sign)
          sign = base64.b64encode(h.digest()).strip()
          signature = urllib.quote_plus(sign)
      }
  • 步骤六:添加签名

    将签名参数附在原有请求串的最后面,最终的 HTTP 请求串示例如下。这里为了查看方便,我们人为地将参数之间用 回车键 分隔开。

    {
        access_key_id=QYACCESSKEYIDEXAMPLE
        &action=DescribeUsers
        &signature_method=HmacSHA256
        &signature_version=1
        &time_stamp=2013-08-27T14%3A30%3A10Z
        &version=1
        &zone=sh1
        &signature=bOQMI8wJ4ikFnadNXc%2BpnVMcUyf83C7b9JO5%2FAvkGyk%3D
    }

附录

UploadMonitorData 接口说明

  • 描述

    上传监控数据。

  • 请求类型

    POST。

  • 请求 URL

    {
        http://cloudsat.qingcloud.com/api/:zone/v1/custom/UploadMonitorData?access_key_id=QEJMCFROGCAPHUOAJMRN&action=DescribeUsers&signature_method=HmacSHA256&signature_version=1&time_stamp=2020-10-13T10%3A28%3A33Z&version=1&zone=test&signature=SO9ZufFb69Om21bK%2BH7Gs6f%2FuuDljHh41STgIX%3D
    }
    注意

    请仔细阅读上文说明,根据实际情况构造验证请求串,拼接成最终的 API 请求 URL 并替换 :zone 信息。

  • Headers

    参数 参数值 是否必须 示例 备注

    Content-Type

    application/json

    'Content-Type':'application/json'

    不可缺少

  • 请求 Body

    {
        "user_id": "usr-123456",
        "namespace": "namespace-1",
        "data": [
          {
              "source": "test",
              "user_id": "usr-KJ8DrfQT",
              "tags": "role=master,interface=eth10",
              "group_id": "group10",
              "resource_id": "i-instance-10" ,
              "resource_name": "name10",
              "resource_type": "instance",
              "root_user_id": "usr-KJ8DrfQ",
              "meter": "disk_ri'",
              "region": "sh1",
              "value": 99,
              "value_type": "percent",
               "time_stamp":"2020-11-03T09:58:44Z"
             },{
              "source": "test",
              "user_id": "usr-KJ8DrfQT",
              "tags": "role=master,interface=eth10",
              "group_id": "group10" ,
              "resource_id": "i-instance-10",
              "resource_name": "name10" ,
              "resource_type": "instance",
              "root_user_id": "usr-KJ8DrfQ",
               "meter": "diskio",
              "region": "sh1",
              "value": 88,
              "value_type": "percent",
              "time_stamp":"2020-11-03T09:58:44Z"
             }
      ]
    }
  • 字段说明

    字段 类型 举例 必填 说明

    namespace

    string

    namespace-1

    命名空间。

    region

    string

    sh1

    Region ID

    source

    string

    custom

    监控数据的来源。

    group_id

    string

    group_1

    监控数据的 Group,或者分组标记。

    resource_id

    string

    i-12345678

    监控数据关联的资源 ID。

    resource_name

    string

    roger-test

    监控数据关联的资源名称。

    resource_type

    string

    instance

    资源类型。

    user_id

    string

    usr-123456

    监控资源资源对应用户的 ID。

    root_user_id

    string

    usr-123456

    主账户 ID。

    meter

    string

    cpu

    监控指标,请于该命名空间下监控配置中的监控指标名称保持一致

    value_type

    string

    raw

    指标值的类型。raw 表示原格式,percent 表示百分比。

    value

    int

    80

    监控的数据,整型的数据

    time_stamp

    string

    2019-12-16T11:14:32Z

    监控数据时间(UTC)

    tags

    string

    role=master,cln-node=node-1,interface=eth0

    数据的 tags,用于归类,key-value 的形式,用于 like 查询。

  • 返回结果

    {
        "data": {
            "upload_count": 2
        },
        "ret_code": 0
    }
  • 结果说明

    ret_code0 表示上传数据成功,dataupload_count 表示上传数据条数。

UploadMonitorData 接口 URL 构造规范

该接口 URL 格式为:构造前 URL + ? + 构造验证请求串。

  • 构造前 URL 示例

    http://cloudsat.qingcloud.com/api/:zone/v1/custom/UploadMonitorData
    说明

    URL 里面 :zone,请根据具体分区信息填写。

  • 申请 API 密钥

    需先在 Console 创建 API 密钥,获得 accesss_key_idsecret_access_key,这里假设:

    access_key_id = 'QYACCESSKEYIDEXAMPLE'
    secret_access_key = 'SECRETACCESSKEY'
  • 构造验证请求串示例

    access_key_id=CCDJRDKCCKZYTEXANZJD&action=DescribeUsers&signature_method=HmacSHA256&signature_version=1&time_stamp=2020-12-23T13%3A32%3A34Z&version=1&zone=sh1&signature=sOdokWwvYJ80mM%2FxYbBTsgTgQl3iu%2F2WDXWjgKFPNNs%3D
    说明

    这部分请根据实际参数进行构造。

  • API 请求中签名

    由于上面构造前 URL 上传监控数据需要对请求进行验证,这里采用类似 API 请求中签名的生成方法,构造验证请求串,拼接在上面构造前 URL 后面。由于需要去 iaas 对用户信息进行确认,所以这里选择 DescribeUsers 这个 action 去构造签名信息。

    构造完成后请求 URL 示例为

    http://cloudsat.qingcloud.com/api/:zone/v1/custom/UploadMonitorData?access_key_id=CCDJRDKCCKZYTEXANZJD&action=DescribeUsers&signature_method=HmacSHA256&signature_version=1&time_stamp=2020-12-23T13%3A32%3A34Z&version=1&zone=sh1&signature=sOdokWwvYJ80mM%2FxYbBTsgTgQl3iu%2F2WDXWjgKFPNNs%3D

示例代码

示例代码以 Golang、Python 语言为例,其他语言类似。

  • Golang 示例

    • 构造验证请求串代码示例,请用实际数据替换 AccessKeyIDSecretAccessKeyZone 等信息。

      package main
      
      import (
      	"crypto/hmac"
      	"crypto/sha256"
      	"encoding/base64"
      	"encoding/json"
      	"fmt"
      	"net/url"
      	"sort"
      	"strconv"
      	"strings"
      	"time"
      )
      
      type verifyInfo struct {
      	httpMothod      string
      	path            string
      	secretAccessKey string
      	describeUsers   *describeUsers
      }
      
      type describeUsers struct {
      	AccessKeyID      string `json:"access_key_id"`
      	Action           string `json:"action"`
      	SignatureMethod  string `json:"signature_method"`
      	SignatureVersion int    `json:"signature_version"`
      	Version          int    `json:"version"`
      	Zone             string `json:"zone"`
      	TimeStamp        string `json:"time_stamp"`
      }
      
      func hmacSha256(data string, secret string) []byte {
      	h := hmac.New(sha256.New, []byte(secret))
      	h.Write([]byte(data))
      	return h.Sum(nil)
      }
      
      func formatParams(v interface{}) (map[string]interface{}, error) {
      	paramMap := make(map[string]interface{})
      	jsonBuf, err := json.Marshal(v)
      	if err != nil {
      		return nil, err
      	}
      	err = json.Unmarshal(jsonBuf, &paramMap)
      	if err != nil {
      		return nil, err
      	}
      	return paramMap, nil
      }
      
      func sortParams(params map[string]interface{}) string {
      	var keys []string
      	for k := range params {
      		keys = append(keys, k)
      	}
      	sort.Strings(keys)
      	var parts []string
      	for _, key := range keys {
      		value := url.QueryEscape(strVal(params[key]))
      		value = strings.Replace(strings.Replace(value, ":", "%3A", -1), " ", "%20", -1)
      		parts = append(parts, key+"="+value)
      	}
      	return strings.Join(parts, "&")
      }
      
      func constructSignature(httpMothod, reqUrl, requestStr, key string) string {
      	signaturePre := httpMothod + "\n" + reqUrl + "\n" + requestStr
      	hmacShaStr := hmacSha256(signaturePre, key)
      	base64Str := base64.StdEncoding.EncodeToString(hmacShaStr)
      	signature := strings.TrimSpace(base64Str)
      	signature = strings.Replace(signature, " ", "+", -1)
      	signature = url.QueryEscape(signature)
      	return signature
      }
      
      func strVal(value interface{}) string {
      	var key string
      	if value == nil {
      		return key
      	}
      	switch value.(type) {
      	case int:
      		it := value.(int)
      		key = strconv.Itoa(it)
      	case string:
      		key = value.(string)
      	default:
      		newValue, _ := json.Marshal(value)
      		key = string(newValue)
      	}
      	return key
      }
      
      func (v *verifyInfo) buildVerifyStr() (string, error) {
      	params, err := formatParams(v.describeUsers)
      	if err != nil {
      		return "", err
      	}
      	sortParams := sortParams(params)
      	signature := constructSignature(v.httpMothod, v.path, sortParams, v.secretAccessKey)
      	rsqParams := sortParams + "&signature=" + signature
      	return rsqParams, nil
      }
      
      
      func main() {
      	vi := &verifyInfo{
      		httpMothod:      "GET",
      		path:            "/iaas/",
      		secretAccessKey: "SECRETACCESSKEY",
      		describeUsers: &describeUsers{
      			AccessKeyID:      "QYACCESSKEYIDEXAMPLE",
      			Action:           "DescribeUsers",
      			SignatureMethod:  "HmacSHA256",
      			SignatureVersion: 1,
      			Version:          1,
      			Zone:             "sh1",
      			TimeStamp:        time.Now().Format("2006-01-02T15:04:05Z"),
      		},
      	}
      
      	result, _ := vi.buildVerifyStr()
      	fmt.Println(result)
      }

      代码中的 result 结果即构造的验证请求串,由于验证信息有时效性,如中断数据发送后重新发送时间间隔大于 5 分钟,请重新构造验证请求串。

    • 验证所构造的验证请求串是否正确,请使用自己构造的验证请求串替换下面代码中 result,执行下面代码。打印结果为 1,验证请求串构造错误;打印结果为 0,验证请求串构造正确。

      type successRespJson struct {
      	Message interface{} `json:"message,required"`
      	RetCode int         `json:"ret_code,required"`
      }
      
      func Verify(url string) int {
      	req, err := http.NewRequest("GET", url, nil)
      	if err != nil {
      		return 1
      	}
      	tr := &http.Transport{
      		TLSClientConfig: &tls.Config{InsecureSkipVerify: true, Renegotiation: tls.RenegotiateOnceAsClient},
      	}
      	cli := http.Client{
      		Transport:     tr,
      		CheckRedirect: nil,
      		Jar:           nil,
      		Timeout:       0,
      	}
      	response, _:= cli.Do(req)
      	defer response.Body.Close()
      	body, _:= ioutil.ReadAll(response.Body)
      	var vr successRespJson
      	if response.StatusCode == http.StatusOK {
      		if err := json.Unmarshal(body, &vr); err != nil {
      			return 1
      		}
      	}
      	return vr.RetCode
      }
      
      func main() {
      	result := "access_key_id=CCDJRDKCCKZYTEXANZJD&action=DescribeUsers&signature_method=HmacSHA256&signature_version=1&time_stamp=2020-12-23T14%3A03%3A44Z&version=1&zone=sh1&signature=d6eMFDgO3E6wBqbbn2AydX%2BxQws7iD%2BtSgfGF6Lq7Uo%3D"
      	url := "https://api.qingcloud.com/iaas/" + "?" + result
      	fmt.Println(Verify(url))
      }
    • 上传自定义数据代码示例

      上传自定义数据需要预先在 CloudSat 自定义监控中创建命名空间和监控配置,并根据实际情况替换下面代码中一些数据,如 UserIdNamespaceRegionMeter 等。Meter 请与对应命名空间上监控配置中的监控指标保持一致。时间使用 UTC 时间,所有上传数据字段严格遵守接口数据规范中字段说明规范。

      package main
      
      import (
      	"bytes"
      	"encoding/json"
      	"fmt"
      	"math/rand"
      	"net/http"
      	"strconv"
      	"time"
      )
      
      type monitorData struct {
      	UserId    string  `json:"user_id"`
      	Namespace string  `json:"namespace"`
      	Data      []meters `json:"data"`
      }
      
      type meters struct {
      	Source       string `json:"source"`
      	UserId       string `json:"user_id"`
      	Tags         string `json:"tags"`
      	GroupId      string `json:"group_id"`
      	ResourceId   string `json:"resource_id"`
      	ResourceName string `json:"resource_name"`
      	ResourceType string `json:"resource_type"`
      	RootUserId   string `json:"root_user_id"`
      	Meter        string `json:"meter"`
      	Region       string `json:"region"`
      	Value        int    `json:"value"`
      	ValueType    string `json:"value_type"`
      	TimeStamp    string `json:"time_stamp"`
      }
      
      type date struct {
      	UploadCount int `json:"upload_count"`
      }
      
      type ret struct {
      	Data date `json:"data"`
      	RetCode int `json:"ret_code"`
      }
      
      var client = http.Client{
      	Timeout: 10 * time.Second,
      }
      
      func HttpPostJson(url string, data interface{}, result interface{}) error {
      	buf := bytes.NewBuffer(nil)
      	encoder := json.NewEncoder(buf)
      	if err := encoder.Encode(data); err != nil {
      		return err
      	}
      
      	request, err := http.NewRequest(http.MethodPost, url, buf)
      	if err != nil {
      		return err
      	}
      
      	request.Header.Add("Content-Type", "application/json")
      	request.Header.Add("Content-Type", "charset=UTF-8")
      	response, err := client.Do(request)
      	if err != nil {
      		return err
      	}
      	defer response.Body.Close()
      
      	decoder := json.NewDecoder(response.Body)
      	if err = decoder.Decode(&result); err != nil {
      		return err
      	}
      	fmt.Println(result)
      	return nil
      }
      
      func main() {
      
          for {
      	    timeStamp := time.Now().UTC().Format("2006-01-02T15:04:05Z")
      		rand.Seed(time.Now().UnixNano())
      		flag := strconv.Itoa(rand.Intn(16))
      		p := monitorData{
      			UserId: "usr-12345678",
      			Namespace: "cloudsat-test",
      			Data: []meters{
      				{
      					Source: "dylan-test",
      					UserId: "usr-12345678",
      					Tags: "role=master,interface=eth10",
      					GroupId: "group1",
      					ResourceId: "i-instance-"+flag ,
      					ResourceName: "name"+flag,
      					ResourceType: "instance",
      					RootUserId: "usr-12345678",
      					Meter: "cpu",
      					Region: "sh1",
      					Value: rand.Intn(100),
      					ValueType: "percent",
      					TimeStamp:timeStamp,
      				},{
      					Source: "dylan-test",
      					UserId: "usr-12345678",
      					Tags: "role=master,interface=eth10",
      					GroupId: "group1" ,
      					ResourceId: "i-instance-"+flag ,
      					ResourceName: "name"+flag,
      					ResourceType: "instance",
      					RootUserId: "usr-12345678",
      					Meter: "memory",
      					Region: "sh1",
      					Value: rand.Intn(100),
      					ValueType: "percent",
      					TimeStamp:timeStamp,
      				},
      			},
      		}
      		var r ret
      
      		url := `http://cloudsat.qingcloud.com/api/sh1/v1/custom/UploadMonitorData?access_key_id=CCDJRDKCCKZYTEXANZJD&action=DescribeUsers&signature_method=HmacSHA256&signature_version=1&time_stamp=2020-12-23T09%3A52%3A10Z&version=1&zone=sh1&signature=vorfODyQzKD0v4En7y5mdwkvUe4OZGQcDdQU5xBSjio%3D`
      		err := HttpPostJson(url,p,r)
      		if err != nil {
      			fmt.Println(err)
      		}
      		time.Sleep(time.Duration(1)*time.Second)
      	}
      
      }
  • python 完整上传自定义数据示例

    请根据实际,使用自己的 access_key_idsecret_access_keyuser_idnamespaceregionmeter 替换代码中相应变量,这里以 上海1区(sh1) 为例,实际应用中需要同步更新 self.CloudSatUrl 中对应的区间信息,详情参见上文。

    import hmac
    import json
    import base64
    import datetime
    import time
    from hashlib import sha256
    from collections import OrderedDict
    
    try:
        import urllib.parse as urllib
    except:
        import urllib
    import requests
    
    def sort_value(old_dict):
        items = sorted(old_dict.items())
        new_dict = OrderedDict()
        for item in items:
            new_dict[item[0]] = old_dict[item[0]]
        return new_dict
    
    class UploadMonitorData(object):
        def __init__(self, access_key_id, secret_access_key):
            self.access_key_id = access_key_id
            self.secret_access_key = secret_access_key
    
            nowtime = datetime.datetime.now()
            self.signature_time = datetime.datetime.strftime(nowtime, "%Y-%m-%dT%H:%M:%SZ")
    
            self.time_stamp = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
            self.time = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
            self.url = "https://api.qingcloud.com/iaas/"
            self.CloudSatUrl = "http://cloudsat.qingcloud.com/api/sh1/v1/custom/UploadMonitorData"
            self.url_path = '/iaas/'
            self.methods = 'GET'
    
        def post_request(self, post_url):
            headers = {"Content-Type": "application/json; charset=UTF-8"}
            body = {
                "user_id": "usr-8Ljvov6I",
                "namespace": "pt-test",
                "data": [
                    {
                        "source": "custom",
                        "user_id": "usr-8Ljvov6I",
                        "resource_id": "i-ets7af6q",
                        "resource_type": "instance",
                        "root_user_id": "usr-8Ljvov6I",
                        "meter": "cpu",
                        "region": "sh1",
                        "value": 99,
                        "value_type": "percent",
                        "time_stamp": self.time_stamp
                    }, {
                        "source": "custom",
                        "user_id": "usr-8Ljvov6I",
                        "resource_id": "i-ets7af6q",
                        "resource_type": "instance",
                        "root_user_id": "usr-8Ljvov6I",
                        "meter": "memory",
                        "region": "sh1",
                        "value": 96,
                        "value_type": "percent",
                        "time_stamp": self.time_stamp
                    }
                ]
            }
            result = requests.post(url=post_url, headers=headers, data=json.dumps(body))
            print(result.status_code)
            print(result.reason)
            print(result.text)
    
        def post_monitor_data(self):
            od = OrderedDict()
            od['access_key_id'] = self.access_key_id
            od['action'] = "DescribeUsers"
            od['signature_method'] = "HmacSHA256"
            od['signature_version'] = 1
            od['time_stamp'] = self.signature_time
            od['version'] = 1
            od['zone'] = "sh1"
    
            od_sort = sort_value(od)
            data = urllib.urlencode(od_sort)
            string_to_sign = self.methods + "\n" + self.url_path + "\n" + data
            print(string_to_sign)
            h = hmac.new(self.secret_access_key.encode(), digestmod=sha256)
            h.update(string_to_sign.encode())
            sign = base64.b64encode(h.digest()).strip()
            signature = urllib.quote_plus(sign)
            post_url = self.CloudSatUrl + "?" + data + "&signature=" + signature
            self.post_request(post_url)
    
    if __name__ == '__main__':
        access_key_id = 'ACCESSKEYID'
        secret_access_key = 'SECRETACCESSKEY'
    
        while True:
            upload_struct_object = UploadMonitorData(access_key_id, secret_access_key)
            upload_struct_object.post_monitor_data()
            time.sleep(3)