Skip to main content

Command Palette

Search for a command to run...

Stakater reloader - Usage in Kubernetes

Updated
6 min read

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

recording_output

After pod restart if we port-forward we could see the patched message from ConfigMap will be rendered.