Java Microservice on Google Kubernetes Engine (GKE) Cluster
Tags: java, microservices, googlecloud, kubernetes
Published: 2022-01-18 13:10:51

## Table of Contents
* [Introduction](#introduction)
* [Prerequisites](#prerequisites)
    * [Install Google Cloud SDK](#install-google-cloud-sdk)
    * [Install Cloud Code Plugin for IntelliJ](#install-cloud-code-plugin-for-intellij)
* [Configure Google Cloud](#configure-google-cloud)
    * [Create a Google Cloud Project](#create-a-google-cloud-project)
    * [Create an Artifact Registry](#create-an-artifact-registry)
    * [Create a Google Kubernetes Engine (GKE) Cluster](#create-a-google-kubernetes-engine-gke-cluster)
* [Code the Java Microservice](#code-the-java-microservice)
    * [Write the application codes](#write-the-application-codes)
    * [Test the application functions locally](#test-the-application-functions-locally)
* [Containerize the Microservice](#containerize-the-microservice)
* [Deploy the Microservice to GKE](#deploy-the-microservice-to-gke)
    * [Configure a Run/Debug Configuration for Cloud Code: Kubernetes](#configure-a-rundebug-configuration-for-cloud-code-kubernetes)
    * [Deploy to GKE](#deploy-to-gke)
    * [Test the application functions in GKE](#test-the-application-functions-in-gke)
* [Clean Up](#clean-up)
* [Conclusion](#conclusion)
* [References](#references)

## Introduction

Java microservices remain as hot topic in 2022.

In this article, I would like to show you the steps to create a Java Microservice, and deploy it to Google Kubernetes Engine (GKE).

## Prerequisites

### Install Google Cloud SDK

Follow the instructions in [Install Cloud SDK](https://cloud.google.com/sdk/docs/install).

### Install Cloud Code Plugin for IntelliJ

Install Cloud Code Plugin for IntelliJ from Preference -> Plugins.  Search by "Cloud Code" in the "Marketplace" tab.

![Install Cloud Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4kws4ojk5g60r72cl5rj.png)


## Configure Google Cloud

### Create a Google Cloud Project

1. In Google Cloud Shell, perform the following steps:

    i. Lists credentialed accounts and make sure the desired account is set active.

    ```shell
    gcloud auth list
    ```

    ii. Create a project.

    ```shell
    gcloud projects create PROJECT_ID
    ```
   
    _Replace PROJECT_ID with your desired project ID._


    iii. Set the default project.

    ```shell
    gcloud config set project PROJECT_ID
    ```
   
    _Replace PROJECT_ID with your project ID._

    iv. Print the project ID.

    ```shell
    echo $GOOGLE_CLOUD_PROJECT
    ```

    v. Set the default [zone](https://cloud.google.com/compute/docs/regions-zones):

    ```shell
    gcloud config set compute/zone COMPUTE_ZONE
    ```

    _Replace COMPUTE_ZONE with your compute zone, such as europe-west2-c._

    vi. Set the default [region](https://cloud.google.com/compute/docs/regions-zones):

    ```shell
    gcloud config set compute/region COMPUTE_REGION
    ```

    _Replace COMPUTE_REGION with your compute region, such as europe-west2._

    vii. Show the configuration.

    ```shell
    gcloud config list
    ```

2. Enable Billing in Google Cloud Console.

3. Enable APIs in Google Cloud Shell.

    i. Enable Artifact Registry API.
   
    ```shell
    gcloud services enable artifactregistry.googleapis.com
    ```

    ii. Enable Kubernetes Engine API.
   
    ```shell
    gcloud services enable container.googleapis.com
    ```

    iii. Enable Firestore.

    ```shell
    gcloud services enable firestore.googleapis.com
    ```

### Create an Artifact Registry

Google Cloud Artifact Registry is a successor of Container Registry, in where container images and language packages can be placed.

We will use Artifact Registry to store our container images.

1. Enable Artifact Registry, if it is not yet enabled.

    ```shell
    gcloud services enable artifactregistry.googleapis.com
    ```

2. View a list of supported locations.

    ```shell
    gcloud artifacts locations list
    ```

3. Create an artifact repository.

    ```shell
    gcloud artifacts repositories create REPO_NAME \
    --repository-format=docker \
    --location=REPO_REGION \
    --description=REPO_DESC \
    --version-policy=REPO_POLICY
    ```

    _Replace REPO_NAME with the desired name for your artifact repository._

    _Replace REPO_REGION with your region, such as europe-west2._

    _Replace REPO_DESC with the description of your artifact repository._
   
    _Replace REPO_POLICY with keyword **snapshot** or **release**._

    Here below is a working example:

    ```shell
    gcloud artifacts repositories create ms-docker-image \
    --repository-format=docker \
    --location=europe-west2 \
    --description="Snapshot repository for docker images" \
    --version-policy=snapshot
    ```

    The full name of the created repository will be

    ```shell
    $(REPO_REGION)-$(REPO_FORMAT).pkg.dev/$(PROJECT_ID)/$(REPO_NAME)
    ```

4. List all artifact repositories created in the project.

    ```shell
    gcloud artifacts repositories list
    ```
   
### Create a Google Kubernetes Engine (GKE) Cluster

1. Enable Kubernetes Engine API, if it is not yet enabled.

    ```shell
    gcloud services enable container.googleapis.com
    ```

2. Create a GKE Cluster.

    ```shell
    gcloud container clusters create CLUSTER_NAME \
    --num-nodes=1 \
    --region europe-west2 \
    --release-channel regular \
    --scopes=datastore,storage-rw,compute-ro
    ```
    _Replace CLUSTER_NAME with the desired name for your GKE cluster._

    It will take several minutes to create the cluster.

3. After the cluster is created, get authentication credentials to interact with the cluster.

    ```shell
    gcloud container clusters get-credentials CLUSTER_NAME
    ```
    _Replace CLUSTER_NAME with your GKE cluster name._

4. List all clusters.

    ```shell
    gcloud container clusters list
    ```

5. We can also check whether the cluster is created or not in Google Cloud Console -> Kubernetes Clusters.

    ![GKE Cluster](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nm9z0fxzv12epc0l280s.png)


## Code the Java Microservice
 
Spring Boot enables quick development of Java microservices with various dependency and plugin support to Google Cloud technologies. 

[Complete Source Code](https://github.com/adafycheng/ms-client-account-gcp) can be found in my GitHub repositories.

### Write the application codes

1. Generate a Spring Boot application.

    To create a Spring Boot application, use the [Spring Initializr](https://start.spring.io) to generate a simple Spring Boot . Add the following dependencies:
    - Spring Web
    - Spring Boot Actuator
    - Lombok

    Spring Boot Actuator provides end points for health check of the application.

    Lombok is a Java annotation library that reduces boilerplate code by using annotations.

    Click the "Generate" button to download the ZIP file.

    Unzip the file to the project root directory.

    ![Spring Initializr](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36fekzc4ev7uuz0j3wl8.png)

2. Modify pom.xml to add dependencies for GCP.

    i. Versions
    ```xml
    <properties>
        <spring-cloud-gcp.version>1.2.8.RELEASE</spring-cloud-gcp.version>
        <spring-cloud.version>2021.0.0</spring-cloud.version>
    </properties>
    ```

    ii. Dependencies
    ```xml
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-gcp-data-datastore</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
        </dependency>
    </dependencies>
    ```

    iii. Dependency Management
    ```xml
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-gcp-dependencies</artifactId>
                <version>${spring-cloud-gcp.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    ```

3. Create a Java model class.

    The Lombok @Data annotation saves the time of writing the getter and setter functions.

    The Lombok @AllArgsConstructor annotation saves the time of writing the constructor.

    The @Entity annotation specifies that it is a Firestore entity in datastore mode.

    ```java
    import org.springframework.cloud.gcp.data.datastore.core.mapping.Entity;
    import org.springframework.data.annotation.Id;

    import lombok.AllArgsConstructor;
    import lombok.Data;

    @Entity
    @Data
    @AllArgsConstructor
    public class CustomerAccount {

        @Id
        private Long accountId;

        private String lastName;
    
        private String firstName;

    }
    ```

4. Create a Spring Repository interface for Google Datastore.

    ```java
    import org.springframework.cloud.gcp.data.datastore.repository.DatastoreRepository;
    import org.springframework.stereotype.Repository;

    @Repository
    public interface CustomerAccountRepository extends DatastoreRepository<CustomerAccount, Long> {

        List<CustomerAccount> findByLastName(String name);

    }
    ```

5. Create a Java Controller class for REST endpoints.

    The Lombok @RequiredArgsConstructor annotation saves the time of writing the constructor.

    The Lombok @Slf4j annotation facilitates SLF4J for logging with the least configurations.

    ```java
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;

    @RestController
    @Slf4j
    @RequiredArgsConstructor
    public class CustomerAccountController {

        private final CustomerAccountRepository customerAccountRepository;

        @GetMapping("/api/customerAccounts")
        public Iterable<CustomerAccount> getAllCustomerAccounts() {
            log.debug("->getAllCustomerAccounts");
            return customerAccountRepository.findAll();
        }

        @GetMapping("/api/customerAccount/{lastName}")
        public List<CustomerAccount> findByLastName(@PathVariable("lastName") String name) {
            log.debug("->findByLastName");
            return customerAccountRepository.findByLastName(name);
        }

        @PostMapping("/api/customerAccounts")
        public CustomerAccount saveCustomerAccount(@RequestBody CustomerAccount customerAccount) {
            log.debug("->saveCustomerAccount {}", customerAccount);
            return customerAccountRepository.save(customerAccount);
        }
    }
    ```

6. Enable Spring Boot Actuator in application.properties.

    ```properties
    management.endpoint.health.enabled=true
    ```

### Test the application functions locally

In IntelliJ Terminal,

1. Lists credentialed accounts and make sure the desired account is set active.

    ```shell
    gcloud auth list
    ```

2. Set the default project.

    ```shell
    gcloud config set project PROJECT_ID
    ```

    _Replace PROJECT_ID with your project ID._
    
3. Show the configuration.

    ```shell
    gcloud config list
    ```

4. Start the Spring Boot application.

    ```shell
    ./mvnw spring-boot:run
    ```

Use a REST client, like Postman, to perform following tests:

5. Perform health check.

    ![Health Check Local Test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zqhxa2ldoq76xfq60gq3.png)

6. Test application function: Create new Customer Account.

    ![Local Test: Create new Customer Account](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/krogqnn8twqmacaaxvwe.png)

7. Test application function: Find by Last Name.

    ![Local Test: Find by Last Name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ex6lxubnfh6p38z8853.png)

8. Test application function: List all Customer Accounts.

    ![Local Test: List all Customer Accounts](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r570v8tgnwyr90feby22.png)


## Containerize the Microservice

In general, we need to write a Dockerfile to dockerize the microservice.

However, in this exercise, we will use Google Cloud's Jib Maven Plugin to build the container images without writing a Dockerfile.

1. Add Jib Maven plugin in pom.xml.

    ```xml
    <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>3.1.4</version>
        <configuration>
            <to>
                <image>europe-west2-docker.pkg.dev/java-microservice-2022/ms-containers/${project.artifactId}</image>
            </to>
        </configuration>
    </plugin>
    ```

    _Replace with your Full Container Name inside the &lt;image&gt; tag._
   
2. At the project root, execute the following command:

    ```shell
    ./mvnw install jib:build
    ```
   
    This will build the docker image and publish it to the Artifact Registry.

    Use the following command to list docker images in the Artifact Registry.

    ```shell
    gcloud artifacts docker images list FULL_CONTAINER_NAME
    ```

    _Replace FULL_CONTAINER_NAME with your Full Container Name._

    Or you can check the image in Google Cloud Console.

    ![Images in Artifact Registry](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t4lgli6qypbfvlj6rnqg.png)

## Deploy the Microservice to GKE

### Configure a Run/Debug Configuration for Cloud Code: Kubernetes

In this process, we need to write manifest files in YAML format.  One is deployment.yaml and the other is service.yaml.

1. Write deployment.yaml and put it in the project root directory.

    ```yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: account-service-deployment
    spec:
      selector:
        matchLabels:
          app: account-service-pod
      replicas: 1
      template:
        metadata:
          labels:
            app: account-service-pod
        spec:
          containers:
            - name: account-service-container
              image: europe-west2-docker.pkg.dev/java-microservice-2022/ms-containers/ms.client.account.gcp
              ports:
                - containerPort: 8080
              livenessProbe:
                initialDelaySeconds: 20
                httpGet:
                  port: 8080
                  path: /actuator/health
              readinessProbe:
                initialDelaySeconds: 30
                httpGet:
                  port: 8080
                  path: /actuator/health
    ```

    Replace *account-service-deployment* with the name of your deployment.

    Replace *account-service-pod* with the name of your pod.

    Replace *account-service-container* with the name of your container.

2. Write server.yaml and put it in the project root directory.

    ```yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: account-service
    spec:
      type: NodePort
      selector:
        app: account-service-pod
      ports:
        - port: 8080
          targetPort: 8080
    ```

    Replace *account-service* with the name of your service.

    Replace *account-service-pod* with the name of your pod.

3. Add a new Run/Debug Configuration for *Cloud Code: Kubernetes*.

    ![New configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cloxht6e7hpownpkcw4m.png)

4. Change the name of the Configuration.

    ![New configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0lbnuc4e2vzdjqwiu76y.png)

5. Click the "Initialize" button in the "Build/Deploy" tab.

    ![Configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofsh4xawhbw1fx2hbfsz.png)

6. Click the "pen" icon under *Build Settings*.

    ![Configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e2zgmx8pepk9bkm6kfb1.png)

7. Select *Jib Maven Pluin* from the *Builder* dropdown list and click the "OK" button.

    ![Configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4rr2t6p78k28ddf2fklq.png)

8. Click the "Initialize" button.

    ![Configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8hwkqp2iep5wjv783wze.png)

    ![Configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ef20t25cf306w00975ia.png)


9. A *skaffold.yaml* file will be generated at the Project Root Directory.

    ![Configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cphzrcr9s6nxqrx9b96z.png)

10. Click the "Run" tab.

    ![Run Configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n2oeq28n1rrordi2qrl0.png)

    An alert message "Current Kubernetes context unknown. Please ensure you have Kubernetes clusters configured on your machine (such as minikube or GKE)." in red in shown.

    To fix this problem, get authentication credentials to interact with the cluster by typing the following command in *Terminal* of InteglliJ.

    ```shell
    gcloud container clusters get-credentials CLUSTER_NAME
    ```
    
    _Replace CLUSTER_NAME with your GKE cluster name._

    The problem should then be solved.

    ![Run Configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ysnxvbcq3vf2n9b79acu.png)

    Please note that the granted credentials are valid in this session only.  If your session to Google Cloud is lost, you have to get the credentials again.

    At last, make sure the option "*Delete deployments when finished*" is unchecked.

    ![Run Configuration for Cloud Code: Kubernetes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iixvijhntdqz11g20wg7.png)

    Click the "OK" button to finish the configuration.

11. In case of any changes of the deployment.yaml and service.yaml files, delete skaffold.yaml file generated and repeat steps 5-9 to generate the skaffold.yaml file again.

### Deploy to GKE

1. Click the "Run" button of the *Cloud Code: Kubernetes* configuration just created.

    ![Cloud Code: Kubernetes Toolbar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jzz3g0fetx3x3bmxh3u8.png)

2. The deployment process should be started.

    ![Cloud Code: Kubernetes Toolbar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8n4novl5j0123t1lz9d5.png)

3. Once the deployment is finished, test locally using the health check endpoint provided by Spring Actuator.

    `http://localhost:8080/actuator/health`

    ![Health Check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gp83sa6w6jbnoys3vgfh.png)

4. In IntelligJ Terminal, type the following command to list the services.

    ```shell
    kubectl get service
    ```

    ![Health Check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lh17pg7b3v87684rwkfa.png)

    The following command lists the service details in yaml format.

    ```shell
    kubectl get service account-service --output yaml
    ```

5. Expose with a target of 8080 using the following command.

    ```shell
    kubectl expose deployment {deployment-name}  --name={load-balancer-service-name} --type=LoadBalancer --port 80 --target-port 8080
    ```

    Here is the actual command I used in this exmaple.

    ```shell
    kubectl expose deployment account-service-deployment --name=lb-account-service-gcp --type=LoadBalancer --port 80 --target-port 8080
    ```

    Use the following command to check the progress:

    ```shell
    kubectl get service
    ```
   
    It may take several minutes to bind the external IP address.

    ![Health Check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0rmhris9arj8x9lw6pd0.png)

    Keep trying until an external IP address is bound.

    ![Health Check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ygmmmgfbe21l28hjy930.png)

    With the external IP, we can now perform tests.

### Test the application functions in GKE

1. Health Check using external IP:

    ![Health Check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m03fb63gd03mpmeudh3t.png)

2. Create new Customer Account using external IP:

    ![Health Check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ntnu2oa9b4a0tgtf0aq6.png)

3. List all Customer Accounts using external IP:

    ![Health Check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1p8v39d3lts566t4jb7.png)

4. Find Customer Account by Last Name using external IP:

    ![Health Check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/srsjzoekstz4hqrjay7j.png)

## Clean up

Remember to clean up in order to avoid incurring cost.

```shell
kubectl delete service account-service
gcloud container clusters delete ms-cluster --region europe-west2-c
```

## Conclusion

Hoorays!

We had created a Java Microservice, which interface with Google Cloud's NoSQL database.  We then created a GKE cluster and deployed our microservice to it.

## References
1. [Complete Source Code](https://github.com/adafycheng/ms-client-account-gcp)
2. [Introduction to SLF4J](https://www.baeldung.com/slf4j-with-log4j2-logback)