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