BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Spring Boot Tutorial: Building Microservices Deployed to Google Cloud

Spring Boot Tutorial: Building Microservices Deployed to Google Cloud

Leia em Português

Lire ce contenu en français

Key Takeaways

  • Using Google Kubernetes Engine (GKE) along with Spring Boot allows you to quickly and easily set up microservices.
  • Jib is a great way to containerize your Java application. It allows you to create optimized images without Docker using Maven or Gradle.
  • Google’s Spring Cloud GCP implementation allows developers to leverage Google Cloud Platform (GCP) services with little configuration and using some of Spring’s patterns.
  • Setting up Skaffold with Cloud Code allows developers to have a nice development cycle. This is especially useful with starting to prototype a new service.

Introduction

With the increasing popularity of microservices in the industry, there’s been a boom in technologies and platforms from which to choose to build applications. Sometimes it's hard to pick something to get started. In this article, I’ll show you how to create a Spring Boot based application that leverages some of the services offered by Google Cloud. This is the approach we’ve been using in our team at Google for quite some time. I hope you find it useful.

The Basics

Let’s start by defining what we will build. We’ll begin with a very basic Spring Boot-based application written in Java. Spring is a mature framework that allows us to quickly create very powerful and feature-rich applications.

We’ll then make a few changes to containerize the application using Jib (builds optimized Docker and OCI images for your Java applications without a Docker) and a distroless version of Java 11. Jib works both with Maven and Gradle. We’ll use Maven for this example.

Next, we will create a Google Cloud Platform (GCP) project and use Spring Cloud GCP to leverage Cloud Firestore. Spring Cloud GCP allows Spring-based applications to easily consume Google services like databases (Cloud Firestore, Cloud Spanner or even Cloud SQL), Google Cloud Pub/Sub, Stackdriver for logging and tracing, etc.

After that, we’ll make changes in our application to deploy it to Google Kubernetes Engine (GKE). GKE is a managed, production-ready environment for deploying containerized Kubernetes-based applications.

Finally, we will use Skaffold and Cloud Code to make development easier. Skaffold handles the workflow for building, pushing and deploying your application. Cloud Code is a plugin for VS Code and IntelliJ that works with Skaffold and your IDE so that you can do things like deploy to GKE with a click of a button. In this article, I’ll be using IntelliJ with Cloud Code.

Setting up our Tools

Before we write any code, let’s make sure we have a Google Cloud project and all the tools installed.

Creating a Google Cloud Project

Setting up a GCP instance is easy. You can accomplish this by following these instructions. This new project will allow us to deploy our application to GKE, get access to a database (Cloud Firestore) and will also allow us to have a place where we can push our images when we containerize the application.

Install Cloud Code

Next, we’ll install Cloud Code. You can follow these instructions on how to install Cloud Code to IntelliJ. Cloud Code manages the installation of Skaffold and Google SDK that we’ll use later in the article. Cloud Code also allows us to inspect our GKE deployments and services. Most importantly, it also has a clever GKE development mode that continuously listens to changes in your code when it detects a change it builds the app, builds the image, pushes the image to your registry, deploys the application to your GKE cluster, starts streaming logs and opens a localhost tunnel so you can test your service locally. It's like magic!

In order to use Cloud Code and proceed with our application, let’s make sure that you log in using the Cloud Code plugin by clicking on the icon that should show up on the top right of your IntelliJ window:

In addition, we’ll run some commands to make sure that the application is running on your machine and can communicate with the services running on your project in Google Cloud. Let’s make sure we are pointing to the correct project and authenticate you using:

gcloud config set project <YOUR PROJECT ID>
gcloud auth login

Next, we’ll make sure your machine has application credentials to run your application locally:

gcloud auth application-default login

Enabling the APIs

Now that we have everything set up we need to enable the API’s we will be using in our application:

  • Google Container Registry API - This will allow us to have a registry where we can privately push our images.
  • Cloud Firestore in Datastore mode - This will allow us to store entities in a NoSQL database. Make sure to select Datastore mode so that we can use Spring Cloud GCP’s support for it.

You can manage the APIs that are enabled in your project by visiting your project’s API Dashboard.

Creating our Dog Service

First things first! We need to get started with a simple application we can run locally. We’ll create something important like a Dog microservice. Since I’m using IntelliJ Ultimate I’ll go to `File -> New -> Project…` and select "Spring Initializr". I’ll select Maven, Jar, Java 11 and change the name to something important like `dog` as shown below:


 
Click next and add: Lombok, Spring Web and GCP Support:


If all went well, you should now have an application that you can run. If you don’t want to use IntelliJ for this, use the equivalent on your IDE or use Spring’s Initilizr.

Next, we’ll add a POJO for our Dog service and a couple of REST endpoints to test our application. Our Dog object will have a name and an age and we’ll use Lombok’s @Data annotation to save us from writing setters, getters, etc. We’ll also use the @AllArgsConstructor annotation to create a constructor for us. We’ll use this later when we are creating Dogs.

@Data
@AllArgsConstructor
public class Dog {
 private String name;
 private int age;
}

We’ll create a controller class for the Dog and the REST endpoints:

@RestController
@Slf4j
public class DogController {

  @GetMapping("/api/v1/dogs")
  public List<Dog> getAllDogs() {
    log.debug("->getAllDogs");
    return ImmutableList.of(new Dog("Fluffy", 5),
        new Dog("Bob", 6),
        new Dog("Cupcake", 11));
  }

  @PostMapping("/api/v1/dogs")
  public Dog saveDog(@RequestBody Dog dog) {
    log.debug("->saveDog {}", dog);
    return dog;
  }
}

The endpoints return a list of predefined dogs and the saveDog endpoint doesn’t really do much, but this is enough for us to get started.

Using Cloud Firestore

Now that we have a skeleton app, let’s try to use some of the services in GCP. Spring Cloud GCP adds Spring Data support for Google Cloud Firestore in Datastore mode. We’ll use this to store our Dogs instead of using a simple list. Users will now also be able to actually save a Dog in our database.

To start we’ll add the Spring Cloud GCP Data Datastore dependency to our POM:

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

Now, we can modify our Dog class so that it can be stored. We’ll add an @Entity annotation and a @Id annotation to a value of type Long to act as an identifier for the entity:

@Entity
@Data
@AllArgsConstructor
public class Dog {
  @Id private Long id;
  private String name;
  private int age;
}

Now we can create a regular Spring Repository class as follows:

@Repository
public interface DogRepository extends DatastoreRepository<Dog, Long> {}

As usual with Spring Repositories, there is no need to write implementations for this interface since we’ll be using very basic methods.

We can now modify the controller class. We’ll inject the DogRepository into the DogController then modify the class to use the repository as follows:

@RestController
@Slf4j
@RequiredArgsConstructor
public class DogController {

  private final DogRepository dogRepository;

  @GetMapping("/api/v1/dogs")
  public Iterable<Dog> getAllDogs() {
    log.debug("->getAllDogs");
    return dogRepository.findAll();
  }

  @PostMapping("/api/v1/dogs")
  public Dog saveDog(@RequestBody Dog dog) {
    log.debug("->saveDog {}", dog);
    return dogRepository.save(dog);
  }
}

Note that we are using Lombok’s @RequiredArgsConstructor to create a constructor to inject our DogRepository. When you run your application, the endpoints will call your Dog service that will attempt to use Cloud Firestore to retrieve or store the Dogs.

TIP: To quickly test this, you can create an HTTP request in IntelliJ with the following:

POST http://localhost:8080/api/v1/dogs
Content-Type: application/json

{
  "name": "bob",
  "age": 5
}

In only a few steps, we now have the application up and running and consuming services from GCP. Awesome! Now, let’s turn this into a container and deploy it!

Containerizing the Dog Service

At this point, you could start writing a Dockerfile to containerize the application we created above. Let’s instead use Jib. One of the things I like about Jib is that it separates your application into multiple layers, splitting dependencies from classes. This allows us to have faster builds so that you don’t have to wait for Docker to rebuild your entire Java application - just deploy the layers that changed. In addition, Jib has a Maven plugin that makes it  easy to set up by just modifying the POM file in your application.

To start using the plugin, we’ll need to modify our POM file to add the following:

<plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>1.8.0</version>
        <configuration>
          <from>
            <image>gcr.io/distroless/java:11</image>
          </from>
          <to>
            <image>gcr.io/<YOUR_GCP_REGISTRY>/${project.artifactId}</image>
          </to>
        </configuration>
</plugin>

Notice we are using Google’s distroless image for Java 11. "Distroless" images contain only your application and its runtime dependencies. They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution.

Restricting what's in your runtime container to precisely what's necessary for your app is a best practice employed by Google and other tech giants that have used containers in production for many years. It improves the signal-to-noise of scanners (e.g. CVE) and reduces the burden of establishing provenance to just what you need.

Make sure to replace your GCP registry in the code above to match the name of your project.

After doing this, you can attempt to build and push the image of the app by running a command like:

$ ./mvnw install jib:build

This will build and test the application. Create the image and then finally push the newly created image to your registry.

NOTE: It's usually a common good practice to use a distro with a specific digest instead of using "latest". I’ll leave it up to the reader to decide what base image and digest to use depending on the version of Java you are using.

Deploying the Dog Service

At this point, we are almost ready to deploy our application. In order to do this, let’s first create a GKE cluster where we will deploy our application.

Creating a GKE Cluster

To create a GKE cluster, follow these instructions. You’ll basically want to visit the GKE page, wait for the API to get enabled and then click on the button to create a cluster. You may use the default settings, but just make sure that you click on the "More options" button  to enable full access to all the Cloud APIs:

This allows your GKE nodes to have permissions to access the rest of the Google Cloud services. After a few moments the cluster will be created:

Applications living inside of Kubernetes

Kubernetes likes to monitor your application to ensure that it's up and running. In the event of a failure, Kubernetes knows that your application is down and that it needs to spin up a new instance. To do this, we need to make sure that our application is able to respond when Kubernetes pokes it. Let’s add an actuator and Spring Cloud Kubernetes.

Add the following dependencies to your POM file:

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

If your application has an application.properties file inside the src/main/resources directory, remove it and create an application.yaml file with the following contents:

spring:
  application:
    name: dog-service

management:
  endpoint:
    health:
      enabled: true

This adds a name to our application and exposes the health endpoint mentioned above. To verify that this is working, you may visit your application at localhost:8080/actuator/health You should see something like:

{
    "status": "UP"
}

Configuring to run in Kubernetes

In order for us to deploy our application to our new GKE cluster, we need to write some additional YAML. We need to create a deployment and a service. Use the deployment that follows. Just remember to replace the GCR name with the one from your project:

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dog-service
spec:
  selector:
    matchLabels:
      app: dog-service
  replicas: 1
  template:
    metadata:
      labels:
        app: dog-service
    spec:
      containers:
        - name: dog-service
          image: gcr.io/<YOUR GCR REGISTRY NAME>/dog
          ports:
            - containerPort: 8080
          livenessProbe:
            initialDelaySeconds: 20
            httpGet:
              port: 8080
              path: /actuator/health
          readinessProbe:
            initialDelaySeconds: 30
            httpGet:
              port: 8080
              path: /actuator/health

Add a service.yaml file with the following:

apiVersion: v1
kind: Service
metadata:
  name: dog-service
spec:
  type: NodePort
  selector:
    app: dog-service
  ports:
    - port: 8080
      targetPort: 8080

The deployment contains a few changes to the readiness and liveness probe. This is so that Kubernetes uses these endpoints to poke the app to see if it's alive. The service exposes the deployment so that other services can consume it.

After doing this, we can now start using the Cloud Code plugin we installed at the beginning of this article. From the Tools menu, select: Cloud Code -> Kubernetes -> Add Kubernetes Support. This will automatically add a Skaffold YAML to your application and set up a few things for you so that you can deploy to your cluster by clicking a button. To confirm that all this worked you can inspect the configuration from the Run/Debug Configurations section in IntelliJ. If you click on the Develop on Kubernetes run, it should have automatically picked up your GKE cluster and Kubernetes configuration files and should look something like this:

Click Ok and then click on the green "Play" button at the top right:

After that, Cloud Code will build the app, create the image, deploy the application to your GKE cluster and stream the logs from Stackdriver into your local machine. It will also open a tunnel so that you can consume your service via localhost:8080. You can also peak at the workloads page in the Google Cloud Console:

Conclusions

Congratulations if you made it this far! The application we built in this article showcases some key technologies that most microservices-based applications would use: a fast, fully managed, serverless, cloud-native NoSQL document database (Cloud Firestore), GKE a managed, production-ready environment for deploying Kubernetes-based containerized applications and finally a simple cloud native microservice build with Spring Boot. Along the way, we also learned how to use a few tools like Cloud Code to streamline your development workflow and Jib to build containerized applications using common Java patterns.

I hope you’ve found the article helpful and that you give these technologies a try. If you found this interesting, have a look at a bunch of codelabs that Google offers where you can learn about Spring and Google Cloud products.

About the Author

Sergio Feilx is a Software Engineer in Google Cloud where he works in Cloud Engineering Productivity, an organization within Google Cloud that focuses on making development frictionless and improving Product & Eng Excellence.

Rate this Article

Adoption
Style

BT