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.