当前位置:   article > 正文

华为云CES监控与飞书通知

华为云CES监控与飞书通知

华为云负载均衡连接数监控与飞书通知

在云服务的日常运维中,持续监控资源状态是保障系统稳定性的关键步骤之一。本文通过一个实际案例展示了如何使用华为云的Go SDK获取负载均衡器的连接数,并通过飞书Webhook发送通知到团队群组,以便运维人员及时获取最新的监控信息。本来准备直接使用ces告警,但是看了一下模版以及最佳实践貌似没有很好的支持webhook,就直接自己使用go sdk实现了!

背景知识

在华为云上,负载均衡服务(ELB)用于分发来自客户端的网络请求到多个云服务器,确保系统在面对不同的负载情况时,仍能够提供稳定、可靠的服务。ELB的性能指标,如每分钟连接数(CPS),是反映当前系统承载能力的重要数据。通常情况下,我们希望能够实时监控这些关键指标。

随着云服务技术的成熟,大型企业往往会将监控数据集成到实时通讯工具中,便于团队成员即时查看和响应潜在的问题。本案例中选择的通讯工具是飞书,华为云Go SDK则是我们与华为云服务交互的媒介。

环境准备

华为云提供的Go SDK是一套围绕华为云API构建的开发工具包,使得开发者可以在Go语言环境中便捷地调用云服务。在这里,我们利用Cloud Eye Service (CES) 的API,通过SDK检索ELB的CPS指标数据。

安装华为云Go SDK

首先需要安装华为云Go SDK。可以通过go get命令安装所需的SDK包:

go get -u github.com/huaweicloud/huaweicloud-sdk-go-v3
  • 1

安装完成后,即可在项目中引入相关的SDK模块。

初始化客户端

要与华为云的服务交互,我们需要创建并初始化一个SDK客户端。如下示例中,我们创建了用于CES(Cloud Eye Service)服务的客户端,并使用了之前提到的AK和SK进行了认证。

package main

// 导入相关的包
import (
    "fmt"
    "bytes"
    "json"
    "http"
    "ioutil"
    "time"

    "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
    "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1"
    ces "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model"
)

const (
    feishuWebhookURL = "xxxx" // 飞书Webhook URL
    ak               = "xxx"  // Access Key
    sk               = "xxxxx" // Secret Key
)

func main() {
    // 构建认证信息
    auth := basic.NewCredentialsBuilder().
       WithAk(ak).
       WithSk(sk).
       Build()

    // 初始化CES客户端
    client := ces.NewCesClient(
       ces.CesClientBuilder().
          WithRegion(region.ValueOf("cn-east-3")).
          WithCredential(auth).
          Build())

    // ...后续代码
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

设置定时器和执行任务

我们通过一个定时器来定期检查负载均衡器的最大连接数,例如:

    ticker := time.NewTicker(1 * time.Minute) // 每分钟触发检查
    for {
       select {
       case t := <-ticker.C:
          currentHour := t.Hour()
          // 只在既定的时间范围内执行
          if currentHour >= 7 && currentHour < 24 {
             // 我们设定在59分时收集数据
             if t.Minute() == 59 {
                go collectDataAndSendToFeishu(client)
             }
          }
       }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这里限制了定时器发送的时间范围早上7点到24点执行,0点-7点默认不执行。并且执行的时间是每个小时的59分执行!

收集和发送数据

一旦定时器触发并满足条件,我们会收集负载均衡的最大连接数并发送给飞书,参考 华为云ces ShowMetricData接口:
image.png
具体实现如下,注意**ShowMetricDataRequest **中具体参数:

func collectDataAndSendToFeishu(client *ces.CesClient) {
    currentTime := time.Now().UTC()
    startTime := currentTime.Truncate(time.Hour).Add(time.Minute * 58)
    endTime := startTime.Add(time.Minute)

    startTimestamp := startTime.UnixNano() / int64(time.Millisecond)
    endTimestamp := endTime.UnixNano() / int64(time.Millisecond)

    request := &model.ShowMetricDataRequest{
       Namespace:  "SYS.ELB",
       MetricName: "m1_cps",
       Dim0:       "lbaas_instance_id,xxxxxx",
       Filter:     model.GetShowMetricDataRequestFilterEnum().MAX,
       Period:     int32(1),
       From:       startTimestamp,
       To:         endTimestamp,
    }

    response, err := client.ShowMetricData(request)
    if err != nil {
       fmt.Println("Error querying CES data:", err)
       return
    }
    fmt.Printf("CES response: %+v\n", response)

    // Extract max value and timestamp from the response
    var maxConnection float64
    var timestamp int64
    if response.Datapoints != nil && len(*response.Datapoints) > 0 {
       datapoints := *response.Datapoints
       maxConnection = *datapoints[0].Max
       timestamp = datapoints[0].Timestamp
    }

    // Format the timestamp to a readable form
    readableTime := time.Unix(timestamp/1000, (timestamp%1000)*int64(time.Millisecond)).Format("2006-01-02 15:04:05")
    // Prepare the message to send to Feishu
    feishuMessage := fmt.Sprintf("当前时间 %s 负载均衡最大连接数是 %.2f", readableTime, maxConnection)

    if err := sendToFeishuWebhook(feishuWebhookURL, feishuMessage); err != nil {
       fmt.Println("Error sending to Feishu webhook:", err)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

发送Webhook通知

最后,实现sendToFeishuWebhook方法以将消息推送到飞书。

func sendToFeishuWebhook(webhookURL string, message string) error {
    webhookMessage := FeishuWebhookMessage{
       MsgType: "text",
    }
    webhookMessage.Content.Text = message

    jsonData, err := json.Marshal(webhookMessage)
    if err != nil {
       return fmt.Errorf("failed to marshal webhook message: %v", err)
    }

    req, err := http.NewRequest("POST", webhookURL, bytes.NewBuffer(jsonData))
    if err != nil {
       return fmt.Errorf("failed to create HTTP request: %v", err)
    }
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
       return fmt.Errorf("failed to send HTTP request: %v", err)
    }
    defer resp.Body.Close()

    responseBody, err := ioutil.ReadAll(resp.Body)
    if err != nil {
       return fmt.Errorf("failed to read webhook response body: %v", err)
    }

    fmt.Println("Feishu webhook response:", string(responseBody))
    return nil
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

完整代码:

每小时59分统计58-59分最大值发送统计到飞书:

package main
type FeishuWebhookMessage struct {
    MsgType string `json:"msg_type"`
    Content struct {
       Text string `json:"text"`
    } `json:"content"`
}

const (
    // 定时器间隔,用于根据特定时间点触发数据检索。例如:59分时执行任务,就是(59 - 当前时间的分钟数) x 每分钟的秒数
    feishuWebhookURL = "xxxx"
    ak               = "xxx"
    sk               = "xxxxx"
)

func main() {
    auth := basic.NewCredentialsBuilder().
       WithAk(ak).
       WithSk(sk).
       Build()

    client := ces.NewCesClient(
       ces.CesClientBuilder().
          WithRegion(region.ValueOf("cn-east-3")).
          WithCredential(auth).
          Build())

    ticker := time.NewTicker(1 * time.Minute) // Check every 10 minutes to adjust for the next 59th minute.
    for {
       select {
       case t := <-ticker.C:
          // 这里设置只在7-24点执行
          currentHour := t.Hour()
          if currentHour >= 7 && currentHour < 24 {
             if t.Minute() == 59 {
                go collectDataAndSendToFeishu(client)
             }
          }
       }
    }
}
func collectDataAndSendToFeishu(client *ces.CesClient) {
    currentTime := time.Now().UTC()
    startTime := currentTime.Truncate(time.Hour).Add(time.Minute * 58)
    endTime := startTime.Add(time.Minute)

    startTimestamp := startTime.UnixNano() / int64(time.Millisecond)
    endTimestamp := endTime.UnixNano() / int64(time.Millisecond)

    request := &model.ShowMetricDataRequest{
       Namespace:  "SYS.ELB",
       MetricName: "m1_cps",
       Dim0:       "lbaas_instance_id,xxxxxx",
       Filter:     model.GetShowMetricDataRequestFilterEnum().MAX,
       Period:     int32(1),
       From:       startTimestamp,
       To:         endTimestamp,
    }

    response, err := client.ShowMetricData(request)
    if err != nil {
       fmt.Println("Error querying CES data:", err)
       return
    }
    fmt.Printf("CES response: %+v\n", response)

    // Extract max value and timestamp from the response
    var maxConnection float64
    var timestamp int64
    if response.Datapoints != nil && len(*response.Datapoints) > 0 {
       datapoints := *response.Datapoints
       maxConnection = *datapoints[0].Max
       timestamp = datapoints[0].Timestamp
    }

    // Format the timestamp to a readable form
    readableTime := time.Unix(timestamp/1000, (timestamp%1000)*int64(time.Millisecond)).Format("2006-01-02 15:04:05")
    // Prepare the message to send to Feishu
    feishuMessage := fmt.Sprintf("当前时间 %s 负载均衡最大连接数是 %.2f", readableTime, maxConnection)

    if err := sendToFeishuWebhook(feishuWebhookURL, feishuMessage); err != nil {
       fmt.Println("Error sending to Feishu webhook:", err)
    }
}

// sendToFeishuWebhook sends a message to Feishu webhook
func sendToFeishuWebhook(webhookURL string, message string) error {
    webhookMessage := FeishuWebhookMessage{
       MsgType: "text",
    }
    webhookMessage.Content.Text = message

    jsonData, err := json.Marshal(webhookMessage)
    if err != nil {
       return fmt.Errorf("failed to marshal webhook message: %v", err)
    }

    req, err := http.NewRequest("POST", webhookURL, bytes.NewBuffer(jsonData))
    if err != nil {
       return fmt.Errorf("failed to create HTTP request: %v", err)
    }
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
       return fmt.Errorf("failed to send HTTP request: %v", err)
    }
    defer resp.Body.Close()

    responseBody, err := ioutil.ReadAll(resp.Body)
    if err != nil {
       return fmt.Errorf("failed to read webhook response body: %v", err)
    }

    fmt.Println("Feishu webhook response:", string(responseBody))
    return nil
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119

运行以上代码:
img_v3_026i_b57d3d26-178b-4f64-8ade-5ecfcd3917dg.jpg

其它的扩展玩法:

每分钟检查一次,当连接数大于100报警触发

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
	ces "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1"
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model"
	region "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/region"
	"io/ioutil"
	"net/http"
	"time"
)

type FeishuWebhookMessage struct {
	MsgType string `json:"msg_type"`
	Content struct {
		Text string `json:"text"`
	} `json:"content"`
}

const (
	feishuWebhookURL = "xxxx"
	ak               = "xxxx"
	sk               = "xxxxx"
)

func main() {
	auth := basic.NewCredentialsBuilder().
		WithAk(ak).
		WithSk(sk).
		Build()

	client := ces.NewCesClient(
		ces.CesClientBuilder().
			WithRegion(region.ValueOf("cn-east-3")).
			WithCredential(auth).
			Build())

	ticker := time.NewTicker(1 * time.Minute) // Check every 10 minutes to adjust for the next 59th minute.
	for {
		select {
		case t := <-ticker.C:
			// 这里设置只在7-24点执行
			currentHour := t.Hour()
			if currentHour >= 7 && currentHour < 24 {
				go collectDataAndSendToFeishu(client)
			}
		}
	}
}
func collectDataAndSendToFeishu(client *ces.CesClient) {
	currentTime := time.Now().UTC().Truncate(time.Minute)
	startTime := currentTime.Add(-1 * time.Minute)
	endTime := currentTime

	startTimestamp := startTime.UnixNano() / int64(time.Millisecond)
	endTimestamp := endTime.UnixNano() / int64(time.Millisecond)

	request := &model.ShowMetricDataRequest{
		Namespace:  "SYS.ELB",
		MetricName: "m1_cps",
		Dim0:       "lbaas_instance_id,xxxxx",
		Filter:     model.GetShowMetricDataRequestFilterEnum().MAX,
		Period:     int32(1),
		From:       startTimestamp,
		To:         endTimestamp,
	}

	response, err := client.ShowMetricData(request)
	if err != nil {
		fmt.Println("Error querying CES data:", err)
		return
	}
	fmt.Printf("CES response: %+v\n", response)

	// Extract max value and timestamp from the response
	var maxConnection float64
	var timestamp int64
	if response.Datapoints != nil && len(*response.Datapoints) > 0 {
		datapoints := *response.Datapoints
		maxConnection = *datapoints[0].Max
		timestamp = datapoints[0].Timestamp
	}

	// Format the timestamp to a readable form
	readableTime := time.Unix(timestamp/1000, (timestamp%1000)*int64(time.Millisecond)).Format("2006-01-02 15:04:05")
	// Prepare the message to send to Feishu
	if maxConnection > 100 {
		// Prepare the alert message to send to Feishu
		feishuMessage := fmt.Sprintf("警告:当前时间 %s 负载均衡连接数超越100,当前数值是 %.2f", readableTime, maxConnection)

		if err := sendToFeishuWebhook(feishuWebhookURL, feishuMessage); err != nil {
			fmt.Println("Error sending to Feishu webhook:", err)
		}
	}
}

// sendToFeishuWebhook sends a message to Feishu webhook
func sendToFeishuWebhook(webhookURL string, message string) error {
	webhookMessage := FeishuWebhookMessage{
		MsgType: "text",
	}
	webhookMessage.Content.Text = message

	jsonData, err := json.Marshal(webhookMessage)
	if err != nil {
		return fmt.Errorf("failed to marshal webhook message: %v", err)
	}

	req, err := http.NewRequest("POST", webhookURL, bytes.NewBuffer(jsonData))
	if err != nil {
		return fmt.Errorf("failed to create HTTP request: %v", err)
	}
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send HTTP request: %v", err)
	}
	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("failed to read webhook response body: %v", err)
	}

	fmt.Println("Feishu webhook response:", string(responseBody))
	return nil
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133

为了方便代码的复用性,可读性。将100作为一个可配置常量提取出来:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
	ces "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1"
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model"
	region "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/region"
	"io/ioutil"
	"net/http"
	"time"
)

type FeishuWebhookMessage struct {
	MsgType string `json:"msg_type"`
	Content struct {
		Text string `json:"text"`
	} `json:"content"`
}

const (
	// 定时器间隔,用于根据特定时间点触发数据检索。例如:59分时执行任务,就是(59 - 当前时间的分钟数) x 每分钟的秒数
	feishuWebhookURL                = "xxxxx"
	ak                              = "xxxx"
	sk                              = "xxxx"
	loadBalancerConnectionThreshold = 100.00 // 负载均衡连接数阈值
)

func main() {
	auth := basic.NewCredentialsBuilder().
		WithAk(ak).
		WithSk(sk).
		Build()

	client := ces.NewCesClient(
		ces.CesClientBuilder().
			WithRegion(region.ValueOf("cn-east-3")).
			WithCredential(auth).
			Build())

	ticker := time.NewTicker(1 * time.Minute) // Check every 10 minutes to adjust for the next 59th minute.
	for {
		select {
		case t := <-ticker.C:
			// 这里设置只在7-24点执行
			currentHour := t.Hour()
			if currentHour >= 7 && currentHour < 24 {
				go collectDataAndSendToFeishu(client)
			}
		}
	}
}
func collectDataAndSendToFeishu(client *ces.CesClient) {
	currentTime := time.Now().UTC().Truncate(time.Minute)
	startTime := currentTime.Add(-1 * time.Minute)
	endTime := currentTime

	startTimestamp := startTime.UnixNano() / int64(time.Millisecond)
	endTimestamp := endTime.UnixNano() / int64(time.Millisecond)

	request := &model.ShowMetricDataRequest{
		Namespace:  "SYS.ELB",
		MetricName: "m1_cps",
		Dim0:       "lbaas_instance_id,xxxx",
		Filter:     model.GetShowMetricDataRequestFilterEnum().MAX,
		Period:     int32(1),
		From:       startTimestamp,
		To:         endTimestamp,
	}

	response, err := client.ShowMetricData(request)
	if err != nil {
		fmt.Println("Error querying CES data:", err)
		return
	}
	fmt.Printf("CES response: %+v\n", response)

	// Extract max value and timestamp from the response
	var maxConnection float64
	var timestamp int64
	if response.Datapoints != nil && len(*response.Datapoints) > 0 {
		datapoints := *response.Datapoints
		maxConnection = *datapoints[0].Max
		timestamp = datapoints[0].Timestamp
	}

	// Format the timestamp to a readable form
	readableTime := time.Unix(timestamp/1000, (timestamp%1000)*int64(time.Millisecond)).Format("2006-01-02 15:04:05")
	// Prepare the message to send to Feishu
	if maxConnection > loadBalancerConnectionThreshold {
		// Prepare the alert message to send to Feishu
		feishuMessage := fmt.Sprintf("警报:在%s,负载均衡器的连接数超过了%.2f,当前连接数:%.2f", readableTime, loadBalancerConnectionThreshold, maxConnection)

		if err := sendToFeishuWebhook(feishuWebhookURL, feishuMessage); err != nil {
			fmt.Println("Error sending to Feishu webhook:", err)
		}
	}
}

// sendToFeishuWebhook sends a message to Feishu webhook
func sendToFeishuWebhook(webhookURL string, message string) error {
	webhookMessage := FeishuWebhookMessage{
		MsgType: "text",
	}
	webhookMessage.Content.Text = message

	jsonData, err := json.Marshal(webhookMessage)
	if err != nil {
		return fmt.Errorf("failed to marshal webhook message: %v", err)
	}

	req, err := http.NewRequest("POST", webhookURL, bytes.NewBuffer(jsonData))
	if err != nil {
		return fmt.Errorf("failed to create HTTP request: %v", err)
	}
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send HTTP request: %v", err)
	}
	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("failed to read webhook response body: %v", err)
	}

	fmt.Println("Feishu webhook response:", string(responseBody))
	return nil
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

注意loadBalancerConnectionThresholdfloat64.

增加 MetricName多个条件

这里以弹性IP EIP与负载均衡为例,我想查询负载均衡连接数大于100报警,并根据负载均衡对应eip的四个指标:“upstream_bandwidth_usage”,“downstream_bandwidth_usage”,“upstream_bandwidth”,"downstream_bandwidth"报警,完整代码如下:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
	ces "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1"
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model"
	region "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/region"
	"io/ioutil"
	"net/http"
	"time"
)

type FeishuWebhookMessage struct {
	MsgType string `json:"msg_type"`
	Content struct {
		Text string `json:"text"`
	} `json:"content"`
}

const (
	// 定时器间隔,用于根据特定时间点触发数据检索。例如:59分时执行任务,就是(59 - 当前时间的分钟数) x 每分钟的秒数
	feishuWebhookURL                = "xxxxxxx"
	ak                              = "xxx"
	sk                              = "xxxx"
	upstreamBandwidthThreshold      = 40    // 出网带宽阈值,单位Mbps
	downstreamBandwidthThreshold    = 80    // 入网带宽阈值,单位Mbps(示例中以百分比为单位,根据实际单位调整)
	upstreamUsageThreshold          = 20    // 出网带宽使用率阈值,单位百分比
	downstreamUsageThreshold        = 40    // 入网带宽使用率阈值,单位百分比
)

var metricNames = []string{
	"upstream_bandwidth_usage",
	"downstream_bandwidth_usage",
	"upstream_bandwidth",
	"downstream_bandwidth",
}

func main() {
	auth := basic.NewCredentialsBuilder().
		WithAk(ak).
		WithSk(sk).
		Build()

	client := ces.NewCesClient(
		ces.CesClientBuilder().
			WithRegion(region.ValueOf("cn-east-3")).
			WithCredential(auth).
			Build())

	ticker := time.NewTicker(time.Minute) // Check every minute.
	for {
		select {
		case <-ticker.C:
			// 每分钟执行
			go collectDataAndSendToFeishu(client)
		}
	}
}

func collectDataAndSendToFeishu(client *ces.CesClient) {
	currentTime := time.Now().UTC().Truncate(time.Minute)
	startTime := currentTime.Add(-1 * time.Minute)
	endTime := currentTime

	startTimestamp := startTime.UnixMilli()
	endTimestamp := endTime.UnixMilli()
	dimensionValues := "bandwidth_id,xxxx"
	for _, metricName := range metricNames {
		request := &model.ShowMetricDataRequest{
			Namespace:  "SYS.VPC",
			MetricName: metricName,
			Dim0:       dimensionValues, // Replace with actual dimension value
			Filter:     model.GetShowMetricDataRequestFilterEnum().MAX,
			Period:     int32(1),
			From:       startTimestamp,
			To:         endTimestamp,
		}

		response, err := client.ShowMetricData(request)
		if err != nil {
			fmt.Printf("Error querying CES data for %s: %v\n", metricName, err)
			continue
		}

		if response.Datapoints == nil || len(*response.Datapoints) == 0 {
			fmt.Printf("No datapoints received for %s\n", metricName)
			continue
		}

		datapoints := *response.Datapoints
		var maxUsage float64
		for _, point := range datapoints {
			if point.Max != nil {
				if metricName == "upstream_bandwidth" || metricName == "downstream_bandwidth" {
					// Convert from bits to Mbits
					maxUsage = *point.Max / 1000000.0
				} else {
					maxUsage = *point.Max // for utilization metrics, which are percentages
				}
				break // Assuming there's only 1 datapoint with MAX filter
			}
		}

		if (metricName == "upstream_bandwidth" && maxUsage > upstreamBandwidthThreshold) ||
			(metricName == "downstream_bandwidth" && maxUsage > downstreamBandwidthThreshold) ||
			(metricName == "upstream_bandwidth_usage" && maxUsage > upstreamUsageThreshold) ||
			(metricName == "downstream_bandwidth_usage" && maxUsage > downstreamUsageThreshold) {

			alertMessage := createAlertMessage(metricName, maxUsage, endTime)
			if err := sendToFeishuWebhook(feishuWebhookURL, alertMessage); err != nil {
				fmt.Printf("Error sending to Feishu webhook: %v\n", err)
			}
		}
	}
}

func createAlertMessage(metricName string, usage float64, endTime time.Time) string {
	readableTime := endTime.Add(+8 * time.Hour).Format("2006-01-02 15:04:05")
	var alertMessage string

	// 注意阈值和单位已更新,具体文本格式根据实际需要调整
	switch metricName {
	case "upstream_bandwidth":
		alertMessage = fmt.Sprintf("警报:在%s,出网带宽超过了%.2fMbps,当前带宽:%.2fMbps", readableTime, upstreamBandwidthThreshold, usage)
	case "downstream_bandwidth":
		alertMessage = fmt.Sprintf("警报:在%s,入网带宽超过了%.2fMbps,当前带宽:%.2fMbps", readableTime, downstreamBandwidthThreshold, usage)
	case "upstream_bandwidth_usage":
		alertMessage = fmt.Sprintf("警报:在%s,出网带宽使用率超过了%.2f%%,当前使用率:%.2f%%", readableTime, upstreamUsageThreshold, usage)
	case "downstream_bandwidth_usage":
		alertMessage = fmt.Sprintf("警报:在%s,入网带宽使用率超过了%.2f%%,当前使用率:%.2f%%", readableTime, downstreamUsageThreshold, usage)
	}

	return alertMessage
}

func sendToFeishuWebhook(webhookURL string, message string) error {
	webhookMessage := FeishuWebhookMessage{
		MsgType: "text",
	}
	webhookMessage.Content.Text = message

	jsonData, err := json.Marshal(webhookMessage)
	if err != nil {
		return fmt.Errorf("failed to marshal webhook message: %v", err)
	}

	req, err := http.NewRequest("POST", webhookURL, bytes.NewBuffer(jsonData))
	if err != nil {
		return fmt.Errorf("failed to create HTTP request: %v", err)
	}
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send HTTP request: %v", err)
	}
	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("failed to read webhook response body: %v", err)
	}

	fmt.Printf("Feishu webhook response: %s\n", responseBody)
	return nil
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171

测试报警如下:
image.png

其他

  1. 遍历负载均衡列表,批量查询所有负载均衡连接数?发送告警时候传入负载均衡的名称?
  2. 根据负载均衡列表查询绑定的eip实例,查询所有eip对应bandwidth_id,输出所有eip的指标?

总结

此文为你展示了如何通过Go SDK获取华为云上的负载均衡最大连接数and eip指标的多个条件查询,并通过飞书Webhook发送通知的过程。以上的实现可以根据你自己的需求进行调整,比如改变监测的指标或者消息发送的方式。希望本文能帮助你更好地监控和管理华为云上的资源。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/298178
推荐阅读
相关标签
  

闽ICP备14008679号