Lab8 (Spring Boot/K8S): Deploy a Spring Boot application on Kubernetes using Helm Chart

In this post, we’ll explore the basic concepts of using Helm to deploy a Spring Boot application on Kubernetes cluster.

· Overview
∘ What is Helm?
∘ Helm Key Concepts
∘ Why use Helm?
∘ Helm Chart Structure
∘ The Chart.yaml File
· Deploy a Spring Boot application in Kubernetes using Helm-Chart
∘ Setting up Helm
∘ Create a Helm chart
∘ Customizing the Helm Chart
∘ Deploy the Helm Chart to Kubernetes
∘ Validate the Helm Chart
∘ Deploy the Helm Chart
∘ Delete resources
· Conclusion
· References


This series of stories shows how to use Kubernetes in the Spring ecosystem. We work with a Spring Boot API and Minikube to have a lightweight and fast development environment similar to production.

In previous posts, we deployed a Spring boot application with a simple manifest YAML file. Let’s assume we are working on large projects that require multiple environments and microservices. Maintaining all these YAML manifest files can be time-consuming and complex. Helm simplifies this process by creating templates for these manifest files and allows them to be reused with variables.

Overview

What is Helm?

Helm is a package manager for Kubernetes applications, which includes templating and lifecycle management functionality. It simplifies Kubernetes application deployment using charts, and pre-configured Kubernetes resource packages.

Helm Key Concepts

  • Chart is a Helm package. It contains all of the resource definitions necessary to run an application, tool, or service inside of a Kubernetes cluster. Think of it like the Kubernetes equivalent of a Homebrew formula, an Apt dpkg, or a Yum RPM file.
  • Repository is a place where charts can be collected and shared. It’s like Perl’s CPAN archive or the Fedora Package Database but for Kubernetes packages.
  • Release is an instance of a chart running in a Kubernetes cluster. One chart can often be installed many times into the same cluster. And each time it is installed, a new release is created.

Why use Helm?

  • It’s open-source
  • It reduces the complexity of deployments and minimizes duplicates across configurations.
  • Helm helps to deploy containers in different environments like development, staging, and production.
  • It improves productivity and makes installation and upgrades easy
  • Helm can help prevent errors and inconsistencies when managing complex systems manually.

Helm Chart Structure

A chart is organized as a collection of files inside a directory. The directory name is the name of the chart (without versioning information).

Helm will expect a structure that matches this:

myapp/
├── charts # A directory containing any charts upon which this chart depends.
├── crds # Custom Resource Definitions
├── Chart.yaml # A YAML file containing information about the chart, such as the chart's name, version, and description.
├── templates # A directory of templates that, when combined with values will generate valid Kubernetes manifest files.
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── NOTES.txt
│ ├── service.yaml
│ └── tests # The test files
│ └── test-connection.yaml
├── values.yaml # The default configuration values for this chart
└── .helmignore # This is where we can define patterns to ignore when packaging (similar in concept to .gitignore)

Helm reserves use of the charts/crds/, and templates/ directories, and of the listed file names. Other files will be left as they are.

The Chart.yaml File

The Chart.yaml file is required for a chart. It contains the following fields:

apiVersion: The chart API version (required)
name: The name of the chart (required)
version: A SemVer 2 version (required)
kubeVersion: A SemVer range of compatible Kubernetes versions (optional)
description: A single-sentence description of this project (optional)
type: The type of the chart (optional)
keywords:
- A list of keywords about this project (optional)
home: The URL of this projects home page (optional)
sources:
- A list of URLs to source code for this project (optional)
dependencies: # A list of the chart requirements (optional)
- name: The name of the chart (nginx)
version: The version of the chart ("1.2.3")
repository: (optional) The repository URL ("https://example.com/charts") or alias ("@repo-name")
condition: (optional) A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled )
tags: # (optional)
- Tags can be used to group charts for enabling/disabling together
import-values: # (optional)
- ImportValues holds the mapping of source values to parent key to be imported. Each item can be a string or pair of child/parent sublist items.
alias: (optional) Alias to be used for the chart. Useful when you have to add the same chart multiple times
maintainers: # (optional)
- name: The maintainers name (required for each maintainer)
email: The maintainers email (optional for each maintainer)
url: A URL for the maintainer (optional for each maintainer)
icon: A URL to an SVG or PNG image to be used as an icon (optional).
appVersion: The version of the app that this contains (optional). Needn't be SemVer. Quotes recommended.
deprecated: Whether this chart is deprecated (optional, boolean)
annotations:
example: A list of annotations keyed by name (optional).

As of v3.3.2, additional fields are not allowed. The recommended approach is to add custom metadata in annotations.

Deploy a Spring Boot application in Kubernetes using Helm-Chart

Setting up Helm

There are several ways to install Helm depending on your operating system. The installation instructions are described on the official install page on Helm.

Verify the installation by running the helm version command in your terminal or command prompt. It should display the installed version of Helm.

Create a Helm chart

Let’s start by cloning the main Spring Boot project.

$ git clone https://github.com/anicetkeric/spring-boot-k8s.git -b main

Remember that this GitHub repository is the codebase for our Spring Boot/K8S series. It is a Spring Boot REST API connected to a MongoDB Atlas database. The application is already dockerized and has K8s yaml manifest files. For more information, refer to previous stories in this series.

Use the “helm create” command in the project’s root directory.

$ helm create lab-helm-chart

It will generate a basic directory structure for the Helm chart, including templates, values, and chart folders.

Customizing the Helm Chart

Helm uses the go templating engine for the templating functionality.

> Chart.yaml

The “helm create” command creates a chart.yaml file and defines basic metadata fields like name, version, and description. If necessary, you can update the values ​​or add new fields.

Here is the generated Chart.yaml file:

apiVersion: v2
name: lab-helm-chart
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.16.0"

> templates

Here is our existing static YAML file:

apiVersion: v1
kind: ConfigMap
metadata:
name: mongodb-config
data:
# property-like keys; each key maps to a simple value
host: cluster0.xpkhnmq.mongodb.net
database: library_db
option: retryWrites=true&w=majority
---
apiVersion: v1
kind: Secret
metadata:
name: mongodb-secret
type: Opaque
data:
# replace the secret values with yours
username: ZGJ1c2VybmFtZQ==
password: ZGJwYXNzd29yZA==
---
apiVersion: v1
kind: Service
metadata:
name: book-api-service
spec:
selector:
app: backend
ports:
- protocol: TCP
port: 8081
targetPort: 8080
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-book-api
spec:
replicas: 2
selector:
matchLabels:
app: backend
environment: dev
template:
metadata:
labels:
app: backend
environment: dev
spec:
containers:
- name: book-api
image: spring-boot-k8s:latest
ports:
- containerPort: 8080
imagePullPolicy: Never
resources:
requests:
cpu: "500m"
memory: "128Mi"
limits:
cpu: "1"
memory: "256Mi"
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 15
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 1
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 1
env: # array of environment variable definitions
- name: MONGODB_URL
valueFrom: # select individual keys in a ConfigMap
configMapKeyRef:
name: mongodb-config
key: host
- name: MONGODB_DATABASE
valueFrom:
configMapKeyRef:
name: mongodb-config
key: database
- name: MONGODB_OPTS
valueFrom:
configMapKeyRef:
name: mongodb-config
key: option
- name: MONGODB_LOGIN
valueFrom:
secretKeyRef:
name: mongodb-secret
key: username
- name: MONGODB_PWD
valueFrom:
secretKeyRef:
name: mongodb-secret
key: password

The goal is to make this file dynamic using Helm.

By default, Helm Chart does not create the configMap and secrets files. We’ll manually add it to the templates folder.

  • configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Chart.Name }}-configmap
data:
# property-like keys; each key maps to a simple value
{{- range $name, $config := .Values.configmap.values }}
{{ $name }}: {{ $config | quote }}
{{- end }}
  • secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: {{ .Chart.Name }}-secrets
type: Opaque
data:
{{- range $name, $config := .Values.secrets.values }}
{{ $name }}: {{ $config | b64enc | quote }}
{{- end }}
  • service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "lab-helm-chart.fullname" . }}
labels:
{{- include "lab-helm-chart.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: {{ .Values.service.protocol | default "TCP" }}
name: http
selector:
{{- include "lab-helm-chart.selectorLabels" . | nindent 4 }}
  • deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "lab-helm-chart.fullname" . }}
labels:
{{- include "lab-helm-chart.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "lab-helm-chart.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "lab-helm-chart.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: book-api-{{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
envFrom:
- configMapRef:
name: {{ .Chart.Name }}-configmap
- secretRef:
name: {{ .Chart.Name }}-secrets

> values.yaml

The values ​​file is a YAML file containing all the values ​​that will be used in the templates.

Here is the default values.yaml content:

# Default values for lab-helm-chart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 2

image:
repository: spring-boot-k8s
pullPolicy: Never
# Overrides the image tag whose default is the chart appVersion.
tag: "latest"

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

service:
type: NodePort
port: 8080
targetPort: 8080

podAnnotations: {}
podLabels: {}

resources:
limits:
cpu: "1"
memory: 256Mi
requests:
cpu: 500m
memory: 128Mi

livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3

configmap:
values:
MONGODB_URL: cluster0.xpkhnmq.mongodb.net
MONGODB_DATABASE: library_db
MONGODB_OPTS: retryWrites=true&w=majority

# replace the secret values with yours
# can be used with external Secrets
secrets:
values:
MONGODB_LOGIN: admin
MONGODB_PWD: admin

The final Helm chart structure looks like this:

Validate the Helm Chart

Now, the configuration is ready. We need to check that our Chart is valid. The Helm lint command is used to validate the Helm charts and detect potential issues or errors before deploying them to a Kubernetes cluster.

There are a few commands that can help to debug.

  • helm template --debug will test rendering chart templates locally.
  • helm install --dry-run --debug will also render the chart locally without installing it, but will also check if conflicting resources are already running on the cluster. Setting --dry-run=server will additionally execute any lookup in your chart towards the server.
  • helm get manifest: This is a good way to see what templates are installed on the server.

Deploy the Helm Chart

Once we’ve verified the chart to be fine, we can finally run this command to install the chart into the Kubernetes cluster.

$ helm install lab-helm ./lab-helm-chart --namespace n-lab-helm --create-namespace

NAME: lab-helm-chart
LAST DEPLOYED: Sun Jul 21 2024
NAMESPACE: n-lab-helm
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services lab-helm-chart)
export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT

All resources are ready.

Let’s try to call some endpoints:

Well done !!.

Delete resources

$ helm delete lab-helm -n n-lab-helm

release "lab-helm" uninstalled

Conclusion

In this post, we learned about Helm and how it simplifies deployment in Kubernetes. We deployed a Spring Boot Rest API using Helm on the Minikube Kubernetes Cluster.

The complete source code of this series is available on GitHub.

Support me through GitHub Sponsors.

Thank you for Reading !! See you in the next story.

References

👉 Link to Medium blog

Related Posts