google-site-verification: google61fe8ba583a51912.html
Managing Multi-Environment Kubernetes Deployments with Helmfile for Java Applications

Introduction

Helmfile is a declarative specification for deploying Helm charts across multiple environments, bringing GitOps practices to Helm deployments. For Java applications running in Kubernetes, Helmfile provides a powerful way to manage environment-specific configurations, dependencies, and release strategies. This guide explores how to structure Helmfile configurations for complex Java microservices deployments.


Article: Streamlining Multi-Environment Java Deployments with Helmfile

Helmfile enhances Helm by providing a way to compose multiple charts, manage environment-specific values, and synchronize releases across namespaces. When combined with Java applications, it creates a robust deployment pipeline that can handle development, staging, and production environments consistently.

1. Project Structure for Multi-Environment Java Apps

java-helmfile-project/ ├── src/ │ └── main/ │ └── java/ │ └── com/myapp/ ├── helm/ │ ├── my-java-app/ │ │ ├── Chart.yaml │ │ ├── values.yaml │ │ ├── templates/ │ │ │ ├── deployment.yaml │ │ │ ├── service.yaml │ │ │ ├── configmap.yaml │ │ │ └── ingress.yaml │ │ └── charts/ │ ├── dependencies/ │ │ ├── redis/ │ │ └── postgresql/ │ └── environments/ │ ├── helmfile.dev.yaml │ ├── helmfile.staging.yaml │ ├── helmfile.prod.yaml │ └── values/ │ ├── dev/ │ │ ├── my-java-app.yaml │ │ ├── redis.yaml │ │ └── postgresql.yaml │ ├── staging/ │ └── production/ ├── helmfile.yaml └── scripts/ └── deploy.sh

2. Java Application Helm Chart

Chart.yaml:

apiVersion: v2 name: order-service description: A Spring Boot Order Service type: application version: 1.0.0 appVersion: "1.0.0" dependencies: - name: redis version: "17.0.0" repository: "https://charts.bitnami.com/bitnami" condition: redis.enabled - name: postgresql version: "12.0.0" repository: "https://charts.bitnami.com/bitnami" condition: postgresql.enabled

values.yaml (Default):

# Default values for Java application image: repository: my-registry/order-service tag: latest pullPolicy: IfNotPresent replicaCount: 2 javaApp: name: order-service port: 8080 serviceType: ClusterIP # JVM Configuration jvm: memory: max: "512m" min: "256m" opts: "-XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom" profiling: false # Spring Boot Actuator actuator: enabled: true path: "/actuator" # Environment Configuration env: SPRING_PROFILES_ACTIVE: "kubernetes" LOGGING_LEVEL_COM_MYAPP: "INFO" # Resource limits resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "768Mi" cpu: "500m" # Probes livenessProbe: path: /actuator/health/liveness initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: path: /actuator/health/readiness initialDelaySeconds: 30 periodSeconds: 5 # Dependencies redis: enabled: true architecture: standalone postgresql: enabled: true auth: database: "order_service" username: "order_user"

templates/deployment.yaml:

apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Chart.Name }} labels: app: {{ .Chart.Name }} chart: {{ .Chart.Name }}-{{ .Chart.Version }} version: {{ .Values.image.tag }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ .Chart.Name }} template: metadata: labels: app: {{ .Chart.Name }} version: {{ .Values.image.tag }} annotations: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: {{ .Values.javaApp.port }} protocol: TCP env: - name: JAVA_OPTS value: "-Xmx{{ .Values.javaApp.jvm.memory.max }} -Xms{{ .Values.javaApp.jvm.memory.min }} {{ .Values.javaApp.jvm.opts }}" {{- if .Values.javaApp.jvm.profiling }} - name: JAVA_TOOL_OPTIONS value: "-agentpath:/opt/java-async-profiler/build/libasyncProfiler.so=start,event=cpu,file=/tmp/profile.html" {{- end }} - name: SPRING_PROFILES_ACTIVE value: {{ .Values.javaApp.env.SPRING_PROFILES_ACTIVE | quote }} - name: LOGGING_LEVEL_COM_MYAPP value: {{ .Values.javaApp.env.LOGGING_LEVEL_COM_MYAPP | quote }} - name: SPRING_DATASOURCE_URL value: "jdbc:postgresql://{{ .Chart.Name }}-postgresql:5432/{{ .Values.postgresql.auth.database }}" - name: SPRING_REDIS_HOST value: "{{ .Chart.Name }}-redis-master" {{- include "java-app.envVars" . }} resources: {{- toYaml .Values.javaApp.resources | nindent 12 }} livenessProbe: httpGet: path: {{ .Values.javaApp.livenessProbe.path }} port: http initialDelaySeconds: {{ .Values.javaApp.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.javaApp.livenessProbe.periodSeconds }} readinessProbe: httpGet: path: {{ .Values.javaApp.readinessProbe.path }} port: http initialDelaySeconds: {{ .Values.javaApp.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.javaApp.readinessProbe.periodSeconds }} {{- if .Values.javaApp.volumeMounts }} volumeMounts: {{- toYaml .Values.javaApp.volumeMounts | nindent 12 }} {{- end }} {{- if .Values.javaApp.volumes }} volumes: {{- toYaml .Values.javaApp.volumes | nindent 8 }} {{- end }}

3. Helmfile Configuration

Root helmfile.yaml:

# Base configuration apiVersion: v1 environments: default: values: - environments/values/dev/my-java-app.yaml dev: values: - environments/values/dev/my-java-app.yaml staging: values: - environments/values/staging/my-java-app.yaml production: values: - environments/values/production/my-java-app.yaml # Common releases releases: # Java Application - name: order-service namespace: "{{ .Environment.Name }}-services" chart: ./helm/my-java-app version: 1.0.0 values: - "{{ .Environment.Name }}/order-service.yaml.gotmpl" wait: true timeout: 600 labels: app: order-service team: backend # Monitoring Stack - name: monitoring namespace: "{{ .Environment.Name }}-monitoring" chart: prometheus-community/kube-prometheus-stack version: 46.0.0 values: - "{{ .Environment.Name }}/monitoring.yaml.gotmpl" labels: app: monitoring team: platform # Templates for value files templates: order-service.yaml.gotmpl: | image: tag: "{{ .Values.imageTag | default "latest" }}" replicaCount: {{ .Values.replicaCount | default 2 }} javaApp: jvm: memory: max: "{{ .Values.jvmMemoryMax | default "512m" }}" min: "{{ .Values.jvmMemoryMin | default "256m" }}" resources: requests: memory: "{{ .Values.resourceRequestsMemory | default "512Mi" }}" cpu: "{{ .Values.resourceRequestsCpu | default "250m" }}" limits: memory: "{{ .Values.resourceLimitsMemory | default "768Mi" }}" cpu: "{{ .Values.resourceLimitsCpu | default "500m" }}"

4. Environment-Specific Values

environments/values/dev/order-service.yaml:

# Development Environment replicaCount: 1 imageTag: "latest" jvmMemoryMax: "256m" jvmMemoryMin: "128m" resourceRequestsMemory: "256Mi" resourceRequestsCpu: "100m" resourceLimitsMemory: "512Mi" resourceLimitsCpu: "250m" javaApp: env: SPRING_PROFILES_ACTIVE: "dev,kubernetes" LOGGING_LEVEL_COM_MYAPP: "DEBUG" jvm: profiling: true opts: "-XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" redis: enabled: true architecture: standalone postgresql: enabled: true auth: password: "dev_password"

environments/values/staging/order-service.yaml:

# Staging Environment replicaCount: 2 imageTag: "{{ requiredEnv "STAGING_IMAGE_TAG" }}" jvmMemoryMax: "512m" jvmMemoryMin: "256m" resourceRequestsMemory: "512Mi" resourceRequestsCpu: "250m" resourceLimitsMemory: "1024Mi" resourceLimitsCpu: "500m" javaApp: env: SPRING_PROFILES_ACTIVE: "staging,kubernetes" LOGGING_LEVEL_COM_MYAPP: "INFO" jvm: profiling: false opts: "-XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom" redis: enabled: true architecture: standalone auth: password: "{{ requiredEnv "REDIS_PASSWORD" }}" postgresql: enabled: true auth: password: "{{ requiredEnv "POSTGRES_PASSWORD" }}"

environments/values/production/order-service.yaml:

# Production Environment replicaCount: 3 imageTag: "{{ requiredEnv "PRODUCTION_IMAGE_TAG" }}" jvmMemoryMax: "1024m" jvmMemoryMin: "512m" resourceRequestsMemory: "1024Mi" resourceRequestsCpu: "500m" resourceLimitsMemory: "2048Mi" resourceLimitsCpu: "1000m" javaApp: env: SPRING_PROFILES_ACTIVE: "production,kubernetes" LOGGING_LEVEL_COM_MYAPP: "WARN" jvm: profiling: false opts: "-XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport -Djava.security.egd=file:/dev/./urandom" # Production-specific configuration ingress: enabled: true className: "nginx" annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" hosts: - host: orders.mycompany.com paths: - path: / pathType: Prefix tls: - secretName: order-service-tls hosts: - orders.mycompany.com redis: enabled: true architecture: replication auth: password: "{{ requiredEnv "REDIS_PASSWORD" }}" postgresql: enabled: true architecture: replication auth: password: "{{ requiredEnv "POSTGRES_PASSWORD" }}"

5. Advanced Helmfile Features

Secrets Management with SOPS:

# helmfile.secrets.yaml releases: - name: order-service namespace: "{{ .Environment.Name }}-services" chart: ./helm/my-java-app values: - "{{ .Environment.Name }}/order-service.yaml.gotmpl" - "secrets/{{ .Environment.Name }}/encrypted-secrets.yaml" # Encrypted secrets file (secrets/production/encrypted-secrets.yaml) javaApp: env: SPRING_DATASOURCE_PASSWORD: "ENC[AES256_GCM,data:...]" SPRING_REDIS_PASSWORD: "ENC[AES256_GCM,data:...]" CUSTOMER_SERVICE_API_KEY: "ENC[AES256_GCM,data:...]"

Dependent Releases:

releases: - name: postgresql namespace: "{{ .Environment.Name }}-data" chart: bitnami/postgresql version: 12.0.0 values: - "{{ .Environment.Name }}/postgresql.yaml.gotmpl" labels: app: database team: data - name: order-service namespace: "{{ .Environment.Name }}-services" chart: ./helm/my-java-app version: 1.0.0 needs: - "{{ .Environment.Name }}-data/postgresql" values: - "{{ .Environment.Name }}/order-service.yaml.gotmpl"

Hooks for Pre/Post Deployment:

releases: - name: order-service namespace: "{{ .Environment.Name }}-services" chart: ./helm/my-java-app values: - "{{ .Environment.Name }}/order-service.yaml.gotmpl" # Pre-install hooks hooks: - events: ["presync"] command: "kubectl" args: ["-n", "{{ .Environment.Name }}-services", "wait", "--for=condition=ready", "pod", "-l", "app=postgresql", "--timeout=300s"] - events: ["postsync"] command: "bash" args: ["-c", "echo 'Deployment completed for {{ .Release.Name }} in {{ .Environment.Name }}'"] # Run database migrations for Java app - events: ["presync"] command: "kubectl" args: - "run" - "{{ .Release.Name }}-migrations" - "--image" - "my-registry/order-service:{{ .Values.imageTag }}" - "--env=SPRING_PROFILES_ACTIVE={{ .Values.javaApp.env.SPRING_PROFILES_ACTIVE }}" - "--command" - "--" - "java" - "-jar" - "/app.jar" - "--spring.flyway.locations=classpath:db/migration" - "--spring.flyway.baseline-on-migrate=true"

6. Java Application Deployment Scripts

deploy.sh:

#!/bin/bash set -e ENVIRONMENT="${1:-dev}" ACTION="${2:-apply}" IMAGE_TAG="${3:-latest}" # Validate environment if [[ ! "$ENVIRONMENT" =~ ^(dev|staging|production)$ ]]; then echo "Error: Environment must be one of: dev, staging, production" exit 1 fi # Export environment variables export ENVIRONMENT=$ENVIRONMENT # Set image tag based on environment if [ "$ENVIRONMENT" == "production" ]; then export PRODUCTION_IMAGE_TAG=$IMAGE_TAG elif [ "$ENVIRONMENT" == "staging" ]; then export STAGING_IMAGE_TAG=$IMAGE_TAG fi echo "Deploying to $ENVIRONMENT environment with image tag: $IMAGE_TAG" # Select appropriate helmfile HELMFILE="helmfile.yaml" case $ACTION in "apply") echo "Applying Helmfile..." helmfile --environment $ENVIRONMENT -f $HELMFILE apply ;; "diff") echo "Showing diff..." helmfile --environment $ENVIRONMENT -f $HELMFILE diff ;; "sync") echo "Syncing releases..." helmfile --environment $ENVIRONMENT -f $HELMFILE sync ;; "destroy") read -p "Are you sure you want to destroy $ENVIRONMENT environment? (y/n): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then helmfile --environment $ENVIRONMENT -f $HELMFILE destroy fi ;; *) echo "Usage: $0 {dev|staging|production} {apply|diff|sync|destroy} [image-tag]" exit 1 ;; esac

GitHub Actions Workflow:

name: Deploy Java Application on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Helm uses: azure/setup-helm@v3 with: version: '3.12.0' - name: Set up Helmfile run: | curl -fsSL -o helmfile_linux_amd64 https://github.com/helmfile/helmfile/releases/download/v0.156.0/helmfile_linux_amd64 chmod +x helmfile_linux_amd64 sudo mv helmfile_linux_amd64 /usr/local/bin/helmfile - name: Lint Helm charts run: | helm lint helm/my-java-app helmfile --environment dev lint deploy-dev: runs-on: ubuntu-latest needs: test if: github.ref == 'refs/heads/develop' environment: dev steps: - uses: actions/checkout@v3 - name: Set up Kubernetes uses: azure/setup-kubectl@v3 with: version: 'v1.27.0' - name: Set up Helmfile run: | curl -fsSL -o helmfile_linux_amd64 https://github.com/helmfile/helmfile/releases/download/v0.156.0/helmfile_linux_amd64 chmod +x helmfile_linux_amd64 sudo mv helmfile_linux_amd64 /usr/local/bin/helmfile - name: Configure Kubernetes run: | echo "${{ secrets.DEV_KUBECONFIG }}" > kubeconfig export KUBECONFIG=kubeconfig - name: Deploy to Dev run: | ./scripts/deploy.sh dev apply ${{ github.sha }} deploy-production: runs-on: ubuntu-latest needs: test if: github.ref == 'refs/heads/main' environment: production steps: - uses: actions/checkout@v3 - name: Set up Kubernetes uses: azure/setup-kubectl@v3 with: version: 'v1.27.0' - name: Set up Helmfile run: | curl -fsSL -o helmfile_linux_amd64 https://github.com/helmfile/helmfile/releases/download/v0.156.0/helmfile_linux_amd64 chmod +x helmfile_linux_amd64 sudo mv helmfile_linux_amd64 /usr/local/bin/helmfile - name: Configure Kubernetes run: | echo "${{ secrets.PRODUCTION_KUBECONFIG }}" > kubeconfig export KUBECONFIG=kubeconfig - name: Deploy to Production run: | ./scripts/deploy.sh production apply ${{ github.sha }}

7. Java-Specific Helmfile Enhancements

Health Check and Readiness Verification:

releases: - name: order-service namespace: "{{ .Environment.Name }}-services" chart: ./helm/my-java-app values: - "{{ .Environment.Name }}/order-service.yaml.gotmpl" hooks: - events: ["postsync"] command: "bash" args: - "-c" - | echo "Waiting for Java application to be ready..." kubectl wait --for=condition=ready pod \ -l app=order-service \ -n {{ .Environment.Name }}-services \ --timeout=300s echo "Testing application health endpoint..." SERVICE_URL=$(kubectl get svc order-service -n {{ .Environment.Name }}-services -o jsonpath='{.status.loadBalancer.ingress[0].ip}') curl -f http://$SERVICE_URL:8080/actuator/health || exit 1 echo "Application is healthy!"

Database Migration Hook:

hooks: - events: ["presync"] command: "kubectl" args: - "run" - "{{ .Release.Name }}-flyway-migration" - "-n" - "{{ .Environment.Name }}-services" - "--image" - "my-registry/order-service:{{ .Values.imageTag }}" - "--restart=Never" - "--rm=true" - "--attach=true" - "--env=SPRING_PROFILES_ACTIVE={{ .Values.javaApp.env.SPRING_PROFILES_ACTIVE }}" - "--env=SPRING_DATASOURCE_URL=jdbc:postgresql://order-service-postgresql:5432/order_service" - "--env=SPRING_DATASOURCE_USERNAME={{ .Values.postgresql.auth.username }}" - "--env=SPRING_DATASOURCE_PASSWORD={{ .Values.postgresql.auth.password }}" - "--command" - "--" - "java" - "-jar" - "/app.jar" - "--spring.flyway.locations=classpath:db/migration" - "--spring.flyway.baseline-on-migrate=true" - "--spring.flyway.validate-on-migrate=true"

8. Best Practices for Java Applications

1. Environment-Specific JVM Configuration:

# Development jvmOpts: "-XX:+UseG1GC -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005" # Production jvmOpts: "-XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

2. Resource Management:

resources: requests: memory: "{{ .Values.jvmMemoryMin | default "256m" | replace "m" "Mi" | replace "g" "Gi" }}" cpu: "{{ .Values.resourceRequestsCpu | default "250m" }}" limits: memory: "{{ .Values.jvmMemoryMax | default "512m" | replace "m" "Mi" | replace "g" "Gi" }}" cpu: "{{ .Values.resourceLimitsCpu | default "500m" }}"

3. Graceful Shutdown Configuration:

javaApp: lifecycle: preStop: exec: command: ["sh", "-c", "sleep 30"] # Give time for graceful shutdown

Benefits of Helmfile for Java Applications

  1. Consistent Environments - Identical configuration across dev, staging, and production
  2. GitOps Workflow - Version-controlled infrastructure changes
  3. Dependency Management - Coordinated deployment of Java apps and their dependencies
  4. Secret Management - Secure handling of database credentials and API keys
  5. Rollback Capabilities - Easy rollback to previous versions
  6. Automated Testing - Integration with CI/CD pipelines

Conclusion

Helmfile provides a powerful, declarative approach to managing multi-environment Kubernetes deployments for Java applications. By combining Helm's packaging capabilities with Helmfile's environment management and lifecycle hooks, teams can create robust, maintainable deployment pipelines.

The key to success with Helmfile is establishing clear patterns for environment separation, secret management, and deployment verification—especially important for stateful Java applications that require database migrations and health checks.

With the approach outlined in this guide, Java development teams can achieve reliable, repeatable deployments across all environments while maintaining the flexibility to customize configurations for each stage of the development lifecycle.


Call to Action: Start by converting a single Java application's Helm chart to use Helmfile. Create environment-specific value files and implement basic hooks for health checks. Gradually expand to include dependencies and more sophisticated deployment patterns as your team becomes comfortable with the workflow.

Java Logistics, Shipping Integration & Enterprise Inventory Automation (Tracking, ERP, RFID & Billing Systems)

https://macronepal.com/blog/aftership-tracking-in-java-enterprise-package-visibility/
Explains how to integrate AfterShip tracking services into Java applications to provide real-time shipment visibility, delivery status updates, and centralized tracking across multiple courier services.

https://macronepal.com/blog/shipping-integration-using-fedex-api-with-java-for-logistics-automation/
Explains how to integrate the FedEx API into Java systems to automate shipping tasks such as creating shipments, calculating delivery costs, generating shipping labels, and tracking packages.

https://macronepal.com/blog/shipping-and-logistics-integrating-ups-apis-with-java-applications/
Explains UPS API integration in Java to enable automated shipping operations including rate calculation, shipment scheduling, tracking, and delivery confirmation management.

https://macronepal.com/blog/generating-and-reading-qr-codes-for-products-in-java/
Explains how Java applications generate and read QR codes for product identification, tracking, and authentication, supporting faster inventory handling and product verification processes.

https://macronepal.com/blog/designing-a-robust-pick-and-pack-workflow-in-java/
Explains how to design an efficient pick-and-pack workflow in Java warehouse systems, covering order processing, item selection, packaging steps, and logistics preparation to improve fulfillment efficiency.

https://macronepal.com/blog/rfid-inventory-management-system-in-java-a-complete-guide/
Explains how RFID technology integrates with Java applications to automate inventory tracking, reduce manual errors, and enable real-time stock monitoring in warehouses and retail environments.

https://macronepal.com/blog/erp-integration-with-odoo-in-java/
Explains how Java applications connect with Odoo ERP systems to synchronize inventory, orders, customer records, and financial data across enterprise systems.

https://macronepal.com/blog/automated-invoice-generation-creating-professional-excel-invoices-with-apache-poi-in-java/
Explains how to automatically generate professional Excel invoices in Java using Apache POI, enabling structured billing documents and automated financial record creation.

https://macronepal.com/blog/enterprise-financial-integration-using-quickbooks-api-in-java-applications/
Explains QuickBooks API integration in Java to automate financial workflows such as invoice management, payment tracking, accounting synchronization, and financial reporting.

Leave a Reply

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


Macro Nepal Helper