karmada/pkg/metricsadapter/provider/resourcemetrics.go

463 lines
15 KiB
Go
Executable File

package provider
import (
"context"
"sync"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
"k8s.io/metrics/pkg/apis/metrics"
metricsv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
clusterlister "github.com/karmada-io/karmada/pkg/generated/listers/cluster/v1alpha1"
"github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager"
"github.com/karmada-io/karmada/pkg/util/helper"
)
const (
// labelSelectorAnnotationInternal is the annotation used internal in karmada-metrics-adapter,
// to record the selector specified by the user
labelSelectorAnnotationInternal = "internal.karmada.io/selector"
)
var (
// podMetricsGVR is the gvr of pod metrics(v1beta1 version)
podMetricsGVR = metricsv1beta1.SchemeGroupVersion.WithResource("pods")
// nodeMetricsGVR is the gvr of node metrics(v1beta1 version)
nodeMetricsGVR = metricsv1beta1.SchemeGroupVersion.WithResource("nodes")
// PodsGVR is the gvr of pods
PodsGVR = corev1.SchemeGroupVersion.WithResource("pods")
// NodesGVR is the gvr of nodes
NodesGVR = corev1.SchemeGroupVersion.WithResource("nodes")
)
type queryResourceFromClustersFunc func(sci genericmanager.SingleClusterInformerManager, clusterName string) error
type queryMetricsFromClustersFunc func(sci genericmanager.SingleClusterInformerManager, clusterName string) (interface{}, error)
// ResourceMetricsProvider is a resource metrics provider, to provide cpu/memory metrics
type ResourceMetricsProvider struct {
PodLister *PodLister
NodeLister *NodeLister
clusterLister clusterlister.ClusterLister
informerManager genericmanager.MultiClusterInformerManager
}
// NewResourceMetricsProvider creates a new resource metrics provider
func NewResourceMetricsProvider(clusterLister clusterlister.ClusterLister, informerManager genericmanager.MultiClusterInformerManager) *ResourceMetricsProvider {
return &ResourceMetricsProvider{
clusterLister: clusterLister,
informerManager: informerManager,
PodLister: NewPodLister(),
NodeLister: NewNodeLister(),
}
}
// getMetricsParallel is a parallel func of to query metrics from member clusters
func (r *ResourceMetricsProvider) getMetricsParallel(resourceFunc queryResourceFromClustersFunc,
metricsFunc queryMetricsFromClustersFunc) ([]interface{}, error) {
clusters, err := r.clusterLister.List(labels.Everything())
if err != nil {
klog.Errorf("Failed to list clusters: %v", err)
return nil, err
}
// step 1. Find out the target clusters with lister cache
var targetClusters []string
for _, cluster := range clusters {
sci := r.informerManager.GetSingleClusterManager(cluster.Name)
if sci == nil {
klog.Errorf("Failed to get cluster(%s) manager", cluster.Name)
continue
}
err := resourceFunc(sci, cluster.Name)
if err != nil {
if !errors.IsNotFound(err) {
klog.Errorf("Failed to query resource in cluster(%s): %v", cluster.Name, err)
}
continue
}
targetClusters = append(targetClusters, cluster.Name)
}
var metrics []interface{}
if len(targetClusters) == 0 {
return metrics, nil
}
// step 2. Query metrics from the filtered target clusters
metricsChanel := make(chan interface{})
var wg sync.WaitGroup
for _, clusterName := range targetClusters {
wg.Add(1)
go func(cluster string) {
defer wg.Done()
sci := r.informerManager.GetSingleClusterManager(cluster)
if sci == nil {
klog.Errorf("Failed to get cluster(%s) manager", cluster)
return
}
metrics, err := metricsFunc(sci, cluster)
if err != nil {
if !errors.IsNotFound(err) {
klog.Errorf("Failed to query metrics in cluster(%s): %v", cluster, err)
}
return
}
// If there are multiple metrics with same name, it's ok because it's an array instead of a map.
// The HPA controller will calculate the average utilization with the array.
metricsChanel <- metrics
}(clusterName)
}
go func() {
wg.Wait()
close(metricsChanel)
}()
for {
data, ok := <-metricsChanel
if !ok {
break
}
metrics = append(metrics, data)
}
return metrics, nil
}
// queryPodMetricsByName queries metrics by pod name from target clusters
func (r *ResourceMetricsProvider) queryPodMetricsByName(name, namespace string) ([]metrics.PodMetrics, error) {
resourceQueryFunc := func(sci genericmanager.SingleClusterInformerManager, _ string) error {
_, err := sci.Lister(PodsGVR).ByNamespace(namespace).Get(name)
return err
}
metricsQueryFunc := func(sci genericmanager.SingleClusterInformerManager, _ string) (interface{}, error) {
metrics, err := sci.GetClient().Resource(podMetricsGVR).
Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{})
return metrics, err
}
metricsQuery, err := r.getMetricsParallel(resourceQueryFunc, metricsQueryFunc)
if err != nil {
return nil, err
}
var podMetrics []metrics.PodMetrics
for index := range metricsQuery {
internalMetrics, err := metricsConvertV1beta1PodToInternalPod(*metricsQuery[index].(*unstructured.Unstructured))
if err != nil {
continue
}
podMetrics = append(podMetrics, internalMetrics...)
}
return podMetrics, nil
}
// queryPodMetricsBySelector queries metrics by pod selector from target clusters
func (r *ResourceMetricsProvider) queryPodMetricsBySelector(selector, namespace string) ([]metrics.PodMetrics, error) {
labelSelector, err := labels.Parse(selector)
if err != nil {
klog.Errorf("Failed to parse label selector: %v", err)
return nil, err
}
resourceQueryFunc := func(sci genericmanager.SingleClusterInformerManager, clusterName string) error {
pods, err := sci.Lister(PodsGVR).ByNamespace(namespace).List(labelSelector)
if err != nil {
klog.Errorf("Failed to list pods in cluster(%s): %v", clusterName, err)
return err
}
if len(pods) == 0 {
return errors.NewNotFound(PodsGVR.GroupResource(), "")
}
return nil
}
metricsQueryFunc := func(sci genericmanager.SingleClusterInformerManager, _ string) (interface{}, error) {
metrics, err := sci.GetClient().Resource(podMetricsGVR).
Namespace(namespace).List(context.Background(), metav1.ListOptions{
LabelSelector: selector,
})
return metrics, err
}
metricsQuery, err := r.getMetricsParallel(resourceQueryFunc, metricsQueryFunc)
if err != nil {
return nil, err
}
var podMetrics []metrics.PodMetrics
for index := range metricsQuery {
metricsData := metricsQuery[index].(*unstructured.UnstructuredList)
internalMetrics, err := metricsConvertV1beta1PodToInternalPod(metricsData.Items...)
if err != nil {
continue
}
podMetrics = append(podMetrics, internalMetrics...)
}
return podMetrics, nil
}
// queryNodeMetricsByName queries metrics by node name from target clusters
func (r *ResourceMetricsProvider) queryNodeMetricsByName(name string) ([]metrics.NodeMetrics, error) {
resourceQueryFunc := func(sci genericmanager.SingleClusterInformerManager, _ string) error {
_, err := sci.Lister(NodesGVR).Get(name)
return err
}
metricsQueryFunc := func(sci genericmanager.SingleClusterInformerManager, _ string) (interface{}, error) {
metrics, err := sci.GetClient().Resource(nodeMetricsGVR).Get(context.Background(), name, metav1.GetOptions{})
return metrics, err
}
metricsQuery, err := r.getMetricsParallel(resourceQueryFunc, metricsQueryFunc)
if err != nil {
return nil, err
}
var nodeMetrics []metrics.NodeMetrics
for index := range metricsQuery {
internalMetrics, err := metricsConvertV1beta1NodeToInternalNode(*metricsQuery[index].(*unstructured.Unstructured))
if err != nil {
continue
}
nodeMetrics = append(nodeMetrics, internalMetrics...)
}
return nodeMetrics, nil
}
// queryNodeMetricsBySelector queries metrics by node selector from target clusters
func (r *ResourceMetricsProvider) queryNodeMetricsBySelector(selector string) ([]metrics.NodeMetrics, error) {
labelSelector, err := labels.Parse(selector)
if err != nil {
klog.Errorf("Failed to parse label selector: %v", err)
return nil, err
}
resourceQueryFunc := func(sci genericmanager.SingleClusterInformerManager, clusterName string) error {
nodes, err := sci.Lister(NodesGVR).List(labelSelector)
if err != nil {
klog.Errorf("Failed to list pods in cluster(%s): %v", clusterName, err)
return err
}
if len(nodes) == 0 {
return errors.NewNotFound(PodsGVR.GroupResource(), "")
}
return nil
}
metricsQueryFunc := func(sci genericmanager.SingleClusterInformerManager, _ string) (interface{}, error) {
metrics, err := sci.GetClient().Resource(nodeMetricsGVR).List(context.Background(), metav1.ListOptions{
LabelSelector: selector,
})
return metrics, err
}
metricsQuery, err := r.getMetricsParallel(resourceQueryFunc, metricsQueryFunc)
if err != nil {
return nil, err
}
var nodeMetrics []metrics.NodeMetrics
for index := range metricsQuery {
metricsData := metricsQuery[index].(*unstructured.UnstructuredList)
internalMetrics, err := metricsConvertV1beta1NodeToInternalNode(metricsData.Items...)
if err != nil {
continue
}
nodeMetrics = append(nodeMetrics, internalMetrics...)
}
return nodeMetrics, nil
}
// GetPodMetrics queries metrics by the internal constructed pod
func (r *ResourceMetricsProvider) GetPodMetrics(pods ...*metav1.PartialObjectMetadata) ([]metrics.PodMetrics, error) {
var podMetrics []metrics.PodMetrics
// In the previous step, we construct pods list with only one element.
if len(pods) != 1 {
return podMetrics, nil
}
// In the previous step, if query with label selector, the name will be set to empty
if pods[0].Name == "" {
namespace := pods[0].Namespace
selectorStr := pods[0].Annotations[labelSelectorAnnotationInternal]
return r.queryPodMetricsBySelector(selectorStr, namespace)
}
return r.queryPodMetricsByName(pods[0].Name, pods[0].Namespace)
}
// GetNodeMetrics queries metrics by the internal constructed node
func (r *ResourceMetricsProvider) GetNodeMetrics(nodes ...*corev1.Node) ([]metrics.NodeMetrics, error) {
var nodeMetrics []metrics.NodeMetrics
// In the previous step, we construct node list with only one element, this should never happen
if len(nodes) != 1 {
// never reach here
return nodeMetrics, nil
}
// In the previous step, if query with label selector, the name will be set to empty
if nodes[0].Name == "" {
selectorStr := nodes[0].Annotations[labelSelectorAnnotationInternal]
return r.queryNodeMetricsBySelector(selectorStr)
}
return r.queryNodeMetricsByName(nodes[0].Name)
}
// PodLister is an internal lister for pods
type PodLister struct {
namespaceSpecified string
}
// NewPodLister creates an internal new PodLister
func NewPodLister() *PodLister {
return &PodLister{}
}
// List returns the internal constructed pod with label selector info
func (p *PodLister) List(selector labels.Selector) (ret []runtime.Object, err error) {
klog.V(4).Infof("List query pods with selector: %v", selector.String())
podData := &v1.PartialObjectMetadata{
TypeMeta: v1.TypeMeta{},
ObjectMeta: v1.ObjectMeta{
Namespace: p.namespaceSpecified,
Annotations: map[string]string{
labelSelectorAnnotationInternal: selector.String(),
},
},
}
return []runtime.Object{podData}, nil
}
// Get returns the internal constructed pod with name info
func (p *PodLister) Get(name string) (runtime.Object, error) {
klog.V(4).Infof("Query pod in namespace(%s) with name:%s", p.namespaceSpecified, name)
podData := &v1.PartialObjectMetadata{
TypeMeta: v1.TypeMeta{},
ObjectMeta: v1.ObjectMeta{
Name: name,
Namespace: p.namespaceSpecified,
},
}
return podData, nil
}
// ByNamespace returns the pod lister with namespace info
func (p *PodLister) ByNamespace(namespace string) cache.GenericNamespaceLister {
klog.V(4).Infof("Query Pods in namespace: %s", namespace)
listerCopy := &PodLister{}
listerCopy.namespaceSpecified = namespace
return listerCopy
}
// NodeLister is an internal lister for nodes
type NodeLister struct {
}
// NewNodeLister creates an internal new NodeLister
func NewNodeLister() *NodeLister {
return &NodeLister{}
}
// List returns the internal constructed node with label selector info
func (n *NodeLister) List(selector labels.Selector) (ret []*corev1.Node, err error) {
klog.V(4).Infof("Query node metrics with selector: %s", selector.String())
node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
labelSelectorAnnotationInternal: selector.String(),
},
},
}
return []*corev1.Node{node}, nil
}
// Get returns the internal constructed node with name info
func (n *NodeLister) Get(name string) (*corev1.Node, error) {
klog.V(4).Infof("Query node metrics with name:%s", name)
node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
return node, nil
}
// metricsConvertV1beta1PodToInternalPod converts metricsv1beta1.PodMetrics to metrics.PodMetrics
func metricsConvertV1beta1PodToInternalPod(objs ...unstructured.Unstructured) ([]metrics.PodMetrics, error) {
var podMetricsV1beta1 []metricsv1beta1.PodMetrics
for index := range objs {
single := metricsv1beta1.PodMetrics{}
if err := helper.ConvertToTypedObject(&objs[index], &single); err != nil {
klog.Errorf("Failed to convert to typed object: %v", err)
return nil, err
}
podMetricsV1beta1 = append(podMetricsV1beta1, single)
}
var podMetricsInternal []metrics.PodMetrics
for index := range podMetricsV1beta1 {
single := metrics.PodMetrics{}
if err := metricsv1beta1.Convert_v1beta1_PodMetrics_To_metrics_PodMetrics(&podMetricsV1beta1[index], &single, nil); err != nil {
klog.Errorf("Failed to convert to typed object: %v", err)
return nil, err
}
podMetricsInternal = append(podMetricsInternal, single)
}
return podMetricsInternal, nil
}
// metricsConvertV1beta1NodeToInternalNode converts metricsv1beta1.NodeMetrics to metrics.NodeMetrics
func metricsConvertV1beta1NodeToInternalNode(objs ...unstructured.Unstructured) ([]metrics.NodeMetrics, error) {
var nodeMetricsV1beta1 []metricsv1beta1.NodeMetrics
for index := range objs {
single := metricsv1beta1.NodeMetrics{}
if err := helper.ConvertToTypedObject(&objs[index], &single); err != nil {
klog.Errorf("Failed to convert to typed object: %v", err)
return nil, err
}
nodeMetricsV1beta1 = append(nodeMetricsV1beta1, single)
}
var nodeMetricsInternal []metrics.NodeMetrics
for index := range nodeMetricsV1beta1 {
single := metrics.NodeMetrics{}
if err := metricsv1beta1.Convert_v1beta1_NodeMetrics_To_metrics_NodeMetrics(&nodeMetricsV1beta1[index], &single, nil); err != nil {
klog.Errorf("Failed to convert to typed object: %v", err)
return nil, err
}
nodeMetricsInternal = append(nodeMetricsInternal, single)
}
return nodeMetricsInternal, nil
}