使用External Metrics API获取成本数据

如果您想要在应用程序开发中使用集群业务的成本或资源数据,除了使用成本数据API,您也可以直接通过Kubernetes External Metrics API进行自定义查询。本文介绍成本套件注册的外部指标(External Metrics)的使用方式和调用示例。

前提条件

External Metrics说明

指标名称

指标说明

cpu_core_request_average

请求的平均CPU核心数。

cpu_core_usage_average

使用的平均CPU核心数。

memory_request_average

请求的平均内存量。

memory_usage_average

使用的平均内存量。

cost_pod_cpu_request

Pod基于CPU请求量的估算成本。

cost_pod_memory_request

Pod基于内存请求量的估算成本。

cost_total

集群所有节点的总成本。

billing_pretax_amount_total

应付的集群总账单费用。

billing_pretax_gross_amount_total

原始的集群总账单费用。

以上的cost_pod_cpu_request和cost_pod_memory_request对应成本估算策略中使用单资源估算策略获得的成本。如需使用权重混合估算,您可以分别获取各资源的成本数据后,再按照如下公式自定义加权聚合。

Pod成本 = cost_pod_cpu_request指标数据 * CPU权重 + cost_pod_memory_request指标数据 * 内存权重

调用示例

Kubernetes客户端目前支持大部分编程语言,例如Java、Go、Python、Ruby等。更多信息,请参见Client Libraries

确定请求参数

名称

类型

是否必选

描述

start

time.Time

查询的开始时间。

end

time.Time

查询的结束时间。

namespace

[]string

过滤命名空间。

controllerKind

[]string

过滤控制器类型。

controllerName

[]string

过滤控制器名称。

pod

[]string

过滤Pod名称。

代码示例

以下示例通过Go语言的客户端库查询Cost External Metrics指标。

package main

import (
	"flag"
	"fmt"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/selection"
	"k8s.io/klog/v2"
	"path/filepath"
	"strings"
	"time"

	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	externalclient "k8s.io/metrics/pkg/client/external_metrics"
)

const (
	WindowLayout = "20060102150405"
)

func getWindowLabelSelectorStr(start, end time.Time) string {
	startStr := start.Format(WindowLayout)
	endStr := end.Format(WindowLayout)

	selector := fmt.Sprintf("window_start=%s,window_end=%s,window_layout=%s", startStr, endStr, WindowLayout)
	klog.Infof("get window label selector str: %s", selector)
	return selector
}

func getFilterLabelSelectorStr(namespace, controllerName, controllerKind, pod []string) string {
	var requirements []labels.Requirement

	addInRequirement := func(key string, values []string) error {
		if len(values) == 0 {
			klog.Infof("filter value is empty: %s", key)
			return nil
		}
		r, err := labels.NewRequirement(key, selection.In, values)
		if err != nil {
			klog.Errorf("failed to parse filter %s to requirement, error: %v", key, err)
			return err
		}
		requirements = append(requirements, *r)
		return nil
	}

	if err := addInRequirement("namespace", namespace); err != nil {
		return ""
	}

	if err := addInRequirement("created_by_name", controllerName); err != nil {
		return ""
	}

	if err := addInRequirement("created_by_kind", controllerKind); err != nil {
		return ""
	}

	if err := addInRequirement("pod", pod); err != nil {
		return ""
	}

	if requirements == nil {
		klog.Infof("filter is empty, do not need to parse.")
		return ""
	}

	selector := labels.NewSelector().Add(requirements...).String()
	klog.Infof("get filter label selector str: %s", selector)
	return selector
}

func main() {
	// 默认从 $HOME/.kube/config 获取集群凭证
	var kubeconfig *string
	if home := homedir.HomeDir(); home != "" {
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}
	flag.Parse()

	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		panic(err.Error())
	}

	externalClient, err := externalclient.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	// external metric名称
	metricName := "cost_pod_cpu_request"

	// 请求开始和结束时间
	start := time.Now().Add(-time.Hour * 2)
	end := time.Now()

	// 请求过滤条件,可选
	var namespace, controllerKind, controllerName, pod []string
	namespace = []string{"default", "kube-system"}
	controllerKind = []string{"DaemonSet"}
	controllerName = []string{"csi-plugin"}

	// 基于请求起止时间和过滤条件构造请求labelSelector
	selectorStr := make([]string, 0)
	if str := getWindowLabelSelectorStr(start, end); str != "" {
		selectorStr = append(selectorStr, str)
	}
	if str := getFilterLabelSelectorStr(namespace, controllerName, controllerKind, pod); str != "" {
		selectorStr = append(selectorStr, str)
	}
	metricSelector, err := labels.Parse(strings.Join(selectorStr, ","))
	if err != nil {
		klog.Errorf("failed to parse metricSelector, error: %v", err)
	}
	
	metrics, err := externalClient.NamespacedMetrics("*").List(metricName, metricSelector)
	if err != nil {
		klog.Errorf("unable to fetch metrics %s from apiServer: %v", metricName, err)
	}
	
	// Pod基于CPU request的估算成本
	for _, item := range metrics.Items {
		klog.Infof("pod: %s, metric: %s, value: %v", item.MetricLabels["pod"], metricName, float64(item.Value.MilliValue())/1000)
	}
}

预期输出:

I0415 17:09:11.327321    9646 main.go:127] pod: csi-plugin-t55b2, metric: cost_pod_cpu_request, value: 0.048
I0415 17:09:11.327371    9646 main.go:127] pod: csi-plugin-8z5ls, metric: cost_pod_cpu_request, value: 0.048
I0415 17:09:11.327381    9646 main.go:127] pod: csi-plugin-97w8x, metric: cost_pod_cpu_request, value: 0.048
I0415 17:09:11.327390    9646 main.go:127] pod: csi-plugin-49crh, metric: cost_pod_cpu_request, value: 0.048
I0415 17:09:11.327397    9646 main.go:127] pod: csi-plugin-22bm5, metric: cost_pod_cpu_request, value: 0.048
I0415 17:09:11.327405    9646 main.go:127] pod: csi-plugin-zmhrk, metric: cost_pod_cpu_request, value: 0.048

相关文档

除了本文的方式外,您也可以通过HTTP API命令查看上报数据,以获取集群成本优化的建议,便于您获取成本数据进行二次开发。具体操作,请参见通过API获取成本数据概述