Update metrics apiserver to support filtering by labels

This commit is contained in:
Sergii Koshel 2020-02-12 17:54:40 +02:00
parent 03bc47e9fb
commit d091fff18b
11 changed files with 331 additions and 187 deletions

View file

@ -118,8 +118,8 @@ func (p *prometheusProvider) metricsFor(valueSet pmodel.Vector, info provider.Cu
}, nil
}
func (p *prometheusProvider) buildQuery(info provider.CustomMetricInfo, namespace string, names ...string) (pmodel.Vector, error) {
query, found := p.QueryForMetric(info, namespace, names...)
func (p *prometheusProvider) buildQuery(info provider.CustomMetricInfo, namespace string, metricSelector labels.Selector, names ...string) (pmodel.Vector, error) {
query, found := p.QueryForMetric(info, namespace, metricSelector, names...)
if !found {
return nil, provider.NewMetricNotFoundError(info.GroupResource, info.Metric)
}
@ -140,9 +140,9 @@ func (p *prometheusProvider) buildQuery(info provider.CustomMetricInfo, namespac
return *queryResults.Vector, nil
}
func (p *prometheusProvider) GetMetricByName(name types.NamespacedName, info provider.CustomMetricInfo) (*custom_metrics.MetricValue, error) {
func (p *prometheusProvider) GetMetricByName(name types.NamespacedName, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error) {
// construct a query
queryResults, err := p.buildQuery(info, name.Namespace, name.Name)
queryResults, err := p.buildQuery(info, name.Namespace, metricSelector, name.Name)
if err != nil {
return nil, err
}
@ -171,7 +171,7 @@ func (p *prometheusProvider) GetMetricByName(name types.NamespacedName, info pro
return p.metricFor(resultValue, name, info)
}
func (p *prometheusProvider) GetMetricBySelector(namespace string, selector labels.Selector, info provider.CustomMetricInfo) (*custom_metrics.MetricValueList, error) {
func (p *prometheusProvider) GetMetricBySelector(namespace string, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
// fetch a list of relevant resource names
resourceNames, err := helpers.ListObjectNames(p.mapper, p.kubeClient, namespace, selector, info)
if err != nil {
@ -181,7 +181,7 @@ func (p *prometheusProvider) GetMetricBySelector(namespace string, selector labe
}
// construct the actual query
queryResults, err := p.buildQuery(info, namespace, resourceNames...)
queryResults, err := p.buildQuery(info, namespace, metricSelector, resourceNames...)
if err != nil {
return nil, err
}

View file

@ -22,6 +22,7 @@ import (
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
"github.com/directxman12/k8s-prometheus-adapter/pkg/naming"
@ -51,7 +52,7 @@ type SeriesRegistry interface {
ListAllMetrics() []provider.CustomMetricInfo
// SeriesForMetric looks up the minimum required series information to make a query for the given metric
// against the given resource (namespace may be empty for non-namespaced resources)
QueryForMetric(info provider.CustomMetricInfo, namespace string, resourceNames ...string) (query prom.Selector, found bool)
QueryForMetric(info provider.CustomMetricInfo, namespace string, metricSelector labels.Selector, resourceNames ...string) (query prom.Selector, found bool)
// MatchValuesToNames matches result values to resource names for the given metric and value set
MatchValuesToNames(metricInfo provider.CustomMetricInfo, values pmodel.Vector) (matchedValues map[string]pmodel.SampleValue, found bool)
}
@ -135,7 +136,7 @@ func (r *basicSeriesRegistry) ListAllMetrics() []provider.CustomMetricInfo {
return r.metrics
}
func (r *basicSeriesRegistry) QueryForMetric(metricInfo provider.CustomMetricInfo, namespace string, resourceNames ...string) (prom.Selector, bool) {
func (r *basicSeriesRegistry) QueryForMetric(metricInfo provider.CustomMetricInfo, namespace string, metricSelector labels.Selector, resourceNames ...string) (prom.Selector, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
@ -156,7 +157,7 @@ func (r *basicSeriesRegistry) QueryForMetric(metricInfo provider.CustomMetricInf
return "", false
}
query, err := info.namer.QueryForSeries(info.seriesName, metricInfo.GroupResource, namespace, resourceNames...)
query, err := info.namer.QueryForSeries(info.seriesName, metricInfo.GroupResource, namespace, metricSelector, resourceNames...)
if err != nil {
klog.Errorf("unable to construct query for metric %s: %v", metricInfo.String(), err)
return "", false

View file

@ -27,7 +27,9 @@ import (
coreapi "k8s.io/api/core/v1"
extapi "k8s.io/api/extensions/v1beta1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/selection"
config "github.com/directxman12/k8s-prometheus-adapter/cmd/config-gen/utils"
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
@ -118,14 +120,23 @@ var seriesRegistryTestSeries = [][]prom.Series{
}
type regTestCase struct {
title string
info provider.CustomMetricInfo
namespace string
resourceNames []string
title string
info provider.CustomMetricInfo
namespace string
resourceNames []string
metricSelector labels.Selector
expectedQuery string
}
func mustNewLabelRequirement(key string, op selection.Operator, vals []string) *labels.Requirement {
req, err := labels.NewRequirement(key, op, vals)
if err != nil {
panic(err)
}
return req
}
var _ = Describe("Series Registry", func() {
var (
registry *basicSeriesRegistry
@ -144,89 +155,110 @@ var _ = Describe("Series Registry", func() {
testCases := []regTestCase{
// container metrics
{
title: "container metrics gauge / multiple resource names",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "pods"}, true, "some_usage"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
title: "container metrics gauge / multiple resource names",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "pods"}, true, "some_usage"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
metricSelector: labels.Everything(),
expectedQuery: "sum(container_some_usage{namespace=\"somens\",pod_name=~\"somepod1|somepod2\",container_name!=\"POD\"}) by (pod_name)",
},
{
title: "container metrics counter",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "pods"}, true, "some_count"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
title: "container metrics counter",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "pods"}, true, "some_count"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(container_some_count_total{namespace=\"somens\",pod_name=~\"somepod1|somepod2\",container_name!=\"POD\"}[1m])) by (pod_name)",
},
{
title: "container metrics seconds counter",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "pods"}, true, "some_time"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
title: "container metrics seconds counter",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "pods"}, true, "some_time"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(container_some_time_seconds_total{namespace=\"somens\",pod_name=~\"somepod1|somepod2\",container_name!=\"POD\"}[1m])) by (pod_name)",
},
// namespaced metrics
{
title: "namespaced metrics counter / multidimensional (service)",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "service"}, true, "ingress_hits"},
namespace: "somens",
resourceNames: []string{"somesvc"},
title: "namespaced metrics counter / multidimensional (service)",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "service"}, true, "ingress_hits"},
namespace: "somens",
resourceNames: []string{"somesvc"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(ingress_hits_total{kube_namespace=\"somens\",kube_service=\"somesvc\"}[1m])) by (kube_service)",
},
{
title: "namespaced metrics counter / multidimensional (ingress)",
info: provider.CustomMetricInfo{schema.GroupResource{Group: "extensions", Resource: "ingress"}, true, "ingress_hits"},
title: "namespaced metrics counter / multidimensional (service) / selection using labels",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "service"}, true, "ingress_hits"},
namespace: "somens",
resourceNames: []string{"someingress"},
resourceNames: []string{"somesvc"},
metricSelector: labels.NewSelector().Add(
*mustNewLabelRequirement("param1", selection.Equals, []string{"value1"}),
),
expectedQuery: "sum(rate(ingress_hits_total{param1=\"value1\",kube_namespace=\"somens\",kube_service=\"somesvc\"}[1m])) by (kube_service)",
},
{
title: "namespaced metrics counter / multidimensional (ingress)",
info: provider.CustomMetricInfo{schema.GroupResource{Group: "extensions", Resource: "ingress"}, true, "ingress_hits"},
namespace: "somens",
resourceNames: []string{"someingress"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(ingress_hits_total{kube_namespace=\"somens\",kube_ingress=\"someingress\"}[1m])) by (kube_ingress)",
},
{
title: "namespaced metrics counter / multidimensional (pod)",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "pod"}, true, "ingress_hits"},
namespace: "somens",
resourceNames: []string{"somepod"},
title: "namespaced metrics counter / multidimensional (pod)",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "pod"}, true, "ingress_hits"},
namespace: "somens",
resourceNames: []string{"somepod"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(ingress_hits_total{kube_namespace=\"somens\",kube_pod=\"somepod\"}[1m])) by (kube_pod)",
},
{
title: "namespaced metrics gauge",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "service"}, true, "service_proxy_packets"},
namespace: "somens",
resourceNames: []string{"somesvc"},
title: "namespaced metrics gauge",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "service"}, true, "service_proxy_packets"},
namespace: "somens",
resourceNames: []string{"somesvc"},
metricSelector: labels.Everything(),
expectedQuery: "sum(service_proxy_packets{kube_namespace=\"somens\",kube_service=\"somesvc\"}) by (kube_service)",
},
{
title: "namespaced metrics seconds counter",
info: provider.CustomMetricInfo{schema.GroupResource{Group: "extensions", Resource: "deployment"}, true, "work_queue_wait"},
namespace: "somens",
resourceNames: []string{"somedep"},
title: "namespaced metrics seconds counter",
info: provider.CustomMetricInfo{schema.GroupResource{Group: "extensions", Resource: "deployment"}, true, "work_queue_wait"},
namespace: "somens",
resourceNames: []string{"somedep"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(work_queue_wait_seconds_total{kube_namespace=\"somens\",kube_deployment=\"somedep\"}[1m])) by (kube_deployment)",
},
// non-namespaced series
{
title: "root scoped metrics gauge",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "node"}, false, "node_gigawatts"},
resourceNames: []string{"somenode"},
title: "root scoped metrics gauge",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "node"}, false, "node_gigawatts"},
resourceNames: []string{"somenode"},
metricSelector: labels.Everything(),
expectedQuery: "sum(node_gigawatts{kube_node=\"somenode\"}) by (kube_node)",
},
{
title: "root scoped metrics counter",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "persistentvolume"}, false, "volume_claims"},
resourceNames: []string{"somepv"},
title: "root scoped metrics counter",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "persistentvolume"}, false, "volume_claims"},
resourceNames: []string{"somepv"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(volume_claims_total{kube_persistentvolume=\"somepv\"}[1m])) by (kube_persistentvolume)",
},
{
title: "root scoped metrics seconds counter",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "node"}, false, "node_fan"},
resourceNames: []string{"somenode"},
title: "root scoped metrics seconds counter",
info: provider.CustomMetricInfo{schema.GroupResource{Resource: "node"}, false, "node_fan"},
resourceNames: []string{"somenode"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(node_fan_seconds_total{kube_node=\"somenode\"}[1m])) by (kube_node)",
},
@ -236,7 +268,7 @@ var _ = Describe("Series Registry", func() {
tc := tc // copy to avoid iteration variable issues
It(fmt.Sprintf("should build a query for %s", tc.title), func() {
By(fmt.Sprintf("composing the query for the %s metric on %v in namespace %s", tc.info, tc.resourceNames, tc.namespace))
outputQuery, found := registry.QueryForMetric(tc.info, tc.namespace, tc.resourceNames...)
outputQuery, found := registry.QueryForMetric(tc.info, tc.namespace, tc.metricSelector, tc.resourceNames...)
Expect(found).To(BeTrue(), "metric %s should be available", tc.info)
By("verifying that the query is as expected")