1. Overview

stateful app
deployment to minikube
  1. First we create a simple REST-endpoint with quarkus.

  2. We create an uber-jar.

  3. We build a docker image with the jar-file.

  4. We push the docker image to an image registry (ghcr.io).

  5. We deploy the docker image to minikube.

  6. we create a gh-actions pipeline to automate the deployment to minikube.

2. Create the quarkus Project

mvn io.quarkus.platform:quarkus-maven-plugin:3.8.2:create \
    -DprojectGroupId=at.htl.minikube \
    -DprojectArtifactId=minikube-pvc \
    -Dextensions='resteasy-reactive-jackson, smallrye-health'
result
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- quarkus:3.8.2:create (default-cli) @ standalone-pom ---
[INFO] -----------
[INFO] selected extensions:
- io.quarkus:quarkus-smallrye-health
- io.quarkus:quarkus-resteasy-reactive-jackson

[INFO]
applying codestarts...
[INFO] πŸ“š java
πŸ”¨ maven
πŸ“¦ quarkus
πŸ“ config-properties
πŸ”§ tooling-dockerfiles
πŸ”§ tooling-maven-wrapper
πŸš€ resteasy-reactive-codestart
πŸš€ smallrye-health-codestart
[INFO]
-----------
[SUCCESS] βœ…  quarkus project has been successfully generated in:
--> /Users/stuetz/work/2024-ph-seminar/_delete/minikube-pvc
-----------
[INFO]
[INFO] ========================================================================================
[INFO] Your new application has been created in /Users/stuetz/work/2024-ph-seminar/_delete/minikube-pvc
[INFO] Navigate into this directory and launch your application with mvn quarkus:dev
[INFO] Your application will be accessible on http://localhost:8080
[INFO] ========================================================================================
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.909 s
[INFO] Finished at: 2024-03-10T11:11:44+01:00
[INFO] ------------------------------------------------------------------------

3. Run the quarkus Project

cd minikupe-pvc
./mvnw clean quarkus:dev

4. Request the REST-Endpoints

  • Create a REST-Client

    • New folder in project-Root: http-requests

    • create a new file in this folder: requests.http

http-requests/requests.http
### hello

GET http://localhost:8080/hello
Content-Type: */*

### Liveness

GET http://localhost:8080/q/health/live

###
http request
  • you can also use cURL:

curl -i http://localhost:8080/hello (1)
1 -i shows the header of the response.
result
HTTP/1.1 200 OK
content-length: 28
Content-Type: text/plain;charset=UTF-8

Hello from RESTEasy Reactive%

5. Delete the current test-files

  • Delete GreetingResourceIT.java and GreetingResourceTest.java

default test files

6. Add Database

6.1. Add Dependencies

add database-dependencies to pom.xml
./mvnw quarkus:add-extension -Dextensions='hibernate-orm-panache, jdbc-postgresql'
result
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< at.htl.minikube:minikube-pvc >--------------------
[INFO] Building minikube-pvc 1.0.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus:3.8.2:add-extension (default-cli) @ minikube-pvc ---
[INFO] Looking for the newly published extensions in registry.quarkus.io
[INFO] [SUCCESS] βœ…  Extension io.quarkus:quarkus-hibernate-orm-panache has been installed
[INFO] [SUCCESS] βœ…  Extension io.quarkus:quarkus-jdbc-postgresql has been installed
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.071 s
[INFO] Finished at: 2024-03-10T12:36:22+01:00
[INFO] ------------------------------------------------------------------------
Dependencies in pom.xml
  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-reactive</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-arc</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm-panache</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-jdbc-postgresql</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-health</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

6.2. Configure the Database

Download files.zip, unzip it and copy the files into the project.

config database
application.properties
# datasource configuration
quarkus.datasource.db-kind = postgresql
quarkus.datasource.username = demo
quarkus.datasource.password = demo
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/demo
%prod.quarkus.datasource.jdbc.url = jdbc:postgresql://postgres:5432/demo

# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation=drop-and-create

quarkus.package.type=uber-jar
quarkus.hibernate-orm.sql-load-script=import.sql
import.sql
INSERT INTO vehicle (brand, model) VALUES ('Opel', 'Kadett');
INSERT INTO vehicle (brand, model) VALUES ('VW', 'KΓ€fer 1400');
INSERT INTO vehicle (brand, model) VALUES ('Opel', 'Blitz');
version: '3.1'

services:

  db:
    container_name: postgres
    image: postgres:15.2-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: db
    ports:
      - 5432:5432
    volumes:
      - ./db-postgres/db:/var/lib/postgresql/data
      - ./db-postgres/import:/import
    networks:
      - postgres

networks:
  postgres:
    driver: bridge

6.3. Start Database Locally

./postgres-create-db.sh
result
Installing postgres into ./db-postgres ...
 ./postgres-start.sh
result
[+] Running 2/2
 βœ” Container postgres  Started
db import datasource
db config datasource
db window
Add Folder db-postgres/ to .gitignore

7. Code Vehicle

vehicle source code
entity/Vehicle.java
package at.htl.minikube.entity;

// imports

@Entity
public class Vehicle {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String brand;
    private String model;

    //region constructors
    public Vehicle() {
    }

    public Vehicle(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }
    //endregion

    // getter and setter


    @Override
    public String toString() {
        return String.format("%d: %s %s", id, brand, model);
    }
}
control/RestConfig.java
package at.htl.minikube.control;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("api")
public class RestConfig extends Application {
}
control/VehicleRepository.java
package at.htl.minikube.control;

// imports

@ApplicationScoped
public class VehicleRepository implements PanacheRepository<Vehicle> {
}
boundary/VehicleResource.java
package at.htl.minikube.boundary;

import at.htl.minikube.control.VehicleRepository;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

@Path("vehicle")
public class VehicleResource {

    @Inject
    VehicleRepository vehicleRepository;

    @GET
    public Response getAll() {
        return Response.ok(
                vehicleRepository.listAll()
                ).build();
    }
}

8. Run Quarkus with Db Access

./mvnw clean quarkus:dev
run quarkus dev

9. Start minikube

minikube start
result
πŸ˜„  minikube v1.32.0 on Darwin 14.3.1 (arm64)
✨  Automatically selected the docker driver
πŸ“Œ  Using Docker Desktop driver with root privileges
πŸ‘  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
πŸ’Ύ  Downloading Kubernetes v1.28.3 preload ...
    > preloaded-images-k8s-v18-v1...:  341.16 MiB / 341.16 MiB  100.00% 1.81 Mi
    > gcr.io/k8s-minikube/kicbase...:  410.57 MiB / 410.58 MiB  100.00% 1.36 Mi
πŸ”₯  Creating docker container (CPUs=2, Memory=7793MB) ...
🐳  Preparing Kubernetes v1.28.3 on Docker 24.0.7 ...
    β–ͺ Generating certificates and keys ...
    β–ͺ Booting up control plane ...
    β–ͺ Configuring RBAC rules ...
πŸ”—  Configuring bridge CNI (Container Networking Interface) ...
πŸ”Ž  Verifying Kubernetes components...
    β–ͺ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: storage-provisioner, default-storageclass
πŸ„  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
  • Falls die Meldung erscheint, dass der Cluster veraltet ist, dann minikube stop und minikube delete. Beim anschließenden minikube start wird ein Cluster mit aktueller kubernetes-Software erstellt.

  • Check, in the "🌟 Enabled addons:"-section, that metrics-server and dashboard are installed.

    • When missing:

      minikube addons enable metrics-server
      minikube addons enable dashboard
check the successfull installation with
minikube addons list |grep enabled
result
| dashboard                   | minikube | enabled βœ…   | Kubernetes                     |
| default-storageclass        | minikube | enabled βœ…   | Kubernetes                     |
| metrics-server              | minikube | enabled βœ…   | Kubernetes                     |
| storage-provisioner         | minikube | enabled βœ…   | minikube                       |

10. .jar File erstellen (uber-jar)

Precondition in application.properties
quarkus.package.type=uber-jar
./mvnw clean package
  • check, if the runner-jar is created

runner jar in target

11. Create docker Image

  • Therefore, we need a Dockerfile.

  • There are already Dockerfiles in src/main/docker - these are not needed and can be deleted (when not already done).

  • Create a new Dockerfile in src/main/docker

result
tree
...
β”œβ”€β”€ src
β”‚Β Β  β”œβ”€β”€ main
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ docker
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── Dockerfile
...
Dockerfile
FROM eclipse-temurin:21-jre

RUN mkdir -p /opt/application
COPY *-runner.jar /opt/application/backend.jar
WORKDIR /opt/application
CMD [ "java", "-jar", "backend.jar" ]
docker build command 2

12. Create build.sh

  • Before we made it manually, but now we use a script in the project root.

build.sh
#!/usr/bin/env bash

mvn -B package
cp src/main/docker/Dockerfile target/
docker login ghcr.io -u $GITHUB_ACTOR -p $GITHUB_TOKEN
docker build --tag ghcr.io/$GITHUB_REPOSITORY/backend:latest ./target
docker push ghcr.io/$GITHUB_REPOSITORY/backend:latest

13. Config gh-actions - Pipeline

build yaml
Figure 1. : .github/workflows/build.yaml
build.yaml
name: Build and Deploy Dockerfiles
run-name: ${{ github.actor }} is building Docker images πŸš€
on: [ push ]
jobs:
  build-images:
    permissions: write-all
    runs-on: ubuntu-22.04
    steps:
      - name: Check out repository code
        uses: actions/checkout@v4

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - run: |
          pwd
          ls -lah
        working-directory: ./k8s

      - uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '21'
          cache: 'maven'

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build with Maven
        run: ./build.sh
  • Make a commit and push it to the repository

gh action build

14. Make the Package Public

Make package public (click for instructions)
gh packages
gh packages 2
gh packages 3
gh packages 4

15. Configure kubernetes Deployment

k8s/appsrv.yaml
# Quarkus Application Server
apiVersion: apps/v1
kind: Deployment
metadata:
  name: appsrv

spec:
  replicas: 1
  selector:
    matchLabels:
      app: appsrv
  template:
    metadata:
      labels:
        app: appsrv
    spec:
      containers:
        - name: appsrv
          image: ghcr.io/quarkus-seminar/2024-lab-minikube-pvc/backend:latest (1)
          # remove this when stable. Currently we do not take care of version numbers
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          startupProbe:
            httpGet:
              path: /q/health
              port: 8080
            timeoutSeconds: 5
            initialDelaySeconds: 15
          readinessProbe:
            tcpSocket:
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /q/health
              port: 8080
            timeoutSeconds: 5
            initialDelaySeconds: 60
            periodSeconds: 120
---
apiVersion: v1
kind: Service
metadata:
  name: appsrv

spec:
  ports:
    - port: 8080
      targetPort: 8080
      protocol: TCP
  selector:
    app: appsrv
1 Check, that your image name is correct
You could also generate this file with kubectl
create deployment in minikube-instance
kubectl create deployment appsrv --image=ghcr.io/htl-leonding/backend:latest --port=8080
result
deployment.apps/appsrv created
write to file
kubectl get deployments/appsrv -o yaml > appsrv.yaml
create service in minikube-instance
kubectl expose deployments/appsrv --port=8080
exposing the port 8080
kubectl expose deployments/appsrv-depl --port=8080

16. Deploy to minikube the first time

kubectl apply -f k8s/postgres.yaml
kubectl apply -f k8s/appsrv.yaml
result
deployment.apps/appsrv created
service/appsrv created

16.1. Check the Success

minikube dashboard
result
πŸ€”  Verifying dashboard health ...
πŸš€  Launching proxy ...
πŸ€”  Verifying proxy health ...
πŸŽ‰  Opening http://127.0.0.1:53209/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser...
  • The following site should be opened in your browser

    • if not just use minikube --url and copy the given url into your browser

dashboard 01
  • We notice there are problems

16.2. Fix problems

dashboard 02
dashboard 03
k8s exec format error
  • This error occurs when running minikube on a arm-processor and the image is build for x86-processor (amd).

  • We will fix this later on

dashboard 04

17. Port Forward from minikube

Port forwarding
kubectl port-forward appsrv-xxxxxx-xxxxx 8080:8080
Use kubectl-autocomplete for the appsrv
result
❯ kubectl port-forward appsrv-65cd45564f-8vlrm 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

18. Access the App

curl -i http://localhost:8080/api/vehicle
result
GET http://localhost:8080/api/vehicle

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
content-length: 126

[
  {
    "id": 1,
    "brand": "Opel",
    "model": "Kadett"
  },
  {
    "id": 2,
    "brand": "VW",
    "model": "KΓ€fer 1400"
  },
  {
    "id": 3,
    "brand": "Opel",
    "model": "Blitz"
  }
]
Response file saved.
> 2024-03-13T062310.200.json

Response code: 200 (OK); Time: 872ms (872 ms); Content length: 125 bytes (125 B)

19. Troubleshooting

open an ssh-shell in minikube
minikube ssh
 __   ___     _   ___      __     _      _
 \ \ / (_)___| | | __|_ _ / _|___| |__ _| |
  \ V /| / -_) | | _|| '_|  _/ _ \ / _` |_|
   \_/ |_\___|_| |___|_| |_| \___/_\__, (_)
                                   |___/