From 360387249feb9a999286aaa874a13007986219a5 Mon Sep 17 00:00:00 2001 From: Matthis Holleville Date: Fri, 31 Mar 2023 18:19:23 +0200 Subject: [PATCH] feat: add hpa analyzer and init additionalAnalyzers Signed-off-by: Matthis Holleville --- README.md | 6 +++ cmd/filters/add.go | 6 ++- cmd/filters/list.go | 5 ++- cmd/filters/remove.go | 4 +- pkg/analyzer/analysis.go | 14 ++++--- pkg/analyzer/analyzer.go | 43 +++++++++++++++++--- pkg/analyzer/hpaAnalyzer.go | 79 +++++++++++++++++++++++++++++++++++++ 7 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 pkg/analyzer/hpaAnalyzer.go diff --git a/README.md b/README.md index 4007263626..1293e9fc3b 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ you will be able to write your own analyzers. ### Built in analyzers +#### Enabled by default + - [x] podAnalyzer - [x] pvcAnalyzer - [x] rsAnalyzer @@ -67,6 +69,10 @@ you will be able to write your own analyzers. - [x] eventAnalyzer - [x] ingressAnalyzer +#### Optional + +- [x] hpaAnalyzer + ## Usage ``` diff --git a/cmd/filters/add.go b/cmd/filters/add.go index 2387fa95a4..72eaa281de 100644 --- a/cmd/filters/add.go +++ b/cmd/filters/add.go @@ -18,7 +18,9 @@ var addCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { inputFilters := strings.Split(args[0], ",") - availableFilters := analyzer.ListFilters() + coreFilters, additionalFilters := analyzer.ListFilters() + + availableFilters := append(coreFilters, additionalFilters...) // Verify filter exist invalidFilters := []string{} @@ -47,7 +49,7 @@ var addCmd = &cobra.Command{ // Get defined active_filters activeFilters := viper.GetStringSlice("active_filters") if len(activeFilters) == 0 { - activeFilters = availableFilters + activeFilters = coreFilters } mergedFilters := append(activeFilters, inputFilters...) diff --git a/cmd/filters/list.go b/cmd/filters/list.go index 349ad8beba..6d40cf629d 100644 --- a/cmd/filters/list.go +++ b/cmd/filters/list.go @@ -16,10 +16,11 @@ var listCmd = &cobra.Command{ Long: `The list command displays a list of available filters that can be used to analyze Kubernetes resources.`, Run: func(cmd *cobra.Command, args []string) { activeFilters := viper.GetStringSlice("active_filters") - availableFilters := analyzer.ListFilters() + coreFilters, additionalFilters := analyzer.ListFilters() + availableFilters := append(coreFilters, additionalFilters...) if len(activeFilters) == 0 { - activeFilters = availableFilters + activeFilters = coreFilters } inactiveFilters := util.SliceDiff(availableFilters, activeFilters) diff --git a/cmd/filters/remove.go b/cmd/filters/remove.go index dab99a1e6e..acaa026368 100644 --- a/cmd/filters/remove.go +++ b/cmd/filters/remove.go @@ -21,8 +21,10 @@ var removeCmd = &cobra.Command{ // Get defined active_filters activeFilters := viper.GetStringSlice("active_filters") + coreFilters, _ := analyzer.ListFilters() + if len(activeFilters) == 0 { - activeFilters = analyzer.ListFilters() + activeFilters = coreFilters } // Check if input input filters is not empty diff --git a/pkg/analyzer/analysis.go b/pkg/analyzer/analysis.go index b05ef07b55..7462e5a4b9 100644 --- a/pkg/analyzer/analysis.go +++ b/pkg/analyzer/analysis.go @@ -2,6 +2,7 @@ package analyzer import ( appsv1 "k8s.io/api/apps/v1" + autoscalingv1 "k8s.io/api/autoscaling/v1" v1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" ) @@ -13,12 +14,13 @@ type AnalysisConfiguration struct { } type PreAnalysis struct { - Pod v1.Pod - FailureDetails []string - ReplicaSet appsv1.ReplicaSet - PersistentVolumeClaim v1.PersistentVolumeClaim - Endpoint v1.Endpoints - Ingress networkingv1.Ingress + Pod v1.Pod + FailureDetails []string + ReplicaSet appsv1.ReplicaSet + PersistentVolumeClaim v1.PersistentVolumeClaim + Endpoint v1.Endpoints + Ingress networkingv1.Ingress + HorizontalPodAutoscalers autoscalingv1.HorizontalPodAutoscaler } type Analysis struct { diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 72bc8ea786..316845778e 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/viper" ) -var analyzerMap = map[string]func(ctx context.Context, config *AnalysisConfiguration, +var coreAnalyzerMap = map[string]func(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error{ "Pod": AnalyzePod, "ReplicaSet": AnalyzeReplicaSet, @@ -20,12 +20,19 @@ var analyzerMap = map[string]func(ctx context.Context, config *AnalysisConfigura "Ingress": AnalyzeIngress, } +var additionalAnalyzerMap = map[string]func(ctx context.Context, config *AnalysisConfiguration, + client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error{ + "HorizontalPodAutoScaler": AnalyzeHpa, +} + func RunAnalysis(ctx context.Context, filters []string, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error { activeFilters := viper.GetStringSlice("active_filters") + analyzerMap := getAnalyzerMap() + // if there are no filters selected and no active_filters then run all of them if len(filters) == 0 && len(activeFilters) == 0 { for _, analyzer := range analyzerMap { @@ -97,10 +104,34 @@ func ParseViaAI(ctx context.Context, config *AnalysisConfiguration, return response, nil } -func ListFilters() []string { - keys := make([]string, 0, len(analyzerMap)) - for k := range analyzerMap { - keys = append(keys, k) +func ListFilters() ([]string, []string) { + coreKeys := make([]string, 0, len(coreAnalyzerMap)) + for k := range coreAnalyzerMap { + coreKeys = append(coreKeys, k) + } + + additionalKeys := make([]string, 0, len(additionalAnalyzerMap)) + for k := range additionalAnalyzerMap { + additionalKeys = append(additionalKeys, k) + } + return coreKeys, additionalKeys +} + +func getAnalyzerMap() map[string]func(ctx context.Context, config *AnalysisConfiguration, + client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error { + + mergedMap := make(map[string]func(ctx context.Context, config *AnalysisConfiguration, + client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error) + + // add core analyzer + for key, value := range coreAnalyzerMap { + mergedMap[key] = value } - return keys + + // add additional analyzer + for key, value := range additionalAnalyzerMap { + mergedMap[key] = value + } + + return mergedMap } diff --git a/pkg/analyzer/hpaAnalyzer.go b/pkg/analyzer/hpaAnalyzer.go new file mode 100644 index 0000000000..3ee4717f4d --- /dev/null +++ b/pkg/analyzer/hpaAnalyzer.go @@ -0,0 +1,79 @@ +package analyzer + +import ( + "context" + "fmt" + + "github.com/k8sgpt-ai/k8sgpt/pkg/ai" + "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" + "github.com/k8sgpt-ai/k8sgpt/pkg/util" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func AnalyzeHpa(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, + analysisResults *[]Analysis) error { + + list, err := client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(config.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + + var preAnalysis = map[string]PreAnalysis{} + + for _, hpa := range list.Items { + var failures []string + + // check ScaleTargetRef exist + scaleTargetRef := hpa.Spec.ScaleTargetRef + scaleTargetRefNotFound := false + + switch scaleTargetRef.Kind { + case "Deployment": + _, err := client.GetClient().AppsV1().Deployments(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{}) + if err != nil { + scaleTargetRefNotFound = true + } + case "ReplicationController": + _, err := client.GetClient().CoreV1().ReplicationControllers(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{}) + if err != nil { + scaleTargetRefNotFound = true + } + case "ReplicaSet": + _, err := client.GetClient().AppsV1().ReplicaSets(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{}) + if err != nil { + scaleTargetRefNotFound = true + } + case "StatefulSet": + _, err := client.GetClient().AppsV1().Deployments(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{}) + if err != nil { + scaleTargetRefNotFound = true + } + } + + if scaleTargetRefNotFound { + failures = append(failures, fmt.Sprintf("HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name)) + } + + if len(failures) > 0 { + preAnalysis[fmt.Sprintf("%s/%s", hpa.Namespace, hpa.Name)] = PreAnalysis{ + HorizontalPodAutoscalers: hpa, + FailureDetails: failures, + } + } + + } + + for key, value := range preAnalysis { + var currentAnalysis = Analysis{ + Kind: "HorizontalPodAutoscaler", + Name: key, + Error: value.FailureDetails, + } + + parent, _ := util.GetParent(client, value.Ingress.ObjectMeta) + currentAnalysis.ParentObject = parent + *analysisResults = append(*analysisResults, currentAnalysis) + } + + return nil +}