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
- Use Application Properties: Externalize configuration using ConfigMaps
- Proper Resource Limits: Set realistic memory and CPU limits for JVM
- Health Checks: Implement comprehensive liveness and readiness probes
- Graceful Shutdown: Handle SIGTERM properly for rolling updates
- Dependency Management: Use Helm dependencies for databases, message brokers
- Security: Use Secrets for sensitive data, implement network policies
- Monitoring: Include ServiceMonitor for Prometheus integration
- 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.