From 532a5ce0332a8466df42bc944800e6668e349801 Mon Sep 17 00:00:00 2001 From: Dominik Augustin Date: Sun, 2 Apr 2023 14:36:47 +0200 Subject: [PATCH] feat: add pda analyzer Adds a PodDisruptionBudget analyzer, that checks if PDBs have matching pods with their defined selector. Signed-off-by: Dominik Augustin --- pkg/analyzer/analysis.go | 2 ++ pkg/analyzer/analyzer.go | 1 + pkg/analyzer/events.go | 31 ++---------------- pkg/analyzer/pdbAnalyzer.go | 64 +++++++++++++++++++++++++++++++++++++ pkg/analyzer/podAnalyzer.go | 2 +- pkg/analyzer/pvcAnalyzer.go | 2 +- 6 files changed, 72 insertions(+), 30 deletions(-) create mode 100644 pkg/analyzer/pdbAnalyzer.go diff --git a/pkg/analyzer/analysis.go b/pkg/analyzer/analysis.go index 7462e5a4b9..5df6c566cb 100644 --- a/pkg/analyzer/analysis.go +++ b/pkg/analyzer/analysis.go @@ -5,6 +5,7 @@ import ( autoscalingv1 "k8s.io/api/autoscaling/v1" v1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" + policyv1 "k8s.io/api/policy/v1" ) type AnalysisConfiguration struct { @@ -21,6 +22,7 @@ type PreAnalysis struct { Endpoint v1.Endpoints Ingress networkingv1.Ingress HorizontalPodAutoscalers autoscalingv1.HorizontalPodAutoscaler + PodDiscruptionBudget policyv1.PodDisruptionBudget } type Analysis struct { diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 316845778e..8247c1a2e4 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -23,6 +23,7 @@ var coreAnalyzerMap = map[string]func(ctx context.Context, config *AnalysisConfi var additionalAnalyzerMap = map[string]func(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error{ "HorizontalPodAutoScaler": AnalyzeHpa, + "PodDisruptionBudget": AnalyzePdb, } func RunAnalysis(ctx context.Context, filters []string, config *AnalysisConfiguration, diff --git a/pkg/analyzer/events.go b/pkg/analyzer/events.go index 8cee7e17d6..b593567757 100644 --- a/pkg/analyzer/events.go +++ b/pkg/analyzer/events.go @@ -2,42 +2,17 @@ package analyzer import ( "context" - "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func FetchLatestPodEvent(ctx context.Context, kubernetesClient *kubernetes.Client, pod *v1.Pod) (*v1.Event, error) { - - // get the list of events - events, err := kubernetesClient.GetClient().CoreV1().Events(pod.Namespace).List(ctx, - metav1.ListOptions{ - FieldSelector: "involvedObject.name=" + pod.Name, - }) - - if err != nil { - return nil, err - } - // find most recent event - var latestEvent *v1.Event - for _, event := range events.Items { - if latestEvent == nil { - latestEvent = &event - } - if event.LastTimestamp.After(latestEvent.LastTimestamp.Time) { - latestEvent = &event - } - } - return latestEvent, nil -} - -func FetchLatestPvcEvent(ctx context.Context, kubernetesClient *kubernetes.Client, pvc *v1.PersistentVolumeClaim) (*v1.Event, error) { +func FetchLatestEvent(ctx context.Context, kubernetesClient *kubernetes.Client, namespace string, name string) (*v1.Event, error) { // get the list of events - events, err := kubernetesClient.GetClient().CoreV1().Events(pvc.Namespace).List(ctx, + events, err := kubernetesClient.GetClient().CoreV1().Events(namespace).List(ctx, metav1.ListOptions{ - FieldSelector: "involvedObject.name=" + pvc.Name, + FieldSelector: "involvedObject.name=" + name, }) if err != nil { diff --git a/pkg/analyzer/pdbAnalyzer.go b/pkg/analyzer/pdbAnalyzer.go new file mode 100644 index 0000000000..4acaa415b7 --- /dev/null +++ b/pkg/analyzer/pdbAnalyzer.go @@ -0,0 +1,64 @@ +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 AnalyzePdb(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, + analysisResults *[]Analysis) error { + + list, err := client.GetClient().PolicyV1().PodDisruptionBudgets(config.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + + var preAnalysis = map[string]PreAnalysis{} + + for _, pdb := range list.Items { + var failures []string + + evt, err := FetchLatestEvent(ctx, client, pdb.Namespace, pdb.Name) + if err != nil || evt == nil { + continue + } + + if evt.Reason == "NoPods" && evt.Message != "" { + if pdb.Spec.Selector != nil { + for k, v := range pdb.Spec.Selector.MatchLabels { + failures = append(failures, fmt.Sprintf("%s, expected label %s=%s", evt.Message, k, v)) + } + for _, v := range pdb.Spec.Selector.MatchExpressions { + failures = append(failures, fmt.Sprintf("%s, expected expression %s", evt.Message, v)) + } + } else { + failures = append(failures, fmt.Sprintf("%s, selector is nil", evt.Message)) + } + } + + if len(failures) > 0 { + preAnalysis[fmt.Sprintf("%s/%s", pdb.Namespace, pdb.Name)] = PreAnalysis{ + PodDiscruptionBudget: pdb, + FailureDetails: failures, + } + } + } + + for key, value := range preAnalysis { + var currentAnalysis = Analysis{ + Kind: "PodDisruptionBudget", + Name: key, + Error: value.FailureDetails, + } + + parent, _ := util.GetParent(client, value.PodDiscruptionBudget.ObjectMeta) + currentAnalysis.ParentObject = parent + *analysisResults = append(*analysisResults, currentAnalysis) + } + + return nil +} diff --git a/pkg/analyzer/podAnalyzer.go b/pkg/analyzer/podAnalyzer.go index 9ce7b803e1..2c02ced644 100644 --- a/pkg/analyzer/podAnalyzer.go +++ b/pkg/analyzer/podAnalyzer.go @@ -47,7 +47,7 @@ func AnalyzePod(ctx context.Context, config *AnalysisConfiguration, if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" { // parse the event log and append details - evt, err := FetchLatestPodEvent(ctx, client, &pod) + evt, err := FetchLatestEvent(ctx, client, pod.Namespace, pod.Name) if err != nil || evt == nil { continue } diff --git a/pkg/analyzer/pvcAnalyzer.go b/pkg/analyzer/pvcAnalyzer.go index 6823bca61f..95b5d9124f 100644 --- a/pkg/analyzer/pvcAnalyzer.go +++ b/pkg/analyzer/pvcAnalyzer.go @@ -27,7 +27,7 @@ func AnalyzePersistentVolumeClaim(ctx context.Context, config *AnalysisConfigura if pvc.Status.Phase == "Pending" { // parse the event log and append details - evt, err := FetchLatestPvcEvent(ctx, client, &pvc) + evt, err := FetchLatestEvent(ctx, client, pvc.Namespace, pvc.Name) if err != nil || evt == nil { continue }