google-site-verification: google61fe8ba583a51912.html
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