Goldilocks for Java Application Resource Tuning: Complete Guide

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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper