Spring Boot & Kubernetes

JKube

Spring Boot project offers several options out-of-the-box for containerizing an application. All of them, might fit your requirements but they just cover one part of the equation, that is creating a continer, but it isn’t implements the creation of the Kubernetes resources nor the deployment of them.

Eclipse JKube is a collection of plugins and libraries that are used for building container images using Docker, JIB or S2I build strategies. Eclipse JKube generates and deploys Kubernetes/OpenShift manifests at compile time too.

Open pom.xml file and add the following properties at <properties> section:

pom.xml
<jkube.build.strategy>jib</jkube.build.strategy>
<jkube.generator.name>quay.io/sunix/sb-hw:${project.version}</jkube.generator.name>
<jkube.createExternalUrls>true</jkube.createExternalUrls>
Substitute quay.io for your container registry and sunix with your username.

Then add the jkube plugin at plugins section just after the spring-boot-maven-plugin plugin:

pom.xml
<plugin>
	<groupId>org.eclipse.jkube</groupId>
	<artifactId>kubernetes-maven-plugin</artifactId>
	<version>1.9.1</version>
  <configuration>
		<resources>
			<imagePullPolicy>Always</imagePullPolicy> (1)
		</resources>
	</configuration>
</plugin>
1 Configures imagePullPolicy attribute

Login into container registry running:

docker login quay.io
You can set username/password in the jkube project or in .m2/settings.xml, but if not then docker login session is used. See https://www.eclipse.org/jkube/docs/kubernetes-maven-plugin#authentication.

Building & Pushing

To build the Linux container image and push it to container registry run the following Maven goals:

./mvnw package -DskipTests k8s:build k8s:push
[INFO] k8s: Running in Kubernetes mode
[INFO] k8s: Building Docker image in Kubernetes mode
[INFO] k8s: Running generator spring-boot
[INFO] k8s: spring-boot: Using Docker image quay.io/jkube/jkube-java:0.0.15 as base / builder
[INFO] k8s: JIB image build started
JIB> Base image 'quay.io/jkube/jkube-java:0.0.15' does not use a specific image digest - build may not be reproducible
JIB> Containerizing application with the following files:
JIB> 	Jkube-generated-layer-original:
JIB> 		/home/sunix/github/sunix/hello-kubernetes/target/hello-kubernetes-0.0.1-SNAPSHOT.jar
JIB> Getting manifest for base image quay.io/jkube/jkube-java:0.0.15...
JIB> Building jkube-generated-layer-original layer...
JIB> Using base image with digest: sha256:50ba176b6e37993a028732b713d6f239597fe124ad7dc81c5abe05b8ab5b2814
JIB> Container program arguments set to [/usr/local/s2i/run] (inherited from base image)
JIB> Building image to tar file...
JIB> [========================      ] 80.0% complete > writing to tar file
JIB> [==============================] 100.0% complete
[INFO] k8s:  /home/sunix/github/sunix/hello-kubernetes/target/docker/quay.io/sunix/sb-hw/0.0.1-SNAPSHOT/tmp/docker-build.tar successfully built
[INFO]
[INFO] --- kubernetes-maven-plugin:1.8.0:push (default-cli) @ hello-kubernetes ---
[INFO] k8s: Running in Kubernetes mode
[INFO] k8s: Building Docker image in Kubernetes mode
[INFO] k8s: Running generator spring-boot
[INFO] k8s: spring-boot: Using Docker image quay.io/jkube/jkube-java:0.0.15 as base / builder
[INFO] k8s: This push refers to: quay.io/sunix/sb-hw:0.0.1-SNAPSHOT
JIB> Containerizing application with the following files:
JIB> Container program arguments set to [/usr/local/s2i/run] (inherited from base image)
JIB> Building a single manifest
JIB> Checking existence of manifest for sha256:03d2b11cfdd191a2fa22f6c10eba3b14012ecb1f6e7d0e51f97553f90bee61d7...
JIB> Skipping manifest existence check; system property set to false
JIB> Skipping push; BLOB already exists on target registry : digest: sha256:38b71301a1d9df24c98b5a5ee8515404f42c929003ad8b13ab83d2de7de34dec, size: 1742
JIB> Skipping push; BLOB already exists on target registry : digest: sha256:4c502bd732043bc253180c5af6b13feeaaaebf4872ad2703c3c092e4d8bf5cef, size: 123456655
JIB> Skipping push; BLOB already exists on target registry : digest: sha256:a9e23b64ace00a199db21d302292b434e9d3956d79319d958ecc19603d00c946, size: 39622437
JIB> Pushing manifest for 0.0.1-SNAPSHOT...
JIB> [===========================   ] 90.9% complete > launching manifest list pushers
JIB> [==============================] 100.0% complete
JIB> Containerizing application with the following files:
JIB> Container program arguments set to [/usr/local/s2i/run] (inherited from base image)
JIB> Building a single manifest
JIB> Checking existence of manifest for sha256:6e3bdcd41a3c43ad81059f4b09f23b80c5234be57800898703d8e679e14f3d52...
JIB> Skipping manifest existence check; system property set to false
JIB> Skipping push; BLOB already exists on target registry : digest: sha256:38b71301a1d9df24c98b5a5ee8515404f42c929003ad8b13ab83d2de7de34dec, size: 1742
JIB> Skipping push; BLOB already exists on target registry : digest: sha256:4c502bd732043bc253180c5af6b13feeaaaebf4872ad2703c3c092e4d8bf5cef, size: 123456655
JIB> Skipping push; BLOB already exists on target registry : digest: sha256:a9e23b64ace00a199db21d302292b434e9d3956d79319d958ecc19603d00c946, size: 39622437
JIB> Skipping push; BLOB already exists on target registry : digest: sha256:2170a78ac8f83c9847552d78f95b35ccfc9c2d44cf93805448f29096f493f693, size: 25356703
JIB> Pushing manifest for latest...
JIB> [===========================   ] 90.9% complete > launching manifest list pushers
JIB> [==============================] 100.0% complete
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Kubernetes Resources

JKube can generate an opinionated Kubernetes resources file for a Spring Boot application setting the image name generated in the previous step:

  • Minikube

  • Others (Openshift,Devsandbox,kubernetes)

./mvnw k8s:resource -Djkube.domain=$(minikube ip).nip.io

Replace the domain with the one of your cluster

./mvnw k8s:resource -Djkube.domain=apps.sandbox-m2.ll9k.p1.openshiftapps.com
[INFO] --- kubernetes-maven-plugin:1.8.0:resource (default-cli) @ hello-kubernetes ---
[INFO] k8s: spring-boot: Using Docker image quay.io/jkube/jkube-java:0.0.15 as base / builder
[INFO] k8s: Using resource templates from /home/sunix/github/sunix/hello-kubernetes/src/main/jkube (1)
[INFO] k8s: jkube-controller: Adding a default Deployment
[INFO] k8s: jkube-service: Adding a default service 'hello-kubernetes' with ports [8080] (2)
[INFO] k8s: jkube-healthcheck-spring-boot: Adding readiness probe on port 8080, path='/actuator/health', scheme='HTTP', with initial delay 10 seconds (3)
[INFO] k8s: jkube-healthcheck-spring-boot: Adding liveness probe on port 8080, path='/actuator/health', scheme='HTTP', with initial delay 180 seconds
[INFO] k8s: jkube-service-discovery: Using first mentioned service port '8080'
[INFO] k8s: jkube-revision-history: Adding revision history limit to 2
[INFO] ------------------------------------------------------------------------
1 Customizations can be placed in this directory.
2 Port is taken from Spring Boot configuration.
3 Since health actuator is registered liveness/readiness probes are set. More about this later.

The generated Kubernetes file is located at arget/classes/META-INF/jkube directory:

cat target/classes/META-INF/jkube/kubernetes.yml
---
apiVersion: v1
kind: List
items:
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      jkube.io/scm-url: https://github.com/spring-projects/spring-boot/hello-kubernetes
      prometheus.io/scrape: "true"
      jkube.io/scm-tag: HEAD
      prometheus.io/path: /metrics
      prometheus.io/port: "9779"
    labels:
      expose: "true"
      app: hello-kubernetes
      provider: jkube
      version: 0.0.1-SNAPSHOT
      group: org.acme
    name: hello-kubernetes
  spec:
    ports:
    - name: http
      port: 8080
      protocol: TCP
      targetPort: 8080
    selector:
      app: hello-kubernetes
      provider: jkube
      group: org.acme
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    annotations:
      jkube.io/scm-tag: HEAD
      jkube.io/scm-url: https://github.com/spring-projects/spring-boot/hello-kubernetes
    labels:
      app: hello-kubernetes
      provider: jkube
      version: 0.0.1-SNAPSHOT
      group: org.acme
    name: hello-kubernetes
  spec:
    replicas: 1
    revisionHistoryLimit: 2
    selector:
      matchLabels:
        app: hello-kubernetes
        provider: jkube
        group: org.acme
    template:
      metadata:
        annotations:
          jkube.io/scm-tag: HEAD
          jkube.io/scm-url: https://github.com/spring-projects/spring-boot/hello-kubernetes
        labels:
          app: hello-kubernetes
          provider: jkube
          version: 0.0.1-SNAPSHOT
          group: org.acme
        name: hello-kubernetes
      spec:
        containers:
        - env:
          - name: KUBERNETES_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: HOSTNAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          image: quay.io/sunix/sb-hw:0.0.1-SNAPSHOT
          imagePullPolicy: Always
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /actuator/health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 180
            successThreshold: 1
          name: spring-boot
          ports:
          - containerPort: 8080
            name: http
            protocol: TCP
          - containerPort: 9779
            name: prometheus
            protocol: TCP
          - containerPort: 8778
            name: jolokia
            protocol: TCP
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /actuator/health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 10
            successThreshold: 1
          securityContext:
            privileged: false
- apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    annotations:
      jkube.io/scm-tag: HEAD
      jkube.io/scm-url: https://github.com/spring-projects/spring-boot/hello-kubernetes
    labels:
      app: hello-kubernetes
      provider: jkube
      version: 0.0.1-SNAPSHOT
      group: org.acme
    name: hello-kubernetes
  spec:
    rules:
    - host: hello-kubernetes.apps.sandbox-m2.ll9k.p1.openshiftapps.com
      http:
        paths:
        - backend:
            service:
              name: hello-kubernetes
              port:
                number: 8080
          path: /
          pathType: ImplementationSpecific

Kubernetes Deploy

The last step is to deploy the application to the Kubernetes cluster. We can deploy application using kubectl CLI tool:

kubectl apply -f target/classes/META-INF/jkube/kubernetes.yml

Or we use k8s:apply goal. This goal takes the resources created with k8s:resource goal :

./mvnw k8s:apply