Configure a Spring Boot Application

Pivotal/Tanzu Labs

Gain more understanding on how to build cloud native applications.

Learning outcomes

After completing the lab, you will be able to:

  • Summarize some of the ways to configure a Spring application
  • Use environment variables to configure an application running locally
  • Use cf logs to view the logs of an application running on Tanzu Application Service
  • Use environment variables to configure an application running on Tanzu Application Service
  • Explain when to use the CLI versus a manifest for application configuration

12 factor applications

If you completed the Cloud Native Development learning path, you should already be familiar with 12 factors guidelines.

In practice, you will have to decide which ones to use and if it makes sense to adhere to all of them.

In the previous lab, you covered the first two factors by setting up your codebase in GitHub and using Gradle to explicitly declare your dependencies.

This lab will focus on the third factor: storing configuration in the environment.

There are many options for how to externalize configuration for a cloud native application. Our first choice is to use environment variables.

Another choice, externalizing configuration using a Config Server, will be introduced in later lessons.

Get started

  1. Review the Environment slides.

  2. You must have completed (or fast-forwarded to) the Deploying a Spring Boot Application lab. You must have your pal-tracker application associated with the spring-boot-solution codebase deployed and running on Tanzu Application Service.

  3. In a terminal window, make sure you start in the ~/workspace/pal-tracker directory.

  4. Pull in pre-authored tests to your codebase:

    git cherry-pick configuration-start
    

    You will see the following tests:

    [main 95c54c1] Add tests for configuration lab
    Author: <redacted>
    Date: Wed Jan 8 11:09:04 2020 -0500
    3 files changed, 67 insertions(+)
    create mode 100644 src/test/java/test/pivotal/pal/tracker/EnvControllerTest.java
    create mode 100644 src/test/java/test/pivotal/pal/tracker/WelcomeControllerTest.java
    create mode 100644 src/test/java/test/pivotal/pal/trackerapi/WelcomeApiTest.java
    

    Your goal is to get your tests to pass by the end of the lab, as well as deploy the updated code to Tanzu Application Service.

If you get stuck

If you get stuck within this lab, you can either view the solution, or you can fast-forward to the configuration-solution tag.

Add test dependencies

Since you now have added tests to your codebase, you need to set up the build to use a testing framework. You will use Junit 5.

Add the following to your build.gradle file:

  1. Add the following line to the dependencies closure in your build.gradle file to enable the necessary test dependencies:

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    
  2. Add the following closure to the end of the build.gradle file:

    test {
        useJUnitPlatform()
    }
    
  3. Take a look at the solution if you get stuck

    git show configuration-solution:build.gradle
    

Environment variables

You will use the environment variable mechanism to provide configuration to a process.

It does not matter if your application is written in Java, Ruby, Golang, or some other language, they all have the capability of accessing environment variables during their process start up time.

Tanzu Application Service can be configured to provide your application with environment variables when it is executed. In fact, it provides several environment variables that your application can use to uniquely identify its execution environment.

You will extend your application to:

  • Configure the “hello” message from the environment.
  • Add a RESTful endpoint that returns some of the system-provided variables.

Externalize configuration

Spring Boot includes a mechanism to get configuration values.

  1. Extract the hello message to a field in the controller.

  2. Create a constructor that accepts a message parameter and assigns it to the field.

  3. Annotate that constructor parameter with @Value.

    @Value takes a specific format to reference an environment variable, for example:

    @Value("${welcome.message}")
    

    Take time to read about annotation-based configuration if you are new to Spring or if you need a refresher. There is more detailed documentation on using Spring expressions in bean definitions and annotations, if you are interested.

  4. If you get stuck, review the solution:

    git show configuration-solution:src/main/java/io/pivotal/pal/tracker/WelcomeController.java
    

Verify it works

Run your app with the bootRun Gradle task. It will fail to start because Spring Boot is unable to find a value for the requested variable.

From now on the WELCOME_MESSAGE environment variable must be set to run your app. One way to do this is to add the environment variable assignment before the Gradle command.

WELCOME_MESSAGE=howdy ./gradlew bootRun

Notice you are using a different welcome message than the previous statically configured message.

Why is the @Value name welcome.message?

You may be wondering why the name given in the @Value annotation is welcome.message and not the name of the environment variable, WELCOME_MESSAGE.

Spring can take configuration information from many different sources. In order to support this, and to have a consistent way of naming configuration items from different sources, Spring uses what is known as relaxed binding. Among other things, this defines the rules that convert environment variable names, such as WELCOME_MESSAGE, to the common format represented by welcome.message. This means that you could later change the source of the welcome message text to, for example, a Java property without having to modify the code.

Manage local configuration

Running your application like this with environment variables in the command line every time is tedious.

You can configure your Gradle build to make this easier.

  1. Extend the bootRun and test tasks to set the required environment variable by adding the following to the end of your build.gradle file:

    bootRun.environment([
        "WELCOME_MESSAGE": "howdy",
    ])
    
    test.environment([
        "WELCOME_MESSAGE": "Hello from test",
    ])
    
  2. You can review the solution here:

    git show configuration-solution:build.gradle
    
  3. This will instruct Gradle to set that environment variable for you when you run the bootRun task, so you can go back to just using:

    ./gradlew bootRun
    

This has the added benefit of documenting required environment variables and supporting multiple operating systems.

Tanzu Application Service environment variables

When Tanzu Application Service starts your application, it will be running with a port provided by the runtime environment. It will also have an instance ID set which will be distinct for each instance, if the app has multiple instances.

Create an endpoint to see some of that information.

  1. Create another controller called EnvController.

  2. Annotate it with @RestController.

  3. Implement a method called getEnv as described in the EnvControllerTest.

  4. Annotate the getEnv method with @GetMapping("/env").

  5. Declare @Value annotated constructor arguments to be populated with values of the following environment variables:

    • PORT
    • MEMORY_LIMIT
    • CF_INSTANCE_INDEX
    • CF_INSTANCE_ADDR
  6. Add a default value to the variables above so that the app will still run correctly locally. For example:

    @Value("${cf.instance.index:NOT SET}") String cfInstanceIndex,
    
  7. Implement getEnv so that the test passes.

    Take a look at our solution if you get stuck:

    git show configuration-solution:src/main/java/io/pivotal/pal/tracker/EnvController.java
    
  8. Run your app and navigate to localhost:8080/env. Make sure you are getting a response. The variables will show NOT SET, which ensures the controller is valid.

  9. Navigate to localhost:8080/. Make sure the root route still has the welcome message you set above.

  10. Make sure all your tests pass before moving on by running the Gradle build task.

Deploy

  1. Push the app to Tanzu Application Service. The app will fail to start and will be reported as a crash.

    To see what the problem is, use the cf logs command. Logging in Tanzu Application Service works by taking everything from standard output and error (System.out and System.err in Java) and storing it in a small buffer.

  2. View the logs from your app through the cf logs command. By default, this will show you a stream of logs. To see logs from the recent past, use the --recent flag.

  3. Check the output and find the problem. You will see something about Spring not having a value for the welcome message.

  4. To fix this issue, use the cf set-env command to set the WELCOME_MESSAGE environment variable to Howdy from Tanzu Application Service in your app’s container on Tanzu Application Service. The CLI will give you a helpful tip:

    TIP: Use 'cf restage pal-tracker' to ensure your env variable changes take effect
    

    Actually, the CLI does not know whether you need to restage, or simply restart. Restaging is necessary if the environment variable is used in the buildpack’s compile stage. Restarting is sufficient if the environment variable is only used by the application at runtime. Restarting is usually faster, since the droplet does not need to be re-created. In this case, restarting is sufficient, so save yourself some time:

    cf restart pal-tracker
    
  5. Once your app is running on Tanzu Application Service, navigate to the root endpoint and check that it displays Howdy from Tanzu Application Service.

  6. Navigate to the /env endpoint and verify actual values are displayed instead of NOT SET.

Manifest

The initial deployment failure could have been avoided by using a manifest.yml.

This file documents requirements for the application and configures variables in the environment such as the WELCOME_MESSAGE and setting the JDK version to use when running the application.

A Tanzu Application Service manifest is also an appropriate place to describe Backing Services dependencies that your app requires. You will see more about backing services in the Backing Services and Database Migrations and Data Access labs.

  1. Checkout the solution manifest.yml file:

    git checkout configuration-solution manifest.yml
    
  2. Notice the WELCOME_MESSAGE must be Hello from Tanzu Application Service.

To see the effect of using a manifest:

  1. Delete your app with the cf delete command.

  2. Push your app using your new manifest with push. The push command will automatically use your manifest file if you push from the same directory.

  3. Visit the root endpoint and /env endpoint. Be aware that the app URL has changed as a result of the random-route: true line in the manifest.

  4. Make a commit and push your code to GitHub once you are sure everything is working.

Wrap up

Review the Tanzu Application Service Environment slides about environment variable configuration on Tanzu Application Service.

Now that you have completed the lab, you should be able to:

  • Summarize some of the ways to configure a Spring application
  • Use environment variables to configure an application running locally
  • Use cf logs to view the logs of an application running on Cloud Foundry
  • Use environment variables to configure an application running on Tanzu Application Service
  • Explain when to use the command line interface (CLI) versus a manifest for application configuration

Extras

Tanzu Application Service command line interface (CLI)

If you have additional time, explore the cf CLI by reading the documentation or by running cf help and cf <command> --help.

Explore the pal-tracker application environment

  1. Review the cf env command.

  2. Run the cf env command for the pal-tracker application. Review the system provided environment variables, as well as the user provided environment variables.

Explore the pal-tracker container

  1. Review the cf ssh command.

  2. Run the cf ssh command to create a secure shell connection to the only running instance (index 0) of your pal-tracker.

  3. Run the following and review the running pal-tracker container:

    • Tanzu Application Service environment variables:

      env | grep CF_

      Notice that Tanzu Application Service sets the IP address, ports, and host name (GUID) of the containers for you.

    • Review the running processes in the container:

      ps -ef

      Notice the Java process, how did Tanzu Application Service know what the run command is for the pal-tracker application?

      Notice the envoy and healthcheck processes. You will see discussion of these in later labs.

    • Review the following directory and its subdirectories:

      ll app/

      Where did the directory and files originate from?