Reference

  • How to deploy a containerized spring boot application , with PostgreSQL as database on minikube. minikube

  • This post will also share details on how to initialize the database with tables and data during the initialization process.

  • Will use a spring boot application order service with a REST endpoint to fetch customer details

    • GET /customers
  • It uses spring JPA to access PostgreSQL

  • Details of the service - https://github.com/rajeshsgr/order-svc-k8

  • This article will demonstrate deploying this service on Minikube , More details on minikube can be found at- https://belowthemalt.com/2022/03/17/minikube-kubectl-and-local-development-deployment-of-apps-in-kubernetes/

  • Key steps for this deployment process are

    • Building a docker image of order-service & upload to docker repository
    • Writing Kubernetes deployment file to initialize and run database
    • Writing Kubernetes deployment file for order-svc
    • Testing the service

Step 1: Building a docker image of order-service & uploading to docker repository

  • Build the application to generate jar file

    • mvn clean install -DskipTests
  • If you want to run this application is local , you will have to update the application.properties file with database details

  • Create a docker image for the service , refer to Dockerfile https://github.com/rajeshsgr/order-svc-k8/blob/main/Dockerfile

    1
    2
    3
    4
    5
    6
    7
    
    FROM openjdk:8-jdk-alpine
    VOLUME /tmp
    EXPOSE 8080
    RUN mkdir -p /app/
    RUN mkdir -p /app/logs/
    ADD target/order-0.0.1-SNAPSHOT.jar /app/app.jar
    ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-Dspring.profiles.active=container", "-jar", "/app/app.jar"]
    
  • Dockerfile has mainly instructions to add openjdk 8 image, expose port 8080 and add the generated jar file from previous step to the image

  • To build an image with the name order-svc-k8 from the Dockerfile , run the below command

    • docker build -t raje/order-svc-k8 .
  • Once the above step is done you can check the image with docker images command docker images

  • To push this image to docker repository, give the command docker push raje/order-svc-k8

    docker push

  • At this stage, we have built our spring boot app, dockerized it and uploaded to the docker repository

Step 2: Writing Kubernetes deployment file to initialize and run database

  • For PostgreSQL, we will use the docker image . Link to repo - https://hub.docker.com/_/postgres

  • Other 2 important use cases of this demo are

    • Mount a path on host , so that the data can be retrieved even if the cluster goes down
    • Initialize database with table and scripts
  • In order to mount a persistent storage, we will have to deploy a resource type of Persistent Volume and Persistent Volume Claim

  • Since we need a persistent storage for PostgreSQL to store data, we will provision the storage using Persistent Volume yaml

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: postgresql-claim0
    labels:
        type: local
    spec:
    storageClassName: manual
    capacity:
        storage: 1Gi
    accessModes:
        - ReadWriteOnce
    hostPath:
        path: "/Users/rajeshp/docker/postgres/docker-pg-vol/data"
    
  • Above yaml is for PersistentVolume object , which has details of host path , storage class , size etc. The name defined for this is - postgresql-claim0

  • Persistent volumes have a lifecycle that is independent of any individual pod that uses the storage.

  • In order for our application to consume this storage space, we will have to request it using a Persistent Volume Claim request.

  • yaml for Persistent Volume Claim

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: postgresql-claim0
    labels:
        app: postgresql
        tier: database
    spec:
    accessModes:
        - ReadWriteOnce
    resources:
        requests:
        storage: 100Mi
    
  • Next we want to build a configuration map object, which will have our database initialization scripts

  • Configuration maps are kubernetes objects which are used to store non-confidential data in key-value pairs

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: postgresql-initdb-config
    data:
    init.sql: |
        CREATE TABLE IF NOT EXISTS customers (
            customer_id bpchar NOT NULL,
            company_name character varying(40) NOT NULL,
            contact_name character varying(30),
            contact_title character varying(30),
            address character varying(60),
            city character varying(15),
            region character varying(15),
            postal_code character varying(10),
            country character varying(15),
            phone character varying(24),
            fax character varying(24)
        );    
    
    INSERT INTO customers VALUES ('ALFKI', 'Alfreds Futterkiste', 'Maria Anders', 'Sales Representative', 'Obere Str. 57', 'Berlin', NULL, '12209', 'Germany', '030-0074321', '030-0076545');
    
  • Since our order service will need to access this database, we will need a Kubernetes service object

  • Service is responsible for enabling network access to a set of pods

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    apiVersion: v1
    kind: Service
    metadata:
    name: postgresql
    labels:
        app: postgresql
        tier: database
    spec:
    ports:
        - port: 5432
        targetPort: 5432
    selector:
        app: postgresql
        tier: database
    
  • Services select Pods based on their labels. When a network request is made to the service, it selects all Pods in the cluster matching the service’s selector, chooses one of them, and forwards the network request to it.

  • Next, we need the deployment object.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: postgresql
    labels:
        app: postgresql
        tier: database
    spec:
    selector:
        matchLabels:
        app: postgresql
    strategy:
        type: Recreate
    template:
        metadata:
        labels:
            app: postgresql
            tier: database
        spec:
        containers:
            - name: postgresql
            image: postgres:12
            imagePullPolicy: "IfNotPresent"
            env:
                - name: POSTGRES_DB
                value: northwind
                - name: POSTGRES_USER
                value: postgres
                - name: POSTGRES_PASSWORD
                value: changeme
            ports:
                - containerPort: 5432
                name: postgresql
            volumeMounts:
                - name: postgresql-claim0
                mountPath: /var/lib/postgresql/data
    
                - mountPath: /docker-entrypoint-initdb.d
                name: postgresql-initdb
        volumes:
            - name: postgresql-claim0
            persistentVolumeClaim:
                claimName: postgresql-claim0
    
            - name: postgresql-initdb
            configMap:
                name: postgresql-initdb-config
    
  • Key elements of the deployment yaml

    • Deploys postgres:12 container
    • Have environment variables , which has the database name, user and password
    • Mounts the persistent volume , defined using Persistent volume object
    • Passes the configmap value for docker to execute
  • Bring minikube up by issuing command: minikube start

  • Let us now deploy all the kubernetes resources by giving following commands

    1
    2
    3
    4
    
    kubectl apply -f postgresql-claim0-persistentvolume.yaml
    kubectl apply -f postgresql-claim0-persistentvolumeclaim.yaml
    kubectl apply -f postgresql-initial-data-configmap.yamlkubectl kubectl apply -f postgresql-deployment.yaml
    kubectl apply -f postgresql-service.yaml
    
  • You can check status by kubectl get all command kubectl get all

  • In case you want to check the logs of database pod, you can check by issuing this command kubectl logs pod/postgresql-7bf5994f6f-kv5gs . Please replace the pod name with your pod name

  • Let us now verify this deployment , by connecting to database. Commands for the operations are -kubectl exec -it pod/postgresql-7bf5994f6f-kv5gs bash

  • Replace pod name with your pod name in the above command

  • Connect to postgres - psql -U postgres

  • Connect to northwind database -\c northwind

  • Check the tables - \dt

  • Check if database is initialized by executing - select * from customers;

  • The application.properties of the service , will get the details of database username, password and connection details from the environment variables mentioned in order service deployment yaml

    1
    2
    3
    4
    5
    6
    
    spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
    spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
    spring.datasource.url=${SPRING_DATASOURCE_URL}
    ]spring.jpa.show-sql=true
    spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
    spring.jpa.hibernate.ddl-auto=update
    
  • We will define a service object for us to access the application. yaml for service object

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    apiVersion: v1
    kind: Service
    metadata:
    name: order-svc-service
    spec:
    ports:
        - protocol: "TCP"
        port: 8080
        targetPort: 8080
    selector:
        app: order-svc
    type: NodePort
    
  • Service type is NodePort . It makes the service accessible on a static port on each Node in the cluster. This means that the service can handle requests that originate from outside the cluster.

  • Next is deployment of order service and the yaml for that is

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: order-svc
    spec:
    replicas: 1
    selector:
        matchLabels:
        app: order-svc
    template:
        metadata:
        labels:
            app: order-svc
        spec:
        containers:
        - image: raje/order-svc-k8
            name: order-svc
            ports:
            - containerPort: 8080
            env:
                - name: SPRING_DATASOURCE_PASSWORD
                value: changeme
                - name: SPRING_DATASOURCE_URL
                value: jdbc:postgresql://postgresql:5432/northwind?useSSL=false
                - name: SPRING_DATASOURCE_USERNAME
                value: postgres
                - name: SPRING_JPA_HIBERNATE_DDL_AUTO
                value: update
    
  • Key elements of the deployment yaml

    • uses - raje/order-svc-k8 container image
    • Defines the enviornment variable
    • database is accessed with the name postgresql which is the name of the service for database
  • Deploy the service and deployment objects by giving below command

    1
    2
    
    kubectl apply -f order-svc-service.yaml
    kubectl apply -f order-svc-deployment.yaml
    
  • You can verify the deployment with the command - kubectl get all

  • During the process if you want to check the logs of order service pod , you can give command kubectl logs <<podname>>

  • To access and test the application , issue the below command

    1
    
    kubectl port-forward service/order-svc-service 7080:8080
    
  • To access the API

    1
    
    curl http://localhost:7080/api/v1/customers
    

Source code for the application: https://github.com/rajeshsgr/order-svc-k8