Spring & Ribbon In Kubernetes
Tags: java, springboot, kubernetes, gke
Published: 2023-04-28 12:22:33

Hi, at this moment I was interest to write something about deploying Springboot based microservices to Kubernetes environment.

So the idea is that I want to have sets of microservices spring boot based, and deployed to kubernetes as the picture bellow:

![The Scenario which We are going to deployed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ghf5cxads9bitpl6phsl.jpeg)

Basically we are going to have 2 microservices
1. [Github : User_Services](https://github.com/noveriojoee/microservice_user)
2. [Github : Account_Services](https://github.com/noveriojoee/microservices_account)

The **client** can call these methods
-/users/v1/save --> Create User & Account
-/users/v1/all --> Get Users data
-/account/v1/all --> Get Accounts data
-/account/v1/save --> Create Account data

While user created, user_services will also call account_service to create the account 
![Scneario](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/erdjw5648last05bumrt.jpeg)

for doing this. the problems will be lay on
> how user_services can call to account_services while account > service can be deployed on multiple instance and sets.
> how can we make N Replicas of account_services and
> User_Services can understand which account_services to 
>consumed
> as the image bellow:


![Scenario Need Client Load Balancing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n06tnni8ao5m2spd59l4.jpeg)

that is where the [The discovery client](https://cloud.spring.io/spring-cloud-static/spring-cloud-kubernetes/2.1.0.RC1/multi/multi__discoveryclient_for_kubernetes.html) and [The Ribbon Client Load Balancing](https://cloud.spring.io/spring-cloud-static/spring-cloud-kubernetes/2.1.0.RC1/multi/multi__ribbon_discovery_in_kubernetes.html) come into play.

## #1 The YAML
Here is the Dependencies you should to take a look.
These are the dependencies for spring cloud kubernetes.
all the microservices must be able to be observe by other services by making the microservices discoverable by client.
to do so you'll need bellow dependencies
```
<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
			<version>4.0.1</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-kubernetes-all</artifactId>
		</dependency>
```

also you need this dependencies, for later on used by the ingress for liveProb and healthProb mechanism.

```
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
```

## #2 The Discovery Client & Feign Clients
now, we need to make our microservices discoverable by other microservices. with spring cloud after we put the dependencies above, we can do this in our `application.java`

```
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class GIDUsersApplicationServices {

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

	@Bean
	public TimedAspect timedAspect(MeterRegistry registry) {
		return new TimedAspect(registry);
	}

}

```

## #3 The Business Logic
remember in our case we will create account during user creation on user_services. here are the code logic

```
@Override
	public Resources<UserDTO> saveUser(User requestData) {
		Resources<UserDTO> serviceResult = new Resources<UserDTO>();
		serviceResult.throwResponseWithCode("99", "failed to save user", null);

		UserModel userData = this.userDAO.addUser(requestData.getUserName());

		if (userData != null) {
			AccountCreation requestAccountCreation = new AccountCreation();
			requestAccountCreation.setUserId(userData.getUserId());
			requestAccountCreation.setAmount("10000");

			// ## Call request data to account services
			System.out.println("request data" + requestAccountCreation.toString());
			Resources<AccountDTO> createAccountResult = this.serviceProxy.saveUser(requestAccountCreation);
			System.out.println("response data" + createAccountResult.toString());
			// ## Call request data to account services

			if (!createAccountResult.getResponseCode().equals(ApplicationConstant.GENERAL_SUCCESS_CODE)) {
				return serviceResult.throwResponseWithCode("99", "Failed to save user", null);
			}
			UserDTO user = new UserDTO();
			user.setUser(userData);

			return serviceResult.throwSucceedResponse(user);
		} else {
			UserDTO responseOBject = new UserDTO();
			responseOBject.setErrorModel(new ErrorModel("ini error"));
			return serviceResult.throwResponseWithCode("99", "Failed to save user", responseOBject);
		}
	}
```
now lets zoom carefully into this line
`this.serviceProxy.saveAccount`

## #4 The FeignClient for saveAccount
now lets take a look into the saveAccount Method bellow:
```
// @FeignClient(value = "account-services", url = "http://34.128.121.224:81/account")
@FeignClient(name = "gid-account-service")
public interface IAccountServicesProxy {

    @RequestMapping(method = RequestMethod.POST, value = "/account/v1/save")
    public Resources<AccountDTO> saveAccount(AccountCreation requestData);

}
```

as you can see, instead of using 
`@FeignClient(value = "account-services", url = "http://34.128.121.224:81/account")` to point out the account services by its ipAddress

we can use this
`@FeignClient(name = "gid-account-service")` and get the available ipAddress from kubernetes Services.

as mention [here in spring cloud, that](https://cloud.spring.io/spring-cloud-static/spring-cloud-kubernetes/2.1.0.RC1/multi/multi__discoveryclient_for_kubernetes.html) 

>This project provides an implementation of Discovery Client for Kubernetes. This allows you to query Kubernetes endpoints (see services) by name. A service is typically exposed by the Kubernetes API server as a collection of endpoints which represent http, https addresses that a client can access from a Spring Boot application running as a pod

now we fetch all the pods ipAddress from the kubernetes services so we don't need to to put the ip address in our code anymore and let the services get the destination ip from kubernetes services. this discovery concept is similar with usage of [eureka](https://cloud.spring.io/spring-cloud-netflix/reference/html/)

> So basically we are no longer need eureka when we use kubernetes.

Notes : in order to make this possible you have to make sure that the services name in here

`spring.application.name=gid-account-service` same as services 
name you put on your k8s-deployment

>IMPORTANT : Makesure your spring.application.name equal with your Kuberenetes Services Name

```
apiVersion: apps/v1
kind: Deployment # Kubernetes resource kind we are creating
metadata:
  name: gid-account-service # Required to be the same as spring config services name
  namespace: default
```

and you also need to make sure that you add this on your application properties, in order to enable the services become discoverable and the client load balancing on.

```
spring.cloud.kubernetes.discovery.enabled=true
spring.cloud.kubernetes.loadbalancer.mode=service
```

now we all sets. move to the deployment.


## #5 The Deployment to Kubernetes.
before we go to the deployment process. make sure that you have
1. PostgreeDB Instance Ready to use. since the services need database
2. GKE Clusters
3. [Install gcloud components](https://cloud.google.com/sdk/gcloud/reference/components/install)
4. [Connect to your Clusters](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl)

now here is the k8s-deployment for user_services

```
apiVersion: apps/v1
kind: Deployment # Kubernetes resource kind we are creating
metadata:
  name: gid-user-service
  namespace: bcad-testing-env
spec:
  selector:
    matchLabels:
      app: gid-user-service
  replicas: 2 # Number of replicas that will be created for this deployment
  template:
    metadata:
      labels:
        app: gid-user-service
    spec:
      containers:
        - name: gid-user-service
          image: noveriojoee/user_services:v0.0.6-staging.8 # Image that will be used to containers in the cluster
          imagePullPolicy: IfNotPresent
          # Configure the readiness probe
          livenessProbe:
            initialDelaySeconds: 90
            timeoutSeconds: 30
            periodSeconds: 30
            successThreshold: 1
            failureThreshold: 3
            httpGet:
              path: /actuator/health/liveness
              port: 8201
          readinessProbe:
            initialDelaySeconds: 60
            timeoutSeconds: 30
            periodSeconds: 30
            successThreshold: 1
            failureThreshold: 3
            httpGet:
              path: /actuator/health/readiness
              port: 8201
          ports:
            - containerPort: 8201 # The port that the container is running on in the cluster

            
---
#THIS IS SERVICES AS CONSUME BY INGRESS
apiVersion: v1
kind: Service
metadata:
  name: gid-user-service
  namespace: bcad-testing-env
  labels:
    app: gid-user-service
spec:
  selector:
    app: gid-user-service
  ports:
    - port: 8207
      protocol: TCP
      targetPort: 8201
  type: NodePort
```

and here is the k8s-deployment for account_services
```
apiVersion: apps/v1
kind: Deployment # Kubernetes resource kind we are creating
metadata:
  name: gid-account-service # Required to be the same as spring config services name
  namespace: default
spec:
  selector:
    matchLabels:
      app: gid-account-service
  replicas: 3 # Number of replicas that will be created for this deployment
  template:
    metadata:
      labels:
        app: gid-account-service
    spec:
      containers:
        - name: gid-account-services-pods
          image: noveriojoee/account_services:v0.0.3-staging.5 # Image that will be used to containers in the cluster
          imagePullPolicy: IfNotPresent
          # Configure the readiness probe
          livenessProbe:
            initialDelaySeconds: 90
            timeoutSeconds: 30
            periodSeconds: 30
            successThreshold: 1
            failureThreshold: 3
            httpGet:
              path: /actuator/health/liveness
              port: 8202
          readinessProbe:
            initialDelaySeconds: 60
            timeoutSeconds: 30
            periodSeconds: 30
            successThreshold: 1
            failureThreshold: 3
            httpGet:
              path: /actuator/health/readiness
              port: 8202
          ports:
            - containerPort: 8202 # The port that the container is running on in the cluster

            
---
# #THIS IS SERVICES AS CONSUME BY INGRESS
apiVersion: v1
kind: Service
metadata:
  name: gid-account-service
  namespace: default
  labels:
    app: gid-account-service
spec:
  selector:
    app: gid-account-service
  ports:
    - port: 8206
      protocol: TCP
      targetPort: 8202
  type: NodePort
```

>by this deployment script we will have 2 sets of user_services and 3 sets of account_services

now lets exposed this to public via Ingress as our api gateway
and use [GCE](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress) as the ingress controller.
[read here for GCE vs NGINX Controller](https://medium.com/omnius/kubernetes-ingress-gce-vs-nginx-controllers-1-3-d89d6dd3da73) 

```
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gid-user-services-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "gce" #REF -> https://medium.com/@glen.yu/nginx-ingress-or-gke-ingress-d87dd9db504c
    kubernetes.io/ingress.allow-http: "true"
    servers.com/ingress.load-balancer-healthcheck-path: "/actuator/health"
    #kubernetes.io/ingress.global-static-ip-name: gid-public-ip #REF -> Create PUBLIC STATIC IP AND IMPLEMENT TO YOUR INGRESS USING GCLOUDCONTROLLER
spec:
  # defaultBackend:
  #   service:
  #     name: gid-user-service
  #     port:
  #       number: 8207
  rules:
  - http:
      paths:
      - path: /* #<-- can be anything. considered as default
        pathType: ImplementationSpecific
        backend:
          service:
            name: gid-user-service
            port:
              number: 8207
      - path: /account/* # <-- Follow your controller request path
        pathType: ImplementationSpecific
        backend:
          service:
            name: gid-account-service
            port:
              number: 8206

```

by above deployment scripts now we have this.
![Kubernetes Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iberll5gqlweam8nhkts.jpeg)

## #6 Permission
in order to ribbon be able to fetch api list ip from the namespaces you can use [RBAC](https://www.google.com/search?client=safari&rls=en&q=RBAC+kubernetes&ie=UTF-8&oe=UTF-8) or you can just give the admin permission with this command

`kubectl create clusterrolebinding default-pod --clusterrole cluster-admin --serviceaccount=<namespace>:default`

## #7. The testing
after we deploy the services and ingress now lets try to hit the api through ingress.

first

lets try `Kubectl get ing -A` it will show all of your ingress
![Get Ingress](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/owa3sl5jnnvebvzuczrr.png)

now you can start calling the api with public ip `34.107.131.195`

and if you take a look deeper on your ingress description using `kubectl describe ing gid-user-services-ingress`


![Ingress Description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5i756pxqws3ewbxbiqm.png)


you'll see that default request will be routed to `user_services` and all request on `[ip_address]/account` will be route to `account_services`.

Happy Trying :D 

References : 
- [Springboot In Kubernetes From Springcloud](https://cloud.spring.io/spring-cloud-static/spring-cloud-kubernetes/2.1.0.RC1/multi/multi_spring-cloud-kubernetes.html)
- [Allowing Client Side Load Balancing to fetch Pods IP](https://stackoverflow.com/questions/66654569/io-fabric8-kubernetes-client-kubernetesclientexception-forbiddenconfigured-ser)
- [Pyothry blogs](https://piotrminkowski.com/2020/09/10/spring-cloud-kubernetes-load-balancer-guide/)