diff --git a/app/models/resource_models.py b/app/models/resource_models.py index 48cf2b4..f503831 100644 --- a/app/models/resource_models.py +++ b/app/models/resource_models.py @@ -115,6 +115,7 @@ class SmartRecommendation(BaseModel): implementation_steps: Optional[List[str]] = None kubectl_commands: Optional[List[str]] = None vpa_yaml: Optional[str] = None + workload_list: Optional[List[str]] = None # List of workloads for grouped recommendations class QoSClassification(BaseModel): """QoS (Quality of Service) classification""" diff --git a/app/services/smart_recommendations.py b/app/services/smart_recommendations.py index 108d930..eb0c6a1 100644 --- a/app/services/smart_recommendations.py +++ b/app/services/smart_recommendations.py @@ -63,7 +63,10 @@ class SmartRecommendationsService: categories: List[WorkloadCategory] ) -> List[SmartRecommendation]: """Generate smart recommendations based on workload analysis""" - recommendations = [] + # Group workloads by recommendation type + vpa_workloads = [] + resource_config_workloads = [] + ratio_adjustment_workloads = [] for category in categories: workload_pods = [p for p in pods if self._extract_workload_name(p.name) == category.workload_name and p.namespace == category.namespace] @@ -71,17 +74,173 @@ class SmartRecommendationsService: if not workload_pods: continue - # Generate recommendations based on category - workload_recommendations = await self._generate_workload_recommendations( - category, workload_pods - ) - recommendations.extend(workload_recommendations) + # Categorize workloads by recommendation type + if category.vpa_candidate: + vpa_workloads.append((category, workload_pods)) + elif category.resource_config_status == "missing_requests": + resource_config_workloads.append((category, workload_pods)) + elif category.resource_config_status == "missing_limits": + resource_config_workloads.append((category, workload_pods)) + elif category.resource_config_status == "suboptimal_ratio": + ratio_adjustment_workloads.append((category, workload_pods)) + + # Generate grouped recommendations + recommendations = [] + + # VPA Activation group + if vpa_workloads: + recommendations.append(self._create_grouped_vpa_recommendation(vpa_workloads)) + + # Resource Config group + if resource_config_workloads: + recommendations.append(self._create_grouped_resource_config_recommendation(resource_config_workloads)) + + # Ratio Adjustment group + if ratio_adjustment_workloads: + recommendations.append(self._create_grouped_ratio_adjustment_recommendation(ratio_adjustment_workloads)) # Sort by priority recommendations.sort(key=lambda x: self._get_priority_score(x.priority), reverse=True) return recommendations + def _create_grouped_vpa_recommendation(self, vpa_workloads: List[tuple]) -> SmartRecommendation: + """Create grouped VPA activation recommendation""" + # Sort workloads by priority + vpa_workloads.sort(key=lambda x: self._get_priority_score(x[0].estimated_impact), reverse=True) + + # Get highest priority + highest_priority = vpa_workloads[0][0].estimated_impact + + # Create workload list + workload_list = [] + for category, _ in vpa_workloads: + workload_list.append(f"{category.workload_name} ({category.namespace}) - {category.estimated_impact.upper()}") + + # Generate consolidated VPA YAML + vpa_yaml = self._generate_consolidated_vpa_yaml(vpa_workloads) + + return SmartRecommendation( + workload_name="Multiple Workloads", + namespace="Multiple", + recommendation_type="vpa_activation", + priority=highest_priority, + title="Activate VPA for Workloads", + description=f"Enable VPA for {len(vpa_workloads)} workloads to get automatic resource recommendations based on usage patterns.", + confidence_level=0.8, + estimated_impact=highest_priority, + implementation_steps=[ + "Create VPA resources for all workloads", + "Set updateMode to 'Off' for recommendation-only mode", + "Monitor VPA recommendations for 24-48 hours", + "Apply recommended values when confident" + ], + kubectl_commands=[ + "oc apply -f vpa-workloads-batch.yaml" + ], + vpa_yaml=vpa_yaml, + workload_list=workload_list + ) + + def _create_grouped_resource_config_recommendation(self, resource_workloads: List[tuple]) -> SmartRecommendation: + """Create grouped resource configuration recommendation""" + # Sort workloads by priority + resource_workloads.sort(key=lambda x: self._get_priority_score(x[0].estimated_impact), reverse=True) + + # Get highest priority + highest_priority = resource_workloads[0][0].estimated_impact + + # Create workload list + workload_list = [] + for category, _ in resource_workloads: + workload_list.append(f"{category.workload_name} ({category.namespace}) - {category.estimated_impact.upper()}") + + return SmartRecommendation( + workload_name="Multiple Workloads", + namespace="Multiple", + recommendation_type="resource_config", + priority=highest_priority, + title="Configure Resource Requests/Limits", + description=f"Define proper resource requests and limits for {len(resource_workloads)} workloads to ensure QoS and prevent resource conflicts.", + confidence_level=0.9, + estimated_impact=highest_priority, + implementation_steps=[ + "Analyze current resource usage for all workloads", + "Set CPU requests based on P95 usage + 20% buffer", + "Set memory requests based on P95 usage + 20% buffer", + "Set limits with 3:1 ratio to requests", + "Update deployments with new resource configuration" + ], + kubectl_commands=[ + "oc patch deployment -n -p '{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"\",\"resources\":{\"requests\":{\"cpu\":\"200m\",\"memory\":\"512Mi\"},\"limits\":{\"cpu\":\"600m\",\"memory\":\"1536Mi\"}}}]}}}}'" + ], + workload_list=workload_list + ) + + def _create_grouped_ratio_adjustment_recommendation(self, ratio_workloads: List[tuple]) -> SmartRecommendation: + """Create grouped ratio adjustment recommendation""" + # Sort workloads by priority + ratio_workloads.sort(key=lambda x: self._get_priority_score(x[0].estimated_impact), reverse=True) + + # Get highest priority + highest_priority = ratio_workloads[0][0].estimated_impact + + # Create workload list + workload_list = [] + for category, _ in ratio_workloads: + workload_list.append(f"{category.workload_name} ({category.namespace}) - {category.estimated_impact.upper()}") + + return SmartRecommendation( + workload_name="Multiple Workloads", + namespace="Multiple", + recommendation_type="ratio_adjustment", + priority=highest_priority, + title="Optimize Resource Ratios", + description=f"Optimize CPU and memory limit:request ratios for {len(ratio_workloads)} workloads to follow best practices (3:1 ratio).", + confidence_level=0.8, + estimated_impact=highest_priority, + implementation_steps=[ + "Analyze current resource ratios for all workloads", + "Adjust limits to maintain 3:1 ratio with requests", + "Test with updated ratios in staging environment", + "Apply changes to production" + ], + kubectl_commands=[ + "oc patch deployment -n -p '{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"\",\"resources\":{\"requests\":{\"cpu\":\"200m\",\"memory\":\"512Mi\"},\"limits\":{\"cpu\":\"600m\",\"memory\":\"1536Mi\"}}}]}}}}'" + ], + workload_list=workload_list + ) + + def _generate_consolidated_vpa_yaml(self, vpa_workloads: List[tuple]) -> str: + """Generate consolidated VPA YAML for multiple workloads""" + yaml_parts = [] + + for category, _ in vpa_workloads: + yaml_parts.append(f"""--- +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + name: {category.workload_name}-vpa + namespace: {category.namespace} +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: {category.workload_name} + updatePolicy: + updateMode: "Off" # Recommendation only + resourcePolicy: + containerPolicies: + - containerName: {category.workload_name} + maxAllowed: + cpu: 2 + memory: 4Gi + minAllowed: + cpu: 100m + memory: 128Mi""") + + return "\n".join(yaml_parts) + def _group_pods_by_workload(self, pods: List[PodResource]) -> Dict[str, List[PodResource]]: """Group pods by workload (deployment) name""" workloads = {} diff --git a/app/static/index.html b/app/static/index.html index 21f0786..eb419b4 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -1569,6 +1569,9 @@ 'low': '⚪' }[rec.priority] || '⚪'; + // Check if this is a grouped recommendation + const isGrouped = rec.workload_name === "Multiple Workloads" && rec.workload_list; + html += `
@@ -1576,9 +1579,13 @@

${priorityIcon} ${rec.title}

- Workload: ${rec.workload_name} | - Namespace: ${rec.namespace} | - Type: ${rec.recommendation_type.replace('_', ' ').toUpperCase()} + ${isGrouped ? + `Workloads: ${rec.workload_list.length} workloads | + Type: ${rec.recommendation_type.replace('_', ' ').toUpperCase()}` : + `Workload: ${rec.workload_name} | + Namespace: ${rec.namespace} | + Type: ${rec.recommendation_type.replace('_', ' ').toUpperCase()}` + }

@@ -1591,6 +1598,15 @@

${rec.description}

+ ${isGrouped && rec.workload_list ? ` +
+

Affected Workloads:

+
    + ${rec.workload_list.map(workload => `
  • ${workload}
  • `).join('')} +
+
+ ` : ''} + ${rec.implementation_steps ? `

Implementation Steps:

@@ -1610,14 +1626,10 @@ ` : ''}
- ${rec.vpa_yaml ? ` ` : ''}
@@ -2036,62 +2048,6 @@ loadSmartRecommendations(); } - function showRecommendationDetails(index) { - const rec = window.currentRecommendations[index]; - if (!rec) return; - - let modalContent = ` - - - `; - - showModal(modalContent); - } function showVPAYaml(index) { const rec = window.currentRecommendations[index]; @@ -2123,13 +2079,6 @@ ${rec.vpa_yaml} showModal(modalContent); } - function copyRecommendationCommands(index) { - const rec = window.currentRecommendations[index]; - if (!rec || !rec.kubectl_commands) return; - - const commands = rec.kubectl_commands.join('\n'); - copyToClipboard(commands); - } function copyVPAYaml(index) { const rec = window.currentRecommendations[index];