In this edition of “Living on the cloud,” walk through the steps of deploying a Spring Boot application to a Kubernetes cluster hosted on IBM Cloud.

Prerequisites

If you want to follow along with the steps in this tutorial on your own system, you will need the following:

The code used in this tutorial can be found in this GitHub repo.

Initialize a Kubernetes cluster

The first step is to initialize a Kubernetes cluster on IBM Cloud. It can take several minutes for IBM Cloud to spin up a new Kubernetes cluster; so, by doing this first, you can have this running in the background while you complete some of the other steps.

  1. Log in to your IBM Cloud account. In the top right of the Dashboard page you should see the button Create resource.

    The Create resource button on the Dashboard page

    If it does not appear at the top of this page, look for Kubernetes Service on the resource catalog page.

    Go to Kubernetes Service if your button isn't there

  2. For this tutorial, you will be creating a free Lite Kubernetes cluster. To initialize a Lite cluster, you will need to upgrade your IBM Cloud account, if you have not already.

    Don’t worry. All the resources in this tutorial are free; they will either be shut down after a period of inactivity, typically 30 days, or throttled to keep them within their free limit. No new charges will appear on your credit card statement.

    I entered my own CC info to complete this, so we are in this together! 🙃

  3. Once you have upgraded your account, select Lite; for the cluster name, use living-on-the-cloud, then select Create cluster:

    Creating your Lite cluster

IBM Cloud will now begin initializing the Kubernetes cluster. As I mentioned, this will take several minutes, so let’s continue on to the next steps while IBM Cloud completes the initialization process.

Create a container registry

  1. Back on the resource catalog page, search for Container Registry and select it; it should look like this:

    Your container registry page

    On the create registry page, you can view the pricing plans. By default, our account will be set to the Lite plan, so it will be rate-limited to fit within the limits of the 0.5GB of storage and 5GB of pull data.

  2. Click Create, and you should be brought to the Registry home page, which gives you an overview of your Container Registry — the number of namespaces, registries, and images available.

    See what's available in your container registry

Configure IBM Cloud and Docker CLI

  1. If you have not already installed the IBM Cloud Command Line Interface (CLI), download and install it.

  2. Open a terminal or command window, and enter the following command to verify the install was successful:

    ibmcloud --version

    You should get a return that looks similar to this:

    ibmcloud version 0.14.0+3303164-2019-02-07T02:13:34+00:00

Let’s start configuring the IBM Cloud CLI to use our IBM Cloud account resources.

  1. Run the following command to install the container-registry plugin:

    ibmcloud plugin install container-registry -r Bluemix

  2. Once the install script has completed, run the following command to ensure it was successful:

    ibmcloud cr info

    You should get output that looks like this:

    Container Registry                us.icr.io
    Container Registry API endpoint   https://us.icr.io/api
    IBM Cloud API endpoint            https://api.ng.bluemix.net
    IBM Cloud account details         <account details>
    IBM Cloud organization details    <organization details>
    

    Note: If you had previously installed the container registry and you have container registry URLs that include the word “bluemix,” learn how to update your API endpoints.

  3. Log in to your IBM Cloud account through the IBM Cloud CLI using the following command:

    ibmcloud login -a https://api.ng.bluemix.net

    You will be prompted to enter the email and password associated with your IBM Cloud account.

    Note: If you are using a federated IBM Cloud account follow these steps.

  4. Once logged in, create a namespace for your container registry. While the name of the namespace can be arbitrary, in this tutorial and series, I will use living-on-the-cloud.

    To create a namespace run the following command:

    ibmcloud cr namespace-add living-on-the-cloud

    You should get the following response:

    Adding namespace 'living-on-the-cloud'...
    
    Successfully added namespace 'living-on-the-cloud'
    
  5. Log in to the container registry with the command line:

    ibmcloud cr login

  6. Create an API token that you will use with the Docker command line.

    ibmcloud cr token-add --description "Living on the Cloud" --non-expiring --readwrite

    The description can again be arbitrary, but for this series, I will use "Living on the Cloud".

    You should get a response that looks something like this:

    Token identifier   b6ff5759-e085-58da-8086-ead373a9e9da
    Token              eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiNmZmNTc1OS1lMDg1LTU4ZGEtODA4Ni1lYWQzNzNhOWU5ZGEiLCJpc3MiOiJyZWdpc3RyeS5uZy5ibHVlbWl4Lm5ldCJ9.1zgwfBe6epmFrh-PfZWR5Kf3ZyyL4M6QFLymLCPyqEM
    
  7. Link the Docker daemon to your container registry:

    docker login us.icr.io -u token

    When prompted for the password, use the token you got back.

Small, but Bootiful

Now it’s time to do a little bit of my favorite thing — coding. It will only be a simple application this time, but as you follow along in the series, I will show you how to add new features and capabilities to this app.

  1. Go to start.spring.io.

    • The Group ID and Artifact can be up to you, but I used com.ibm.developer and storm-tracker respectively (because we are talking cloud, and storms are clouds, get it?).
    • For Dependencies, you will want to bring in Web.

    The Spring Initializr

  2. Click Generate Project, which will download a zip archive of a scaffolded-out Spring Boot project using the values you just entered.

  3. Unzip the archive and open that project in your IDE.

For now, the storm-tracker app will only be performing a very simple “Hello World.”

  1. Create a new class called StormTrackerController and add to it a @GetMapping that returns `”Hello World”“ as the response.

    Here is what the completed class should look like:

    @RestController
    @RequestMapping("/api/v1/storms")
    public class StormTrackerController {
    
      @GetMapping
      public ResponseEntity<String> helloWorld(){
       return ResponseEntity.ok("Hello World");
      }
    }
    

    At this point, it might be good to start up the application and verify everything is working correctly. If you are unfamiliar with Spring Boot, the endpoint should reside at http:localhost:8080/api/v1/storms.

  2. In the pom.xml, you will add a plugin to containizer your application and push it to the container registry that you just set up on your IBM Cloud account.

    Under \<build\>\<plugins\> in pom.xml, add the following:

    <plugin>
      <groupId>io.fabric8</groupId>
      <artifactId>docker-maven-plugin</artifactId>
      <extensions>true</extensions>
      <configuration>
        <images>
          <image>
            <name>us.icr.io/living-on-the-cloud/${project.name}</name>
            <build>
              <from>adoptopenjdk/openjdk8-openj9</from>
              <entryPoint>
                <exec>
                  <arg>java</arg>
                  <arg>-jar</arg>
                  <arg>/${project.build.finalName}.jar</arg>
                </exec>
              </entryPoint>
              <assembly>
                <targetDir>/</targetDir>
                <mode>dir</mode>
                <descriptorRef>artifact</descriptorRef>
              </assembly>
            </build>
          </image>
        </images>
      </configuration>
    </plugin>
    

    Let’s quickly step through the important parts of this plugin configuration.

    • Under \<plugin\>\<configuration\>\<images\>\<image\>, you are setting up the name and where the image will be stored in the container registry:

      <name>us.icr.io/living-on-the-cloud/${project.name}</name>

      • us.icr.io is the API endpoint for IBM Cloud’s container registry (this might need to be changed to match your region).
      • living-on-the-cloud is the container namespace you created earlier.
      • ${project.name} is the name of the repository — a container repository is where all related images will be stored; if you are using the same values provided earlier, this would resolve to storm-tracker.
    • Under \<build\>, you are declaring what image your Docker image should be built from:

      <from>adoptopenjdk/openjdk8-openj9</from>

      For the base Docker image, you are using the Java 8 version of the AdoptOpenJDK OpenJ9 image. (Learn more about AdoptOpenJDK and OpenJ9.) Eclipse OpenJ9 is a free-to-use, open source JVM implementation that uses ~60 percent of the memory that a Hotspot would use.

    • Also under \<build\>, you are defining the entry point for the Docker image and copying the project artifact into the Docker image:

       <entryPoint>
         <exec>
           <arg>java</arg>
           <arg>-jar</arg>
           <arg>/${project.build.finalName}.jar</arg>
         </exec>
       </entryPoint>
       <assembly>
         <targetDir>/</targetDir>
         <mode>dir</mode>
         <descriptorRef>artifact</descriptorRef>
       </assembly>
      

    The project artifact is the JAR file that is built when mvn package is executed.

  3. With all that hopefully clear, execute the following command to build the project, containerize it, and push it to your container registry:

    mvn package docker:build docker:push

    You should see the following output near the end of the build execution:

    [INFO] DOCKER> Pushed us.icr.io/living-on-the-cloud/storm-tracker in 14 seconds

If you go back to your container registry home page, you should see that it now has an image stored in it:

The image is stored

Deploy to Kubernetes

By now, your Kubernetes cluster has hopefully finished initializing. To verify that it has, go back to the Dashboard page and see if the status is Normal for the cluster you just created.

Verify the initialized Kubernetes cluster

The next part will involve executing quite a few commands. But don’t worry, I’ll cover what is happening with each command.

  1. Add the container-service plugin to the IBM Cloud CLI, which will also download and install kubectl:

    ibmcloud plugin install container-service

    Note: If you want to learn how to pronounce “kubectl,” watch the definitive guide by Waldo Grundenwald.

  2. Set the IBM Cloud CLI to the region your cluster is located in. In the previous image, the region is Dallas, which translates to us-south. If your cluster is in a different region, you can view all regions by running the command ibmcloud regions.

    To set the region run:

    ibmcloud ks region-set us-south

  3. Download the configuration information for your cluster:

    ibmcloud ks cluster-config living-on-the-cloud

    The output response from this command should look something like this:

    export KUBECONFIG=/Users/<username>/.bluemix/plugins/container-service/clusters/living-on-the-cloud/kube-config-hou02-living-on-the-cloud.yml

    We will copy and paste this line in the terminal to set the environment variable KUBECONFIG, which kubectl will read from.

  4. To verify that kubectl is able to connect to your cluster, run the following:

    kubectl get nodes

    The returned output should look something like this:

    NAME            STATUS    ROLES     AGE       VERSION
    10.77.223.210   Ready     <none>    8h        v1.11.7+IKS
    

With your command line properly configured, now it’s time to get your Spring Boot application deployed and configured.

  1. Deploy and run the image you created earlier on your cluster:

    kubectl run storm-tracker --image=us.icr.io/living-on-the-cloud/storm-tracker

    This line tells Kubernetes to run an image, defined by the --image argument with the name storm-tracker.

  2. To make your service accessible from an external IP, you will need to expose it, like this:

    kubectl expose deployment storm-tracker --port=8080 --target-port=8080 --name=storm-tracker-service --type=NodePort

    A lot is going on in that code, so I’ll break it down for you:

    • expose tells Kubernetes to make a resource publicly available.
    • deployment is the type of resource, in this case a deployment.
    • storm-tracker is the name of the resource being exposed.
    • --port=8080 is the port on which the service serves.
    • --targetPort=8080 is the port on which the service directs traffic.
  3. To find the port that has been publicly exposed, you can ask Kubernetes to provide a description of the NodePort you just created:

    kubectl describe service storm-tracker-service

    This should provide the following output:

    Name:                     storm-tracker-service
    Namespace:                default
    Labels:                   run=storm-tracker
    Annotations:              <none>
    Selector:                 run=storm-tracker
    Type:                     NodePort
    IP:                       XXX.XXX.XXX.XXX
    Port:                     <unset>  8080/TCP
    TargetPort:               8080/TCP
    NodePort:                 <unset>  30299/TCP
    Endpoints:                XXX.XXX.XXX.XXX:8080
    Session Affinity:         None
    External Traffic Policy:  Cluster
    Events:                   <none>
    

    In this example output, the exposed port is 30299.

  4. You will also need to get the public IP of your Kubernetes cluster:

    ibmcloud ks workers --cluster living-on-the-cloud

    The output should look similar to this:

    ID                                                 Public IP         Private IP        Machine Type   State    Status   Zone    Version
    kube-hou02-paeb33817993d9417f9ad8cfad7fcf270e-w1   184.172.XXX.XXX   XXX.XXX.XXX.XXX   free           normal   Ready    hou02   1.11.7_1544
    
  5. Using the public IP and port from these outputs, you should be able to call your Spring Boot application at <public IP>:<exposed port>/api/v1/storms.

Summary

While it might feel like it took a lot of effort to get here, in reality, deploying an image to Kubernetes is pretty easy. A lot of the steps I’ve demonstrated are part of a one-time, initial setup. And still it was pretty simple — in less than 30 minutes, you went from nothing to deploying an application that is accessible to the whole world!

Of course this guide is only a small portion of what Kubernetes can do and what is usually expected of a modern application. As you work through this series, you will continue to encounter new features and capabilities that are added to this application, as well as look at ways to automate a lot of the behavior around building, deploying, and operating your applications in production.

Be sure to check out the next tutorial, where I will show you how to connect your application to a cloud-hosted PostgresSQL database!

The code used in this tutorial can be found in my GitHub repository.