From 14df7188c38ef15c258474bafcddc2e747b0ebd1 Mon Sep 17 00:00:00 2001 From: liusong Date: Wed, 10 May 2023 10:11:34 +0800 Subject: [PATCH 1/3] feat:Find the Orphan Volume in k8s cluster --- deploy/craned/deployment.yaml | 4 ++ pkg/recommendation/framework/context.go | 9 +++++ pkg/recommendation/manager.go | 3 ++ pkg/recommendation/recommender/const.go | 3 ++ .../recommender/volumes/filter.go | 29 +++++++++++++++ .../recommender/volumes/observe.go | 10 +++++ .../recommender/volumes/prepare.go | 22 +++++++++++ .../recommender/volumes/recommend.go | 20 ++++++++++ .../recommender/volumes/registry.go | 27 ++++++++++++++ pkg/utils/pod.go | 37 +++++++++++++++++++ 10 files changed, 164 insertions(+) create mode 100644 pkg/recommendation/recommender/volumes/filter.go create mode 100644 pkg/recommendation/recommender/volumes/observe.go create mode 100644 pkg/recommendation/recommender/volumes/prepare.go create mode 100644 pkg/recommendation/recommender/volumes/recommend.go create mode 100644 pkg/recommendation/recommender/volumes/registry.go diff --git a/deploy/craned/deployment.yaml b/deploy/craned/deployment.yaml index be013e17e..f7913bbc3 100644 --- a/deploy/craned/deployment.yaml +++ b/deploy/craned/deployment.yaml @@ -119,6 +119,10 @@ data: acceptedResources: - kind: Node apiVersion: v1 + - name: Volumes + acceptedResources: + - kind: PersistentVolume + apiVersion: v1 --- apiVersion: v1 kind: ConfigMap diff --git a/pkg/recommendation/framework/context.go b/pkg/recommendation/framework/context.go index dbeeb416e..32377109f 100644 --- a/pkg/recommendation/framework/context.go +++ b/pkg/recommendation/framework/context.go @@ -69,6 +69,8 @@ type RecommendationContext struct { HPA *autoscalingv2.HorizontalPodAutoscaler // HPA Object EHPA *autoscalingapi.EffectiveHorizontalPodAutoscaler + // Orphan Volumes + Volumes []corev1.PersistentVolume } func NewRecommendationContext(context context.Context, identity ObjectIdentity, recommendationRule *v1alpha1.RecommendationRule, predictorMgr predictormgr.Manager, dataProviders map[providers.DataSourceType]providers.History, recommendation *v1alpha1.Recommendation, client client.Client, scaleClient scale.ScalesGetter) RecommendationContext { @@ -173,6 +175,13 @@ func RetrievePods(ctx *RecommendationContext) error { pods, err := utils.GetDaemonSetPods(ctx.Client, ctx.Recommendation.Spec.TargetRef.Namespace, ctx.Recommendation.Spec.TargetRef.Name) ctx.Pods = pods return err + } else if ctx.Recommendation.Spec.TargetRef.Kind == "PersistentVolume" { + volumes, err := utils.GetOrphanVolumes(ctx.Client) + if err != nil { + return err + } + ctx.Volumes = volumes + return nil } else { pods, err := utils.GetPodsFromScale(ctx.Client, ctx.Scale) ctx.Pods = pods diff --git a/pkg/recommendation/manager.go b/pkg/recommendation/manager.go index 3b4c64616..de482df61 100644 --- a/pkg/recommendation/manager.go +++ b/pkg/recommendation/manager.go @@ -18,6 +18,7 @@ import ( "github.com/gocrane/crane/pkg/recommendation/recommender/idlenode" "github.com/gocrane/crane/pkg/recommendation/recommender/replicas" "github.com/gocrane/crane/pkg/recommendation/recommender/resource" + "github.com/gocrane/crane/pkg/recommendation/recommender/volumes" ) type RecommenderManager interface { @@ -74,6 +75,8 @@ func (m *manager) GetRecommenderWithRule(recommenderName string, recommendationR return resource.NewResourceRecommender(r, recommendationRule, m.oomRecorder) case recommender.IdleNodeRecommender: return idlenode.NewIdleNodeRecommender(r, recommendationRule) + case recommender.VolumesRecommender: + return volumes.NewServiceRecommender(r, recommendationRule) default: return nil, fmt.Errorf("unknown recommender name: %s", recommenderName) } diff --git a/pkg/recommendation/recommender/const.go b/pkg/recommendation/recommender/const.go index 56d7ad097..180e27404 100644 --- a/pkg/recommendation/recommender/const.go +++ b/pkg/recommendation/recommender/const.go @@ -12,4 +12,7 @@ const ( // IdleNodeRecommender name IdleNodeRecommender string = "IdleNode" + + // VolumesRecommender name + VolumesRecommender string = "Volumes" ) diff --git a/pkg/recommendation/recommender/volumes/filter.go b/pkg/recommendation/recommender/volumes/filter.go new file mode 100644 index 000000000..b1aef8dd3 --- /dev/null +++ b/pkg/recommendation/recommender/volumes/filter.go @@ -0,0 +1,29 @@ +package volumes + +import ( + "github.com/gocrane/crane/pkg/recommendation/framework" +) + +// Filter out k8s resources that are not supported by the recommender. +func (rr *VolumesRecommender) Filter(ctx *framework.RecommendationContext) error { + var err error + + // filter resource that not match objectIdentity + if err = rr.BaseRecommender.Filter(ctx); err != nil { + return err + } + + if err = framework.RetrievePodTemplate(ctx); err != nil { + return err + } + + if err = framework.RetrieveScale(ctx); err != nil { + return err + } + + if err = framework.RetrievePods(ctx); err != nil { + return err + } + + return nil +} diff --git a/pkg/recommendation/recommender/volumes/observe.go b/pkg/recommendation/recommender/volumes/observe.go new file mode 100644 index 000000000..ab48d714a --- /dev/null +++ b/pkg/recommendation/recommender/volumes/observe.go @@ -0,0 +1,10 @@ +package volumes + +import ( + "github.com/gocrane/crane/pkg/recommendation/framework" +) + +// Observe enhance the observability. +func (s *VolumesRecommender) Observe(ctx *framework.RecommendationContext) error { + return nil +} diff --git a/pkg/recommendation/recommender/volumes/prepare.go b/pkg/recommendation/recommender/volumes/prepare.go new file mode 100644 index 000000000..dc7227123 --- /dev/null +++ b/pkg/recommendation/recommender/volumes/prepare.go @@ -0,0 +1,22 @@ +package volumes + +import ( + "github.com/gocrane/crane/pkg/recommendation/framework" +) + +// CheckDataProviders in PrePrepare phase, will create data source provider via your recommendation config. +func (rr *VolumesRecommender) CheckDataProviders(ctx *framework.RecommendationContext) error { + if err := rr.BaseRecommender.CheckDataProviders(ctx); err != nil { + return err + } + + return nil +} + +func (rr *VolumesRecommender) CollectData(ctx *framework.RecommendationContext) error { + return nil +} + +func (rr *VolumesRecommender) PostProcessing(ctx *framework.RecommendationContext) error { + return nil +} diff --git a/pkg/recommendation/recommender/volumes/recommend.go b/pkg/recommendation/recommender/volumes/recommend.go new file mode 100644 index 000000000..5463bf983 --- /dev/null +++ b/pkg/recommendation/recommender/volumes/recommend.go @@ -0,0 +1,20 @@ +package volumes + +import ( + "github.com/gocrane/crane/pkg/recommendation/framework" +) + +func (s *VolumesRecommender) PreRecommend(ctx *framework.RecommendationContext) error { + return nil +} + +func (s *VolumesRecommender) Recommend(ctx *framework.RecommendationContext) error { + ctx.Recommendation.Status.Action = "Delete" + ctx.Recommendation.Status.Description = "It is an Orphan Volumes" + return nil +} + +// Policy add some logic for result of recommend phase. +func (s *VolumesRecommender) Policy(ctx *framework.RecommendationContext) error { + return nil +} diff --git a/pkg/recommendation/recommender/volumes/registry.go b/pkg/recommendation/recommender/volumes/registry.go new file mode 100644 index 000000000..a215c446e --- /dev/null +++ b/pkg/recommendation/recommender/volumes/registry.go @@ -0,0 +1,27 @@ +package volumes + +import ( + analysisv1alph1 "github.com/gocrane/api/analysis/v1alpha1" + "github.com/gocrane/crane/pkg/recommendation/config" + "github.com/gocrane/crane/pkg/recommendation/recommender" + "github.com/gocrane/crane/pkg/recommendation/recommender/apis" + "github.com/gocrane/crane/pkg/recommendation/recommender/base" +) + +var _ recommender.Recommender = &VolumesRecommender{} + +type VolumesRecommender struct { + base.BaseRecommender +} + +func (s *VolumesRecommender) Name() string { + return recommender.VolumesRecommender +} + +// NewVolumesRecommender create a new Volumes recommender. +func NewServiceRecommender(recommender apis.Recommender, recommendationRule analysisv1alph1.RecommendationRule) (*VolumesRecommender, error) { + recommender = config.MergeRecommenderConfigFromRule(recommender, recommendationRule) + return &VolumesRecommender{ + *base.NewBaseRecommender(recommender), + }, nil +} diff --git a/pkg/utils/pod.go b/pkg/utils/pod.go index 22d40547e..cd3574408 100644 --- a/pkg/utils/pod.go +++ b/pkg/utils/pod.go @@ -312,6 +312,43 @@ func GetNodePods(kubeClient client.Client, nodeName string) ([]corev1.Pod, error return podList.Items, nil } +// GetOrphanVolumes returns Orphan Volumes +func GetOrphanVolumes(kubeClient client.Client) ([]corev1.PersistentVolume, error) { + // Get a list of all volumes + volumes := &corev1.PersistentVolumeList{} + if err := kubeClient.List(context.Background(), volumes); err != nil { + return nil, err + } + + // Get a list of all pods + pods := &corev1.PodList{} + if err := kubeClient.List(context.Background(), pods); err != nil { + return nil, err + } + + // Check if each volume is being used by any pods + orphanVolumes := []corev1.PersistentVolume{} + for _, volume := range volumes.Items { + if isOrphanVolume(&volume, pods) { + orphanVolumes = append(orphanVolumes, volume) + } + } + + return orphanVolumes, nil +} + +// volume is not being used by any pod +func isOrphanVolume(volume *corev1.PersistentVolume, pods *corev1.PodList) bool { + for _, pod := range pods.Items { + for _, volumeClaim := range pod.Spec.Volumes { + if volumeClaim.PersistentVolumeClaim != nil && volumeClaim.PersistentVolumeClaim.ClaimName == volume.Spec.ClaimRef.Name { + return false + } + } + } + return true +} + func IsPodTerminated(pod *corev1.Pod) bool { return pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed } From cb541ce246d92ac669a2bfe1a30a7435077b19cf Mon Sep 17 00:00:00 2001 From: liusong Date: Wed, 10 May 2023 10:51:01 +0800 Subject: [PATCH 2/3] delete --- pkg/recommendation/recommender/volumes/filter.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/recommendation/recommender/volumes/filter.go b/pkg/recommendation/recommender/volumes/filter.go index b1aef8dd3..c116704bd 100644 --- a/pkg/recommendation/recommender/volumes/filter.go +++ b/pkg/recommendation/recommender/volumes/filter.go @@ -13,14 +13,6 @@ func (rr *VolumesRecommender) Filter(ctx *framework.RecommendationContext) error return err } - if err = framework.RetrievePodTemplate(ctx); err != nil { - return err - } - - if err = framework.RetrieveScale(ctx); err != nil { - return err - } - if err = framework.RetrievePods(ctx); err != nil { return err } From 7c3e187f259defde4e83adbc4126baa87beb860a Mon Sep 17 00:00:00 2001 From: liusong Date: Tue, 23 May 2023 08:51:52 +0800 Subject: [PATCH 3/3] refactor volume recommender --- pkg/recommendation/framework/context.go | 23 +++++++++++-------- .../recommender/volumes/filter.go | 6 ++--- .../recommender/volumes/registry.go | 6 ++++- pkg/utils/pod.go | 8 +++---- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/pkg/recommendation/framework/context.go b/pkg/recommendation/framework/context.go index 63c185104..ab6b600c4 100644 --- a/pkg/recommendation/framework/context.go +++ b/pkg/recommendation/framework/context.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "sync" jsonpatch "github.com/evanphx/json-patch" @@ -73,8 +74,6 @@ type RecommendationContext struct { HPA *autoscalingv2.HorizontalPodAutoscaler // HPA Object EHPA *autoscalingapi.EffectiveHorizontalPodAutoscaler - // Orphan Volumes - Volumes []corev1.PersistentVolume } func NewRecommendationContext(context context.Context, identity ObjectIdentity, recommendationRule *v1alpha1.RecommendationRule, predictorMgr predictormgr.Manager, dataProviders map[providers.DataSourceType]providers.History, recommendation *v1alpha1.Recommendation, client client.Client, scaleClient scale.ScalesGetter, oomRecorder oom.Recorder) RecommendationContext { @@ -180,6 +179,19 @@ func RetrieveScale(ctx *RecommendationContext) error { return nil } +func RetrieveVolumes(ctx *RecommendationContext) error { + if ctx.Recommendation.Spec.TargetRef.Kind == "PersistentVolume" { + volumes, err := utils.GetOrphanVolumes(ctx.Client) + if len(volumes) == 0 { + return err + } + str := strings.Join(volumes, ",") + ctx.Recommendation.Status.RecommendedValue = str + return err + } + return nil +} + func RetrievePods(ctx *RecommendationContext) error { if ctx.Recommendation.Spec.TargetRef.Kind == "Node" { pods, err := utils.GetNodePods(ctx.Client, ctx.Recommendation.Spec.TargetRef.Name) @@ -194,13 +206,6 @@ func RetrievePods(ctx *RecommendationContext) error { pods, err := utils.GetDaemonSetPods(ctx.Client, ctx.Recommendation.Spec.TargetRef.Namespace, ctx.Recommendation.Spec.TargetRef.Name) ctx.Pods = pods return err - } else if ctx.Recommendation.Spec.TargetRef.Kind == "PersistentVolume" { - volumes, err := utils.GetOrphanVolumes(ctx.Client) - if err != nil { - return err - } - ctx.Volumes = volumes - return nil } else { pods, err := utils.GetPodsFromScale(ctx.Client, ctx.Scale) ctx.Pods = pods diff --git a/pkg/recommendation/recommender/volumes/filter.go b/pkg/recommendation/recommender/volumes/filter.go index c116704bd..087399bed 100644 --- a/pkg/recommendation/recommender/volumes/filter.go +++ b/pkg/recommendation/recommender/volumes/filter.go @@ -5,15 +5,15 @@ import ( ) // Filter out k8s resources that are not supported by the recommender. -func (rr *VolumesRecommender) Filter(ctx *framework.RecommendationContext) error { +func (vr *VolumesRecommender) Filter(ctx *framework.RecommendationContext) error { var err error // filter resource that not match objectIdentity - if err = rr.BaseRecommender.Filter(ctx); err != nil { + if err = vr.BaseRecommender.Filter(ctx); err != nil { return err } - if err = framework.RetrievePods(ctx); err != nil { + if err = framework.RetrieveVolumes(ctx); err != nil { return err } diff --git a/pkg/recommendation/recommender/volumes/registry.go b/pkg/recommendation/recommender/volumes/registry.go index a215c446e..be242a8d6 100644 --- a/pkg/recommendation/recommender/volumes/registry.go +++ b/pkg/recommendation/recommender/volumes/registry.go @@ -14,12 +14,16 @@ type VolumesRecommender struct { base.BaseRecommender } +func init() { + recommender.RegisterRecommenderProvider(recommender.VolumesRecommender, NewVolumesRecommender) +} + func (s *VolumesRecommender) Name() string { return recommender.VolumesRecommender } // NewVolumesRecommender create a new Volumes recommender. -func NewServiceRecommender(recommender apis.Recommender, recommendationRule analysisv1alph1.RecommendationRule) (*VolumesRecommender, error) { +func NewVolumesRecommender(recommender apis.Recommender, recommendationRule analysisv1alph1.RecommendationRule) (recommender.Recommender, error) { recommender = config.MergeRecommenderConfigFromRule(recommender, recommendationRule) return &VolumesRecommender{ *base.NewBaseRecommender(recommender), diff --git a/pkg/utils/pod.go b/pkg/utils/pod.go index cd3574408..505ec3c9d 100644 --- a/pkg/utils/pod.go +++ b/pkg/utils/pod.go @@ -313,7 +313,7 @@ func GetNodePods(kubeClient client.Client, nodeName string) ([]corev1.Pod, error } // GetOrphanVolumes returns Orphan Volumes -func GetOrphanVolumes(kubeClient client.Client) ([]corev1.PersistentVolume, error) { +func GetOrphanVolumes(kubeClient client.Client) ([]string, error) { // Get a list of all volumes volumes := &corev1.PersistentVolumeList{} if err := kubeClient.List(context.Background(), volumes); err != nil { @@ -327,14 +327,14 @@ func GetOrphanVolumes(kubeClient client.Client) ([]corev1.PersistentVolume, erro } // Check if each volume is being used by any pods - orphanVolumes := []corev1.PersistentVolume{} + orphanVolumesName := []string{} for _, volume := range volumes.Items { if isOrphanVolume(&volume, pods) { - orphanVolumes = append(orphanVolumes, volume) + orphanVolumesName = append(orphanVolumesName, volume.Spec.ClaimRef.Name) } } - return orphanVolumes, nil + return orphanVolumesName, nil } // volume is not being used by any pod