mirror of
https://github.com/zalando-incubator/kube-metrics-adapter.git
synced 2025-03-12 13:14:33 +00:00

* This commit adds a --disregard-incompatible-hpas that makes the HPA provider stop erroring out when a collector cannot be created for a metric in a HPA. Useful when kube-metrics-adapter runs alongside another metrics provider. Fixes issue #94. Signed-off-by: Tomás Pinho <me@tomaspinho.com> * Make tests pass Signed-off-by: Tomás Pinho <me@tomaspinho.com> * Wraps the Plugin Not Found error in a new type that can be checked by the caller of a function to determine if its contents should be logged or added as an event to the HPA, when this HPA is incompatible. The disregardIncompatibleHPAs is now targetting only the log or addition of the same event. Signed-off-by: Tomás Pinho <me@tomaspinho.com> * Invert if expression to select when we should log CreateNewMetricsCollector errors: don't log when both conditions are true - it's not a PluginNotFoundError and disregardIncompatibleHPAs flag is set to true. This way, if an error is NOT PluginNotFoundError it will always be logged, and when it IS PluginNotFoundError it will only be logged when disregardIncompatibleHPAs is false. Signed-off-by: Tomás Pinho <me@tomaspinho.com> * Remove redundant "whether to" Signed-off-by: Tomás Pinho <me@tomaspinho.com> * Add test case for updating HPAs via HPA Provider while disregarding incompatible HPAs. Signed-off-by: Tomás Pinho <me@tomaspinho.com>
244 lines
6.6 KiB
Go
244 lines
6.6 KiB
Go
package collector
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/zalando-incubator/kube-metrics-adapter/pkg/annotations"
|
|
autoscalingv2 "k8s.io/api/autoscaling/v2beta2"
|
|
"k8s.io/metrics/pkg/apis/custom_metrics"
|
|
"k8s.io/metrics/pkg/apis/external_metrics"
|
|
)
|
|
|
|
type ObjectReference struct {
|
|
autoscalingv2.CrossVersionObjectReference
|
|
Namespace string
|
|
}
|
|
|
|
type CollectorFactory struct {
|
|
podsPlugins pluginMap
|
|
objectPlugins objectPluginMap
|
|
externalPlugins map[string]CollectorPlugin
|
|
}
|
|
|
|
type objectPluginMap struct {
|
|
Any pluginMap
|
|
Named map[string]*pluginMap
|
|
}
|
|
|
|
type pluginMap struct {
|
|
Any CollectorPlugin
|
|
Named map[string]CollectorPlugin
|
|
}
|
|
|
|
func NewCollectorFactory() *CollectorFactory {
|
|
return &CollectorFactory{
|
|
podsPlugins: pluginMap{Named: map[string]CollectorPlugin{}},
|
|
objectPlugins: objectPluginMap{
|
|
Any: pluginMap{},
|
|
Named: map[string]*pluginMap{},
|
|
},
|
|
externalPlugins: map[string]CollectorPlugin{},
|
|
}
|
|
}
|
|
|
|
type CollectorPlugin interface {
|
|
NewCollector(hpa *autoscalingv2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration) (Collector, error)
|
|
}
|
|
|
|
type PluginNotFoundError struct {
|
|
metricTypeName MetricTypeName
|
|
}
|
|
|
|
func (p *PluginNotFoundError) Error() string {
|
|
return fmt.Sprintf("no plugin found for %s", p.metricTypeName)
|
|
}
|
|
|
|
func (c *CollectorFactory) RegisterPodsCollector(metricCollector string, plugin CollectorPlugin) error {
|
|
if metricCollector == "" {
|
|
c.podsPlugins.Any = plugin
|
|
} else {
|
|
c.podsPlugins.Named[metricCollector] = plugin
|
|
}
|
|
return nil
|
|
|
|
}
|
|
|
|
func (c *CollectorFactory) RegisterObjectCollector(kind, metricCollector string, plugin CollectorPlugin) error {
|
|
if kind == "" {
|
|
if metricCollector == "" {
|
|
c.objectPlugins.Any.Any = plugin
|
|
} else {
|
|
if c.objectPlugins.Any.Named == nil {
|
|
c.objectPlugins.Any.Named = map[string]CollectorPlugin{
|
|
metricCollector: plugin,
|
|
}
|
|
} else {
|
|
c.objectPlugins.Any.Named[metricCollector] = plugin
|
|
}
|
|
}
|
|
} else {
|
|
if named, ok := c.objectPlugins.Named[kind]; ok {
|
|
if metricCollector == "" {
|
|
named.Any = plugin
|
|
} else {
|
|
named.Named[metricCollector] = plugin
|
|
}
|
|
} else {
|
|
if metricCollector == "" {
|
|
c.objectPlugins.Named[kind] = &pluginMap{
|
|
Any: plugin,
|
|
}
|
|
} else {
|
|
c.objectPlugins.Named[kind] = &pluginMap{
|
|
Named: map[string]CollectorPlugin{
|
|
metricCollector: plugin,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *CollectorFactory) RegisterExternalCollector(metrics []string, plugin CollectorPlugin) {
|
|
for _, metric := range metrics {
|
|
c.externalPlugins[metric] = plugin
|
|
}
|
|
}
|
|
|
|
func (c *CollectorFactory) NewCollector(hpa *autoscalingv2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration) (Collector, error) {
|
|
switch config.Type {
|
|
case autoscalingv2.PodsMetricSourceType:
|
|
// first try to find a plugin by format
|
|
if plugin, ok := c.podsPlugins.Named[config.CollectorName]; ok {
|
|
return plugin.NewCollector(hpa, config, interval)
|
|
}
|
|
|
|
// else try to use the default plugin if set
|
|
if c.podsPlugins.Any != nil {
|
|
return c.podsPlugins.Any.NewCollector(hpa, config, interval)
|
|
}
|
|
case autoscalingv2.ObjectMetricSourceType:
|
|
// first try to find a plugin by kind
|
|
if kinds, ok := c.objectPlugins.Named[config.ObjectReference.Kind]; ok {
|
|
if plugin, ok := kinds.Named[config.CollectorName]; ok {
|
|
return plugin.NewCollector(hpa, config, interval)
|
|
}
|
|
|
|
if kinds.Any != nil {
|
|
return kinds.Any.NewCollector(hpa, config, interval)
|
|
}
|
|
break
|
|
}
|
|
|
|
// else try to find a default plugin for this kind
|
|
if plugin, ok := c.objectPlugins.Any.Named[config.CollectorName]; ok {
|
|
return plugin.NewCollector(hpa, config, interval)
|
|
}
|
|
|
|
if c.objectPlugins.Any.Any != nil {
|
|
return c.objectPlugins.Any.Any.NewCollector(hpa, config, interval)
|
|
}
|
|
case autoscalingv2.ExternalMetricSourceType:
|
|
if plugin, ok := c.externalPlugins[config.Metric.Name]; ok {
|
|
return plugin.NewCollector(hpa, config, interval)
|
|
}
|
|
}
|
|
|
|
return nil, &PluginNotFoundError{metricTypeName: config.MetricTypeName}
|
|
}
|
|
|
|
type MetricTypeName struct {
|
|
Type autoscalingv2.MetricSourceType
|
|
Metric autoscalingv2.MetricIdentifier
|
|
}
|
|
|
|
type CollectedMetric struct {
|
|
Type autoscalingv2.MetricSourceType
|
|
Custom custom_metrics.MetricValue
|
|
External external_metrics.ExternalMetricValue
|
|
}
|
|
|
|
type Collector interface {
|
|
GetMetrics() ([]CollectedMetric, error)
|
|
Interval() time.Duration
|
|
}
|
|
|
|
type MetricConfig struct {
|
|
MetricTypeName
|
|
CollectorName string
|
|
Config map[string]string
|
|
ObjectReference custom_metrics.ObjectReference
|
|
PerReplica bool
|
|
Interval time.Duration
|
|
MetricSpec autoscalingv2.MetricSpec
|
|
}
|
|
|
|
// ParseHPAMetrics parses the HPA object into a list of metric configurations.
|
|
func ParseHPAMetrics(hpa *autoscalingv2.HorizontalPodAutoscaler) ([]*MetricConfig, error) {
|
|
metricConfigs := make([]*MetricConfig, 0, len(hpa.Spec.Metrics))
|
|
|
|
// TODO: validate that the specified metric names are defined
|
|
// in the HPA
|
|
parser := make(annotations.AnnotationConfigMap)
|
|
err := parser.Parse(hpa.Annotations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, metric := range hpa.Spec.Metrics {
|
|
typeName := MetricTypeName{
|
|
Type: metric.Type,
|
|
}
|
|
|
|
var ref custom_metrics.ObjectReference
|
|
switch metric.Type {
|
|
case autoscalingv2.PodsMetricSourceType:
|
|
typeName.Metric = metric.Pods.Metric
|
|
case autoscalingv2.ObjectMetricSourceType:
|
|
typeName.Metric = metric.Object.Metric
|
|
ref = custom_metrics.ObjectReference{
|
|
APIVersion: metric.Object.DescribedObject.APIVersion,
|
|
Kind: metric.Object.DescribedObject.Kind,
|
|
Name: metric.Object.DescribedObject.Name,
|
|
Namespace: hpa.Namespace,
|
|
}
|
|
case autoscalingv2.ExternalMetricSourceType:
|
|
typeName.Metric = metric.External.Metric
|
|
case autoscalingv2.ResourceMetricSourceType:
|
|
continue // kube-metrics-adapter does not collect resource metrics
|
|
}
|
|
|
|
config := &MetricConfig{
|
|
MetricTypeName: typeName,
|
|
ObjectReference: ref,
|
|
Config: map[string]string{},
|
|
MetricSpec: metric,
|
|
}
|
|
|
|
if metric.Type == autoscalingv2.ExternalMetricSourceType &&
|
|
metric.External.Metric.Selector != nil &&
|
|
metric.External.Metric.Selector.MatchLabels != nil {
|
|
for k, v := range metric.External.Metric.Selector.MatchLabels {
|
|
config.Config[k] = v
|
|
}
|
|
}
|
|
|
|
annotationConfigs, present := parser.GetAnnotationConfig(typeName.Metric.Name, typeName.Type)
|
|
if present {
|
|
config.CollectorName = annotationConfigs.CollectorName
|
|
config.Interval = annotationConfigs.Interval
|
|
config.PerReplica = annotationConfigs.PerReplica
|
|
// configs specified in annotations takes precedence
|
|
// over labels
|
|
for k, v := range annotationConfigs.Configs {
|
|
config.Config[k] = v
|
|
}
|
|
}
|
|
metricConfigs = append(metricConfigs, config)
|
|
}
|
|
return metricConfigs, nil
|
|
}
|