2018-10-08 13:17:05 +02:00
|
|
|
package collector
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2020-01-30 17:49:22 +01:00
|
|
|
"net/http"
|
2018-10-08 13:17:05 +02:00
|
|
|
"time"
|
|
|
|
|
2018-10-24 17:27:30 +02:00
|
|
|
log "github.com/sirupsen/logrus"
|
2019-04-03 10:23:52 +02:00
|
|
|
autoscalingv2 "k8s.io/api/autoscaling/v2beta2"
|
2019-04-27 15:35:08 +02:00
|
|
|
corev1 "k8s.io/api/core/v1"
|
2018-10-08 13:17:05 +02:00
|
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
|
|
"k8s.io/client-go/kubernetes"
|
|
|
|
"k8s.io/metrics/pkg/apis/custom_metrics"
|
|
|
|
)
|
|
|
|
|
|
|
|
type PodCollectorPlugin struct {
|
|
|
|
client kubernetes.Interface
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewPodCollectorPlugin(client kubernetes.Interface) *PodCollectorPlugin {
|
|
|
|
return &PodCollectorPlugin{
|
|
|
|
client: client,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-03 10:23:52 +02:00
|
|
|
func (p *PodCollectorPlugin) NewCollector(hpa *autoscalingv2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration) (Collector, error) {
|
2018-10-08 13:17:05 +02:00
|
|
|
return NewPodCollector(p.client, hpa, config, interval)
|
|
|
|
}
|
|
|
|
|
|
|
|
type PodCollector struct {
|
|
|
|
client kubernetes.Interface
|
|
|
|
Getter PodMetricsGetter
|
2019-04-05 16:05:37 +02:00
|
|
|
podLabelSelector *metav1.LabelSelector
|
2018-10-08 13:17:05 +02:00
|
|
|
namespace string
|
2019-04-03 10:23:52 +02:00
|
|
|
metric autoscalingv2.MetricIdentifier
|
|
|
|
metricType autoscalingv2.MetricSourceType
|
2018-10-08 13:17:05 +02:00
|
|
|
interval time.Duration
|
2018-10-24 17:27:30 +02:00
|
|
|
logger *log.Entry
|
2020-01-30 17:49:22 +01:00
|
|
|
httpClient *http.Client
|
2018-10-08 13:17:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type PodMetricsGetter interface {
|
2019-04-27 15:35:08 +02:00
|
|
|
GetMetric(pod *corev1.Pod) (float64, error)
|
2018-10-08 13:17:05 +02:00
|
|
|
}
|
|
|
|
|
2019-04-03 10:23:52 +02:00
|
|
|
func NewPodCollector(client kubernetes.Interface, hpa *autoscalingv2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration) (*PodCollector, error) {
|
2018-10-08 13:17:05 +02:00
|
|
|
// get pod selector based on HPA scale target ref
|
|
|
|
selector, err := getPodLabelSelector(client, hpa)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get pod label selector: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c := &PodCollector{
|
|
|
|
client: client,
|
|
|
|
namespace: hpa.Namespace,
|
2019-04-03 10:23:52 +02:00
|
|
|
metric: config.Metric,
|
2018-10-08 13:17:05 +02:00
|
|
|
metricType: config.Type,
|
|
|
|
interval: interval,
|
|
|
|
podLabelSelector: selector,
|
2018-10-24 17:27:30 +02:00
|
|
|
logger: log.WithFields(log.Fields{"Collector": "Pod"}),
|
2018-10-08 13:17:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var getter PodMetricsGetter
|
|
|
|
switch config.CollectorName {
|
|
|
|
case "json-path":
|
|
|
|
var err error
|
|
|
|
getter, err = NewJSONPathMetricsGetter(config.Config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("format '%s' not supported", config.CollectorName)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Getter = getter
|
|
|
|
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *PodCollector) GetMetrics() ([]CollectedMetric, error) {
|
|
|
|
opts := metav1.ListOptions{
|
2019-04-05 16:05:37 +02:00
|
|
|
LabelSelector: labels.Set(c.podLabelSelector.MatchLabels).String(),
|
2018-10-08 13:17:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pods, err := c.client.CoreV1().Pods(c.namespace).List(opts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
values := make([]CollectedMetric, 0, len(pods.Items))
|
|
|
|
|
|
|
|
// TODO: get metrics in parallel
|
|
|
|
for _, pod := range pods.Items {
|
|
|
|
value, err := c.Getter.GetMetric(&pod)
|
|
|
|
if err != nil {
|
2018-10-24 17:27:30 +02:00
|
|
|
c.logger.Errorf("Failed to get metrics from pod '%s/%s': %v", pod.Namespace, pod.Name, err)
|
2018-10-08 13:17:05 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
metricValue := CollectedMetric{
|
|
|
|
Type: c.metricType,
|
|
|
|
Custom: custom_metrics.MetricValue{
|
|
|
|
DescribedObject: custom_metrics.ObjectReference{
|
|
|
|
APIVersion: "v1",
|
|
|
|
Kind: "Pod",
|
|
|
|
Name: pod.Name,
|
|
|
|
Namespace: pod.Namespace,
|
|
|
|
},
|
2019-04-05 16:05:37 +02:00
|
|
|
Metric: custom_metrics.MetricIdentifier{Name: c.metric.Name, Selector: c.podLabelSelector},
|
2019-04-03 10:23:52 +02:00
|
|
|
Timestamp: metav1.Time{Time: time.Now().UTC()},
|
|
|
|
Value: *resource.NewMilliQuantity(int64(value*1000), resource.DecimalSI),
|
2018-10-08 13:17:05 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
values = append(values, metricValue)
|
|
|
|
}
|
|
|
|
|
|
|
|
return values, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *PodCollector) Interval() time.Duration {
|
|
|
|
return c.interval
|
|
|
|
}
|
|
|
|
|
2019-04-05 16:05:37 +02:00
|
|
|
func getPodLabelSelector(client kubernetes.Interface, hpa *autoscalingv2.HorizontalPodAutoscaler) (*metav1.LabelSelector, error) {
|
2018-10-08 13:17:05 +02:00
|
|
|
switch hpa.Spec.ScaleTargetRef.Kind {
|
|
|
|
case "Deployment":
|
|
|
|
deployment, err := client.AppsV1().Deployments(hpa.Namespace).Get(hpa.Spec.ScaleTargetRef.Name, metav1.GetOptions{})
|
|
|
|
if err != nil {
|
2019-04-05 16:05:37 +02:00
|
|
|
return nil, err
|
2018-10-08 13:17:05 +02:00
|
|
|
}
|
2019-04-05 16:05:37 +02:00
|
|
|
return deployment.Spec.Selector, nil
|
2018-10-08 13:17:05 +02:00
|
|
|
case "StatefulSet":
|
|
|
|
sts, err := client.AppsV1().StatefulSets(hpa.Namespace).Get(hpa.Spec.ScaleTargetRef.Name, metav1.GetOptions{})
|
|
|
|
if err != nil {
|
2019-04-05 16:05:37 +02:00
|
|
|
return nil, err
|
2018-10-08 13:17:05 +02:00
|
|
|
}
|
2019-04-05 16:05:37 +02:00
|
|
|
return sts.Spec.Selector, nil
|
2018-10-08 13:17:05 +02:00
|
|
|
}
|
|
|
|
|
2019-04-05 16:05:37 +02:00
|
|
|
return nil, fmt.Errorf("unable to get pod label selector for scale target ref '%s'", hpa.Spec.ScaleTargetRef.Kind)
|
2018-10-08 13:17:05 +02:00
|
|
|
}
|