Spring Data REST Service on Kubernetes

We build a spring data backed REST service, imaged with cloud native build pack and deployed to k8s.

Spring Data REST Service on Kubernetes

Simple data backed Hateous REST service deployed to K8s.

Create the code:

https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.3.1.RELEASE&packaging=jar&jvmVersion=1.8&groupId=uk.co.actualcode&artifactId=dataservice&name=dataservice&description=vadal%20data%20service&packageName=uk.co.actualcode.vadal.dataservice&dependencies=lombok,devtools,data-rest,h2,data-jpa

@SpringBootApplication
public class VadalDataRestApplication {

    public static void main(String[] args) {
        SpringApplication.run(VadalDataRestApplication.class, args);
    }

    @Autowired
    UserRepo userRepo;

    @Autowired
    RoleRepo roleRepo;

    @PostConstruct
    public void init() {
        Role boss = new Role("boss");
        Role director = new Role("director");
        roleRepo.saveAll(Arrays.asList(boss, director));
        userRepo.saveAll(Arrays.asList(new User("fred", boss), new User("wilma", director)));
    }

}

Turn on SQL logging.

application.yml

server.port: 7777

spring:
  jpa:
    show-sql: true

Create the image (https://buildpacks.io):

This will create a cloud native build pack.

mvn spring-boot:build-image

docker images

vadal-data-rest     0.0.1-SNAPSHOT     363629e2dd33        40 years ago        256MB

Let's run this on K8s (I'm using desktop for docker K8s locally so this k8s can see the docker images, for minikube you need to point to it's VM's docker).

NOTE: If you have CI setup then this could do a similar thing, after you push the code. See my previous blog on connecting GitLab to K8s. Here, building the java image would just require the above mvn command instead of the docker file.

K8s local on Docker Desktop (macos)

Point kubectl to docker-for-desktop (rather than minikube)

kubectl config use-context docker-for-desktop
kubectl get cs
NAME                 STATUS    MESSAGE              ERROR
controller-manager   Healthy   ok                   
scheduler            Healthy   ok                   
etcd-0               Healthy   {"health": "true"}   
helm init (first time only to install tiller)

Wait for it to install. Install the dashboard.

helm install --wait --name k8s-dash --set service.type=NodePort,service.nodePort=31111 stable/kubernetes-dashboard

chrome://flags/#allow-insecure-localhost

https://localhost:31111/#!/login

You'll need a token to  access the UI

token=`kubectl -n kube-system describe secret default | grep 'token:' | awk '{print $2}'`

kubectl config set-credentials docker-for-desktop --token="${token}"

File will be in ~/.kube/config

or echo $token and use that.

Deploy the Spring REST service image

kubectl create deployment vadal-data-rest --image=vadal-data-rest:0.0.1-SNAPSHOT
deployment.apps/vadal-data-rest created

Expose the Internal Port 7777

kubectl expose deployment vadal-data-rest --type NodePort --port 8888 --target-port 7777

kubectl get svc vadal-data-rest

NAME              TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE

vadal-data-rest   NodePort   10.108.4.93   <none>        8888:31894/TCP   1m

localhost:31894

{
	"_links": {
		"roles": {
			"href": "http://localhost:31894/roles"
		},
		"user": {
			"href": "http://localhost:31894/u"
		},
		"profile": {
			"href": "http://localhost:31894/profile"
		}
	}
}


Load balancing with Nginx

helm install stable/nginx-ingress
kubectl get svc
NAME                                          TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
k8s-dash-kubernetes-dashboard                 NodePort       10.109.122.227   <none>        443:31111/TCP                59m
kubernetes                                    ClusterIP      10.96.0.1        <none>        443/TCP                      1d
ponderous-olm-nginx-ingress-controller        LoadBalancer   10.104.194.23    localhost     80:32192/TCP,443:31817/TCP   1m
ponderous-olm-nginx-ingress-default-backend   ClusterIP      10.96.101.109    <none>        80/TCP                       1m
vadal-data-rest                               NodePort       10.108.4.93      <none>        8888:31894/TCP               39m

Apply (name this lb.yml)

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: vadal-ingress
spec:
  rules:
  - host: vadal-data.info
    http:
      paths:
      - backend:
          serviceName: vadal-data-rest
          servicePort: 8888
kubectl apply -f lb.yml
ingress.extensions/vadal-ingress created

Add vadal-data.info to /etc/hosts

127.0.0.1 vadal-data.info

Browse to: http://vadal-data.info

Should respond with the rest data details.

Logs

K8s Nginx logs:

192.168.65.3 - - [17/Jun/2020:22:09:36 +0000] "GET /favicon.ico HTTP/1.1" 404 146 "http://vadal-data.info/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.98 Safari/537.36" 378 0.013 [default-vadal-data-rest-8888] [] 10.1.0.8:7777 124 0.010 404 7c9566da4f3c0f31e9eacbfd890ac431
1

Scale

Scale to two

kubectl scale deployment/vadal-data-rest --replicas=2
deployment.extensions/vadal-data-rest scaled
kubectl get po
NAME                                                           READY   STATUS    RESTARTS   AGE
k8s-dash-kubernetes-dashboard-84cb4cc6f-6t8kc                  1/1     Running   0          1h
ponderous-olm-nginx-ingress-controller-6b4d695d86-7gxmq        1/1     Running   0          15m
ponderous-olm-nginx-ingress-default-backend-6fb8ff9645-59mvr   1/1     Running   0          15m
vadal-data-rest-5744484f95-87788                               1/1     Running   0          1m
vadal-data-rest-5744484f95-v9d4h

Now that is neat. A new pod.

kubectl logs -f vadal-data-rest-5744484f95-87788
kubectl logs -f vadal-data-rest-5744484f95-v9d4h

Load balancing

Hit the endpoint repeatedly:

http://vadal-data.info

Log should round robin between the two:

Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.role_id as role_id3_1_ from user user0_

Update the pod

kubectl patch deploy vadal-data-rest -p '{"spec":{"template":{"spec":{"terminationGracePeriodSeconds":31}}}}'

Conclusion

Docker imaged based deployment and scale in kubernetes makes it very easy to scale up (or down), easy to add any load balancer without polluting the code, and gives you choices. The docker image can be deployed anywhere (12Factor compliant).

K8s has inbuilt service discovery and levels of access. Fairly easy to add any load balancer, we added Nginx via helm without needing any yml file. The one yml we needed was to connect it to load balance the service.

Like it?

What about metrics and monitoring? Next time we add prometheus and grafana to monitor our services and display metrics.

Source code can be found here

https://gitlab.com/lightphos/spring/vadal

Related Article