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:
Environment | Database | Booksservice version |
DEV | H2 in-memory | 1.0 |
2.0 | ||
TST | MySQL | 1.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 .
Docker image ls
With the following command, a list of all the docker images on your system is returned: docker image ls
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 key | Label value |
app | mysql |
version | 1.0 |
environment | testing |
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
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
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:
Environment | Port | Booksservice version |
DEV | 9090 | 1.0 |
2.0 | ||
TST | 9091 | 1.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 key | Label value |
app | booksservice |
version | 1.0 |
environment | development |
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 key | Label value |
app | booksservice |
version | 2.0 |
environment | development |
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 key | Label value |
app | booksservice |
version | 1.0 |
environment | testing |
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
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
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
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:
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 key | Label value |
app | booksservice |
version | 1.0 |
environment | testing |
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.
Thanks for this great tutorial…. I am wondering if you left out a step where you need to push your booksservice Docker images to a registry (e.g. docker hub) in order for the deployment yaml files to work. Thanks!
Hello Gerardo,
I didn’t push the booksservice Docker images to a registry because that is not necessary in my case.
In my article I described a step in which I used a Docker file for books_service_1.0, for example. In the file applications.sh there is a part that builds the image (docker build -t booksservice:v1.0) based on the Docker file (in the books_Service_1.0 directory).
In the file deployment-booksservice-dev-v1.0.yaml this image (image: booksservice:v1.0) is used for creating the booksservice-v1-0-container when the deployment is done (with the command: kubectl create -f deployment-booksservice-dev-v1.0.yaml).