Introduction
For the integration of Spring Cloud and Spring Boot applications that are running inside Kubernetes, we can use Spring Cloud Kubernetes which provides Spring Cloud common interface implementations that are easy to use and ready for production.
In k8s deployment, configmap can be used as a source for application configuration properties.
In this article, I will take you through how to use spring cloud Kubernetes to provide configuration properties for spring boot applications in k8s deployment using congfigmap.
This example needs kubectl
minikube
helm
command-line tools in your machine.
Create Microservice
Let’s create one simple spring boot microservice that manages orders coming from a customer. In my example, order microservice exposes basic CRUD operations APIS, along with Spring Webflux I used Spring Data R2DBC for data access and Kotlin language.
Initialize Project
NAME='Configmap in Spring Cloud Kubernetes' && PRJ=configmap-in-spring-cloud-kubernetes && \
mkdir -p $PRJ && cd $PRJ && \
curl https://start.spring.io/starter.tgz \
-d dependencies=actuator,webflux,cloud-starter,data-r2dbc,h2,postgresql \
-d groupId=io.github.bhuwanupadhyay -d artifactId=$PRJ -d packageName=io.github.bhuwanupadhyay.example \
-d applicationName=Spring Boot -d name=$NAME -d description=$NAME \
-d language=kotlin -d platformVersion=2.3.1.RELEASE -d javaVersion=11 \
-o demo.tgz && \
tar -xzvf demo.tgz && rm -rf demo.tgz
Basic CRUD operations APIS
@Table("ORDERS")
data class OrderEntity(@Id var id: Long?, var item: String, var quantity: Int)
@Configuration
class OrderRoutes(private val handler: OrderHandler) {
@Bean
fun router() = router {
accept(APPLICATION_JSON).nest {
POST("/orders", handler::save)
GET("/orders", handler::findAll)
GET("/orders/{id}", handler::findOne)
PUT("/orders/{id}", handler::update)
}
}
}
Containerizing Spring Boot Application
From Spring Boot 2.3.0.RELEASE the maven plugin of spring boot by default support build-image
goal during execution which creates an OCI image using Cloud Native Buildpacks.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-image</goal>
</goals>
<configuration>
<imageName>docker.io/bhuwanupadhyay/${project.artifactId}:${project.version}</imageName>
</configuration>
</execution>
</executions>
</plugin>
Run mvn clean install
: spring boot maven plugin will create a docker image. The end part of the output log:
[INFO]
[INFO] Successfully built image 'docker.io/bhuwanupadhyay/configmap-in-spring-cloud-kubernetes:0.0.1-SNAPSHOT'
[INFO]
To publish docker image in the registry run the following command
docker push docker.io/bhuwanupadhyay/configmap-in-spring-cloud-kubernetes:0.0.1-SNAPSHOT
Helm Chart
To create a helm chart from your project directory run the following command.
helm create src/helm-chart
Replace value image repository and tag with your published docker image name and tag in src/helm-chart/values.yaml
inside the helm chart.
image:
repository: docker.io/bhuwanupadhyay/configmap-in-spring-cloud-kubernetes
pullPolicy: IfNotPresent
tag: '0.0.1-SNAPSHOT'
Order microservice required data source configuration properties in the deployment to connect with the database to store order information.
Let’s say we have two spring profiles dev
and prod
. In dev
profile we want to run an application using h2
database while prod
profile we want to run an application using postgresql
database.
H2 is an embedded database so no need to create different pod instance, but it is not the case for postgresql because it is not embeddable, so we need to run in a different pod.
To run postgresql
in helm deployment you need to add dependency inside helm chart src/helm-chart/Chart.yaml
.
dependencies:
- name: postgresql
alias: db
version: 8.10.5
repository: https://charts.bitnami.com/bitnami
In Kubernetes config map is used to provide application configuration properties. To add configmap in your helm chart simply create a new YAML file in src/helm-chart/templates/configmap.yaml
. For our example, we have to define multiple spring profiles for h2 and postgresql that are given below:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "helm-chart.fullname" . }}-config
labels:
{{- include "helm-chart.labels" . | nindent 4 }}
data:
application.yaml: |-
management:
endpoints:
web:
base-path: /actuator
exposure:
include: ['configprops', 'env', 'health', 'info', 'logfile', 'loggers', 'threaddump']
endpoint:
health:
show-details: always
spring:
application:
name: order-service
---
spring:
profiles: dev
r2dbc:
url: r2dbc:h2:mem://test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
---
spring:
profiles: prod
r2dbc:
url: r2dbc:postgresql://{{ .Release.Name }}-db:5432/{{ .Values.db.postgresqlDatabase }}
username: {{ .Values.db.postgresqlUsername }}
password: {{ .Values.db.postgresqlPassword }}
Spring Cloud Kubernetes
Add dependency that used to load application properties from Kubernetes ConfigMaps and Secrets or reload application properties when a ConfigMap or Secret changes.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-config</artifactId>
</dependency>
You need to make sure a pod that runs with spring-cloud-Kubernetes
has access to the Kubernetes API. Using helm you can create a service account with Role
and RoleBinding
that has access to read configmaps and secrets by defining new yaml file in src/helm-chart/templates/cluster-reader.yaml
.
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "helm-chart.fullname" . }}-reader
labels:
{{- include "helm-chart.labels" . | nindent 4 }}
rules:
- apiGroups: ["", "extensions", "apps"]
resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "helm-chart.fullname" . }}
labels:
{{- include "helm-chart.labels" . | nindent 4 }}
roleRef:
kind: Role
name: {{ include "helm-chart.fullname" . }}-reader
apiGroup: ""
subjects:
- kind: ServiceAccount
name: {{ include "helm-chart.fullname" . }}
apiGroup: ""
Finally, you need to provide environment variables to tell the application to use your configmap and namespace for spring cloud Kubernetes. Also, In your helm chart under src/microservice/templates/deployment.yaml
change env
, readinessProbe
and livenessProbe
health check settings, also modify container port to 8080
is the default for spring boot application.
env:
- name: SPRING_PROFILES_ACTIVE
value: {{ .Values.spring.profiles.active }}
- name: SPRING_CLOUD_KUBERNETES_CONFIG_NAME
value: {{ include "helm-chart.fullname" . }}-config
- name: SPRING_CLOUD_KUBERNETES_CONFIG_NAMESPACE
value: {{ .Release.Namespace }}
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /actuator/health
port: http
initialDelaySeconds: 30
periodSeconds: 60
timeoutSeconds: 5
failureThreshold: 5
readinessProbe:
httpGet:
path: /actuator/health
port: http
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 5
Deployment
Get ready for the deployment!
Start Minikube
minikube start
Add helm repositories
helm repo add bitnami https://charts.bitnami.com/bitnami
Update charts
helm dependency update src/helm-chart
DEV
Profile helm deployment
helm upgrade \
--install -f src/helm-chart/values.yaml \
--set spring.profiles.active=dev \
--set db.enabled=false \
example-deployment src/helm-chart --force
PROD
Profile helm deployment
helm upgrade \
--install -f src/helm-chart/values.yaml \
--set spring.profiles.active=prod \
--set db.enabled=true \
--set db.postgresqlDatabase=orders-db \
--set db.postgresqlUsername=user \
--set db.postgresqlPassword=password \
--set db.persistence.enabled=false \
example-deployment src/helm-chart --force
Watch the deployment
watch kubectl get pods
Test Order Microservice APIS
Firstly, install httpie
command line tool.
sudo apt install httpie
Open New Terminal
- Port forward for order microservice
# Get pods -> Run the following command
kubectl get pods
# Output
NAME READY STATUS RESTARTS AGE
example-deployment-helm-chart-8ff55d4db-rnt6j 1/1 Running 0 12m
example-deployment-db-0 1/1 Running 0 12m
# Port forward -> Run the following command
kubectl port-forward example-deployment-helm-chart-8ff55d4db-rnt6j 8080:8080
Open New Terminal
- Call order service APIS
# POST orders
echo '{"item": "k8s-item", "quantity": 20}' | http POST :8080/orders
# GET orders
http :8080/orders
We are done ! Thanks for reading. Github
References
- Versions:
helm - v3.2.3
minikube - v1.11.0
kubectl - v1.17.0
- https://docs.spring.io/initializr/docs/current/reference/html/
- https://buildpacks.io/
- https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/maven-plugin/reference/html/
- https://spring.io/projects/spring-cloud-kubernetes