Using a RESTful Web Service Spring Boot application in Minikube, together with an external “Dockerized” MySQL database

0

In a previous article, I talked about an environment, I prepared on my Windows laptop, with a guest Operating System, Docker and Minikube available within an Oracle VirtualBox appliance.
[https://technology.amis.nl/2019/02/12/rapidly-spinning-up-a-vm-with-ubuntu-docker-and-minikube-using-the-vm-drivernone-option-on-my-windows-laptop-using-vagrant-and-oracle-virtualbox/]
In another article I created two versions of a RESTful Web Service Spring Boot application, being a book service. With this service you can add, update, delete and retrieve books from a catalog.
The application uses an H2 in-memory database but is also prepared for using an external MySQL database. For demo purposes I created a 1.0 and 2.0 version of the application.
The latter has some additional fields representing a book.
[https://technology.amis.nl/2019/02/26/building-a-restful-web-service-with-spring-boot-using-an-h2-in-memory-database-and-also-an-external-mysql-database/]

In this article I will describe how these applications will be used in Minikube, together with an external “Dockerized” MySQL database.

Minikube

Minikube is a tool that makes it easy to run Kubernetes locally. Minikube runs a single-node Kubernetes cluster for users looking to try out Kubernetes or develop with it day-to-day.
[https://kubernetes.io/docs/setup/minikube/]

As described in my previous article, I created a subdirectory named env on my Windows laptop. In this directory I created an applications subdirectory.
[https://technology.amis.nl/2019/02/26/building-a-restful-web-service-with-spring-boot-using-an-h2-in-memory-database-and-also-an-external-mysql-database/]

For this article, in the applications directory I created a subdirectory books_service_1.0 and a subdirectory books_service_2.0.
In the books_service_1.0 directory I created a subdirectory target and I placed the following file in there: books_service-1.0.0-SNAPSHOT.jar
In the books_service_2.0 directory I created a subdirectory target and I placed the following file in there: books_service-2.0.0-SNAPSHOT.jar

You can read about how these jar files were created in my previous article.
[https://technology.amis.nl/2019/02/26/building-a-restful-web-service-with-spring-boot-using-an-h2-in-memory-database-and-also-an-external-mysql-database/]

Now that we have the jar files, let’s build the Docker Images for the applications.

I am going to create a development (DEV) and a testing (TST) environment.
In the DEV environment the applications (version 1.0 and 2.0) will be using an H2 in-memory database.
In the TST environment the application (version 1.0) will be using an external MySQL database.

I the table below you can see the overview of the “application landscape” I want to create:

EnvironmentDatabaseBooksservice version
DEVH2 in-memory1.0
2.0
TSTMySQL1.0

Because on my Windows laptop, Minikube runs within an Oracle VirtualBox appliance, I will be using a Linux Command Prompt via ssh.

In the books_service_1.0 directory, I created a Dockerfile with the following content:

 
# Start with a base image containing Java runtime
FROM openjdk:8-jdk-alpine

# Add Maintainer Info
LABEL maintainer="amis"

# Application runs on port 8080
EXPOSE 8080

# The application's jar file
ARG JAR_FILE=target/books_service-1.0.0-SNAPSHOT.jar

# Add the application's jar to the container
ADD ${JAR_FILE} books_service.jar

# Run the jar file 
ENTRYPOINT ["java","-jar","/books_service.jar"]

In the books_service_2.0, I created a Dockerfile with the following content:

 
# Start with a base image containing Java runtime
FROM openjdk:8-jdk-alpine

# Add Maintainer Info
LABEL maintainer="amis"

# Application runs on port 8080
EXPOSE 8080

# The application's jar file
ARG JAR_FILE=target/books_service-2.0.0-SNAPSHOT.jar

# Add the application's jar to the container
ADD ${JAR_FILE} books_service.jar

# Run the jar file 
ENTRYPOINT ["java","-jar","/books_service.jar"]

For my own convenience I created a shell script with several commands. I used this script for setting up an external MySQL database.
In the scripts directory I therefor created file applications.sh with the following content:

 
#!/bin/bash
echo "**** Begin building Docker images"

cd /vagrant
cd applications
cd books_service_1.0
docker build -t booksservice:v1.0 .

cd ..
cd books_service_2.0
docker build -t booksservice:v2.0 .

echo "**** End building Docker images"

echo "**** Begin listing Docker images"

docker image ls

echo "**** End listing Docker images"

echo "**** Begin creating Minikube resources"

cd /vagrant
cd yaml
kubectl create -f namespace-development.yaml
kubectl create -f namespace-testing.yaml
kubectl create -f persistent-volume-mysql.yaml
kubectl create -f persistent-volume-claim-mysql.yaml
kubectl create -f deployment-mysql.yaml
kubectl create -f service-mysql.yaml

echo "**** End creating Minikube resources"

Interact with the Cluster via kubectl

I went to the env directory and opened a Windows Command Prompt (cmd) to access linux (within the VirtualBox Appliance) via ssh: vagrant ssh

Linux Command Prompt: cd /vagrant

Linux Command Prompt: cd scripts

Linux Command Prompt: ./applications.sh

Although a shell script is used, I will describe each command and its output separately.

Docker build

With the following command, a Docker Image for application books_service_1.0 is build: docker build -t booksservice:v1.0 .
With the following command, a Docker Image for application books_service_2.0 is build: docker build -t booksservice:v2.0 .

These commands returned the following output:

**** Begin building Docker images
Sending build context to Docker daemon 37.84MB
Step 1/6 : FROM openjdk:8-jdk-alpine
8-jdk-alpine: Pulling from library/openjdk
6c40cc604d8e: Pull complete
e78b80385239: Pull complete
47317d99e629: Pull complete
Digest: sha256:893271fc760d6adabd79b6b8c20e24bdba47f7b253ab63b13836ac3a2a16ff70
Status: Downloaded newer image for openjdk:8-jdk-alpine
—> 792ff45a2a17
Step 2/6 : LABEL maintainer=”amis”
—> Running in af59bb458dc8
Removing intermediate container af59bb458dc8
—> 9a5551c8c55a
Step 3/6 : EXPOSE 8080
—> Running in b2db926a00b3
Removing intermediate container b2db926a00b3
—> 9e21698eab45
Step 4/6 : ARG JAR_FILE=target/books_service-1.0.0-SNAPSHOT.jar
—> Running in 8622706e774a
Removing intermediate container 8622706e774a
—> ed8ef8c169b0
Step 5/6 : ADD ${JAR_FILE} books_service.jar
—> 51c5cffec129
Step 6/6 : ENTRYPOINT [“java”,”-jar”,”/books_service.jar”]
—> Running in 895f7003d342
Removing intermediate container 895f7003d342
—> 9b384295f40c
Successfully built 9b384295f40c
Successfully tagged booksservice:v1.0
Sending build context to Docker daemon 37.84MB
Step 1/6 : FROM openjdk:8-jdk-alpine
—> 792ff45a2a17
Step 2/6 : LABEL maintainer=”amis”
—> Using cache
—> 9a5551c8c55a
Step 3/6 : EXPOSE 8080
—> Using cache
—> 9e21698eab45
Step 4/6 : ARG JAR_FILE=target/books_service-2.0.0-SNAPSHOT.jar
—> Running in 92e4e6577bea
Removing intermediate container 92e4e6577bea
—> 95b391fd15a6
Step 5/6 : ADD ${JAR_FILE} books_service.jar
—> e54353066caf
Step 6/6 : ENTRYPOINT [“java”,”-jar”,”/books_service.jar”]
—> Running in ba0f84be6a63
Removing intermediate container ba0f84be6a63
—> 62f8e35b747d
Successfully built 62f8e35b747d
Successfully tagged booksservice:v2.0
**** End building Docker images

Docker image ls

With the following command, a list of all the docker images on your system is returned: docker image ls

This command returned the following output:

**** Begin listing Docker images
REPOSITORY                                TAG                 IMAGE ID            CREATED                  SIZE
booksservice                              v2.0                62f8e35b747d        Less than a second ago   143MB
booksservice                              v1.0                9b384295f40c        1 second ago             143MB
openjdk                                   8-jdk-alpine        792ff45a2a17        3 weeks ago              105MB
k8s.gcr.io/kubernetes-dashboard-amd64     v1.10.1             f9aed6605b81        2 months ago             122MB
k8s.gcr.io/kube-proxy                     v1.12.4             6d393e89739f        2 months ago             96.5MB
k8s.gcr.io/kube-apiserver                 v1.12.4             c04b373449d3        2 months ago             194MB
k8s.gcr.io/kube-controller-manager        v1.12.4             51b2a8e5ff78        2 months ago             164MB
k8s.gcr.io/kube-scheduler                 v1.12.4             c1b5e63c0b56        2 months ago             58.4MB
k8s.gcr.io/etcd                           3.2.24              3cab8e1b9802        5 months ago             220MB
k8s.gcr.io/coredns                        1.2.2               367cdc8433a4        6 months ago             39.2MB
k8s.gcr.io/kube-addon-manager             v8.6                9c16409588eb        12 months ago            78.4MB
k8s.gcr.io/pause                          3.1                 da86e6ba6ca1        14 months ago            742kB
gcr.io/k8s-minikube/storage-provisioner   v1.8.1              4689081edb10        15 months ago            80.8MB
**** End listing Docker images

Use kubectl to create a container, based on a Docker Image

Instead of using docker run, we are now going to use kubectl.

You can create and manage a resource by using the Kubernetes command line interface, kubectl.
Kubectl uses the Kubernetes API to interact with the cluster.

For creating a resource with kubectl , a file can be used. This file is called a manifest.
Kubernetes manifests can be defined in json or yaml. The file extension .yaml, .yml, and .json can be used.
[https://kubernetes.io/docs/reference/kubectl/cheatsheet/]

I used yaml files.

For setting up a yaml file for a certain resource I kindly refer you to the Kubernetes documentation.
[https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/]

In the env directory I created a subdirectory yaml.

Navigate to the yaml directory.
Linux Command Prompt: cd yaml

As a naming convention for the yaml files, I used: <resource kind>-<specific name>.yaml

Namespaces

Within Minikube, I created resources with a specific namespace. So first I had to create these namespaces.

I added to the yaml directory a file namespace-development.yaml with the following contents:

 
apiVersion: v1
kind: Namespace
metadata:
  name: "nl-amis-development"
  labels:
    name: "nl-amis-development"

I added to the yaml directory a file namespace-testing.yaml with the following contents:

 
apiVersion: v1
kind: Namespace
metadata:
  name: "nl-amis-testing"
  labels:
    name: "nl-amis-testing"

With the following commands, the namespaces are created:

 
kubectl create -f namespace-development.yaml

namespace/nl-amis-development created
 
kubectl create -f namespace-testing.yaml

namespace/nl-amis-testing created

You can list the current namespaces in a cluster using:

 
kubectl get namespaces

This command returned the following output:

NAME                  STATUS   AGE
default               Active   7m1s
kube-public           Active   6m56s
kube-system           Active   7m1s
nl-amis-development   Active   2m49s
nl-amis-testing       Active   2m49s

Or via the Kubernetes Web UI (Dashboard):
http://127.0.0.1:8001/api/v1/namespaces/kube-system/services/http:kubernetes-dashboard:/proxy/#!/node?namespace=default

Navigate to Cluster | Namespaces:

Remark:
In this article some resources that I created have the Namespace: nl-amis-development
In a yaml file this can be done via:
namespace: nl-amis-development

Setting up the external “Dockerized” MySQL database

So now let’s focus on setting up an external MySQL database, running in a separate Docker container.

I used the following yaml files to set everything up:

  • persistent-volume-mysql.yaml
  • persistent-volume-claim-mysql.yaml
  • deployment-mysql.yaml
  • service-mysql.yaml

PersistentVolume (MySQL)

I added to the yaml directory a file persistent-volume-mysql.yaml with the following contents:

 
kind: PersistentVolume
apiVersion: v1
metadata:
  name: mysql-persistent-volume
  namespace: nl-amis-testing
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"

Create the PersistentVolume:

 
kubectl create -f persistent-volume-mysql.yaml

persistentvolume/mysql-persistent-volume created

You can list the current persistentvolumes in a cluster using:

 
kubectl get persistentvolumes

This command returned the following output:

NAME                      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                            STORAGECLASS   REASON   AGE
mysql-persistent-volume   1Gi        RWO            Retain           Bound    nl-amis-testing/mysql-pv-claim   manual                  3m51s

Via the Kubernetes Web UI (Dashboard):
http://127.0.0.1:8001/api/v1/namespaces/kube-system/services/http:kubernetes-dashboard:/proxy/#!/node?namespace=nl-amis-testing

Navigate to Cluster | Persistent Volumes:

I f you click on mysql-persistent-volume the following details are shown:

PersistentVolumeClaim (MySQL)

Add to the yaml directory a file persistent-volume-claim-mysql.yaml with the following contents:

 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  namespace: nl-amis-testing
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Create the PersistentVolumeClaim:

 
kubectl create -f persistent-volume-claim-mysql.yaml

persistentvolumeclaim/mysql-pv-claim created

You can list the current persistentvolumeclaims in a cluster using:

 
kubectl get persistentvolumeclaim --all-namespaces

This command returned the following output:

NAMESPACE         NAME             STATUS   VOLUME                    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
nl-amis-testing   mysql-pv-claim   Bound    mysql-persistent-volume   1Gi        RWO            manual         13m

Via the Kubernetes Web UI (Dashboard):
http://127.0.0.1:8001/api/v1/namespaces/kube-system/services/http:kubernetes-dashboard:/proxy/#!/node?namespace=nl-amis-testing

Navigate to Config and Storage | Persistent Volume Claims:

If you click on mysql-pv-claim the following details are shown:

Deployments (MySQL)

I added to the yaml directory a file deployment-mysql.yaml with the following contents:

 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: nl-amis-testing
  labels:
    app: mysql
    version: "1.0"
    environment: testing
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
      version: "1.0"
      environment: testing
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
        version: "1.0"
        environment: testing
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
          # Use secret in real usage
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim

Remark:
This deployment uses the mysql:5.6 image.
The replicas are set to 1, so the ReplicaSet ensures that 1 pod replica is running at any given time.
The ReplicaSet manages all the pods with labels that match the selector. In my case these labels are:

Label keyLabel value
appmysql
version1.0
environmenttesting

Create the deployment:

 
kubectl create -f deployment-mysql.yaml

deployment.apps/mysql created

You can list the deployments in a cluster using:

 
kubectl get deployments --all-namespaces

With the following output:

NAMESPACE         NAME                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
kube-system       coredns                2         2         2            2           27m
kube-system       kubernetes-dashboard   1         1         1            1           26m
nl-amis-testing   mysql                  1         1         1            1           23m

Via the Kubernetes Web UI (Dashboard):
http://127.0.0.1:8001/api/v1/namespaces/kube-system/services/http:kubernetes-dashboard:/proxy/#!/node?namespace=nl-amis-testing

Navigate to Workloads | Deployments:

The deployment I created contains a Replica Set:

Replica Sets (MySQL)

The Replica Set contains one Pod:

Pods (MySQL)

The Pod includes 1 container (based on image mysql:5.6) and has IP address 172.17.0.5:

You can list the pods in a cluster using:

 
kubectl get pods --all-namespaces

With the following output:

NAMESPACE         NAME                                    READY   STATUS    RESTARTS   AGE
kube-system       coredns-576cbf47c7-8v48m                1/1     Running   0          31m
kube-system       coredns-576cbf47c7-n4b9r                1/1     Running   0          31m
kube-system       etcd-minikube                           1/1     Running   0          30m
kube-system       kube-addon-manager-minikube             1/1     Running   0          30m
kube-system       kube-apiserver-minikube                 1/1     Running   0          30m
kube-system       kube-controller-manager-minikube        1/1     Running   0          30m
kube-system       kube-proxy-clpgn                        1/1     Running   0          31m
kube-system       kube-scheduler-minikube                 1/1     Running   0          30m
kube-system       kubernetes-dashboard-5bff5f8fb8-z2cfc   1/1     Running   0          31m
kube-system       storage-provisioner                     1/1     Running   0          31m
nl-amis-testing   mysql-64846c7974-cnmch                  1/1     Running   0          27m

Services (MySQL)

I added to the yaml directory a file service-mysql.yaml with the following contents:

 
kind: Service
apiVersion: v1
metadata:
  name: mysql-service
  namespace: nl-amis-testing
  labels:
    app: mysql
    version: "1.0"
    environment: testing
spec:
  selector:
    app: mysql
    version: "1.0"
    environment: testing
  ports:
  - port: 3306
  selector:
    app: mysql
  clusterIP: None

Create the service:

 
kubectl create -f service-mysql.yaml

service/mysql-service created

You can list the services in a cluster using:

 
kubectl get services --all-namespaces

With the following output:

NAMESPACE         NAME                   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE
default           kubernetes             ClusterIP   10.96.0.1      <none>        443/TCP         34m
kube-system       kube-dns               ClusterIP   10.96.0.10     <none>        53/UDP,53/TCP   34m
kube-system       kubernetes-dashboard   ClusterIP   10.97.35.192   <none>        80/TCP          34m
nl-amis-testing   mysql-service          ClusterIP   None           <none>        3306/TCP        30m

Via the Kubernetes Web UI (Dashboard):
http://127.0.0.1:8001/api/v1/namespaces/kube-system/services/http:kubernetes-dashboard:/proxy/#!/node?namespace=nl-amis-testing

Navigate to Discovery and Load Balancing| Services:

Click on the service.

Via the Details you can see the following:

  • The Cluster IP address of this service is: None
  • The Internal endpoints of this service is:
    • mysql-service.nl-amis-testing:3306 TCP
  • The Endpoint of the Pod/Container is:
    • 172.17.0.5:3306 TCP

Mysql-client

In my previous article, in the application-testing.properties you can see that a MySQL database named test is used.
[https://technology.amis.nl/2019/02/26/building-a-restful-web-service-with-spring-boot-using-an-h2-in-memory-database-and-also-an-external-mysql-database/]

So, I first created that database. For this I used a mysql-client.

 
kubectl --namespace=nl-amis-testing run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql-service.nl-amis-testing -ppassword

If you don't see a command prompt, try pressing enter.

I had to click on the Enter button.

 
mysql> show databases;

With the following output:

 
mysql> create database test;

Query OK, 1 row affected (0.00 sec)
 
mysql> show databases;

With the following output:

 
mysql> exit

Bye
pod "mysql-client" deleted

Deployments (booksservice)

So now that the MySQL database is set up and the test database is created, let’s focus on creating deployments for both versions of the booksservice (RESTful Web Service Spring Boot application).

I wanted to create the following deployments:

EnvironmentPortBooksservice version
DEV90901.0
2.0
TST90911.0

In my previous article I talked about a RESTful Web Service Spring Boot application, being able to use different profiles.
[https://technology.amis.nl/2019/02/26/building-a-restful-web-service-with-spring-boot-using-an-h2-in-memory-database-and-also-an-external-mysql-database/]
For the booksservice (RESTful Web Service Spring Boot application), I created a development profile and a testing profile. In the development profile I use an H2 in-memory database, while in the testing profile I use an external MySQL database.

I added to the yaml directory a file deployment-booksservice-dev-v1.0.yaml with the following contents:

 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: booksservice-v1.0
  namespace: nl-amis-development
  labels:
    app: booksservice
    version: "1.0"
    environment: development
spec:
  replicas: 2
  selector:
    matchLabels:
      app: booksservice
      version: "1.0"
      environment: development
  template:
    metadata:
      labels:
        app: booksservice
        version: "1.0"
        environment: development
    spec:
      containers:
      - name: booksservice-v1-0-container
        image: booksservice:v1.0
        env:
        - name: spring.profiles.active
          value: "development"
        ports:
        - containerPort: 9090

Remark:
This deployment uses the booksservice (RESTful Web Service Spring Boot application) with H2 as an embedded in-memory database.
The replicas are set to 2, so the ReplicaSet ensures that 2 pod replicas are running at any given time.
The ReplicaSet manages all the pods with labels that match the selector. In my case these labels are:

Label keyLabel value
appbooksservice
version1.0
environmentdevelopment

Via the Environment variables, the active Spring profile has to be set up.

With regard to the metadata name (metadata.name), the following convention must be used:
a DNS-1123 subdomain must consist of lower case alphanumeric characters, ‘-‘ or ‘.’, and must start and end with an alphanumeric character (e.g. ‘example.com’, regex used for validation is ‘[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*’)

With regard to the metadata name (spec.template.spec.containers[0].name), the following convention must be used:
a DNS-1123 label must consist of lower case alphanumeric characters or ‘-‘, and must start and end with an alphanumeric character (e.g. ‘my-name’, or ‘123-abc’, regex used for validation is ‘[a-z0-9]([-a-z0-9]*[a-z0-9])?’)

Create the deployment:

 
kubectl create -f deployment-booksservice-dev-v1.0.yaml

deployment.apps/booksservice-v1.0 created

I added to the yaml directory a file deployment-booksservice-dev-v2.0.yaml with the following contents:

 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: booksservice-v2.0
  namespace: nl-amis-development
  labels:
    app: booksservice
    version: "2.0"
    environment: development
spec:
  replicas: 2
  selector:
    matchLabels:
      app: booksservice
      version: "2.0"
      environment: development
  template:
    metadata:
      labels:
        app: booksservice
        version: "2.0"
        environment: development
    spec:
      containers:
      - name: booksservice-v2-0-container
        image: booksservice:v2.0
        env:
        - name: spring.profiles.active
          value: "development"
        ports:
        - containerPort: 9090

Remark:
This deployment uses the booksservice (RESTful Web Service Spring Boot application) with H2 as an embedded in-memory database.
The replicas are set to 2, so the ReplicaSet ensures that 2 pod replicas are running at any given time.
The ReplicaSet manages all the pods with labels that match the selector. In my case these labels are:

Label keyLabel value
appbooksservice
version2.0
environmentdevelopment

Via the Environment variables, the active Spring profile has to be set up.

Create the deployment:

 
kubectl create -f deployment-booksservice-dev-v2.0.yaml

deployment.apps/booksservice-v2.0 created

I added to the yaml directory a file deployment-booksservice-tst-v1.0.yaml with the following contents:

 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: booksservice-v1.0
  namespace: nl-amis-testing
  labels:
    app: booksservice
    version: "1.0"
    environment: testing
spec:
  replicas: 2
  selector:
    matchLabels:
      app: booksservice
      version: "1.0"
      environment: testing
  template:
    metadata:
      labels:
        app: booksservice
        version: "1.0"
        environment: testing
    spec:
      containers:
      - name: booksservice-v1-0-container
        image: booksservice:v1.0
        env:
        - name: spring.profiles.active
          value: "testing"
        - name: spring.datasource.url
          value: "jdbc:mysql://mysql-service.nl-amis-testing/test?allowPublicKeyRetrieval=true&useSSL=false"
        ports:
        - containerPort: 9091

Remark:
This deployment uses the booksservice (RESTful Web Service Spring Boot application) with H2 as an embedded in-memory database.
The replicas are set to 2, so the ReplicaSet ensures that 2 pod replicas are running at any given time.
The ReplicaSet manages all the pods with labels that match the selector. In my case these labels are:

Label keyLabel value
appbooksservice
version1.0
environmenttesting

Via the Environment variables, the active Spring profile has to be set up. Also the correct Spring datasource url has to be set up. See the internal endpoint of the mysql-service resource, I created earlier.

Create the deployment:

 
kubectl create -f deployment-booksservice-tst-v1.0.yaml

deployment.apps/booksservice-v1.0 created

You can list the deployments in a cluster using:

 
kubectl get deployments --all-namespaces

With the following output:

NAMESPACE             NAME                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
kube-system           coredns                2         2         2            2           38m
kube-system           kubernetes-dashboard   1         1         1            1           38m
nl-amis-development   booksservice-v1.0      2         2         2            2           36s
nl-amis-development   booksservice-v2.0      2         2         2            2           23s
nl-amis-testing       booksservice-v1.0      2         2         2            2           10s
nl-amis-testing       mysql                  1         1         1            1           34m

Or via the Kubernetes Web UI (Dashboard):
http://127.0.0.1:8001/api/v1/namespaces/kube-system/services/http:kubernetes-dashboard:/proxy/#!/node?namespace=default

Navigate to Workloads | Deployments:

Remember, in Minikube the Namespace that is used is: default
So, we first have to switch to the development namespace (via Namespace | nl-amis-development) and then navigate to Workloads | Deployments:

Next, we switch to the testing namespace (via Namespace | nl-amis-testing) and then navigate to Workloads | Deployments:

Each deployment I have created contains a Replica Set. Let’s for example focus on the testing namespace. Click on booksservice-v1.0.

The booksservice-v1.0 deployment we have created contains a Replica Set:

Replica Sets (booksservice)

The Replica Set contains two Pods:

Pods (booksservice)

The first Pod includes 1 container (based on image booksservice:v1.0) and has IP address 172.17.0.10:

The second Pod includes 1 container (based on image booksservice:v1.0) and has IP address 172.17.0.11:

You can list the pods in a cluster using:

 
kubectl get pods --all-namespaces

With the following output:

NAMESPACE             NAME                                    READY   STATUS    RESTARTS   AGE
kube-system           coredns-576cbf47c7-8v48m                1/1     Running   0          54m
kube-system           coredns-576cbf47c7-n4b9r                1/1     Running   0          54m
kube-system           etcd-minikube                           1/1     Running   0          53m
kube-system           kube-addon-manager-minikube             1/1     Running   0          53m
kube-system           kube-apiserver-minikube                 1/1     Running   0          53m
kube-system           kube-controller-manager-minikube        1/1     Running   0          53m
kube-system           kube-proxy-clpgn                        1/1     Running   0          54m
kube-system           kube-scheduler-minikube                 1/1     Running   0          53m
kube-system           kubernetes-dashboard-5bff5f8fb8-z2cfc   1/1     Running   0          54m
kube-system           storage-provisioner                     1/1     Running   0          54m
nl-amis-development   booksservice-v1.0-68785bc6ff-k9cg6      1/1     Running   0          16m
nl-amis-development   booksservice-v1.0-68785bc6ff-q2fkj      1/1     Running   0          16m
nl-amis-development   booksservice-v2.0-869c5bb47d-6shjh      1/1     Running   0          16m
nl-amis-development   booksservice-v2.0-869c5bb47d-dqftq      1/1     Running   0          16m
nl-amis-testing       booksservice-v1.0-5bcd5fddbd-fxzxs      1/1     Running   0          16m
nl-amis-testing       booksservice-v1.0-5bcd5fddbd-pb64h      1/1     Running   0          16m
nl-amis-testing       mysql-64846c7974-cnmch                  1/1     Running   0          50m

You can list the pods in a certain namespace (of a cluster) using:

 
kubectl --namespace=nl-amis-testing get pods

With the following output:

NAME                                 READY   STATUS    RESTARTS   AGE
booksservice-v1.0-5bcd5fddbd-fxzxs   1/1     Running   0          16m
booksservice-v1.0-5bcd5fddbd-pb64h   1/1     Running   0          16m
mysql-64846c7974-cnmch               1/1     Running   0          50m

Services (booksservice)

I added to the yaml directory a file service-booksservice-dev-v1.0.yaml with the following contents:

 
kind: Service
apiVersion: v1
metadata:
  name: booksservice-v1-0-service
  namespace: nl-amis-development
  labels:
    app: booksservice
    version: "1.0"
    environment: development
spec:
  selector:
    app: booksservice
    version: "1.0"
    environment: development
  type: NodePort
  ports:
  - protocol: TCP
    nodePort: 30010
    port: 9190
    targetPort: 9090

Remark:
With regard to metadata name (metadata.name) the following convention must be used:
a DNS-1035 label must consist of lower case alphanumeric characters or ‘-‘, start with an alphabetic character, and end with an alphanumeric character (e.g. ‘my-name’, or ‘abc-123’, regex used for validation is ‘[a-z]([-a-z0-9]*[a-z0-9])?’)

Create the service:

 
kubectl create -f service-booksservice-dev-v1.0.yaml

service/booksservice-v1-0-service created

I added to the yaml directory a file service-booksservice-dev-v2.0.yaml with the following contents:

 
kind: Service
apiVersion: v1
metadata:
  name: booksservice-v2-0-service
  namespace: nl-amis-development
  labels:
    app: booksservice
    version: "2.0"
    environment: development
spec:
  selector:
    app: booksservice
    version: "2.0"
    environment: development
  type: NodePort
  ports:
  - protocol: TCP
    nodePort: 30020
    port: 9190
    targetPort: 9090

Create the service:

 

kubectl create -f service-booksservice-dev-v2.0.yaml

service/booksservice-v2-0-service created

I added to the yaml directory a file service-booksservice-tst-v1.0.yaml with the following contents:

 
kind: Service
apiVersion: v1
metadata:
  name: booksservice-v1-0-service
  namespace: nl-amis-testing
  labels:
    app: booksservice
    version: "1.0"
    environment: testing
spec:
  selector:
    app: booksservice
    version: "1.0"
    environment: testing
  type: NodePort
  ports:
  - protocol: TCP
    nodePort: 30110
    port: 9191
    targetPort: 9091

Create the service:

 

kubectl create -f service-booksservice-tst-v1.0.yaml

service/booksservice-v1-0-service created

You can list the services in a cluster using:

 
kubectl get services --all-namespaces

With the following output:

NAMESPACE             NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
default               kubernetes                  ClusterIP   10.96.0.1        <none>        443/TCP          56m
kube-system           kube-dns                    ClusterIP   10.96.0.10       <none>        53/UDP,53/TCP    56m
kube-system           kubernetes-dashboard        ClusterIP   10.97.35.192     <none>        80/TCP           55m
nl-amis-development   booksservice-v1-0-service   NodePort    10.96.159.109    <none>        9190:30010/TCP   37s
nl-amis-development   booksservice-v2-0-service   NodePort    10.110.146.146   <none>        9190:30020/TCP   26s
nl-amis-testing       booksservice-v1-0-service   NodePort    10.99.232.66     <none>        9191:30110/TCP   13s
nl-amis-testing       mysql-service               ClusterIP   None             <none>        3306/TCP         52m

You can list the services in a certain namespace (of a cluster) using:

 
kubectl --namespace=nl-amis-testing get services

With the following output:

NAME                        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
booksservice-v1-0-service   NodePort    10.99.232.66   <none>        9191:30110/TCP   31s
mysql-service               ClusterIP   None           <none>        3306/TCP         52m

Or via the Kubernetes Web UI (Dashboard):
http://127.0.0.1:8001/api/v1/namespaces/kube-system/services/http:kubernetes-dashboard:/proxy/#!/node?namespace=default

Again, we first have to switch to the development namespace (via Namespace | nl-amis-development) and then navigate to Discovery and Load Balancing | Services:

Next, we switch to the testing namespace (via Namespace | nl-amis-testing) and then Navigate to Discovery and Load Balancing | Services:

Each service I have created contains details. Let’s for example focus on the testing namespace. Click on the booksservice-v1-0-service service.

Via the Details you can see the following:

  • The Cluster IP address of this service is: 10.99.232.66
  • The Internal endpoints of this service are:
    • booksservice-v1-0-service.nl-amis-testing:9191 TCP
    • booksservice-v1-0-service.nl-amis-testing:30110 TCP
  • The Endpoints of the Pods/Containers are:
    • 172.17.0.10:9091 TCP
    • 172.17.0.11:9091 TCP

Pods

The first Pod includes 1 container (based on image booksservice:v1.0) and has IP address 172.17.0.10:

Here you can also see the Environment variables, this Pod/Container uses:

 
spring.profiles.active: tetsting
spring.datasource.url: jdbc:mysql://mysql-service.nl-amis-testing/test?allowPublicKeyRetrieval=true&useSSL=falsea

The second Pod includes 1 container (based on image booksservice:v1.0) and has IP address 172.17.0.11:

Here you can also see the Environment variables, this Pod/Container uses:

 
spring.profiles.active: tetsting
spring.datasource.url: jdbc:mysql://mysql-service.nl-amis-testing/test?allowPublicKeyRetrieval=true&useSSL=falsea

Next we take a look at the Logs of the Pods:

Logs from booksservice-v1-0-container, in booksservice-v1.0-5bcd5fddbd-kcxjq:

[ main] n.a.d.s.b.BooksServiceApplication : Started BooksServiceApplication in 23.842 seconds (JVM running for 27.708)
[ main] n.a.d.s.b.BooksServiceApplication : —-Begin logging BooksServiceApplication—-
[ main] n.a.d.s.b.BooksServiceApplication : —-System Properties from VM Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: null
[ main] n.a.d.s.b.BooksServiceApplication : —-Program Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : Currently active profile – testing
[ main] n.a.d.s.b.BooksServiceApplication : —-Environment Properties—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: 9091
[ main] n.a.d.s.b.BooksServiceApplication : nl.amis.environment: testing
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.url: jdbc:mysql://mysql-service.nl-amis-testing/test?allowPublicKeyRetrieval=true&useSSL=false
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.username: root
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.password: password
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.hibernate.ddl-auto: create
[ main] n.a.d.s.b.BooksServiceApplication : —-End logging BooksServiceApplication—-

Logs from booksservice-v1-0–container, in booksservice-v1.0-5bcd5fddbd-vjplh:

[ main] n.a.d.s.b.BooksServiceApplication : Started BooksServiceApplication in 23.684 seconds (JVM running for 27.622)
[ main] n.a.d.s.b.BooksServiceApplication : —-Begin logging BooksServiceApplication—-
[ main] n.a.d.s.b.BooksServiceApplication : —-System Properties from VM Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: null
[ main] n.a.d.s.b.BooksServiceApplication : —-Program Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : Currently active profile – testing
[ main] n.a.d.s.b.BooksServiceApplication : —-Environment Properties—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: 9091
[ main] n.a.d.s.b.BooksServiceApplication : nl.amis.environment: testing
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.url: jdbc:mysql://mysql-service.nl-amis-testing/test?allowPublicKeyRetrieval=true&useSSL=false
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.username: root
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.password: password
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.hibernate.ddl-auto: create
[ main] n.a.d.s.b.BooksServiceApplication : —-End logging BooksServiceApplication—-

In these logs, you can see the Environment variables that were used, when creating this Pod/Container.

Calling the booksservice application

Alright, so now the application should be running two Docker container. Let’s test how the application responds by firing some requests.

First, I added some books with POST-requests to the booksservice application:

 
curl --header "Content-Type: application/json" --request POST --data '{"id": 1, "title": "The Threat: How the FBI Protects America in the Age of Terror and Trump", "author": "Andrew G. McCabe", "type": "Hardcover", "price": 17.99, "numOfPages": 288, "language": "English", "isbn13": "978-1250207579"}' http://10.99.232.66:9191/books
 
curl --header "Content-Type: application/json" --request POST --data '{"id": 2, "title": "Becoming", "publishDate": "2018-11-13", "author": "Michelle Obama", "type": "Hardcover", "price": 17.88, "numOfPages": 448, "publisher": "Crown Publishing Group; First Edition edition", "language": "English", "isbn13": "978-1524763138"}' http://10.99.232.66:9191/books

As you can see, I used the Cluster IP address of the service.

Next, we should be able to see our updated list at the /books path with a GET request. First I tried the service IP address:

 
curl http://10.99.232.66:9191/books

[{"id":"1","title":"The Threat: How the FBI Protects America in the Age of Terror and Trump","author":"Andrew G. McCabe","type":"Hardcover","price":17.99,"numOfPages":288,"language":"English","isbn13":"978-1250207579"},{"id":"2","title":"Becoming","author":"Michelle Obama","type":"Hardcover","price":17.88,"numOfPages":448,"language":"English","isbn13":"978-1524763138"}]

I also tried, the first Pod IP address:

 
curl http://172.17.0.10:9091/books

[{"id":"1","title":"The Threat: How the FBI Protects America in the Age of Terror and Trump","author":"Andrew G. McCabe","type":"Hardcover","price":17.99,"numOfPages":288,"language":"English","isbn13":"978-1250207579"},{"id":"2","title":"Becoming","author":"Michelle Obama","type":"Hardcover","price":17.88,"numOfPages":448,"language":"English","isbn13":"978-1524763138"}]

And then I tried, the second Pod IP address:

 
curl http://172.17.0.11:9091/books

[{"id":"1","title":"The Threat: How the FBI Protects America in the Age of Terror and Trump","author":"Andrew G. McCabe","type":"Hardcover","price":17.99,"numOfPages":288,"language":"English","isbn13":"978-1250207579"},{"id":"2","title":"Becoming","author":"Michelle Obama","type":"Hardcover","price":17.88,"numOfPages":448,"language":"English","isbn13":"978-1524763138"}]

Remember, this deployment uses the booksservice (RESTful Web Service Spring Boot Application) with an external MySQL database, running in a separate Docker container. So, the first and second Pod both use the same external MySQL database. Therefor it doesn’t matter to which Pod the service sent the POST-requests. Each of the above GET-requests responds with the same answer (all 2 books).
As a final step I checked the contents of the book table. Again I used a mysql-client for this.

 
kubectl --namespace=nl-amis-testing run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql-service.nl-amis-testing -ppassword

If you don't see a command prompt, try pressing enter.

I had to click on the Enter button.

 
mysql> use test;

Reading table information for completion of table and column names

You can turn off this feature to get a quicker startup with -A

Database changed
 
mysql> select * from book;

With the following output:

Again, both books that I added, were returned.

 
mysql> exit

Bye

pod "mysql-client" deleted

ReplicaSet

A ReplicaSet’s purpose is to maintain a stable set of replica Pods running at any given time. As such, it is often used to guarantee the availability of a specified number of identical Pods.

A ReplicaSet is defined with fields, including a selector that specifies how to identify Pods it can acquire, a number of replicas indicating how many Pods it should be maintaining, and a pod template specifying the data of new Pods it should create to meet the number of replicas criteria. A ReplicaSet then fulfills its purpose by creating and deleting Pods as needed to reach the desired number. When a ReplicaSet needs to create new Pods, it uses its Pod template.
[https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/]

Remember, I added to the yaml directory a file deployment-booksservice-tst-v1.0.yaml. Below you can see the fields, mentioned above:

 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: booksservice-v1.0
  namespace: nl-amis-testing
  labels:
    app: booksservice
    version: "1.0"
    environment: testing
spec:
  replicas: 2
  selector:
    matchLabels:
      app: booksservice
      version: "1.0"
      environment: testing
  template:
    metadata:
      labels:
        app: booksservice
        version: "1.0"
        environment: testing
    spec:
      containers:
      - name: booksservice-v1-0-container
        image: booksservice:v1.0
        env:
        - name: spring.profiles.active
          value: "testing"
        - name: spring.datasource.url
          value: "jdbc:mysql://mysql-service.nl-amis-testing/test?allowPublicKeyRetrieval=true&useSSL=false"
        ports:
        - containerPort: 9091

Remark:
This deployment uses the booksservice (RESTful Web Service Spring Boot application) with H2 as an embedded in-memory database.
The replicas are set to 2, so the ReplicaSet ensures that 2 pod replicas are running at any given time.
The ReplicaSet manages all the pods with labels that match the selector. In my case these labels are:

Label keyLabel value
appbooksservice
version1.0
environmenttesting

As a test case I then deleted a pod, in order to check what happens next.

I deleted the first Pod (with name booksservice-v1.0-5bcd5fddbd-fxzxs) which includes 1 container (based on image booksservice:v1.0) and has IP address 172.17.0.10:

Below, you can see that a new Pod is being created (with Status = Waiting: ContainerCreating):

After some time, the new Pod (with name booksservice-v1.0-5bcd5fddbd-mp95s) is running and the total pods is 2 again.

Click on the service.

So again, let’s test how the application responds by firing some requests.

We should be able to see our list of books, at the /books path with a GET request. First I tried the service IP address:

 
curl http://10.99.232.66:9191/books

[{"id":"1","title":"The Threat: How the FBI Protects America in the Age of Terror and Trump","author":"Andrew G. McCabe","type":"Hardcover","price":17.99,"numOfPages":288,"language":"English","isbn13":"978-1250207579"},{"id":"2","title":"Becoming","author":"Michelle Obama","type":"Hardcover","price":17.88,"numOfPages":448,"language":"English","isbn13":"978-1524763138"}]

I also tried, the old Pod IP address:

 
curl http://172.17.0.10:9091/books

curl: (7) Failed to connect to 172.17.0.10 port 9091: No route to host

As expected, an error is shown, because this Pod no longer exists.

And then I tried, the second Pod IP address:

 
curl http://172.17.0.11:9091/books

[{"id":"1","title":"The Threat: How the FBI Protects America in the Age of Terror and Trump","author":"Andrew G. McCabe","type":"Hardcover","price":17.99,"numOfPages":288,"language":"English","isbn13":"978-1250207579"},{"id":"2","title":"Becoming","author":"Michelle Obama","type":"Hardcover","price":17.88,"numOfPages":448,"language":"English","isbn13":"978-1524763138"}]

And then I tried, the new Pod IP address:

 
curl http://172.17.0.12:9091/books

[{"id":"1","title":"The Threat: How the FBI Protects America in the Age of Terror and Trump","author":"Andrew G. McCabe","type":"Hardcover","price":17.99,"numOfPages":288,"language":"English","isbn13":"978-1250207579"},{"id":"2","title":"Becoming","author":"Michelle Obama","type":"Hardcover","price":17.88,"numOfPages":448,"language":"English","isbn13":"978-1524763138"}]

So, the ReplicaSet did fulfill its purpose by creating a new Pod. The service, via the selector (and using label matching) can identify the new Pod.

With this final check I conclude this article.

About Author

Marc, active in IT (and with Oracle) since 1995, is a Principal Oracle SOA Consultant with focus on Oracle Cloud, Oracle Service Bus, Oracle SOA Suite, Oracle Database (SQL & PL/SQL) and Java, Docker, Kubernetes, Minikube and Helm. He's Oracle SOA Suite 12c Certified Implementation Specialist. Over the past 20 years he has worked for several customers in the Netherlands. Marc likes to share his knowledge through publications, blog’s and presentations.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.