Stakater reloader - Usage in Kubernetes
Stakater reloader usage
In this article have detailed the use of open source project Stakater reloader to reload the pod whenever the ConfigMap or Secret is updated.
To understand the use case a simple Spring Boot application is created with JBang, this application exposes an API endpoint which renders the value of a key from a application.yaml file. When deploying the application to the Kubernetes cluster the ConfigMap defines the application yaml file and mounted as volume in deployment manifest. JBang is used to avoid the whole project structure and start application with single file. This is not a production ready code.
Have created the application container image with the Dockerfile that uses the JBang base image. The resource are deployed to the a KinD cluster for demonstration.
The Stakater reloader manifests are deploy to the KinD cluster, the Deployment and ConfigMap manifest are annotated with Stakater reloader specific annotation based on which the pods reloads whenever the ConfigMap or Secret is updated. There are few additional options to ignore the change, for more details refer the Stakater reloader git repo.
Below is the annotation to be defined in Deployment for Stakater reloader to use for reloading scenarios
annotations:
reloader.stakater.com/auto: "true"
Pre-requisites
- KinD CLI installed
- JBang installed
- Docker Desktop or Daemon running
Full source code
The full source code with the simple folder structure could be found in my git-repo
Spring Boot code
The JBang Spring boot application code is shown below. We have the root project folder stakater-jbang-spring-app and placed the below file under the app/ folder as App.java. The Dockerfile uses this path in the CMD, so when the container runs it uses this path the start the application.
The application.yaml is placed under config folder different from app folder . The path is specified in the RUNTIME_OPTIONS so when the spring application starts in local or container it could scan the config file. The java runtime options in this case looks like spring.config.location=file:./config/application.yaml.
Note, the application.yaml is placed under config directory, this is because when the ConfigMap is mounted as volume in to the pod all the existing files will replaced with application.yaml. The mount path is /src/config since the Dockerfile defines WORKDIR as /src. When exec to the container image we could see the property message: "default-from-app" but when we deploy this would be message: "message-from-k8s-configmap"
We could check this once the docker image is created, exec to the container using below
docker run -it --entrypoint sh jbang-spring-app` and check `cat config/application.yaml
By using the app folder which has the application code and application.yaml, if the mount path is /src/app the ConfigMap would replace all the file within the container when deployed to Kubernetes.
Below is the JBang Spring application complete code snippet
///usr/bin/env jbang "$0" "$@" ; exit $?
package app;
//JAVA 25+
//RUNTIME_OPTIONS -Dspring.config.location=file:./config/application.yaml
//DEPS org.springframework.boot:spring-boot-dependencies:4.0.1@pom
//DEPS org.springframework.boot:spring-boot-starter-web
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@ComponentScan(basePackages = {".","app"})
@RestController
@RequestMapping("/api")
public class App{
void main(String... args) {
SpringApplication.run(App.class, args);
}
String formatStr = "{\"message\": \"%s\"}";
@Value("${app.message}")
private String msg;
@GetMapping("/message")
public String getMessage(){
return String.format(formatStr, msg);
}
}
To execute the application locally use below command in case of Windows
jbang "<path>\<project-root>\app\App.java"
application.yaml
The application.yaml file is placed under the config folder of the root project directory (same level as the app directory).
# file to be created under <project-root>/config/application.yaml
# this path is used in RUNTIME_OPTIONS
app:
message: "default-from-app"
Dockerfile
The JBang base image jbangdev/jbang-action is used to build the container image. Only necessary files are copied to the container and others are ignored by definting it in .dockerignore file.
FROM jbangdev/jbang-action
WORKDIR /src
COPY . .
EXPOSE 8080
CMD ["./app/App.java"]
.dockerignore file content is shown below which ignore some files or folder from getting copied to container image.
Dockerfile
README.md
k8s/*
To create the docker image use below command, navigate to the root project folder.
docker build -t jbang-spring-app .
To run the docker image in the docker use below command
docker run -d -p 8080:8085 jbang-spring-app
Kubernetes resources
Below is the list of the Kubernetes manifest for Deployment, ConfigMap and Service to deploy into the KinD cluster with the generated image.
The Deployment and ConfigMap includes the Stakater annoation when we patch the config map the pods will reload itself.
# file-name: <project-parent-folder>/k8s/app_resource_manfiest.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jbang-spring-app
annotations:
reloader.stakater.com/auto: "true"
spec:
replicas: 3
selector:
matchLabels:
app: jbang-spring-app
template:
metadata:
labels:
app: jbang-spring-app
spec:
volumes:
- name: jbang-app-volume
configMap:
name: jbang-app-config
containers:
- name: jbang-spring-app
image: jbang-spring-app
imagePullPolicy: IfNotPresent
volumeMounts:
- name: jbang-app-volume
mountPath: /src/config
readOnly: true
ports:
- containerPort: 8080
---
# service
apiVersion: v1
kind: Service
metadata:
name: jbang-app-service
spec:
selector:
app: jbang-spring-app
ports:
- protocol: TCP
port: 8086
targetPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: jbang-app-config
annotations:
reloader.stakater.com/auto: "true"
#reloader.stakater.com/rollout-strategy: "restart" # rollout is the default stratergy
data:
application.yaml: |
app:
message: "message-from-k8s-configmap"
---
Deploying the resource to KinD cluster
With Docker running in the machine, we can use the kind CLI and we can create a cluster using below command
kind create cluster --name test
To install the Stakater reloader using helm chart we use below command, the reloader deploys to the default namespace
helm repo add stakater https://stakater.github.io/stakater-charts
helm repo update
helm install reloader stakater/reloader
To deploy the application manifest we use below command, navigate to the root project directory.
# create a new namespace
kubectl create ns reloader
# create the application resource
kubectl -n reloader apply -f k8s/app_resource_manfiest.yaml
Note, the application might now start reporting the Error pulling image. We can manually push the local image to the KinD cluster using below command
# test is the name of the kind cluster
kind load docker-image jbang-spring-app:latest --name test
Once the application is deployed we could verify the application is deployed and working by checking the logs.
kubectl -n reloader logs deploy/jbang-spring-app
To access the deployed application we can port-forward the service deployed and access from a browser
Port forward the service
kubectl -n reloader port-forward svc/jbang-app-service 8086:8086
To access the application use below url
http://localhost:8086/api/message
The response would looke like
{"message": "message-from-k8s-configmap"}
Patch the ConfigMap
When we patch the configmap we could see the pods are getting restarting automatically
kubectl -n reloader patch configmap jbang-app-config --type merge \
-p '{
"data": {
"application.yaml": "app:\n message: \"new-message\""
}
}'
Output gif
On the right side we patch the config map, and the pods auto restarts
After pod restart if we port-forward we could see the patched message from ConfigMap will be rendered.