Goldilocks is a Kubernetes tool that provides recommendations for resource requests and limits using Vertical Pod Autoscaler (VPA). It helps find the "just right" resource allocation for your Java applications.
1. Understanding Goldilocks for Java Workloads
How Goldilocks Works
Java App + VPA → Metrics Collection → Recommendation Engine → Resource Suggestions
Benefits for Java Applications
- Prevents OOMKills by recommending appropriate memory limits
- Optimizes CPU allocation for JVM efficiency
- Reduces resource waste in Kubernetes clusters
- Provides data-driven resource planning
2. Installation and Setup
Installing Goldilocks in Kubernetes
# Add the Fairwinds Helm repository helm repo add fairwinds-stable https://fairwinds-stable.github.io/charts helm repo update # Install Goldilocks helm upgrade --install goldilocks fairwinds-stable/goldilocks \ --namespace goldilocks \ --create-namespace \ --set vpa.enabled=true
Installing VPA (if not included)
# Add VPA Helm repository helm repo add fairwinds-stable https://fairwinds-stable.github.io/charts # Install VPA helm upgrade --install vpa fairwinds-stable/vpa \ --namespace goldilocks \ --set recommender.enabled=true \ --set updater.enabled=true \ --set admissionController.enabled=true
Verify Installation
kubectl get pods -n goldilocks kubectl get vpa -A
3. Java Application Configuration for Goldilocks
Sample Java Deployment with Resource Monitoring
apiVersion: apps/v1 kind: Deployment metadata: name: java-order-service labels: app: java-order-service goldilocks.fairwinds.com/enabled: "true" goldilocks.fairwinds.com/vpa-update-mode: "Off" spec: replicas: 3 selector: matchLabels: app: java-order-service template: metadata: labels: app: java-order-service goldilocks.fairwinds.com/enabled: "true" annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" prometheus.io/path: "/actuator/prometheus" spec: serviceAccountName: java-app-sa containers: - name: java-app image: company/java-order-service:1.0.0 ports: - containerPort: 8080 env: - name: JAVA_OPTS value: > -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0 -XX:MinRAMPercentage=25.0 -Djava.security.egd=file:/dev/./urandom -Duser.timezone=UTC -Dmanagement.endpoints.web.exposure.include=health,metrics,prometheus,info -Dmanagement.metrics.export.prometheus.enabled=true # Initial resource requests - will be tuned by Goldilocks resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 5 timeoutSeconds: 3 startupProbe: httpGet: path: /actuator/health/startup port: 8080 initialDelaySeconds: 10 periodSeconds: 10 failureThreshold: 10 --- apiVersion: v1 kind: Service metadata: name: java-order-service labels: app: java-order-service spec: ports: - port: 8080 targetPort: 8080 selector: app: java-order-service
VPA Configuration for Java Application
apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: java-order-service-vpa namespace: default spec: targetRef: apiVersion: "apps/v1" kind: Deployment name: java-order-service updatePolicy: updateMode: "Off" # Start with Off, change to Auto after validation resourcePolicy: containerPolicies: - containerName: java-app minAllowed: cpu: "100m" memory: "256Mi" maxAllowed: cpu: "2" memory: "4Gi" controlledResources: ["cpu", "memory"]
4. Java-Specific Resource Monitoring
Spring Boot Actuator Configuration
application.yml for Resource Monitoring:
management: endpoints: web: exposure: include: health,metrics,prometheus,info,env,configprops endpoint: health: show-details: always show-components: always prometheus: enabled: true metrics: export: prometheus: enabled: true distribution: percentiles-histogram: http.server.requests: true tags: application: java-order-service environment: production # JVM Metrics configuration app: monitoring: jvm: enabled: true gc: logging: true resources: tracking: enabled: true logging: level: org.springframework.boot.actuate.metrics: INFO
Custom JVM Metrics Exporter
@Component
public class JvmResourceMetrics {
private final MeterRegistry meterRegistry;
private final Runtime runtime;
private final ScheduledExecutorService scheduler;
public JvmResourceMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.runtime = Runtime.getRuntime();
this.scheduler = Executors.newScheduledThreadPool(1);
startMonitoring();
}
private void startMonitoring() {
// Monitor JVM memory usage
scheduler.scheduleAtFixedRate(this::recordMemoryMetrics, 0, 30, TimeUnit.SECONDS);
// Monitor GC activity
scheduler.scheduleAtFixedRate(this::recordGcMetrics, 0, 60, TimeUnit.SECONDS);
// Monitor thread usage
scheduler.scheduleAtFixedRate(this::recordThreadMetrics, 0, 30, TimeUnit.SECONDS);
}
private void recordMemoryMetrics() {
long maxMemory = runtime.maxMemory();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
double memoryUsagePercent = (double) usedMemory / maxMemory * 100;
Gauge.builder("jvm.memory.used.bytes")
.description("Used JVM memory in bytes")
.register(meterRegistry)
.set(usedMemory);
Gauge.builder("jvm.memory.max.bytes")
.description("Max JVM memory in bytes")
.register(meterRegistry)
.set(maxMemory);
Gauge.builder("jvm.memory.usage.percent")
.description("JVM memory usage percentage")
.register(meterRegistry)
.set(memoryUsagePercent);
}
private void recordGcMetrics() {
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean gc : gcBeans) {
String gcName = gc.getName().replace(" ", "_").toLowerCase();
Counter.builder("jvm.gc.collection.count")
.tag("gc", gcName)
.register(meterRegistry)
.increment(gc.getCollectionCount());
Counter.builder("jvm.gc.collection.time")
.tag("gc", gcName)
.register(meterRegistry)
.increment(gc.getCollectionTime());
}
}
private void recordThreadMetrics() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
Gauge.builder("jvm.threads.live")
.description("Current live threads")
.register(meterRegistry)
.set(threadBean.getThreadCount());
Gauge.builder("jvm.threads.daemon")
.description("Daemon threads")
.register(meterRegistry)
.set(threadBean.getDaemonThreadCount());
Gauge.builder("jvm.threads.peak")
.description("Peak thread count")
.register(meterRegistry)
.set(threadBean.getPeakThreadCount());
}
@PreDestroy
public void cleanup() {
scheduler.shutdown();
}
}
Kubernetes Resource Usage Tracker
@Component
@EnableScheduling
public class KubernetesResourceTracker {
private static final Logger logger = LoggerFactory.getLogger(KubernetesResourceTracker.class);
private final MeterRegistry meterRegistry;
public KubernetesResourceTracker(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Scheduled(fixedRate = 60000) // Every minute
public void trackContainerResources() {
try {
String memoryLimit = System.getenv("CONTAINER_MEMORY_LIMIT");
String cpuLimit = System.getenv("CONTAINER_CPU_LIMIT");
if (memoryLimit != null) {
long memoryBytes = parseMemoryString(memoryLimit);
Gauge.builder("container.memory.limit.bytes")
.register(meterRegistry)
.set(memoryBytes);
}
if (cpuLimit != null) {
double cpuCores = parseCpuString(cpuLimit);
Gauge.builder("container.cpu.limit.cores")
.register(meterRegistry)
.set(cpuCores);
}
} catch (Exception e) {
logger.warn("Failed to track container resources", e);
}
}
private long parseMemoryString(String memory) {
if (memory.endsWith("Gi")) {
return (long) (Double.parseDouble(memory.replace("Gi", "")) * 1024 * 1024 * 1024);
} else if (memory.endsWith("Mi")) {
return (long) (Double.parseDouble(memory.replace("Mi", "")) * 1024 * 1024);
} else if (memory.endsWith("Ki")) {
return Long.parseLong(memory.replace("Ki", "")) * 1024;
}
return Long.parseLong(memory);
}
private double parseCpuString(String cpu) {
if (cpu.endsWith("m")) {
return Double.parseDouble(cpu.replace("m", "")) / 1000.0;
}
return Double.parseDouble(cpu);
}
}
5. Goldilocks Dashboard and Recommendations
Accessing Goldilocks Dashboard
# Port forward to access the dashboard kubectl port-forward -n goldilocks svc/goldilocks-dashboard 8080:80 # Access at http://localhost:8080
Goldilocks Controller Configuration
apiVersion: v1 kind: ConfigMap metadata: name: goldilocks-controller-config namespace: goldilocks data: GOLDILOCKS_CONTROLLER_ARGS: | --vpa-label=goldilocks.fairwinds.com/enabled --on-by-default=false --exclude-namespaces=kube-system,goldilocks --interval=1h
Namespace Configuration for Goldilocks
# Enable Goldilocks for specific namespace kubectl label namespace default goldilocks.fairwinds.com/enabled=true # Enable for all workloads in namespace kubectl label deployments --all goldilocks.fairwinds.com/enabled=true -n default
6. Analyzing Goldilocks Recommendations
Sample Goldilocks Recommendation Output
# kubectl get vpa java-order-service-vpa -o yaml apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: java-order-service-vpa namespace: default status: recommendation: containerRecommendations: - containerName: java-app lowerBound: cpu: "350m" memory: "768Mi" target: cpu: "450m" memory: "1024Mi" upperBound: cpu: "850m" memory: "1536Mi" uncappedTarget: cpu: "480m" memory: "1100Mi"
Interpreting Recommendations for Java Apps
Memory Analysis:
- Current: 512Mi request, 1Gi limit
- Recommended: 768Mi-1536Mi range, 1024Mi target
- Action: Increase memory request to 1024Mi
CPU Analysis:
- Current: 250m request, 500m limit
- Recommended: 350m-850m range, 450m target
- Action: Increase CPU request to 450m
7. Implementing Goldilocks Recommendations
Updated Deployment with Goldilocks Recommendations
apiVersion: apps/v1 kind: Deployment metadata: name: java-order-service-optimized labels: app: java-order-service spec: replicas: 3 selector: matchLabels: app: java-order-service template: metadata: labels: app: java-order-service spec: containers: - name: java-app image: company/java-order-service:1.0.0 ports: - containerPort: 8080 env: - name: JAVA_OPTS value: > -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0 -XX:MinRAMPercentage=25.0 -Xms768m -Xmx1024m -Djava.security.egd=file:/dev/./urandom -Duser.timezone=UTC # Optimized resources based on Goldilocks recommendations resources: requests: memory: "1024Mi" # From Goldilocks target cpu: "450m" # From Goldilocks target limits: memory: "1536Mi" # From Goldilocks upperBound cpu: "850m" # From Goldilocks upperBound livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 5 --- # Updated VPA for continuous optimization apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: java-order-service-vpa-optimized spec: targetRef: apiVersion: "apps/v1" kind: Deployment name: java-order-service-optimized updatePolicy: updateMode: "Auto" # Now in Auto mode after validation resourcePolicy: containerPolicies: - containerName: java-app minAllowed: cpu: "350m" memory: "768Mi" maxAllowed: cpu: "850m" memory: "1536Mi" controlledResources: ["cpu", "memory"]
8. Advanced Goldilocks Configuration for Java
Multi-Environment Goldilocks Setup
# values-goldilocks.yaml vpa: enabled: true image: tag: "0.10.0" controller: args: - --vpa-label=goldilocks.fairwinds.com/enabled - --on-by-default=false - --exclude-namespaces=kube-system,goldilocks,monitoring - --interval=1h - --recommendation-margin-fraction=0.15 dashboard: enabled: true service: type: ClusterIP # Resource limits for Goldilocks itself resources: limits: cpu: 100m memory: 128Mi requests: cpu: 50m memory: 64Mi
Java-Specific VPA Resource Policy
apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: java-app-vpa-advanced spec: targetRef: apiVersion: "apps/v1" kind: Deployment name: java-order-service updatePolicy: updateMode: "Initial" minReplicas: 2 resourcePolicy: containerPolicies: - containerName: java-app mode: "Auto" minAllowed: cpu: "200m" memory: "512Mi" maxAllowed: cpu: "2" memory: "4Gi" controlledResources: ["cpu", "memory"] controlledValues: "RequestsAndLimits" # Java-specific scaling policies scalingMode: "Default"
9. Monitoring and Validation
Goldilocks Validation Script
#!/bin/bash
# validate-goldilocks.sh
NAMESPACE=${1:-default}
DEPLOYMENT=${2:-java-order-service}
echo "=== Goldilocks Validation for $DEPLOYMENT in $NAMESPACE ==="
# Check VPA status
echo "VPA Status:"
kubectl get vpa -n $NAMESPACE $DEPLOYMENT-vpa -o jsonpath='{.status.conditions[*].type}{"\n"}'
# Get current resource usage
echo -e "\nCurrent Resource Usage:"
kubectl top pods -n $NAMESPACE -l app=$DEPLOYMENT
# Get VPA recommendations
echo -e "\nVPA Recommendations:"
kubectl get vpa -n $NAMESPACE $DEPLOYMENT-vpa -o jsonpath='{.status.recommendation.containerRecommendations[0].target}' | jq .
# Check for OOMKills
echo -e "\nOOMKill Events:"
kubectl get events -n $NAMESPACE --field-selector reason=OOMKilling
# Check pod restarts
echo -e "\nPod Restarts:"
kubectl get pods -n $NAMESPACE -l app=$DEPLOYMENT -o jsonpath='{range .items[*]}{.metadata.name}: {.status.containerStatuses[0].restartCount} restarts{"\n"}{end}'
Prometheus Alerts for Resource Optimization
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: java-resource-optimization-alerts
namespace: monitoring
spec:
groups:
- name: java-resource-optimization
rules:
- alert: JavaAppUnderprovisionedCPU
expr: rate(container_cpu_usage_seconds_total{container="java-app"}[5m]) * 100 > 80
for: 10m
labels:
severity: warning
category: resources
annotations:
summary: "Java app CPU usage is high"
description: "CPU usage for {{ $labels.pod }} is {{ $value }}%. Consider increasing CPU limits."
- alert: JavaAppUnderprovisionedMemory
expr: container_memory_working_set_bytes{container="java-app"} / container_spec_memory_limit_bytes > 0.8
for: 5m
labels:
severity: warning
category: resources
annotations:
summary: "Java app memory usage is high"
description: "Memory usage for {{ $labels.pod }} is {{ $value | humanizePercentage }} of limit."
- alert: GoldilocksRecommendationAvailable
expr: time() - vpa_recommendation_timestamp_seconds > 3600
for: 0m
labels:
severity: info
category: optimization
annotations:
summary: "Goldilocks recommendations available"
description: "VPA has new resource recommendations available for review."
10. Continuous Optimization Workflow
GitHub Actions for Goldilocks Validation
name: Goldilocks Resource Validation
on:
schedule:
- cron: '0 6 * * 1' # Every Monday at 6 AM
workflow_dispatch:
jobs:
validate-resources:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.26.0'
- name: Configure kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > kubeconfig.yaml
export KUBECONFIG=kubeconfig.yaml
- name: Run Goldilocks Validation
run: |
./scripts/validate-goldilocks.sh default java-order-service
- name: Generate Resource Report
run: |
kubectl get vpa -A -o json > vpa-recommendations.json
# Process recommendations and generate report
python scripts/generate-resource-report.py
- name: Upload Report
uses: actions/upload-artifact@v3
with:
name: resource-optimization-report
path: resource-report.html
This comprehensive guide shows how to effectively use Goldilocks for Java application resource tuning in Kubernetes, helping you achieve optimal resource allocation, reduce costs, and improve application stability through data-driven resource recommendations.