Title: Comprehensive Guide to Helm Charts for Java Microservices

Article

Helm is the package manager for Kubernetes, often described as "apt/yum for K8s." For Java microservices, Helm charts provide a powerful way to define, install, and upgrade complex applications in Kubernetes environments. This guide covers creating, managing, and optimizing Helm charts specifically for Java microservices deployments.


Why Helm for Java Microservices?

Java microservices typically have common requirements:

  • Multiple deployment artifacts
  • Environment-specific configuration
  • Service discovery and networking
  • Monitoring and logging setup
  • Database migrations and initialization

Helm addresses these needs through:

  • Templating: Reusable manifests with parameterization
  • Packaging: Single package containing all K8s resources
  • Versioning: Track and manage application versions
  • Dependencies: Manage dependent services and infrastructure

Helm Chart Structure for Java Applications

Basic Chart Structure

order-service/ ├── Chart.yaml ├── values.yaml ├── values-dev.yaml ├── values-prod.yaml ├── charts/ ├── templates/ │ ├── deployment.yaml │ ├── service.yaml │ ├── configmap.yaml │ ├── secret.yaml │ ├── ingress.yaml │ ├── hpa.yaml │ └── helpers.tpl └── .helmignore

Core Chart Components

1. Chart.yaml - Chart Metadata

apiVersion: v2 name: order-service description: A Spring Boot Order Service for e-commerce platform type: application version: 1.2.3 appVersion: "2.1.0" dependencies: - name: postgresql version: "12.1.0" repository: "https://charts.bitnami.com/bitnami" condition: postgresql.enabled - name: redis version: "17.0.0" repository: "https://charts.bitnami.com/bitnami" condition: redis.enabled annotations: category: Backend team: orders-team repository: https://github.com/company/order-service

2. values.yaml - Default Configuration

# Application Configuration image: repository: company/order-service tag: "latest" pullPolicy: IfNotPresent pullSecrets: [] # Application Settings java: jvmMemory: "512m" jvmMaxMemory: "1024m" extraJvmArgs: "-XX:+UseG1GC -XX:MaxGCPauseMillis=200" springProfiles: "kubernetes" # Deployment Configuration replicaCount: 2 minReplicas: 2 maxReplicas: 10 # Resource Configuration resources: requests: memory: "768Mi" cpu: "500m" limits: memory: "1536Mi" cpu: "1000m" # Service Configuration service: type: ClusterIP port: 8080 targetPort: 8080 # Ingress Configuration ingress: enabled: true className: "nginx" hosts: - host: orders.company.com paths: - path: / pathType: Prefix tls: [] # Database Configuration database: url: jdbc:postgresql://postgresql:5432/orders username: orders_user password: "" # External Services externalServices: paymentService: http://payment-service:8080 inventoryService: http://inventory-service:8080 # Monitoring monitoring: enabled: true prometheus: scrape: true path: /actuator/prometheus port: 8080 # Health Checks probes: liveness: path: /actuator/health/liveness initialDelaySeconds: 90 periodSeconds: 30 readiness: path: /actuator/health/readiness initialDelaySeconds: 30 periodSeconds: 10 # Auto-scaling autoscaling: enabled: true targetCPUUtilizationPercentage: 70 targetMemoryUtilizationPercentage: 85 # Database Dependencies postgresql: enabled: true auth: database: orders username: orders_user password: "" redis: enabled: true architecture: standalone

Template Files

1. templates/deployment.yaml

apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Chart.Name }} labels: {{- include "order-service.labels" . | nindent 4 }} app.kubernetes.io/component: api spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "order-service.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "order-service.labels" . | nindent 8 }} app.kubernetes.io/component: api annotations: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} rollme: {{ randAlphaNum 5 | quote }} spec: {{- with .Values.image.pullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 8080 protocol: TCP env: - name: SPRING_PROFILES_ACTIVE value: {{ .Values.java.springProfiles }} - name: JAVA_OPTS value: "-Xms{{ .Values.java.jvmMemory }} -Xmx{{ .Values.java.jvmMaxMemory }} {{ .Values.java.extraJvmArgs }}" - name: DATABASE_URL value: {{ .Values.database.url }} - name: DATABASE_USERNAME valueFrom: secretKeyRef: name: {{ .Chart.Name }}-secrets key: database-username - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: {{ .Chart.Name }}-secrets key: database-password - name: PAYMENT_SERVICE_URL value: {{ .Values.externalServices.paymentService }} resources: {{- toYaml .Values.resources | nindent 12 }} livenessProbe: httpGet: path: {{ .Values.probes.liveness.path }} port: http initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }} periodSeconds: {{ .Values.probes.liveness.periodSeconds }} timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: {{ .Values.probes.readiness.path }} port: http initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }} periodSeconds: {{ .Values.probes.readiness.periodSeconds }} timeoutSeconds: 3 failureThreshold: 3 startupProbe: httpGet: path: {{ .Values.probes.readiness.path }} port: http initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 10

2. templates/service.yaml

apiVersion: v1 kind: Service metadata: name: {{ .Chart.Name }} labels: {{- include "order-service.labels" . | nindent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: {{ .Values.service.targetPort }} protocol: TCP name: http selector: {{- include "order-service.selectorLabels" . | nindent 4 }}

3. templates/configmap.yaml

apiVersion: v1 kind: ConfigMap metadata: name: {{ .Chart.Name }}-config labels: {{- include "order-service.labels" . | nindent 4 }} data: application.yaml: | server: port: 8080 spring: datasource: url: ${DATABASE_URL} username: ${DATABASE_USERNAME} password: ${DATABASE_PASSWORD} hikari: maximum-pool-size: 20 connection-timeout: 30000 jpa: hibernate: ddl-auto: validate show-sql: false redis: host: {{ .Chart.Name }}-redis port: 6379 management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always probes: enabled: true logging: level: com.company.orders: INFO org.hibernate.SQL: WARN

4. templates/hpa.yaml

{{- if .Values.autoscaling.enabled }} apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: {{ .Chart.Name }} labels: {{- include "order-service.labels" . | nindent 4 }} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: {{ .Chart.Name }} minReplicas: {{ .Values.minReplicas }} maxReplicas: {{ .Values.maxReplicas }} metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} - type: Resource resource: name: memory target: type: Utilization averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 60 {{- end }}

5. templates/ingress.yaml

{{- if .Values.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Chart.Name }} labels: {{- include "order-service.labels" . | nindent 4 }} annotations: nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/proxy-body-size: "10m" {{- with .Values.ingress.annotations }} {{- toYaml . | nindent 4 }} {{- end }} spec: ingressClassName: {{ .Values.ingress.className }} rules: {{- range .Values.ingress.hosts }} - host: {{ .host | quote }} http: paths: {{- range .paths }} - path: {{ .path }} pathType: {{ .pathType }} backend: service: name: {{ $.Chart.Name }} port: number: {{ $.Values.service.port }} {{- end }} {{- end }} {{- if .Values.ingress.tls }} tls: {{- range .Values.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} {{- end }} secretName: {{ .secretName }} {{- end }} {{- end }} {{- end }}

Helper Templates (templates/_helpers.tpl)

{{/* Common labels */}} {{- define "order-service.labels" -}} helm.sh/chart: {{ include "order-service.chart" . }} {{ include "order-service.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "order-service.selectorLabels" -}} app.kubernetes.io/name: {{ .Chart.Name }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Chart name and version as used by the chart label. */}} {{- define "order-service.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Create database connection secret name */}} {{- define "order-service.databaseSecretName" -}} {{- if .Values.database.existingSecret }} {{- .Values.database.existingSecret }} {{- else }} {{- printf "%s-database" .Chart.Name }} {{- end }} {{- end }}

Environment-Specific Values

values-dev.yaml

replicaCount: 1 minReplicas: 1 maxReplicas: 3 image: tag: "develop" pullPolicy: Always java: jvmMemory: "256m" jvmMaxMemory: "512m" springProfiles: "dev,kubernetes" resources: requests: memory: "512Mi" cpu: "200m" limits: memory: "1024Mi" cpu: "500m" autoscaling: enabled: false monitoring: enabled: false database: url: jdbc:postgresql://postgresql:5432/orders_dev

values-prod.yaml

replicaCount: 3 minReplicas: 3 maxReplicas: 10 image: tag: "2.1.0" pullPolicy: IfNotPresent java: jvmMemory: "1024m" jvmMaxMemory: "2048m" extraJvmArgs: "-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport" springProfiles: "prod,kubernetes" resources: requests: memory: "1536Mi" cpu: "1000m" limits: memory: "3072Mi" cpu: "2000m" autoscaling: enabled: true targetCPUUtilizationPercentage: 70 targetMemoryUtilizationPercentage: 85 monitoring: enabled: true database: url: jdbc:postgresql://postgresql-prod:5432/orders

Deployment Workflow

1. Development Deployment

# Install with development values helm install order-service ./order-service -f values-dev.yaml -n dev-namespace # Upgrade deployment helm upgrade order-service ./order-service -f values-dev.yaml -n dev-namespace # Dry-run to test templates helm template order-service ./order-service -f values-dev.yaml --debug

2. Production Deployment

# Create namespace kubectl create namespace production # Install with production values helm install order-service ./order-service \ -f values-prod.yaml \ --set database.password=$DB_PASSWORD \ --namespace production # Rollback if needed helm rollback order-service 1 --namespace production

3. CI/CD Pipeline Integration

# Example GitLab CI deploy:production: stage: deploy image: alpine/helm:3.12.0 script: - helm upgrade --install order-service ./order-service -f values-prod.yaml --set image.tag=$CI_COMMIT_SHA --set database.password=$PROD_DB_PASSWORD --namespace production --wait --timeout 10m only: - main

Best Practices for Java Microservices

  1. Use Application Properties: Externalize configuration using ConfigMaps
  2. Proper Resource Limits: Set realistic memory and CPU limits for JVM
  3. Health Checks: Implement comprehensive liveness and readiness probes
  4. Graceful Shutdown: Handle SIGTERM properly for rolling updates
  5. Dependency Management: Use Helm dependencies for databases, message brokers
  6. Security: Use Secrets for sensitive data, implement network policies
  7. Monitoring: Include ServiceMonitor for Prometheus integration
  8. Multi-environment Support: Maintain separate values files for each environment

Conclusion

Helm charts provide a robust packaging and deployment mechanism for Java microservices in Kubernetes. By creating well-structured charts with proper templating, environment-specific configurations, and comprehensive resource definitions, you can achieve reliable, repeatable deployments across all environments. The key is to balance flexibility through values files with consistency through standardized templates, ensuring your Java applications run efficiently in containerized environments.

Leave a Reply

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


Macro Nepal Helper