diff --git a/cmd/craned/app/manager.go b/cmd/craned/app/manager.go index 6a41830a9..5cb3c913b 100644 --- a/cmd/craned/app/manager.go +++ b/cmd/craned/app/manager.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "golang.org/x/sync/errgroup" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilfeature "k8s.io/apiserver/pkg/util/feature" @@ -18,6 +19,7 @@ import ( "k8s.io/client-go/scale" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" analysisapi "github.com/gocrane/api/analysis/v1alpha1" @@ -49,6 +51,7 @@ import ( "github.com/gocrane/crane/pkg/recommendation/config" recommender "github.com/gocrane/crane/pkg/recommendation/recommender" "github.com/gocrane/crane/pkg/recommendation/recommender/hpa" + "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/server" @@ -125,6 +128,7 @@ func Run(ctx context.Context, opts *options.Options) error { recommenderMgr := initRecommenderManager(recommenders, realtimeDataSources, historyDataSources) initScheme() + initFieldIndexer(mgr) initWebhooks(mgr, opts) initControllers(ctx, mgr, opts, predictorMgr, recommenderMgr, historyDataSources[providers.PrometheusDataSource]) // initialize custom collector metrics @@ -157,6 +161,12 @@ func initRecommenders(opts *options.Options) (map[string]recommender.Recommender recommenders[recommender.HPARecommender] = hpaRecommender case recommender.ResourceRecommender: recommenders[recommender.ResourceRecommender] = resource.NewResourceRecommender(r) + case recommender.IdleNodeRecommender: + idleNodeRecommender, err := idlenode.NewIdleNodeRecommender(r) + if err != nil { + return nil, err + } + recommenders[recommender.IdleNodeRecommender] = idleNodeRecommender default: return nil, fmt.Errorf("unknown recommender name: %s", r.Name) } @@ -184,6 +194,23 @@ func initScheme() { } } +func initFieldIndexer(mgr ctrl.Manager) { + // register nodeName indexer + if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &corev1.Pod{}, "spec.nodeName", func(obj client.Object) []string { + pod, ok := obj.(*corev1.Pod) + if !ok { + return []string{} + } + if len(pod.Spec.NodeName) == 0 { + return []string{} + } else { + return []string{pod.Spec.NodeName} + } + }); err != nil { + panic(err) + } +} + func initMetricCollector(mgr ctrl.Manager) { // register as prometheus metric collector metrics.CustomCollectorRegister(metrics.NewTspMetricCollector(mgr.GetClient())) diff --git a/examples/analytics/config_set.yaml b/examples/analytics/config_set.yaml index 11f96febf..aeb521b40 100644 --- a/examples/analytics/config_set.yaml +++ b/examples/analytics/config_set.yaml @@ -7,12 +7,5 @@ configs: replicas.workload-min-replicas: "3" replicas.pod-min-ready-seconds: "30" replicas.pod-available-ratio: "0.5" - replicas.default-min-replicas: "3" - replicas.max-replicas-factor: "3" - replicas.min-cpu-usage-threshold: "1" - replicas.fluctuation-threshold: "1.5" - replicas.min-cpu-target-utilization: "30" - replicas.max-cpu-target-utilization: "75" - replicas.cpu-target-utilization: "50" - replicas.cpu-percentile: "95" - replicas.reference-hpa: "true" \ No newline at end of file + replicas.cpu-target-utilization: "0.5" + replicas.cpu-percentile: "95" \ No newline at end of file diff --git a/examples/analytics/workloads-rule.yaml b/examples/analytics/preinstall-rule.yaml similarity index 56% rename from examples/analytics/workloads-rule.yaml rename to examples/analytics/preinstall-rule.yaml index 781dc3b3a..aad973ddc 100644 --- a/examples/analytics/workloads-rule.yaml +++ b/examples/analytics/preinstall-rule.yaml @@ -16,3 +16,21 @@ spec: recommenders: # 使用 Workload 的副本和资源推荐器 - name: Replicas - name: Resource + +--- + +apiVersion: analysis.crane.io/v1alpha1 +kind: RecommendationRule +metadata: + name: idlenodes-rule + labels: + analysis.crane.io/recommendation-rule-preinstall: "true" +spec: + runInterval: 24h # 每24h运行一次 + resourceSelectors: # 资源的信息 + - kind: Node + apiVersion: v1 + namespaceSelector: + any: true # 扫描所有namespace + recommenders: + - name: IdleNode \ No newline at end of file diff --git a/examples/analytics/recommendation-configuration.yaml b/examples/analytics/recommendation-configuration.yaml index feea3edf1..023c347d3 100644 --- a/examples/analytics/recommendation-configuration.yaml +++ b/examples/analytics/recommendation-configuration.yaml @@ -12,4 +12,8 @@ recommenders: - kind: Deployment apiVersion: apps/v1 - kind: StatefulSet - apiVersion: apps/v1 \ No newline at end of file + apiVersion: apps/v1 + - name: IdleNode + acceptedResources: + - kind: Node + apiVersion: v1 diff --git a/pkg/controller/recommendation/recommendation_rule_controller.go b/pkg/controller/recommendation/recommendation_rule_controller.go index 731f620df..e547be918 100644 --- a/pkg/controller/recommendation/recommendation_rule_controller.go +++ b/pkg/controller/recommendation/recommendation_rule_controller.go @@ -239,10 +239,15 @@ func (c *RecommendationRuleController) doReconcile(ctx context.Context, recommen func (c *RecommendationRuleController) CreateRecommendationObject(recommendationRule *analysisv1alph1.RecommendationRule, target corev1.ObjectReference, id ObjectIdentity, recommenderName string) *analysisv1alph1.Recommendation { + namespace := known.CraneSystemNamespace + if id.Namespace != "" { + namespace = id.Namespace + } + recommendation := &analysisv1alph1.Recommendation{ ObjectMeta: metav1.ObjectMeta{ GenerateName: fmt.Sprintf("%s-%s-", recommendationRule.Name, strings.ToLower(recommenderName)), - Namespace: id.Namespace, + Namespace: namespace, OwnerReferences: []metav1.OwnerReference{ *newOwnerRef(recommendationRule), }, @@ -260,6 +265,9 @@ func (c *RecommendationRuleController) CreateRecommendationObject(recommendation recommendation.Labels[known.RecommendationRuleNameLabel] = recommendationRule.Name recommendation.Labels[known.RecommendationRuleUidLabel] = string(recommendationRule.UID) recommendation.Labels[known.RecommendationRuleRecommenderLabel] = recommenderName + recommendation.Labels[known.RecommendationRuleTargetKindLabel] = target.Kind + recommendation.Labels[known.RecommendationRuleTargetApiVersionLabel] = target.APIVersion + recommendation.Labels[known.RecommendationRuleTargetNameLabel] = target.Name return recommendation } diff --git a/pkg/known/label.go b/pkg/known/label.go index 8695d5130..7c90bad71 100644 --- a/pkg/known/label.go +++ b/pkg/known/label.go @@ -17,7 +17,10 @@ const ( ) const ( - RecommendationRuleNameLabel = "analysis.crane.io/recommendation-rule-name" - RecommendationRuleUidLabel = "analysis.crane.io/recommendation-rule-uid" - RecommendationRuleRecommenderLabel = "analysis.crane.io/recommendation-rule-recommender" + RecommendationRuleNameLabel = "analysis.crane.io/recommendation-rule-name" + RecommendationRuleUidLabel = "analysis.crane.io/recommendation-rule-uid" + RecommendationRuleRecommenderLabel = "analysis.crane.io/recommendation-rule-recommender" + RecommendationRuleTargetKindLabel = "analysis.crane.io/recommendation-target-kind" + RecommendationRuleTargetApiVersionLabel = "analysis.crane.io/recommendation-target-apiversion" + RecommendationRuleTargetNameLabel = "analysis.crane.io/recommendation-target-name" ) diff --git a/pkg/recommendation/framework/context.go b/pkg/recommendation/framework/context.go index ff8bb1969..29bc06a25 100644 --- a/pkg/recommendation/framework/context.go +++ b/pkg/recommendation/framework/context.go @@ -142,11 +142,11 @@ func RetrieveScale(ctx *RecommendationContext) error { } func RetrievePods(ctx *RecommendationContext) error { - if ctx.Recommendation.Spec.TargetRef.Kind != "DaemonSet" { - pods, err := utils.GetPodsFromScale(ctx.Client, ctx.Scale) + if ctx.Recommendation.Spec.TargetRef.Kind == "Node" { + pods, err := utils.GetNodePods(ctx.Client, ctx.Recommendation.Spec.TargetRef.Name) ctx.Pods = pods return err - } else { + } else if ctx.Recommendation.Spec.TargetRef.Kind == "DaemonSet" { var daemonSet appsv1.DaemonSet err := ObjectConversion(ctx.Object, &daemonSet) if err != nil { @@ -155,6 +155,10 @@ 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 { + pods, err := utils.GetPodsFromScale(ctx.Client, ctx.Scale) + ctx.Pods = pods + return err } } diff --git a/pkg/recommendation/recommender/base/filter.go b/pkg/recommendation/recommender/base/filter.go index e831cd67b..6b0b56dad 100644 --- a/pkg/recommendation/recommender/base/filter.go +++ b/pkg/recommendation/recommender/base/filter.go @@ -21,13 +21,18 @@ func (br *BaseRecommender) Filter(ctx *framework.RecommendationContext) error { // 3. if not support, abort the recommendation flow supported := IsIdentitySupported(identity, accepted) if !supported { - return fmt.Errorf("recommender %s is failed at fliter, your kubernetes resource is not supported for recommender %s.", br.Name(), br.Name()) + return fmt.Errorf("recommender %s is failed at filter, your kubernetes resource is not supported for recommender %s ", br.Name(), br.Name()) } // 4. skip the objects that just created - creationCheckingTime := ctx.Object.GetCreationTimestamp().Add(CoolDownForCreation) + creationCheckingTime := ctx.Object.GetCreationTimestamp().Add(br.CreationCoolDown) if time.Now().Before(creationCheckingTime) { + return fmt.Errorf("recommender %s is failed at filter, Creation Cool Down %s ", br.Name(), ctx.Object.GetCreationTimestamp()) + } + // 5. skip the objects that deleting + if ctx.Object.GetDeletionTimestamp() != nil { + return fmt.Errorf("recommender %s is failed at filter, Is deleting ", br.Name()) } return nil diff --git a/pkg/recommendation/recommender/base/registry.go b/pkg/recommendation/recommender/base/registry.go index 2ad140f8b..19fa7840d 100644 --- a/pkg/recommendation/recommender/base/registry.go +++ b/pkg/recommendation/recommender/base/registry.go @@ -9,7 +9,7 @@ import ( var _ recommender.Recommender = &BaseRecommender{} -const DefaultCreationCoolDown = time.Minute * 5 +const DefaultCreationCoolDown = time.Minute * 3 type BaseRecommender struct { apis.Recommender @@ -17,7 +17,7 @@ type BaseRecommender struct { } func (br *BaseRecommender) Name() string { - return recommender.ReplicasRecommender + return "" } // NewBaseRecommender create a new base recommender. diff --git a/pkg/recommendation/recommender/idlenode/filter.go b/pkg/recommendation/recommender/idlenode/filter.go index 4d71c9b2d..5d6a3d2ae 100644 --- a/pkg/recommendation/recommender/idlenode/filter.go +++ b/pkg/recommendation/recommender/idlenode/filter.go @@ -1,8 +1,6 @@ -package resource +package idlenode import ( - "fmt" - "github.com/gocrane/crane/pkg/recommendation/framework" ) @@ -15,27 +13,9 @@ func (inr *IdleNodeRecommender) Filter(ctx *framework.RecommendationContext) err 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 } - // filter workloads that are downing - if len(ctx.Pods) == 0 { - return fmt.Errorf("pod not found") - } - - pod := ctx.Pods[0] - if len(pod.OwnerReferences) == 0 { - return fmt.Errorf("owner reference not found") - } - return nil } diff --git a/pkg/recommendation/recommender/idlenode/observe.go b/pkg/recommendation/recommender/idlenode/observe.go index 660f45868..ca5bf904a 100644 --- a/pkg/recommendation/recommender/idlenode/observe.go +++ b/pkg/recommendation/recommender/idlenode/observe.go @@ -1,60 +1,10 @@ -package resource +package idlenode import ( - "encoding/json" - "fmt" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - "github.com/gocrane/crane/pkg/metrics" "github.com/gocrane/crane/pkg/recommendation/framework" ) // Observe enhance the observability. func (inr *IdleNodeRecommender) Observe(ctx *framework.RecommendationContext) error { - // get new PodTemplate - var newObject map[string]interface{} - if err := json.Unmarshal([]byte(ctx.Recommendation.Status.RecommendedInfo), &newObject); err != nil { - return err - } - - podTemplateObject, found, err := unstructured.NestedMap(newObject, "spec", "template") - if !found || err != nil { - return fmt.Errorf("get template from unstructed object failed. ") - } - - var newPodTemplate v1.PodTemplateSpec - err = framework.ObjectConversion(podTemplateObject, &newPodTemplate) - if err != nil { - return err - } - - for _, container := range newPodTemplate.Spec.Containers { - inr.recordResourceRecommendation(ctx, container.Name, v1.ResourceCPU, container.Resources.Requests[v1.ResourceCPU]) - inr.recordResourceRecommendation(ctx, container.Name, v1.ResourceMemory, container.Resources.Requests[v1.ResourceMemory]) - } - return nil } - -func (inr *IdleNodeRecommender) recordResourceRecommendation(ctx *framework.RecommendationContext, containerName string, resName v1.ResourceName, quantity resource.Quantity) { - labels := map[string]string{ - "apiversion": ctx.Recommendation.Spec.TargetRef.APIVersion, - "owner_kind": ctx.Recommendation.Spec.TargetRef.Kind, - "namespace": ctx.Recommendation.Spec.TargetRef.Namespace, - "owner_name": ctx.Recommendation.Spec.TargetRef.Name, - "container": containerName, - "resource": resName.String(), - } - - labels["owner_replicas"] = fmt.Sprintf("%d", len(ctx.Pods)) - - switch resName { - case v1.ResourceCPU: - metrics.ResourceRecommendation.With(labels).Set(float64(quantity.MilliValue()) / 1000.) - case v1.ResourceMemory: - metrics.ResourceRecommendation.With(labels).Set(float64(quantity.Value())) - } -} diff --git a/pkg/recommendation/recommender/idlenode/prepare.go b/pkg/recommendation/recommender/idlenode/prepare.go index 821917f99..ba85be80e 100644 --- a/pkg/recommendation/recommender/idlenode/prepare.go +++ b/pkg/recommendation/recommender/idlenode/prepare.go @@ -1,4 +1,4 @@ -package resource +package idlenode import ( "github.com/gocrane/crane/pkg/recommendation/framework" diff --git a/pkg/recommendation/recommender/idlenode/recommend.go b/pkg/recommendation/recommender/idlenode/recommend.go index c17d7a3e3..7ce854d48 100644 --- a/pkg/recommendation/recommender/idlenode/recommend.go +++ b/pkg/recommendation/recommender/idlenode/recommend.go @@ -1,228 +1,33 @@ -package resource +package idlenode import ( - "encoding/json" "fmt" - "reflect" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/klog/v2" - "sigs.k8s.io/yaml" - - predictionapi "github.com/gocrane/api/prediction/v1alpha1" - - "github.com/gocrane/crane/pkg/metricnaming" - "github.com/gocrane/crane/pkg/prediction/config" - "github.com/gocrane/crane/pkg/recommend/types" "github.com/gocrane/crane/pkg/recommendation/framework" - "github.com/gocrane/crane/pkg/utils" ) -const callerFormat = "ResourceRecommendationCaller-%s-%s" - -type PatchResource struct { - Spec PatchResourceSpec `json:"spec,omitempty"` -} - -type PatchResourceSpec struct { - Template PatchResourcePodTemplateSpec `json:"template"` -} - -type PatchResourcePodTemplateSpec struct { - Spec PatchResourcePodSpec `json:"spec,omitempty"` -} - -type PatchResourcePodSpec struct { - // +patchMergeKey=name - // +patchStrategy=merge - Containers []corev1.Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name"` -} - func (inr *IdleNodeRecommender) PreRecommend(ctx *framework.RecommendationContext) error { return nil } -func (inr *IdleNodeRecommender) makeCpuConfig() *config.Config { - return &config.Config{ - Percentile: &predictionapi.Percentile{ - Aggregated: true, - HistoryLength: inr.CpuModelHistoryLength, - SampleInterval: inr.CpuSampleInterval, - MarginFraction: inr.CpuRequestMarginFraction, - TargetUtilization: inr.CpuTargetUtilization, - Percentile: inr.CpuRequestPercentile, - Histogram: predictionapi.HistogramConfig{ - HalfLife: "24h", - BucketSize: "0.1", - MaxValue: "100", - }, - }, - } -} - -func (inr *IdleNodeRecommender) makeMemConfig() *config.Config { - return &config.Config{ - Percentile: &predictionapi.Percentile{ - Aggregated: true, - HistoryLength: inr.MemHistoryLength, - SampleInterval: inr.MemSampleInterval, - MarginFraction: inr.MemMarginFraction, - Percentile: inr.MemPercentile, - TargetUtilization: inr.MemTargetUtilization, - Histogram: predictionapi.HistogramConfig{ - HalfLife: "48h", - BucketSize: "104857600", - MaxValue: "104857600000", - }, - }, - } -} - func (inr *IdleNodeRecommender) Recommend(ctx *framework.RecommendationContext) error { - predictor := ctx.PredictorMgr.GetPredictor(predictionapi.AlgorithmTypePercentile) - if predictor == nil { - return fmt.Errorf("predictor %v not found", predictionapi.AlgorithmTypePercentile) - } - - resourceRecommendation := &types.ResourceRequestRecommendation{} - - var newContainers []corev1.Container - var oldContainers []corev1.Container - - namespace := ctx.Object.GetNamespace() - for _, c := range ctx.PodTemplate.Spec.Containers { - cr := types.ContainerRecommendation{ - ContainerName: c.Name, - Target: map[corev1.ResourceName]string{}, - } - - caller := fmt.Sprintf(callerFormat, klog.KObj(ctx.Recommendation), ctx.Recommendation.UID) - metricNamer := metricnaming.ResourceToContainerMetricNamer(namespace, ctx.Recommendation.Spec.TargetRef.APIVersion, - ctx.Recommendation.Spec.TargetRef.Kind, ctx.Recommendation.Spec.TargetRef.Name, c.Name, corev1.ResourceCPU, caller) - klog.V(6).Infof("CPU query for resource request recommendation: %s", metricNamer.BuildUniqueKey()) - cpuConfig := inr.makeCpuConfig() - tsList, err := utils.QueryPredictedValuesOnce(ctx.Recommendation, predictor, caller, cpuConfig, metricNamer) - if err != nil { - return err - } - if len(tsList) < 1 || len(tsList[0].Samples) < 1 { - return fmt.Errorf("no value retured for queryExpr: %s", metricNamer.BuildUniqueKey()) - } - v := int64(tsList[0].Samples[0].Value * 1000) - cpuQuantity := resource.NewMilliQuantity(v, resource.DecimalSI) - cr.Target[corev1.ResourceCPU] = cpuQuantity.String() - - metricNamer = metricnaming.ResourceToContainerMetricNamer(namespace, ctx.Recommendation.Spec.TargetRef.APIVersion, - ctx.Recommendation.Spec.TargetRef.Kind, ctx.Recommendation.Spec.TargetRef.Name, c.Name, corev1.ResourceMemory, caller) - klog.V(6).Infof("Memory query for resource request recommendation: %s", metricNamer.BuildUniqueKey()) - memConfig := inr.makeMemConfig() - tsList, err = utils.QueryPredictedValuesOnce(ctx.Recommendation, predictor, caller, memConfig, metricNamer) - if err != nil { - return err - } - if len(tsList) < 1 || len(tsList[0].Samples) < 1 { - return fmt.Errorf("no value retured for queryExpr: %s", metricNamer.BuildUniqueKey()) - } - v = int64(tsList[0].Samples[0].Value) - if v <= 0 { - return fmt.Errorf("no enough metrics") - } - memQuantity := resource.NewQuantity(v, resource.BinarySI) - cr.Target[corev1.ResourceMemory] = memQuantity.String() - - newContainerSpec := corev1.Container{ - Name: c.Name, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: *cpuQuantity, - corev1.ResourceMemory: *memQuantity, - }, - }, - } - oldContainerSpec := corev1.Container{ - Name: c.Name, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: c.Resources.Requests[corev1.ResourceCPU], - corev1.ResourceMemory: c.Resources.Requests[corev1.ResourceMemory], - }, - }, + // check if all pods in Node are owned by DaemonSet + allDaemonSetPod := true + for _, pod := range ctx.Pods { + for _, ownRef := range pod.OwnerReferences { + if ownRef.Kind != "DaemonSet" { + allDaemonSetPod = false + } } - - newContainers = append(newContainers, newContainerSpec) - oldContainers = append(oldContainers, oldContainerSpec) - - resourceRecommendation.Containers = append(resourceRecommendation.Containers, cr) - } - - value := types.ProposedRecommendation{ - ResourceRequest: resourceRecommendation, - } - - valueBytes, err := yaml.Marshal(value) - if err != nil { - return fmt.Errorf("%s yaml marshal failed: %v", inr.Name(), err) } - ctx.Recommendation.Status.RecommendedValue = string(valueBytes) - - /* var newContainerItems []interface{} - for _, container := range newContainers { - out, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(&container) - newContainerItems = append(newContainerItems, out) - } - - err = unstructured.SetNestedSlice(newObject.Object, newContainerItems, "spec", "template", "spec", "containers") - if err != nil { - return fmt.Errorf("%s set new patch containers failed: %v", rr.Name(), err) - } - newPatch, _, err := framework.ConvertToRecommendationInfos(ctx.Object, newObject.Object) - if err != nil { - return fmt.Errorf("convert to recommendation infos failed: %s. ", err) - } - - var oldContainerItems []interface{} - for _, container := range oldContainers { - out, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(&container) - oldContainerItems = append(oldContainerItems, out) - } - - originObject := newObject.DeepCopy() - err = unstructured.SetNestedSlice(originObject.Object, oldContainerItems, "spec", "template", "spec", "containers") - if err != nil { - return fmt.Errorf("set old container failed: %s. ", err) - } - oldPatch, _, err := framework.ConvertToRecommendationInfos(newObject.Object, originObject.Object) - if err != nil { - return fmt.Errorf("convert to recommendation infos failed: %s. ", err) - }*/ - - var newPatch PatchResource - newPatch.Spec.Template.Spec.Containers = newContainers - newPatchBytes, err := json.Marshal(newPatch) - if err != nil { - return fmt.Errorf("marshal newPatch failed %s. ", err) - } - - var oldPatch PatchResource - oldPatch.Spec.Template.Spec.Containers = oldContainers - oldPatchBytes, err := json.Marshal(oldPatch) - if err != nil { - return fmt.Errorf("marshal oldPatch failed %s. ", err) - } - - if reflect.DeepEqual(&newPatch, &oldPatch) { - ctx.Recommendation.Status.Action = "None" + if allDaemonSetPod { + ctx.Recommendation.Status.Action = "Delete" + return nil } else { - ctx.Recommendation.Status.Action = "Patch" + return fmt.Errorf("Node %s is not a idle node ", ctx.Object.GetName()) } - - ctx.Recommendation.Status.RecommendedInfo = string(newPatchBytes) - ctx.Recommendation.Status.CurrentInfo = string(oldPatchBytes) - - return nil } // Policy add some logic for result of recommend phase. diff --git a/pkg/recommendation/recommender/idlenode/registry.go b/pkg/recommendation/recommender/idlenode/registry.go index b86cf1ced..c093d6794 100644 --- a/pkg/recommendation/recommender/idlenode/registry.go +++ b/pkg/recommendation/recommender/idlenode/registry.go @@ -1,10 +1,11 @@ -package resource +package idlenode import ( + "strconv" + "github.com/gocrane/crane/pkg/recommendation/recommender" "github.com/gocrane/crane/pkg/recommendation/recommender/apis" "github.com/gocrane/crane/pkg/recommendation/recommender/base" - "strconv" ) var _ recommender.Recommender = &IdleNodeRecommender{} diff --git a/pkg/recommendation/recommender/resource/recommend.go b/pkg/recommendation/recommender/resource/recommend.go index 8d605850e..695a036a7 100644 --- a/pkg/recommendation/recommender/resource/recommend.go +++ b/pkg/recommendation/recommender/resource/recommend.go @@ -91,7 +91,7 @@ func (rr *ResourceRecommender) Recommend(ctx *framework.RecommendationContext) e var oldContainers []corev1.Container namespace := ctx.Object.GetNamespace() - for _, c := range ctx.PodTemplate.Spec.Containers { + for _, c := range ctx.Pods[0].Spec.Containers { cr := types.ContainerRecommendation{ ContainerName: c.Name, Target: map[corev1.ResourceName]string{}, diff --git a/pkg/server/handler/clusters/cluster.go b/pkg/server/handler/clusters/cluster.go index 0302d7d01..0aec7efa9 100644 --- a/pkg/server/handler/clusters/cluster.go +++ b/pkg/server/handler/clusters/cluster.go @@ -43,6 +43,25 @@ spec: - name: Resource ` +const RecommendationRuleIdleNodeName = "idlenodes-rule" +const RecommendationRuleIdleNodeYAML = ` +apiVersion: analysis.crane.io/v1alpha1 +kind: RecommendationRule +metadata: + name: idlenodes-rule + labels: + analysis.crane.io/recommendation-rule-preinstall: "true" +spec: + runInterval: 24h # 每24h运行一次 + resourceSelectors: # 资源的信息 + - kind: Node + apiVersion: v1 + namespaceSelector: + any: true # 扫描所有namespace + recommenders: + - name: IdleNode +` + type AddClustersRequest struct { Clusters []*store.Cluster `json:"clusters"` } @@ -129,30 +148,16 @@ func (ch *ClusterHandler) AddClusters(c *gin.Context) { } if cluster.PreinstallRecommendation && err == nil { - key := types.NamespacedName{ - Namespace: known.CraneSystemNamespace, - Name: RecommendationRuleWorkloadsName, + err := ch.upsertRecommendationRule(RecommendationRuleWorkloadsName, RecommendationRuleWorkloadsYAML) + if err != nil { + ginwrapper.WriteResponse(c, err, nil) + return } - var recommendationRule analysisapi.RecommendationRule - err = ch.client.Get(context.TODO(), key, &recommendationRule) + + err = ch.upsertRecommendationRule(RecommendationRuleIdleNodeName, RecommendationRuleIdleNodeYAML) if err != nil { - if errors.IsNotFound(err) { - var workloadRecommendationRule analysisapi.RecommendationRule - err = yaml.Unmarshal([]byte(RecommendationRuleWorkloadsYAML), &workloadRecommendationRule) - if err != nil { - ginwrapper.WriteResponse(c, err, nil) - return - } - err = ch.client.Create(context.TODO(), &workloadRecommendationRule) - if err != nil { - ginwrapper.WriteResponse(c, err, nil) - return - } - } else { - klog.Errorf("get preinstall recommendation failed: %v", err) - ginwrapper.WriteResponse(c, err, nil) - return - } + ginwrapper.WriteResponse(c, err, nil) + return } } else if err != nil { ginwrapper.WriteResponse(c, err, nil) @@ -249,3 +254,30 @@ func (ch *ClusterHandler) getClusterMap() (map[string]*store.Cluster, error) { } return clustersMap, nil } + +func (ch *ClusterHandler) upsertRecommendationRule(name string, yamlString string) error { + key := types.NamespacedName{ + Namespace: known.CraneSystemNamespace, + Name: name, + } + var recommendationRule analysisapi.RecommendationRule + err := ch.client.Get(context.TODO(), key, &recommendationRule) + if err != nil { + if errors.IsNotFound(err) { + var workloadRecommendationRule analysisapi.RecommendationRule + err = yaml.Unmarshal([]byte(yamlString), &workloadRecommendationRule) + if err != nil { + return err + } + err = ch.client.Create(context.TODO(), &workloadRecommendationRule) + if err != nil { + return err + } + } else { + klog.Errorf("get preinstall recommendation failed: %v", err) + return err + } + } + + return nil +} \ No newline at end of file diff --git a/pkg/utils/pod.go b/pkg/utils/pod.go index 047c37494..18a19942f 100644 --- a/pkg/utils/pod.go +++ b/pkg/utils/pod.go @@ -297,3 +297,17 @@ func GetDaemonSetPods(kubeClient client.Client, namespace string, name string) ( return podList.Items, nil } + +func GetNodePods(kubeClient client.Client, nodeName string) ([]corev1.Pod, error) { + opts := []client.ListOption{ + client.MatchingFields{"spec.nodeName": nodeName}, + } + + podList := &corev1.PodList{} + err := kubeClient.List(context.TODO(), podList, opts...) + if err != nil { + return nil, err + } + + return podList.Items, nil +}