Comprehensive VPA Implementation Guide
1. VPA Core Components
VPA Controller Service
// VPAControllerService.java package com.example.vpa.core; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1Pod; import io.kubernetes.client.openapi.models.V1PodList; import io.kubernetes.client.openapi.models.V1Container; import io.kubernetes.client.openapi.models.V1ResourceRequirements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.Instant; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @Service public class VPAControllerService { private static final Logger logger = LoggerFactory.getLogger(VPAControllerService.class); private final CoreV1Api coreV1Api; private final MetricsService metricsService; private final RecommendationEngine recommendationEngine; private final VPAConfiguration config; private final Map<String, VPARecommendation> recommendations = new ConcurrentHashMap<>(); private final Map<String, PodResourceUsage> podUsageHistory = new ConcurrentHashMap<>(); public VPAControllerService(ApiClient apiClient, MetricsService metricsService, RecommendationEngine recommendationEngine, VPAConfiguration config) { this.coreV1Api = new CoreV1Api(apiClient); this.metricsService = metricsService; this.recommendationEngine = recommendationEngine; this.config = config; } @Scheduled(fixedRateString = "${vpa.update-interval:300000}") // 5 minutes public void updateRecommendations() { try { logger.info("Starting VPA recommendation update cycle"); // Get all pods in the namespace V1PodList podList = coreV1Api.listNamespacedPod( config.getNamespace(), null, null, null, null, config.getVpaSelector(), null, null, null, null, null); for (V1Pod pod : podList.getItems()) { processPod(pod); } logger.info("VPA recommendation update cycle completed. Processed {} pods", podList.getItems().size()); } catch (ApiException e) { logger.error("Failed to update VPA recommendations", e); } } private void processPod(V1Pod pod) { String podName = pod.getMetadata().getName(); String namespace = pod.getMetadata().getNamespace(); try { // Collect current resource usage PodResourceUsage currentUsage = metricsService.getPodResourceUsage(podName, namespace); // Store usage history storeUsageHistory(podName, currentUsage); // Generate recommendation VPARecommendation recommendation = generateRecommendation(pod, currentUsage); // Store recommendation recommendations.put(podName, recommendation); // Apply recommendation if auto-update is enabled if (config.isAutoUpdateEnabled() && shouldApplyRecommendation(recommendation)) { applyRecommendation(pod, recommendation); } logger.debug("Processed pod: {} - CPU: {} -> {}, Memory: {} -> {}", podName, formatResource(currentUsage.getCpuUsage()), formatResource(recommendation.getTargetCpu()), formatResource(currentUsage.getMemoryUsage()), formatResource(recommendation.getTargetMemory())); } catch (Exception e) { logger.error("Failed to process pod: {}", podName, e); } } private void storeUsageHistory(String podName, PodResourceUsage currentUsage) { PodUsageHistory history = podUsageHistory.computeIfAbsent(podName, k -> new PodUsageHistory()); history.addUsage(currentUsage); // Keep only recent history history.trimHistory(config.getHistoryWindow()); } private VPARecommendation generateRecommendation(V1Pod pod, PodResourceUsage currentUsage) { String podName = pod.getMetadata().getName(); PodUsageHistory history = podUsageHistory.get(podName); return recommendationEngine.generateRecommendation(pod, currentUsage, history); } private boolean shouldApplyRecommendation(VPARecommendation recommendation) { if (!recommendation.isValid()) { return false; } // Check if recommendation is significantly different from current double cpuChange = Math.abs(recommendation.getTargetCpu() - recommendation.getCurrentCpu()); double memoryChange = Math.abs(recommendation.getTargetMemory() - recommendation.getCurrentMemory()); boolean significantChange = cpuChange > recommendation.getCurrentCpu() * config.getMinChangeThreshold() || memoryChange > recommendation.getCurrentMemory() * config.getMinChangeThreshold(); // Check confidence level boolean confident = recommendation.getConfidence() > config.getMinConfidence(); // Check if within safety bounds boolean withinBounds = !recommendation.exceedsSafetyLimits(); return significantChange && confident && withinBounds; } private void applyRecommendation(V1Pod pod, VPARecommendation recommendation) { try { String podName = pod.getMetadata().getName(); String namespace = pod.getMetadata().getNamespace(); // Update pod resources (this would typically be done via patch) updatePodResources(pod, recommendation); logger.info("Applied VPA recommendation for pod: {} - CPU: {}, Memory: {}", podName, recommendation.getTargetCpu(), recommendation.getTargetMemory()); } catch (Exception e) { logger.error("Failed to apply VPA recommendation for pod: {}", pod.getMetadata().getName(), e); } } private void updatePodResources(V1Pod pod, VPARecommendation recommendation) throws ApiException { // This is a simplified implementation // In production, you would use strategic merge patch or update the deployment String podName = pod.getMetadata().getName(); String namespace = pod.getMetadata().getNamespace(); // Get the deployment name from pod labels Map<String, String> labels = pod.getMetadata().getLabels(); String deploymentName = labels.get("app"); if (deploymentName != null) { updateDeploymentResources(namespace, deploymentName, recommendation); } } private void updateDeploymentResources(String namespace, String deploymentName, VPARecommendation recommendation) { // Implementation to update deployment resources // This would involve updating the deployment spec with new resource requests/limits logger.info("Would update deployment {}/{} with CPU: {}, Memory: {}", namespace, deploymentName, recommendation.getTargetCpu(), recommendation.getTargetMemory()); } private String formatResource(double value) { if (value < 1000) { return String.format("%.0fm", value); } else { return String.format("%.2f", value / 1000); } } public VPARecommendation getRecommendation(String podName) { return recommendations.get(podName); } public Map<String, VPARecommendation> getAllRecommendations() { return new HashMap<>(recommendations); } public PodUsageHistory getPodUsageHistory(String podName) { return podUsageHistory.get(podName); } public VPAStatus getVPAStatus() { return new VPAStatus( recommendations.size(), podUsageHistory.size(), Instant.now(), calculateAverageRecommendation() ); } private Map<String, Double> calculateAverageRecommendation() { double avgCpu = recommendations.values().stream() .mapToDouble(VPARecommendation::getTargetCpu) .average() .orElse(0.0); double avgMemory = recommendations.values().stream() .mapToDouble(VPARecommendation::getTargetMemory) .average() .orElse(0.0); return Map.of( "cpu", avgCpu, "memory", avgMemory ); } } 2. Data Models
Resource Usage Models
// PodResourceUsage.java package com.example.vpa.model; import java.time.Instant; public class PodResourceUsage { private String podName; private String namespace; private double cpuUsage; // in millicores private double memoryUsage; // in MiB private double cpuRequest; private double memoryRequest; private double cpuLimit; private double memoryLimit; private Instant timestamp; public PodResourceUsage(String podName, String namespace, double cpuUsage, double memoryUsage, double cpuRequest, double memoryRequest, double cpuLimit, double memoryLimit) { this.podName = podName; this.namespace = namespace; this.cpuUsage = cpuUsage; this.memoryUsage = memoryUsage; this.cpuRequest = cpuRequest; this.memoryRequest = memoryRequest; this.cpuLimit = cpuLimit; this.memoryLimit = memoryLimit; this.timestamp = Instant.now(); } // Getters public String getPodName() { return podName; } public String getNamespace() { return namespace; } public double getCpuUsage() { return cpuUsage; } public double getMemoryUsage() { return memoryUsage; } public double getCpuRequest() { return cpuRequest; } public double getMemoryRequest() { return memoryRequest; } public double getCpuLimit() { return cpuLimit; } public double getMemoryLimit() { return memoryLimit; } public Instant getTimestamp() { return timestamp; } public double getCpuUtilization() { return cpuRequest > 0 ? (cpuUsage / cpuRequest) * 100 : 0; } public double getMemoryUtilization() { return memoryRequest > 0 ? (memoryUsage / memoryRequest) * 100 : 0; } public boolean isOverutilized() { return getCpuUtilization() > 80 || getMemoryUtilization() > 80; } public boolean isUnderutilized() { return getCpuUtilization() < 20 && getMemoryUtilization() < 20; } } // PodUsageHistory.java package com.example.vpa.model; import java.time.Duration; import java.time.Instant; import java.util.*; import java.util.concurrent.ConcurrentLinkedDeque; public class PodUsageHistory { private final Deque<PodResourceUsage> usageHistory = new ConcurrentLinkedDeque<>(); private final int maxHistorySize = 1000; public void addUsage(PodResourceUsage usage) { usageHistory.addFirst(usage); // Maintain history size limit while (usageHistory.size() > maxHistorySize) { usageHistory.removeLast(); } } public void trimHistory(Duration window) { Instant cutoff = Instant.now().minus(window); usageHistory.removeIf(usage -> usage.getTimestamp().isBefore(cutoff)); } public List<PodResourceUsage> getRecentUsage(Duration duration) { Instant cutoff = Instant.now().minus(duration); return usageHistory.stream() .filter(usage -> usage.getTimestamp().isAfter(cutoff)) .toList(); } public PodResourceUsage getPeakUsage(Duration duration) { List<PodResourceUsage> recentUsage = getRecentUsage(duration); if (recentUsage.isEmpty()) { return null; } PodResourceUsage peakUsage = recentUsage.get(0); for (PodResourceUsage usage : recentUsage) { if (usage.getCpuUsage() > peakUsage.getCpuUsage() && usage.getMemoryUsage() > peakUsage.getMemoryUsage()) { peakUsage = usage; } } return peakUsage; } public ResourceStatistics calculateStatistics(Duration duration) { List<PodResourceUsage> recentUsage = getRecentUsage(duration); if (recentUsage.isEmpty()) { return new ResourceStatistics(0, 0, 0, 0, 0, 0); } double avgCpu = recentUsage.stream() .mapToDouble(PodResourceUsage::getCpuUsage) .average() .orElse(0.0); double avgMemory = recentUsage.stream() .mapToDouble(PodResourceUsage::getMemoryUsage) .average() .orElse(0.0); double p95Cpu = calculatePercentile(recentUsage, usage -> usage.getCpuUsage(), 95); double p95Memory = calculatePercentile(recentUsage, usage -> usage.getMemoryUsage(), 95); double maxCpu = recentUsage.stream() .mapToDouble(PodResourceUsage::getCpuUsage) .max() .orElse(0.0); double maxMemory = recentUsage.stream() .mapToDouble(PodResourceUsage::getMemoryUsage) .max() .orElse(0.0); return new ResourceStatistics(avgCpu, avgMemory, p95Cpu, p95Memory, maxCpu, maxMemory); } private <T> double calculatePercentile(List<PodResourceUsage> usageList, java.util.function.Function<PodResourceUsage, Double> extractor, double percentile) { List<Double> values = usageList.stream() .map(extractor) .sorted() .toList(); if (values.isEmpty()) { return 0.0; } int index = (int) Math.ceil(percentile / 100.0 * values.size()); return values.get(Math.min(index, values.size() - 1)); } public int size() { return usageHistory.size(); } } // ResourceStatistics.java package com.example.vpa.model; public class ResourceStatistics { private final double avgCpu; private final double avgMemory; private final double p95Cpu; private final double p95Memory; private final double maxCpu; private final double maxMemory; public ResourceStatistics(double avgCpu, double avgMemory, double p95Cpu, double p95Memory, double maxCpu, double maxMemory) { this.avgCpu = avgCpu; this.avgMemory = avgMemory; this.p95Cpu = p95Cpu; this.p95Memory = p95Memory; this.maxCpu = maxCpu; this.maxMemory = maxMemory; } // Getters public double getAvgCpu() { return avgCpu; } public double getAvgMemory() { return avgMemory; } public double getP95Cpu() { return p95Cpu; } public double getP95Memory() { return p95Memory; } public double getMaxCpu() { return maxCpu; } public double getMaxMemory() { return maxMemory; } } VPA Recommendation Models
// VPARecommendation.java package com.example.vpa.model; import java.time.Instant; public class VPARecommendation { private String podName; private String namespace; private double currentCpu; private double currentMemory; private double targetCpu; private double targetMemory; private double targetCpuLimit; private double targetMemoryLimit; private double confidence; private RecommendationType type; private Instant timestamp; private String reason; public VPARecommendation(String podName, String namespace, double currentCpu, double currentMemory, double targetCpu, double targetMemory, double confidence, RecommendationType type, String reason) { this.podName = podName; this.namespace = namespace; this.currentCpu = currentCpu; this.currentMemory = currentMemory; this.targetCpu = targetCpu; this.targetMemory = targetMemory; this.targetCpuLimit = targetCpu * 2; // Default limit: 2x request this.targetMemoryLimit = targetMemory * 1.5; // Default limit: 1.5x request this.confidence = confidence; this.type = type; this.timestamp = Instant.now(); this.reason = reason; } // Getters public String getPodName() { return podName; } public String getNamespace() { return namespace; } public double getCurrentCpu() { return currentCpu; } public double getCurrentMemory() { return currentMemory; } public double getTargetCpu() { return targetCpu; } public double getTargetMemory() { return targetMemory; } public double getTargetCpuLimit() { return targetCpuLimit; } public double getTargetMemoryLimit() { return targetMemoryLimit; } public double getConfidence() { return confidence; } public RecommendationType getType() { return type; } public Instant getTimestamp() { return timestamp; } public String getReason() { return reason; } public boolean isValid() { return targetCpu > 0 && targetMemory > 0 && confidence >= 0 && confidence <= 1; } public boolean exceedsSafetyLimits() { // Safety limits to prevent excessive resource allocation return targetCpu > 4000 || // 4 CPU cores targetMemory > 8192; // 8 GiB memory } public double getCpuChangePercentage() { return currentCpu > 0 ? ((targetCpu - currentCpu) / currentCpu) * 100 : 0; } public double getMemoryChangePercentage() { return currentMemory > 0 ? ((targetMemory - currentMemory) / currentMemory) * 100 : 0; } public boolean isSignificantChange() { return Math.abs(getCpuChangePercentage()) > 10 || Math.abs(getMemoryChangePercentage()) > 10; } } // RecommendationType.java package com.example.vpa.model; public enum RecommendationType { OPTIMAL, // Optimal resource allocation CONSERVATIVE, // Conservative allocation with safety margin AGGRESSIVE, // Aggressive allocation for performance MAINTENANCE // No change needed } 3. Recommendation Engine
// RecommendationEngine.java package com.example.vpa.core; import com.example.vpa.model.*; import io.kubernetes.client.openapi.models.V1Pod; import org.springframework.stereotype.Component; import java.time.Duration; @Component public class RecommendationEngine { private final VPAConfiguration config; public RecommendationEngine(VPAConfiguration config) { this.config = config; } public VPARecommendation generateRecommendation(V1Pod pod, PodResourceUsage currentUsage, PodUsageHistory history) { String podName = pod.getMetadata().getName(); String namespace = pod.getMetadata().getNamespace(); // Calculate statistics over different time windows ResourceStatistics shortTermStats = history.calculateStatistics(Duration.ofHours(1)); ResourceStatistics mediumTermStats = history.calculateStatistics(Duration.ofHours(6)); ResourceStatistics longTermStats = history.calculateStatistics(Duration.ofHours(24)); // Generate recommendation based on strategy switch (config.getRecommendationStrategy()) { case CONSERVATIVE: return generateConservativeRecommendation(podName, namespace, currentUsage, shortTermStats, mediumTermStats, longTermStats); case AGGRESSIVE: return generateAggressiveRecommendation(podName, namespace, currentUsage, shortTermStats, mediumTermStats, longTermStats); case BALANCED: default: return generateBalancedRecommendation(podName, namespace, currentUsage, shortTermStats, mediumTermStats, longTermStats); } } private VPARecommendation generateBalancedRecommendation(String podName, String namespace, PodResourceUsage currentUsage, ResourceStatistics shortTerm, ResourceStatistics mediumTerm, ResourceStatistics longTerm) { // Use P95 of medium-term usage with safety buffer double targetCpu = calculateTargetCpu(mediumTerm, currentUsage); double targetMemory = calculateTargetMemory(mediumTerm, currentUsage); double confidence = calculateConfidence(shortTerm, mediumTerm, longTerm); String reason = generateReason(shortTerm, mediumTerm, longTerm); return new VPARecommendation(podName, namespace, currentUsage.getCpuRequest(), currentUsage.getMemoryRequest(), targetCpu, targetMemory, confidence, RecommendationType.OPTIMAL, reason); } private VPARecommendation generateConservativeRecommendation(String podName, String namespace, PodResourceUsage currentUsage, ResourceStatistics shortTerm, ResourceStatistics mediumTerm, ResourceStatistics longTerm) { // Use P95 of long-term usage with larger safety buffer double targetCpu = calculateTargetCpu(longTerm, currentUsage) * 1.2; // 20% buffer double targetMemory = calculateTargetMemory(longTerm, currentUsage) * 1.3; // 30% buffer double confidence = calculateConfidence(shortTerm, mediumTerm, longTerm) * 0.9; // Lower confidence String reason = "Conservative recommendation based on long-term usage patterns"; return new VPARecommendation(podName, namespace, currentUsage.getCpuRequest(), currentUsage.getMemoryRequest(), targetCpu, targetMemory, confidence, RecommendationType.CONSERVATIVE, reason); } private VPARecommendation generateAggressiveRecommendation(String podName, String namespace, PodResourceUsage currentUsage, ResourceStatistics shortTerm, ResourceStatistics mediumTerm, ResourceStatistics longTerm) { // Use P95 of short-term usage with minimal buffer double targetCpu = calculateTargetCpu(shortTerm, currentUsage) * 1.05; // 5% buffer double targetMemory = calculateTargetMemory(shortTerm, currentUsage) * 1.1; // 10% buffer double confidence = calculateConfidence(shortTerm, mediumTerm, longTerm); String reason = "Aggressive recommendation optimized for recent performance"; return new VPARecommendation(podName, namespace, currentUsage.getCpuRequest(), currentUsage.getMemoryRequest(), targetCpu, targetMemory, confidence, RecommendationType.AGGRESSIVE, reason); } private double calculateTargetCpu(ResourceStatistics stats, PodResourceUsage currentUsage) { // Use P95 CPU usage with minimum and maximum bounds double baseCpu = stats.getP95Cpu(); // Ensure minimum CPU allocation double minCpu = config.getMinCpu(); baseCpu = Math.max(baseCpu, minCpu); // Add safety buffer baseCpu *= config.getCpuSafetyMargin(); // Round to nearest 25m for Kubernetes compatibility return Math.ceil(baseCpu / 25) * 25; } private double calculateTargetMemory(ResourceStatistics stats, PodResourceUsage currentUsage) { // Use P95 memory usage with minimum and maximum bounds double baseMemory = stats.getP95Memory(); // Ensure minimum memory allocation double minMemory = config.getMinMemory(); baseMemory = Math.max(baseMemory, minMemory); // Add safety buffer baseMemory *= config.getMemorySafetyMargin(); // Round to nearest 32Mi for Kubernetes compatibility return Math.ceil(baseMemory / 32) * 32; } private double calculateConfidence(ResourceStatistics shortTerm, ResourceStatistics mediumTerm, ResourceStatistics longTerm) { // Calculate confidence based on data consistency across time windows double shortMediumDiff = calculateStatsDifference(shortTerm, mediumTerm); double mediumLongDiff = calculateStatsDifference(mediumTerm, longTerm); // Lower difference = higher confidence double consistencyScore = 1.0 - (shortMediumDiff + mediumLongDiff) / 2.0; // Consider data volume (more data = higher confidence) double dataVolumeScore = Math.min(1.0, mediumTerm.getAvgCpu() > 0 ? 1.0 : 0.5); return (consistencyScore + dataVolumeScore) / 2.0; } private double calculateStatsDifference(ResourceStatistics stats1, ResourceStatistics stats2) { double cpuDiff = Math.abs(stats1.getAvgCpu() - stats2.getAvgCpu()) / Math.max(stats1.getAvgCpu(), stats2.getAvgCpu()); double memoryDiff = Math.abs(stats1.getAvgMemory() - stats2.getAvgMemory()) / Math.max(stats1.getAvgMemory(), stats2.getAvgMemory()); return (cpuDiff + memoryDiff) / 2.0; } private String generateReason(ResourceStatistics shortTerm, ResourceStatistics mediumTerm, ResourceStatistics longTerm) { StringBuilder reason = new StringBuilder(); if (shortTerm.getMaxCpu() > mediumTerm.getMaxCpu() * 1.5) { reason.append("Recent CPU spikes detected. "); } if (mediumTerm.getAvgCpu() > longTerm.getAvgCpu() * 1.2) { reason.append("Medium-term CPU usage trending upward. "); } if (shortTerm.getP95Memory() > mediumTerm.getP95Memory() * 1.3) { reason.append("Recent memory usage increases observed. "); } if (reason.length() == 0) { reason.append("Stable usage patterns observed."); } return reason.toString(); } } 4. Metrics Service
// MetricsService.java package com.example.vpa.core; import com.example.vpa.model.PodResourceUsage; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1Pod; import io.kubernetes.client.openapi.models.V1Container; import io.kubernetes.client.openapi.models.V1ResourceRequirements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.Map; @Service public class MetricsService { private static final Logger logger = LoggerFactory.getLogger(MetricsService.class); private final CoreV1Api coreV1Api; private final MetricsCollector metricsCollector; public MetricsService(ApiClient apiClient, MetricsCollector metricsCollector) { this.coreV1Api = new CoreV1Api(apiClient); this.metricsCollector = metricsCollector; } public PodResourceUsage getPodResourceUsage(String podName, String namespace) { try { // Get pod details V1Pod pod = coreV1Api.readNamespacedPod(podName, namespace, null); // Get current resource usage from metrics server Map<String, Double> currentUsage = metricsCollector.getPodMetrics(podName, namespace); // Extract resource requests and limits ResourceAllocation allocation = extractResourceAllocation(pod); return new PodResourceUsage( podName, namespace, currentUsage.getOrDefault("cpu", 0.0), currentUsage.getOrDefault("memory", 0.0), allocation.cpuRequest, allocation.memoryRequest, allocation.cpuLimit, allocation.memoryLimit ); } catch (ApiException e) { logger.error("Failed to get resource usage for pod: {}/{}", namespace, podName, e); throw new RuntimeException("Failed to get pod resource usage", e); } } private ResourceAllocation extractResourceAllocation(V1Pod pod) { double cpuRequest = 0; double memoryRequest = 0; double cpuLimit = 0; double memoryLimit = 0; for (V1Container container : pod.getSpec().getContainers()) { V1ResourceRequirements resources = container.getResources(); if (resources != null) { if (resources.getRequests() != null) { cpuRequest += parseCpu(resources.getRequests().get("cpu")); memoryRequest += parseMemory(resources.getRequests().get("memory")); } if (resources.getLimits() != null) { cpuLimit += parseCpu(resources.getLimits().get("cpu")); memoryLimit += parseMemory(resources.getLimits().get("memory")); } } } return new ResourceAllocation(cpuRequest, memoryRequest, cpuLimit, memoryLimit); } private double parseCpu(String cpu) { if (cpu == null) return 0; if (cpu.endsWith("m")) { return Double.parseDouble(cpu.substring(0, cpu.length() - 1)); } else { return Double.parseDouble(cpu) * 1000; // Convert cores to millicores } } private double parseMemory(String memory) { if (memory == null) return 0; if (memory.endsWith("Ki")) { return Double.parseDouble(memory.substring(0, memory.length() - 2)) / 1024; // Convert to MiB } else if (memory.endsWith("Mi")) { return Double.parseDouble(memory.substring(0, memory.length() - 2)); } else if (memory.endsWith("Gi")) { return Double.parseDouble(memory.substring(0, memory.length() - 2)) * 1024; } else { // Assume bytes return Double.parseDouble(memory) / (1024 * 1024); } } private static class ResourceAllocation { final double cpuRequest; final double memoryRequest; final double cpuLimit; final double memoryLimit; ResourceAllocation(double cpuRequest, double memoryRequest, double cpuLimit, double memoryLimit) { this.cpuRequest = cpuRequest; this.memoryRequest = memoryRequest; this.cpuLimit = cpuLimit; this.memoryLimit = memoryLimit; } } } // MetricsCollector.java package com.example.vpa.core; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class MetricsCollector { private static final Logger logger = LoggerFactory.getLogger(MetricsCollector.class); public Map<String, Double> getPodMetrics(String podName, String namespace) { // In production, this would integrate with Kubernetes Metrics Server // or Prometheus to get actual resource usage // Mock implementation for demonstration Map<String, Double> metrics = new HashMap<>(); // Simulate CPU usage in millicores metrics.put("cpu", Math.random() * 500 + 100); // 100-600 millicores // Simulate memory usage in MiB metrics.put("memory", Math.random() * 200 + 50); // 50-250 MiB logger.debug("Collected metrics for pod {}/{}: CPU={}, Memory={}", namespace, podName, metrics.get("cpu"), metrics.get("memory")); return metrics; } } 5. VPA Configuration
// VPAConfiguration.java package com.example.vpa.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.time.Duration; @Component @ConfigurationProperties(prefix = "vpa") public class VPAConfiguration { private boolean enabled = true; private String namespace = "default"; private String vpaSelector = "vpa-enabled=true"; private boolean autoUpdateEnabled = false; private Duration updateInterval = Duration.ofMinutes(5); private Duration historyWindow = Duration.ofHours(24); // Recommendation settings private RecommendationStrategy recommendationStrategy = RecommendationStrategy.BALANCED; private double minChangeThreshold = 0.1; // 10% minimum change private double minConfidence = 0.7; // 70% minimum confidence // Resource bounds private double minCpu = 10; // 10m CPU private double minMemory = 32; // 32Mi memory private double maxCpu = 4000; // 4 CPU cores private double maxMemory = 8192; // 8Gi memory // Safety margins private double cpuSafetyMargin = 1.2; // 20% buffer private double memorySafetyMargin = 1.3; // 30% buffer // Getters and setters public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getVpaSelector() { return vpaSelector; } public void setVpaSelector(String vpaSelector) { this.vpaSelector = vpaSelector; } public boolean isAutoUpdateEnabled() { return autoUpdateEnabled; } public void setAutoUpdateEnabled(boolean autoUpdateEnabled) { this.autoUpdateEnabled = autoUpdateEnabled; } public Duration getUpdateInterval() { return updateInterval; } public void setUpdateInterval(Duration updateInterval) { this.updateInterval = updateInterval; } public Duration getHistoryWindow() { return historyWindow; } public void setHistoryWindow(Duration historyWindow) { this.historyWindow = historyWindow; } public RecommendationStrategy getRecommendationStrategy() { return recommendationStrategy; } public void setRecommendationStrategy(RecommendationStrategy recommendationStrategy) { this.recommendationStrategy = recommendationStrategy; } public double getMinChangeThreshold() { return minChangeThreshold; } public void setMinChangeThreshold(double minChangeThreshold) { this.minChangeThreshold = minChangeThreshold; } public double getMinConfidence() { return minConfidence; } public void setMinConfidence(double minConfidence) { this.minConfidence = minConfidence; } public double getMinCpu() { return minCpu; } public void setMinCpu(double minCpu) { this.minCpu = minCpu; } public double getMinMemory() { return minMemory; } public void setMinMemory(double minMemory) { this.minMemory = minMemory; } public double getMaxCpu() { return maxCpu; } public void setMaxCpu(double maxCpu) { this.maxCpu = maxCpu; } public double getMaxMemory() { return maxMemory; } public void setMaxMemory(double maxMemory) { this.maxMemory = maxMemory; } public double getCpuSafetyMargin() { return cpuSafetyMargin; } public void setCpuSafetyMargin(double cpuSafetyMargin) { this.cpuSafetyMargin = cpuSafetyMargin; } public double getMemorySafetyMargin() { return memorySafetyMargin; } public void setMemorySafetyMargin(double memorySafetyMargin) { this.memorySafetyMargin = memorySafetyMargin; } public enum RecommendationStrategy { CONSERVATIVE, BALANCED, AGGRESSIVE } } 6. VPA Status and Monitoring
// VPAStatus.java package com.example.vpa.model; import java.time.Instant; import java.util.Map; public class VPAStatus { private final int monitoredPods; private final int recommendationsGenerated; private final Instant lastUpdate; private final Map<String, Double> averageRecommendations; private final String status; public VPAStatus(int monitoredPods, int recommendationsGenerated, Instant lastUpdate, Map<String, Double> averageRecommendations) { this.monitoredPods = monitoredPods; this.recommendationsGenerated = recommendationsGenerated; this.lastUpdate = lastUpdate; this.averageRecommendations = averageRecommendations; this.status = calculateStatus(); } // Getters public int getMonitoredPods() { return monitoredPods; } public int getRecommendationsGenerated() { return recommendationsGenerated; } public Instant getLastUpdate() { return lastUpdate; } public Map<String, Double> getAverageRecommendations() { return averageRecommendations; } public String getStatus() { return status; } private String calculateStatus() { if (monitoredPods == 0) return "INACTIVE"; if (recommendationsGenerated == 0) return "COLLECTING_DATA"; return "ACTIVE"; } public Map<String, Object> toMap() { return Map.of( "monitoredPods", monitoredPods, "recommendationsGenerated", recommendationsGenerated, "lastUpdate", lastUpdate.toString(), "averageRecommendations", averageRecommendations, "status", status ); } } 7. REST Controller for VPA
Pyroscope Profiling in Java
Explains how to use Pyroscope for continuous profiling in Java applications, helping developers analyze CPU and memory usage patterns to improve performance and identify bottlenecks.
https://macronepal.com/blog/pyroscope-profiling-in-java/
OpenTelemetry Metrics in Java: Comprehensive Guide
Provides a complete guide to collecting and exporting metrics in Java using OpenTelemetry, including counters, histograms, gauges, and integration with monitoring tools. (MACRO NEPAL)
https://macronepal.com/blog/opentelemetry-metrics-in-java-comprehensive-guide/
OTLP Exporter in Java: Complete Guide for OpenTelemetry
Explains how to configure OTLP exporters in Java to send telemetry data such as traces, metrics, and logs to monitoring systems using HTTP or gRPC protocols. (MACRO NEPAL)
https://macronepal.com/blog/otlp-exporter-in-java-complete-guide-for-opentelemetry/
Thanos Integration in Java: Global View of Metrics
Explains how to integrate Thanos with Java monitoring systems to create a scalable global metrics view across multiple Prometheus instances.
https://macronepal.com/blog/thanos-integration-in-java-global-view-of-metrics
Time Series with InfluxDB in Java: Complete Guide (Version 2)
Explains how to manage time-series data using InfluxDB in Java applications, including storing, querying, and analyzing metrics data.
https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide-2
Time Series with InfluxDB in Java: Complete Guide
Provides an overview of integrating InfluxDB with Java for time-series data handling, including monitoring applications and managing performance metrics.
https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide
Implementing Prometheus Remote Write in Java (Version 2)
Explains how to configure Java applications to send metrics data to Prometheus-compatible systems using the remote write feature for scalable monitoring.
https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide-2
Implementing Prometheus Remote Write in Java: Complete Guide
Provides instructions for sending metrics from Java services to Prometheus servers, enabling centralized monitoring and real-time analytics.
https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide
Building a TileServer GL in Java: Vector and Raster Tile Server
Explains how to build a TileServer GL in Java for serving vector and raster map tiles, useful for geographic visualization and mapping applications.
https://macronepal.com/blog/building-a-tileserver-gl-in-java-vector-and-raster-tile-server
Indoor Mapping in Java
Explains how to create indoor mapping systems in Java, including navigation inside buildings, spatial data handling, and visualization techniques.