Deploy a Spring Boot Java app to Kubernetes on Google Kubernetes Engine

1. Before you begin

Kubernetes is an open source project, which can run in many different environments, from laptops to high-availability multi-node clusters, from public clouds to on-premise deployments, and from virtual machine (VM) instances to bare metal.

In this codelab, you'll deploy a simple Spring Boot Java web app to Kubernetes on GKE, with the goal being for you to run your web app as a replicated app on Kubernetes. You'll take code that you develop on your machine, turn it into a Docker container image, and run the image on GKE.

You'll use GKE, a fully managed Kubernetes service on Google Cloud, to allow you to focus more on experiencing Kubernetes, rather than setting up the underlying infrastructure.

If you're interested in running Kubernetes on your local machine, such as a development laptop, then look into Minikube, which offers a simple setup of a single-node Kubernetes cluster for development and testing purposes. You can use Minikube to go through the codelab if you wish.

The codelab will use the sample code from the guide about Building an App with Spring Boot.

Prerequisites

  • Familiarity with Java programming language and tools
  • Knowledge of standard Linux text editors, such as Vim, Emacs, and nano

What you'll do

  • Package a simple Java app as a Docker container.
  • Create your Kubernetes cluster on GKE.
  • Deploy your Java app to Kubernetes on GKE.
  • Scale up your service and roll out an upgrade.
  • Access Dashboard, a web-based Kubernetes user interface.

What you'll need

2. Setup and requirements

Self-paced environment setup

  1. Sign-in to the Google Cloud Console and create a new project or reuse an existing one. If you don't already have a Gmail or Google Workspace account, you must create one.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • The Project name is the display name for this project's participants. It is a character string not used by Google APIs. You can always update it.
  • The Project ID is unique across all Google Cloud projects and is immutable (cannot be changed after it has been set). The Cloud Console auto-generates a unique string; usually you don't care what it is. In most codelabs, you'll need to reference your Project ID (typically identified as PROJECT_ID). If you don't like the generated ID, you might generate another random one. Alternatively, you can try your own, and see if it's available. It can't be changed after this step and remains for the duration of the project.
  • For your information, there is a third value, a Project Number, which some APIs use. Learn more about all three of these values in the documentation.
  1. Next, you'll need to enable billing in the Cloud Console to use Cloud resources/APIs. Running through this codelab won't cost much, if anything at all. To shut down resources to avoid incurring billing beyond this tutorial, you can delete the resources you created or delete the project. New Google Cloud users are eligible for the $300 USD Free Trial program.

Activate Cloud Shell

  1. From the Cloud Console, click Activate Cloud Shell 853e55310c205094.png.

55efc1aaa7a4d3ad.png

If this is your first time starting Cloud Shell, you're presented with an intermediate screen describing what it is. If you were presented with an intermediate screen, click Continue.

9c92662c6a846a5c.png

It should only take a few moments to provision and connect to Cloud Shell.

9f0e51b578fecce5.png

This virtual machine is loaded with all the development tools needed. It offers a persistent 5 GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. Much, if not all, of your work in this codelab can be done with a browser.

Once connected to Cloud Shell, you should see that you are authenticated and that the project is set to your project ID.

  1. Run the following command in Cloud Shell to confirm that you are authenticated:
gcloud auth list

Command output

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Run the following command in Cloud Shell to confirm that the gcloud command knows about your project:
gcloud config list project

Command output

[core]
project = <PROJECT_ID>

If it is not, you can set it with this command:

gcloud config set project <PROJECT_ID>

Command output

Updated property [core/project].

3. Get source code

After Cloud Shell launches, you can use the command line to clone the example source code in the home directory.

$ git clone https://github.com/spring-guides/gs-spring-boot.git
$ cd gs-spring-boot/complete

4. Locally run the app

  1. Make sure JAVA_HOME is set to the correct version:
$ export JAVA_HOME=/usr/lib/jvm/java-1.17.0-openjdk-amd64
  1. You can start the Spring Boot app normally with the Spring Boot plugin.
$ ./mvnw -DskipTests spring-boot:run
  1. After the app starts, click on Web Preview 1a94d5bd10bfc072.pngin the Cloud Shell toolbar and select Preview on port 8080.

6252b94905f3f7bd.png

A tab in your browser opens and connects to the server you just started.

9b6c29059957bd0.jpeg

5. Package the Java app as a Docker container

Next, you need to prepare your app to run on Kubernetes. The first step is to define the container and its contents.

  1. Create the JAR deployable for the app.
$ ./mvnw -DskipTests package
  1. Enable the Artifact Registry API to store the container image that you'll create.
$ gcloud services enable artifactregistry.googleapis.com
  1. Create a new Docker repository if one doesn't exist. You must create a repository before you can push any images to it:
$ gcloud artifacts repositories create codelabrepo     --repository-format=docker --location=us-central1 
  1. Your image is going to be of the format:

{LOCATION}-docker.pkg.dev/{PROJECT-ID}/{REPOSITORY}/{IMAGE-NAME}

For example, if you've created the repository in location us-central1 named codelabrepo, and you want to name your image hello-java:v1, the image will be:

us-central1-docker.pkg.dev/{PROJECT-ID}/codelabrepo/hello-java:v1

  1. Use Jib to create the container image and push it to the Artifact Registry.
$ export GOOGLE_CLOUD_PROJECT=`gcloud config list --format="value(core.project)"`

$ ./mvnw -DskipTests com.google.cloud.tools:jib-maven-plugin:build -Dimage=us-central1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/codelabrepo/hello-java:v1
  1. You should be able to see the container image listed in the console by navigating to the Artifacts Registry Images page in the Cloud Console. You now have a project-wide Docker image available, which Kubernetes can access and orchestrate as you'll see in a few minutes.
  2. (Optional) After completion (it'll take some time to download and extract everything), test the image with the following command, which will run a Docker container as a daemon on port 8080 from your newly created container image. If you run into permissions issues, run gcloud auth configure-docker us-central1-docker.pkg.dev first:
$ docker run -ti --rm -p 8080:8080 \
  us-central1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/codelabrepo/hello-java:v1
  1. Again, take advantage of the web preview feature of Cloud Shell.

6252b94905f3f7bd.png

  1. You should see the default page in a new tab. After you verify that the app is locally running in a Docker container, you can stop the running container by pressing Control+C.

6. Create your cluster

You're ready to create your GKE cluster. A cluster consists of a Kubernetes API server managed by Google and a set of worker nodes. The worker nodes are Compute Engine VMs.

  1. First, make sure that the related API features are enabled.
$ gcloud services enable compute.googleapis.com container.googleapis.com
  1. Create a cluster with two n1-standard-1 nodes (it will take a few minutes to complete).
$ gcloud container clusters create hello-java-cluster \
  --num-nodes 2 \
  --machine-type n1-standard-1 \
  --zone us-central1-c

In the end, you should see the cluster created.

Creating cluster hello-java-cluster...done.
Created [https://container.googleapis.com/v1/projects/...].
kubeconfig entry generated for hello-dotnet-cluster.
NAME                  ZONE            MASTER_VERSION  
hello-java-cluster  us-central1-c  ...

You should now have a fully functioning Kubernetes cluster powered by GKE.

758c7fca14f70623.png

It's now time to deploy your containerized app to the Kubernetes cluster! From now on, you'll use the kubectl command line (already set up in your Cloud Shell environment). The rest of the codelab requires the Kubernetes client and server version to be 1.2 or higher. kubectl version will show you the current version of the command.

7. Deploy your app to Kubernetes

  1. A Kubernetes deployment can create, manage, and scale multiple instances of your app using the container image that you created. Deploy one instance of your app to Kubernetes using the kubectl run command.
$ kubectl create deployment hello-java --image=us-central1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/codelabrepo/hello-java:v1
  1. To view the deployment that you created, simply run the following command:
$ kubectl get deployments

NAME         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-java   1         1         1            1           37s
  1. To view the app instances created by the deployment, run the following command:
$ kubectl get pods

NAME                         READY     STATUS    RESTARTS   AGE
hello-java-714049816-ztzrb   1/1       Running   0          57s

At this point, you should have your container running under the control of Kubernetes, but you still have to make it accessible to the outside world.

8. Allow external traffic

By default, the Pod is only accessible by its internal IP within the cluster. In order to make the hello-java container accessible from outside the Kubernetes virtual network, you have to expose the Pod as a Kubernetes service.

  1. In Cloud Shell, you can expose the Pod to the public internet by creating a Kubernetes LoadBalancer service.
$ kubectl create service loadbalancer hello-java --tcp=8080:8080

Note that you directly expose the deployment, not the Pod. That will cause the resulting service to load balance traffic across all Pods managed by the deployment (in this case, only one Pod, but you'll add more replicas later).

The Kubernetes Master creates the load balancer and related Compute Engine forwarding rules, target pools, and firewall rules to make the service fully accessible from outside of Google Cloud.

  1. To find the publicly accessible IP address of the service, simply request kubectl to list all the cluster services.
$ kubectl get services

NAME         CLUSTER-IP     EXTERNAL-IP      PORT(S)    AGE
hello-java   10.3.253.62    aaa.bbb.ccc.ddd  8080/TCP    1m
kubernetes   10.3.240.1     <none>           443/TCP    5m
  1. You should now be able to reach the service by pointing your browser to http://<EXTERNAL_IP>:8080.

9. Scale your service

One of the powerful features offered by Kubernetes is how easy it is to scale your app. Suppose that you suddenly need more capacity for your app. You can simply tell the replication controller to manage a new number of replicas for your app instances.

$ kubectl scale deployment hello-java --replicas=3

deployment "hello-java" scaled

$ kubectl get deployment
NAME         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-java   3         3         3            3           22m

Notice the declarative approach. Rather than starting or stopping new instances, you declare how many instances should be running at all times. Kubernetes reconciliation loops simply make sure that the reality matches what you requested and take action, if needed.

10. Roll out an upgrade to your service

At some point, the app that you deployed to production will require bug fixes or additional features. Kubernetes can help you deploy a new version to production without impacting your users.

  1. Open the code editor by clicking Open editor 2109d75686c889a.pngin the Cloud Shell menu.
  2. Navigate to src/main/java/com/example/springboot/HelloController.java and update the value of the response.
package com.example.springboot;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloController {

    @RequestMapping("/")
    public String index() {
        return "Greetings from Google Kubernetes Engine!";
    }
}
  1. Use Jib to build and push a new version of the container image. Building and pushing the updated image should be much quicker as you take full advantage of caching.
$ ./mvnw -DskipTests package com.google.cloud.tools:jib-maven-plugin:build -Dimage=us-central1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/codelabrepo/hello-java:v2

You're ready for Kubernetes to smoothly update your replication controller to the new version of the app!

  1. In order to change the image label for your running container, you need to edit the existing hello-java deployment and change the image from us-central1-docker.pkg.dev/PROJECT_ID/codelabrepo/hello-java:v1

to us-central1-docker.pkg.dev/PROJECT_ID/codelabrepo/hello-java:v2

  1. You can use the kubectl set image command to ask Kubernetes to deploy the new version of your app across the entire cluster one instance at a time with rolling updates.
$ kubectl set image deployment/hello-java hello-java=us-central1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/codelabrepo/hello-java:v2

deployment "hello-java" image updated
  1. Check http://EXTERNAL_IP:8080 again to see that it's returning the new response.

11. Roll back

Oops! Did you make a mistake with a new version of the app? Perhaps the new version contained an error and you need to quickly roll it back. With Kubernetes, you can roll it back to the previous state easily. Roll back the app by running the following command:

$ kubectl rollout undo deployment/hello-java

You should see the old response when you check http://EXTERNAL_IP:8080 again.

12. Congratulations

You learned to build and deploy a new Java-based web app to Kubernetes on GKE.

Clean up

$ gcloud container clusters delete hello-java-cluster --zone us-central1-c

$ gcloud container images delete us-central1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/codelabrepo/hello-java:v1 us-central1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/codelabrepo/hello-java:v2

Learn more