如果您想要在应用程序开发中使用集群业务的成本或资源数据,除了使用成本数据API,您也可以直接通过Kubernetes External Metrics API进行自定义查询。本文介绍成本套件注册的外部指标(External Metrics)的使用方式和调用示例。
前提条件
已启用成本洞察功能。
已开启阿里云Prometheus监控。
已安装alibaba-cloud-metrics-adapter组件,且在安装组件时设置
AlibabaCloudMetricsAdapter.prometheus.url
参数为阿里云Prometheus监控的地址。具体操作,请参见基于阿里云Prometheus指标的容器水平伸缩。
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。
确定请求参数
名称 | 类型 | 是否必选 | 描述 |
| time.Time | 是 | 查询的开始时间。 |
| time.Time | 是 | 查询的结束时间。 |
| []string | 否 | 过滤命名空间。 |
| []string | 否 | 过滤控制器类型。 |
| []string | 否 | 过滤控制器名称。 |
| []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获取成本数据概述。