2018-10-08 13:17:05 +02:00
package collector
import (
2020-06-27 18:39:12 +02:00
"context"
2018-10-08 13:17:05 +02:00
"fmt"
"time"
2024-03-25 11:12:48 -07:00
argoRolloutsClient "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned"
2018-10-24 17:27:30 +02:00
log "github.com/sirupsen/logrus"
2023-05-03 23:01:19 +02:00
autoscalingv2 "k8s.io/api/autoscaling/v2"
2020-06-27 18:39:12 +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"
2021-04-19 18:51:18 +03:00
"github.com/zalando-incubator/kube-metrics-adapter/pkg/collector/httpmetrics"
2018-10-08 13:17:05 +02:00
)
type PodCollectorPlugin struct {
2024-03-25 11:12:48 -07:00
client kubernetes . Interface
argoRolloutsClient argoRolloutsClient . Interface
2018-10-08 13:17:05 +02:00
}
2024-03-25 11:12:48 -07:00
func NewPodCollectorPlugin ( client kubernetes . Interface , argoRolloutsClient argoRolloutsClient . Interface ) * PodCollectorPlugin {
2018-10-08 13:17:05 +02:00
return & PodCollectorPlugin {
2024-03-25 11:12:48 -07:00
client : client ,
argoRolloutsClient : argoRolloutsClient ,
2018-10-08 13:17:05 +02:00
}
}
2019-04-03 10:23:52 +02:00
func ( p * PodCollectorPlugin ) NewCollector ( hpa * autoscalingv2 . HorizontalPodAutoscaler , config * MetricConfig , interval time . Duration ) ( Collector , error ) {
2024-03-25 11:12:48 -07:00
return NewPodCollector ( p . client , p . argoRolloutsClient , hpa , config , interval )
2018-10-08 13:17:05 +02:00
}
type PodCollector struct {
client kubernetes . Interface
2020-04-01 20:32:09 +02:00
Getter httpmetrics . 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
2021-04-19 18:51:18 +03:00
minPodReadyAge time . Duration
2018-10-08 13:17:05 +02:00
interval time . Duration
2018-10-24 17:27:30 +02:00
logger * log . Entry
2018-10-08 13:17:05 +02:00
}
2024-03-25 11:12:48 -07:00
func NewPodCollector ( client kubernetes . Interface , argoRolloutsClient argoRolloutsClient . 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
2024-03-25 11:12:48 -07:00
selector , err := getPodLabelSelector ( client , argoRolloutsClient , hpa )
2018-10-08 13:17:05 +02:00
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 ,
2021-04-19 18:51:18 +03:00
minPodReadyAge : config . MinPodReadyAge ,
2018-10-08 13:17:05 +02:00
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
}
2020-04-01 20:32:09 +02:00
var getter httpmetrics . PodMetricsGetter
2020-11-04 20:40:53 +01:00
switch config . CollectorType {
2018-10-08 13:17:05 +02:00
case "json-path" :
var err error
2020-04-01 20:32:09 +02:00
getter , err = httpmetrics . NewPodMetricsJSONPathGetter ( config . Config )
2018-10-08 13:17:05 +02:00
if err != nil {
return nil , err
}
default :
2020-11-04 20:40:53 +01:00
return nil , fmt . Errorf ( "format '%s' not supported" , config . CollectorType )
2018-10-08 13:17:05 +02:00
}
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
}
2020-06-27 18:39:12 +02:00
pods , err := c . client . CoreV1 ( ) . Pods ( c . namespace ) . List ( context . TODO ( ) , opts )
2018-10-08 13:17:05 +02:00
if err != nil {
return nil , err
}
2020-05-13 20:05:29 -06:00
ch := make ( chan CollectedMetric )
errCh := make ( chan error )
2021-04-17 00:41:10 +03:00
skippedPodsCount := 0
2018-10-08 13:17:05 +02:00
for _ , pod := range pods . Items {
2021-04-17 00:41:10 +03:00
2021-04-19 18:51:18 +03:00
isPodReady , podReadyAge := GetPodReadyAge ( pod )
if isPodReady {
2021-04-30 16:03:57 +03:00
if pod . DeletionTimestamp != nil {
skippedPodsCount ++
2021-05-05 11:22:51 +03:00
c . logger . Debugf ( "Skipping metrics collection for pod %s/%s because it is being terminated (DeletionTimestamp: %s)" , pod . Namespace , pod . Name , pod . DeletionTimestamp )
2021-04-30 16:03:57 +03:00
} else if podReadyAge < c . minPodReadyAge {
2021-04-17 00:41:10 +03:00
skippedPodsCount ++
2021-04-20 11:21:20 +03:00
c . logger . Warnf ( "Skipping metrics collection for pod %s/%s because it's ready age is %s and min-pod-ready-age is set to %s" , pod . Namespace , pod . Name , podReadyAge , c . minPodReadyAge )
2021-04-30 16:03:57 +03:00
} else {
2021-04-30 17:58:39 +03:00
go c . getPodMetric ( pod , ch , errCh )
2021-04-17 00:41:10 +03:00
}
} else {
skippedPodsCount ++
2021-05-05 11:22:51 +03:00
c . logger . Debugf ( "Skipping metrics collection for pod %s/%s because it's status is not Ready." , pod . Namespace , pod . Name )
2021-04-17 00:41:10 +03:00
}
2020-05-13 20:05:29 -06:00
}
2018-10-08 13:17:05 +02:00
2021-04-17 00:41:10 +03:00
values := make ( [ ] CollectedMetric , 0 , ( len ( pods . Items ) - skippedPodsCount ) )
for i := 0 ; i < ( len ( pods . Items ) - skippedPodsCount ) ; i ++ {
2020-05-13 20:05:29 -06:00
select {
2020-06-27 18:39:12 +02:00
case err := <- errCh :
2020-05-13 20:05:29 -06:00
c . logger . Error ( err )
2020-06-27 18:39:12 +02:00
case resp := <- ch :
2020-05-13 20:05:29 -06:00
values = append ( values , resp )
2018-10-08 13:17:05 +02:00
}
}
return values , nil
}
func ( c * PodCollector ) Interval ( ) time . Duration {
return c . interval
}
2020-05-13 20:05:29 -06:00
func ( c * PodCollector ) getPodMetric ( pod corev1 . Pod , ch chan CollectedMetric , errCh chan error ) {
value , err := c . Getter . GetMetric ( & pod )
if err != nil {
errCh <- fmt . Errorf ( "Failed to get metrics from pod '%s/%s': %v" , pod . Namespace , pod . Name , err )
return
}
ch <- CollectedMetric {
2021-02-19 11:11:29 +01:00
Namespace : c . namespace ,
Type : c . metricType ,
2020-05-13 20:05:29 -06:00
Custom : custom_metrics . MetricValue {
DescribedObject : custom_metrics . ObjectReference {
APIVersion : "v1" ,
Kind : "Pod" ,
Name : pod . Name ,
Namespace : pod . Namespace ,
} ,
Metric : custom_metrics . MetricIdentifier { Name : c . metric . Name , Selector : c . podLabelSelector } ,
Timestamp : metav1 . Time { Time : time . Now ( ) . UTC ( ) } ,
Value : * resource . NewMilliQuantity ( int64 ( value * 1000 ) , resource . DecimalSI ) ,
} ,
}
}
2024-03-25 11:12:48 -07:00
func getPodLabelSelector ( client kubernetes . Interface , argoRolloutsClient argoRolloutsClient . Interface , hpa * autoscalingv2 . HorizontalPodAutoscaler ) ( * metav1 . LabelSelector , error ) {
2018-10-08 13:17:05 +02:00
switch hpa . Spec . ScaleTargetRef . Kind {
case "Deployment" :
2020-06-27 18:39:12 +02:00
deployment , err := client . AppsV1 ( ) . Deployments ( hpa . Namespace ) . Get ( context . TODO ( ) , hpa . Spec . ScaleTargetRef . Name , metav1 . GetOptions { } )
2018-10-08 13:17:05 +02:00
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" :
2020-06-27 18:39:12 +02:00
sts , err := client . AppsV1 ( ) . StatefulSets ( hpa . Namespace ) . Get ( context . TODO ( ) , hpa . Spec . ScaleTargetRef . Name , metav1 . GetOptions { } )
2018-10-08 13:17:05 +02:00
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
2024-03-25 11:12:48 -07:00
case "Rollout" :
rollout , err := argoRolloutsClient . ArgoprojV1alpha1 ( ) . Rollouts ( hpa . Namespace ) . Get ( context . TODO ( ) , hpa . Spec . ScaleTargetRef . Name , metav1 . GetOptions { } )
if err != nil {
return nil , err
}
return rollout . 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
}
2021-04-17 00:41:10 +03:00
2021-04-19 18:51:18 +03:00
// GetPodReadyAge extracts corev1.PodReady condition from the given pod object and
2021-04-20 11:21:20 +03:00
// returns true, time.Duration() for LastTransitionTime if the condition corev1.PodReady is found. Returns time.Duration(0s), false if the condition is not present.
2021-04-19 18:51:18 +03:00
func GetPodReadyAge ( pod corev1 . Pod ) ( bool , time . Duration ) {
podReadyAge := time . Duration ( 0 * time . Second )
2021-04-17 00:41:10 +03:00
conditions := pod . Status . Conditions
if conditions == nil {
2021-04-19 18:51:18 +03:00
return false , podReadyAge
2021-04-17 00:41:10 +03:00
}
for i := range conditions {
2021-04-21 18:25:42 +03:00
if conditions [ i ] . Type == corev1 . PodReady && conditions [ i ] . Status == corev1 . ConditionTrue {
2021-04-20 11:21:20 +03:00
podReadyAge = time . Since ( conditions [ i ] . LastTransitionTime . Time )
2021-04-19 18:51:18 +03:00
return true , podReadyAge
2021-04-17 00:41:10 +03:00
}
}
2021-04-19 18:51:18 +03:00
return false , podReadyAge
2021-04-17 00:41:10 +03:00
}