夜莺监控云平台短信服务

开发与运维

0.前言

之前自行开发了一个exporter用来监控服务器接收请求数的情况,是基于命令的方式来获取服务器信息,今天我们通过请求外部服务的方式来获取监控信息。日常工作中短信通知业务通常会用到云上的短信服务,短信服务的使用情况就会是需要关注的一个点,那么今天我们就通过调用云上短信业务相关接口,来实现云上短信使用情况的监控,因为我使用的阿里云短信服务,所以这里以阿里云为例。

1.资源准备

操作系统IP地址CPU内存用途
rocky9 linux10.173.100.158核32G夜莺、exporter所在服务器

以上配置仅供参考,大家用其他操作系统和配置的服务器也可以,因为是调用阿里云的接口,所以在一台服务器上调用接口查询数据即可。

2.短信接口解析

因为要调用阿里云短信接口,所以调用之前先来看下云短信SDK使用方法。
阿里云接口文档地址:阿里云API入口地址
进入之后出现如下界面:

picture.image 之后在搜索框输入"短信",然后点击【搜索】按钮,之后进入短信API文档界面,如下图所示:

picture.image 我们今天主要要做的就是查询短信发送情况,所以本次调用的接口就是:"QuerySendStatistics",找到该接口点击【去调试】按钮,进入调试界面之后选中"SDK示例"并选中"Go"选项,采用Go语言调试该接口,如下图所示:

picture.image 这里会告诉用户查询短信使用情况的SDK的用法,配上了代码和代码解释说明以及需要添加的参数作用,我们可以稍微看下每个函数的作用,之后化身CV战士将代码拷贝到我们的IDE即可。
(1)SDK依赖:

import (
  "encoding/json"
  "strings"
  "fmt"
  "os"
  dysmsapi20170525  "github.com/alibabacloud-go/dysmsapi-20170525/v5/client"
  openapi  "github.com/alibabacloud-go/darabonba-openapi/v2/client"
  util  "github.com/alibabacloud-go/tea-utils/v2/service"
  credential  "github.com/aliyun/credentials-go/credentials"
  "github.com/alibabacloud-go/tea/tea"
)

(2)创建连接客户端:

// Description:
// 
// 使用凭据初始化账号Client
// 
// @return Client
// 
// @throws Exception
func CreateClient () (_result *dysmsapi20170525.Client, _err error) {
  // 工程代码建议使用更安全的无AK方式,凭据配置方式请参见:https://help.aliyun.com/document_detail/378661.html。
  credential, _err := credential.NewCredential(nil)
  if _err != nil {
    return _result, _err
  }

  config := &openapi.Config{
    Credential: credential,
  }
  // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
  config.Endpoint = tea.String("dysmsapi.aliyuncs.com")
  _result = &dysmsapi20170525.Client{}
  _result, _err = dysmsapi20170525.NewClient(config)
  return _result, _err
}

示例代码里获取的是临时的用户凭据,真正调试的时候用户需要使用AK(access-key)方式,才允许创建连接短信接口的客户端,AK请求方式代码需要修改如下:

import (
	"fmt"
	"os"
	"github.com/aliyun/credentials-go/credentials"
)

func main() {
	config := new(credentials.Config).
		SetType("access_key").
		SetAccessKeyId(os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")).
		SetAccessKeySecret(os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"))

	akCredential, err := credentials.NewCredential(config)
	if err != nil {
		return
	}
	accessKeyId, err := akCredential.GetAccessKeyId()
	accessSecret, err := akCredential.GetAccessKeySecret()
	credentialType := akCredential.GetType()
	fmt.Println(accessKeyId, accessSecret, credentialType)
}

(3)私有main函数:

func _main (args []*string) (_err error) {
  client, _err := CreateClient()
  if _err != nil {
    return _err
  }

  querySendStatisticsRequest := &dysmsapi20170525.QuerySendStatisticsRequest{}
  runtime := &util.RuntimeOptions{}
  tryErr := func()(_e error) {
    defer func() {
      if r := tea.Recover(recover()); r != nil {
        _e = r
      }
    }()
    // 复制代码运行请自行打印 API 的返回值
    _, _err = client.QuerySendStatisticsWithOptions(querySendStatisticsRequest, runtime)
    if _err != nil {
      return _err
    }

    return nil
  }()

  if tryErr != nil {
    var error = &tea.SDKError{}
    if _t, ok := tryErr.(*tea.SDKError); ok {
      error = _t
    } else {
      error.Message = tea.String(tryErr.Error())
    }
    // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
    // 错误 message
    fmt.Println(tea.StringValue(error.Message))
    // 诊断地址
    var data interface{}
    d := json.NewDecoder(strings.NewReader(tea.StringValue(error.Data)))
    d.Decode(&data)
    if m, ok := data.(map[string]interface{}); ok {
      recommend, _ := m["Recommend"]
      fmt.Println(recommend)
    }
    _, _err = util.AssertAsString(error.Message)
    if _err != nil {
      return _err
    }
  }
  return _err
}

获取数据的主要逻辑,以及最终获取数据的结果,都封装在了_main函数里面,我们姑且称其为私有的main函数,要注意的是获取数据的代码为:

  _, _err = client.QuerySendStatisticsWithOptions(querySendStatisticsRequest, runtime)

这里的下划线就是获取的返回结果,阿里云示例代码隐去了,让用户自行打印,我们可以将其赋值给request变量。
(4)真正的main函数:

func main() {
  err := _main(tea.StringSlice(os.Args[1:]))
  if err != nil {
    panic(err)
  }
}

main函数,调用_main函数,并最终运行程序。

3.exporter开发

其实和获取服务器请求量的exporter没太大区别,只不过指标结构体和请求数据的方式需要改变,还是带着大家把完整的代码过一遍。
(1)项目依赖:

import (
    "encoding/json"
    "fmt"
    openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
    dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20170525/v5/client"
    util "github.com/alibabacloud-go/tea-utils/v2/service"
    "github.com/alibabacloud-go/tea/tea"
    credential "github.com/aliyun/credentials-go/credentials"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "log"
    "net/http"
    "strings"
    "time"
)

主要需要关注的依赖有阿里云短信SDK和prometheus的SDK。

(2)collector结构体:

type messageCollector struct {
    noRespondedCountMetric      *prometheus.Desc
    respondedFailCountMetric    *prometheus.Desc
    respondedSuccessCountMetric *prometheus.Desc
    totalCountMetric            *prometheus.Desc
}

根据阿里云短信接口获取到的数据类型,我们可以采集四个指标:无响应短信条数,响应失败短信条数,响应成功短信条数,总短信条数。

(3)初始化collector结构体:

func newMessageCollector() *messageCollector {
    return &messageCollector{
       noRespondedCountMetric: prometheus.NewDesc("no_responded_count_message",
          "collect no responded count message",
          []string{"date"}, nil,
       ),
       respondedFailCountMetric: prometheus.NewDesc("responded_failed_count_message",
          "collect responded failed count message",
          []string{"date"}, nil,
       ),
       respondedSuccessCountMetric: prometheus.NewDesc("responded_success_count_message",
          "collect responded success count message",
          []string{"date"}, nil,
       ),
       totalCountMetric: prometheus.NewDesc("total_count_message",
       "collect message total count",
```,
          []string{"date"}, nil,
       ),
    }
}

指定四个指标的基础信息,并以当天日期为标签。
(4)采集指标必备函数Describe:

func (mc *messageCollector) Describe(ch chan<- *prometheus.Desc) {
    ch <- mc.noRespondedCountMetric
    ch <- mc.respondedFailCountMetric
    ch <- mc.respondedSuccessCountMetric
    ch <- mc.totalCountMetric
}

该函数主要是向prometheus解释指标的基础信息。
(5)程序必要变量:

var (
    messageSecretId  = "XXXXXXXXXXXXXXXXXX" // 阿里云secretId
    messageSecretKey = "XXXXXXXXXXXXXXXXXX" // 阿里云secretKey
    isGlobe          = 1 // 表示查询国内短信
    pageIndex        = 1 // 页码
    pageSize         = 1 // 每页的条目
)

我们只调用当天的短信使用情况,所以只采集当前采集到的条目即可。
(6)创建阿里云客户端:

func createClient() (client *dysmsapi20170525.Client, err error) {
    // 工程代码建议使用更安全的无AK方式,凭据配置方式请参见:https://help.aliyun.com/document_detail/378661.html。
    credentialsConfig := new(credential.Config).
       SetType("access_key").
       SetAccessKeyId(messageSecretId).
       SetAccessKeySecret(messageSecretKey)

    akCredential, err := credential.NewCredential(credentialsConfig)
    if err != nil {
       return client, err
    }

    config := &openapi.Config{}
    // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
    config.Endpoint = tea.String("dysmsapi.aliyuncs.com")
    config.Credential = akCredential

    client = &dysmsapi20170525.Client{}
    client, err = dysmsapi20170525.NewClient(config)
    return
}

采用的AK的方式创建阿里云客户端,代码可以直接从阿里云SDK拷贝。
(7)获取短信消费情况:

func getMessages() (*dysmsapi20170525.QuerySendStatisticsResponse, error) {
    client, err := createClient()
    if err != nil {
       return nil, err
    }

    todayTm := time.Now().Format("20060102")

    querySendStatisticsRequest := &dysmsapi20170525.QuerySendStatisticsRequest{
       IsGlobe:   tea.Int32(int32(isGlobe)),
       StartDate: tea.String(todayTm),
       EndDate:   tea.String(todayTm),
       PageIndex: tea.Int32(int32(pageIndex)),
       PageSize:  tea.Int32(int32(pageSize)),
    }

    runtime := &util.RuntimeOptions{}
    request := &dysmsapi20170525.QuerySendStatisticsResponse{}

    tryErr := func() (_e error) {
       defer func() {
          if r := tea.Recover(recover()); r != nil {
             _e = r
          }
       }()
       // 复制代码运行请自行打印 API 的返回值
       request, err = client.QuerySendStatisticsWithOptions(querySendStatisticsRequest, runtime)
       if err != nil {
          return err
       }
       fmt.Println("request:", request)
       return nil
    }()

    if tryErr != nil {
       var error = &tea.SDKError{}
       if _t, ok := tryErr.(*tea.SDKError); ok {
          error = _t
       } else {
          error.Message = tea.String(tryErr.Error())
       }
       // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
       // 错误 message
       fmt.Println(tea.StringValue(error.Message))
       // 诊断地址
       var data interface{}
       d := json.NewDecoder(strings.NewReader(tea.StringValue(error.Data)))
       d.Decode(&data)
       if m, ok := data.(map[string]interface{}); ok {
          recommend, _ := m["Recommend"]
          fmt.Println(recommend)
       }
       _, err = util.AssertAsString(error.Message)
       if err != nil {
          return nil, err
       }
    }
    return request, nil
}

通过client请求阿里云短信消费信息,并获取响应数据。 (8)采集指标必要函数collect:

func (mc *messageCollector) Collect(ch chan<- prometheus.Metric) {
    collector := newMessageCollector()
    request, err := getMessages()
    if err != nil {
       log.Fatal(err)
    }
    targets := request.Body.Data.TargetList
    for _, t := range targets {
       noResponseCount := float64(*t.NoRespondedCount)
       m1 := prometheus.MustNewConstMetric(collector.noRespondedCountMetric, prometheus.GaugeValue,
          noResponseCount, *t.SendDate)
       ch <- m1

       respondedFailCount := float64(*t.RespondedFailCount)
       m2 := prometheus.MustNewConstMetric(collector.respondedFailCountMetric, prometheus.GaugeValue,
          respondedFailCount, *t.SendDate)
       ch <- m2

       respondedSuccessCount := float64(*t.RespondedSuccessCount)
       m3 := prometheus.MustNewConstMetric(collector.respondedSuccessCountMetric, prometheus.GaugeValue,
          respondedSuccessCount, *t.SendDate)
       ch <- m3

       totalCount := float64(*t.TotalCount)
       m4 := prometheus.MustNewConstMetric(collector.totalCountMetric, prometheus.GaugeValue,
          totalCount, *t.SendDate)
       ch <- m4
    }
}

根据请求接口获取到的数据,转化为指标数据展示。
(9)初始化函数和主函数:

func init() {
    prometheus.MustRegister(newMessageCollector())
}

func main() {
    http.Handle("/metrics", promhttp.Handler())

    log.Println("Listening on :9095")
    log.Fatal(http.ListenAndServe(":9095", nil))
}

初始化函数用来注册collector,主函数用来启动exporter服务,exporter服务使用的是9095端口。

4.prometheus指标采集

exporter已经开发完成,接下来启动exporter服务,然后prometheus就可以采集exporter暴露的数据了。

4.1 启动exporter

在go语言环境下,执行以下命令初始化依赖,并运行exporter服务:

cd /data/go/gopath/src/message_exporter/
go mod init
go mod tidy
nohup go run ./main.go > server.log &

4.2 prometheus注册exporter

修改prometheus的配置,采集exporter暴露出来的数据,步骤如下:

vim /usr/local/prometheus/prometheus.yml
- targets: ["10.173.100.15:9095"]
        labels:
          app: "message-exporter"
          product: "message-counter"
          env: "pro"

exporter的标签可以根据自己需要修改,修改完配置之后记得重启prometheus。

4.3 指标查询

通过IP:Port(10.173.100.15:9090)的形式访问prometheus查询页面,然后依次输入之前定义的查询指标:total_count_message,no_responded_count_message,responded_failed_count_message,responded_success_count_message,查询结果如下图所示:

picture.image 可以看到指标被成功采集到,之后就可以在夜莺平台制作展示页面了。

5.自制仪表盘

现在监控指标已经采集到了,并接成功的集成到了prometheus当中,接下来只需要在夜莺绘制一个仪表盘查询指标即可,如下图所示:

picture.image

6.总结

本次通过调用阿里云短信服务的接口获取短信使用情况,阿里云调用接口的SDK首次使用还是有很多坑的,需要多看下文档,多调试,exporter的代码编写也需要认真仔细,否则很容易出现指标错误的情况。

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
高性能存储虚拟化方案 NVMe over Fabric 在火山引擎的演进
在云计算中,虚拟化存储扮演着重要角色,其中 iSCSI 协议在业界开放、流行多年。近年来,拥有更优性能的 NVMe over Fabrics 协议也得到了发展。本次分享介绍了 NVMe over Fabrics 在云原生和虚拟化方向的演进工作和成果。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论