Create Spring Boot Images in Jenkins/CI in K8s, Part 1

  Working out Jenkins/CI pipelines it is a common task to create Spring Boot Images. Doing this with kubernetes agents is a challenge since docker is no more available from inside of containers.

The task is quiet simple, create a spring boot image with `mvn spring-boot:build-image` inside a kubernetes pod.

By default this fails because spring try to execute 'buildpacks' but the needed docker service is not available. The docker service is needed to create the container image. With spring 2.6 and beneath it is not possible to use another service then docker. But it is possible to use buildpacks separately after creating the project jar file.

As replacement of docker it is possible to use podman as service. podman is compatible with docker and also supports the docker.sock. Looking forward to spring boot 2.7 which supports also podman as alternative to docker I did not try to use 'kaniko'.

Objectives of this post:

  • Create a woking pod and script to create spring boot images inside of Kubernetes
  • Prove how creation could be done in K8s 
  • Push the image to docker hub
Out of scope
  • Creating the Jenkins pipeline

To do the task I create a 'pod.yaml' with container needed to create a spring boot image and a 'build.sh' containing all the commands executing at the pods containers.

To test the script a simple spring application will be used (https://github.com/mhus/sample-spring-service). - This files are included in the repository in '/test'.

So let's do it:

(0) First a management pod is needed to download the project. Usually this is done by Jenkins at the start of the pipeline. In this case I need to do it manually. I need an image which contains the command 'git' to clone the git repo. The image 'dockercore/docker' will do the job. As usually we need to start a dummy command to let the container alive. My name for the container is 'box'.

To simulate Jenkins I will share the folder '/home/jenkins/agent/' over all pods.

      - name: box
        image: dockercore/docker
        command:
        - cat
        tty: true
        volumeMounts:
          - name: workspace-volume
            mountPath: /home/jenkins/agent

 Now it is possible to clone the repository

kubectl exec -it -c box agent -- /bin/bash -c \
 "cd /home/jenkins/agent/;
 git clone https://github.com/mhus/sample-spring-service.git"

Preparation is done.

(1) First pipeline step is to create the jar file using maven. This is a common task in Jenkins pipelines. Using the 'maven' image and execute `mvn package` for the project.

- name: maven
     image:  maven:3.8-openjdk-11
     command:
     - cat
     tty: true
     volumeMounts:
       - name: workspace-volume
         mountPath: /home/jenkins/agent

and execute

kubectl exec -it -c maven agent -- \
 /bin/bash -c "cd /home/jenkins/agent/sample-spring-service;
 mvn package"

(2) More complicated is to execute the buildpack command. To create the image the command is 

pack build <image name> -p <jar file> -B paketobuildpacks/builder:base"` and there is an official buildpack image in docker hub 'buildpacksio/pack

But the image do not include a bash or sh command and it is not possible to start the container doing nothing and wait for exec commands - wtf. Therefore I was forced to create a separate image containing buildpacks AND a shell.

Dockerfile

FROM debian
RUN set +x \
 && apt update \
 && apt install -y curl git \
 && (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.26.0/pack-v0.26.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack) \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 

ENTRYPOINT ["/usr/local/bin/pack"]

And I pushed the image to docker hub as 'mhus/buildpacks:0.26.0-0'. Adding the container specification to the pod I created a shared folder to share the docker socket from podman into buildpacks.

- name: buildpacks
     image: mhus/buildpacks:0.26.0-0
     command: ["/bin/bash","-c"]
     args: ["cat"]
     tty: true
     volumeMounts:
       - name: workspace-volume
         mountPath: /home/jenkins/agent
       - name: docker-socket
         mountPath: /var/run

And the podman container as helper for the buildpacks command. To provide the socket it's important to start the podman as a service and set the path for the socket.

- name: podman
     image: quay.io/podman/stable
     args: ["/usr/bin/podman","system","service","-t","0","--log-level=info", "unix:///var/run/docker.sock"]
     tty: true
     securityContext:
      privileged: true
     volumeMounts:
       - name: workspace-volume
         mountPath: /home/jenkins/agent
       - name: docker-socket
         mountPath: /var/run

Now it's possible to start buildpack to create the container image for the spring boot project:

kubectl exec -it -c buildpacks agent -- /bin/bash -c \
 "cd /home/jenkins/agent/sample-spring-service;
 pack build sample-spring-service -p target/sample-spring-service-1.0.0-SNAPSHOT.jar -B paketobuildpacks/builder:base"

(3) Last step is to push the image to docker hub. To do this I need to tag the image, login to the hub and push the image. Since the podman container already exists I only need to call the command in the existing container.

kubectl exec -it -c podman agent -- /bin/bash -c \
 "podman tag sample-spring-service docker.io/$docker_user/sample-spring-service:$version;"
kubectl exec -it -c podman agent -- /bin/bash -c \
 "podman login -u $docker_user -p '$docker_pass' docker.io/$docker_user"
kubectl exec -it -c podman agent -- /bin/bash -c \
 "podman push docker.io/$docker_user/sample-spring-service:$version"

Before execution set the variables 

version="latest"
docker_user="mhus"
docker_pass="xxx"

Let's test the new image with docker:


docker run --rm --name sample-spring-service -p 8080:8080 mhus/sample-spring-service:latest

And test the rest service in the browser 'http://localhost:8080/hello'.

This proof could be more generic but I get the goal to create a spring boot image. Next this should be done all together to create a pipeline that works for this project and with parameters for all other projects too.


Comments

Popular posts from this blog

Sonatype Nexus fails with random "peer not authenticated" errors behind ingress

Creating a flux sync configuration referring a config map for substitution

[mhus lib] Reorg in generation 7 nearly finished