Running a Cloud Foundry Spring Boot App on Azure Kubernetes Service (AKS)

Using the sample Spring Music app, this tutorial explains how to take the same artifact pushed to Cloud Foundry and run it on AKS unchanged.

When working with various systems and cloud platforms, an engineer aims to avoid the burden of rewriting or even customizing legacy apps. With the rise of Kubernetes and its distributions, developers and operators are exploring the ways to integrate these platforms with their development environments and existing code base.

In this tutorial, we describe the steps needed to take the de-facto sample Spring Music app designed for Cloud Foundry and run it on the Kubernetes distribution from Microsoft without any changes. The article also highlights the differences between the environments, workflows, and configurations.

AKS cluster architecture (Image credit)

 

How to prepare an app for AKS

 
PaaS vs. CaaS workflow

In a platform-as-a-service (PaaS) environment such as Cloud Foundry, the base unit is the application. Workflows and tooling are focused on applications. In our case, we provide the spring-music.jar file and Cloud Foundry’s buildpack then wraps the application with any dependencies and places everything in a container called a Droplet. Cloud Foundry then schedules the droplet on a node for execution, binding services to the application. More on this binding later.

Azure Kubernetes Service is described as container as a service (CaaS). In this case, we place the spring-music.jar file into a container image that is based on an image with the JVM already installed. After that, we describe a pod that uses this container as a Kubernetes object. The orchestration platform (Kubernetes) then schedules the pod for execution on a node. To connect the app to services, we provide configuration to Kubernetes telling it to expose the environment variables and files that our application can understand.

 
Service binding, Spring Boot, and profiles

At runtime, Cloud Foundry will expose the environment variable VCAP_SERVICES to the application, providing service names, service types, and connection information for all the services bound to an application. Spring Boot Cloud Foundry libraries provide connectors that work with VCAP_SERVICES to automatically wire the application for the use of these services. Spring Boot provides a “cloud” profile to enable using the runtime configuration specific to Cloud Foundry. It is common practice to have runtime-specific profiles for local development vs. cloud deployment, which is consistent with the 12-factor application development.

Although Azure Kubernetes Service does not provide a feature like VCAP_SERVICES, the platform provides mechanisms for exposing environment variables and files into containers. We can make use of these two methods to provide service information to the application in a similar fashion as Cloud Foundry.

Exposing the VCAP_SERVICES environment variable with a JSON of all needed services—along with SPRING_PROFILES_ACTIVE=cloud—creates an environment similar to Cloud Foundry, which can actually work. However, Spring Boot does not require such complex manipulations. Making use of Spring profiles and Kubernetes ConfigMaps and Secrets is a more elegant solution.

Kubernetes services on Azure (Image credit)

 

Preparing the app

The following steps are required to prepare the Spring Music app to run on AKS:

  • Dockerize the application in a standard Java 8 runtime.
  • MySQL is deployed as a pod and used as a data store. This could run in another pod or come from a managed service.
  • Our Spring profile is exposed as the file application-aks.properties, which is mounted inside the Spring Music container. The file contains boilerplate configuration and references environment variables for more environment-specific configuration, such as database credentials.
  • The active Spring profile is set with the environment variable SPRING_PROFILES_ACTIVE=aks.
  • Additional environment variables are exposed in the container providing an environment-specific configuration.

Now that we understand how to deploy the app on Cloud Foundry and have a strategy for dealing with services in Azure Kubernetes Service, we will go through the steps outlined above.

 

Running the app on Azure Kubernetes Service

In this article, we will not go over the instructions on how to deploy an Azure Kubernetes Service cluster. You can refer to this tutorial for help.

The Spring Music App main page

 
Building a Spring Music Docker image

Unlike Kubernetes, Cloud Foundry builds a container image internally. Since Kubernetes is a CaaS platform, we need to make and provide a container image.

This Dockerfile starts with Java 8 and copies in the spring-music.jar file. The CMD line runs the jar according to the Spring Music documentation.

FROM openjdk:8u232-jdk
COPY spring-music.jar /app/
EXPOSE 8080
WORKDIR /app
 
CMD java -jar spring-music.jar

 
Configuration strategy

We will expose configuration with environment variables in combination with a properties file. The following file will be created in the container before the application starts. (File location: /app/config/application-aks.properties)

spring.datasource.url=spring.datasource.url=jdbc:mysql://${MYSQLDB_SERVICE_HOST}:${MYSQLDB_SERVICE_PORT}/spring_music
spring.datasource.username=${MYSQL_USERNAME}
spring.datasource.password=${MYSQL_PASS}
 
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.testOnBorrow=true
spring.datasource.validationQuery=SELECT 1
spring.datasource.platform=mysql

We will also set the environment variable SPRING_PROFILES_ACTIVE=aks, which is seen in the Deployment spec a little later. This ensures Spring Music will use our exposed properties file.

In Kubernetes, when we create our mysqldb Kubernetes Service (not shown), it exposes basic connection information about the service to our pods automatically using the environment variables MYSQLDB_SERVICE_HOST and MYSQLDB_SERVICE_PORT. This follows an exact convention, so we know that as long as we name our service mysqldb the information about this service will get exposed the same way every time, even if the host or port changes.

Kubernetes objects depicting the management of configuration

 
ConfigMaps and Secrets

In Kubernetes, we will use both ConfigMaps and Secrets to expose the configuration. Below is the YAML file for the MySQL Secret. It contains a username and password for our database.

apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
data:
  # notapassword
  password: bm90YXBhc3N3b3Jk
 
  # root
  username: cm9vdA==

With this ConfigMap, we will expose application-aks.properties as a file in the container.

apiVersion: v1
kind: ConfigMap
metadata:
  name: sm-config
data:
  application-aks.properties: |
     spring.datasource.url=jdbc:mysql://${MYSQLDB_SERVICE_HOST}:${MYSQLDB_SERVICE_PORT}/spring_music
    spring.datasource.username=${MYSQL_USERNAME}
    spring.datasource.password=${MYSQL_PASSWORD}
 
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.testOnBorrow=true
    spring.datasource.validationQuery=SELECT 1
    spring.datasource.platform=mysql

Below is the Spring Music Deployment. Note that the MySQL username and password properties get exposed as environment variables, and the application-aks.properties file is exposed as a volume. The properties file will seldom change and is more boilerplate, which reduces the maintenance on the ConfigMap. We also set SPRING_PROFILES_ACTIVE without using a Secret or ConfigMap.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sm
spec:
  selector:
    matchLabels:
      app: sm
  template:
    metadata:
      labels:
        app: sm
      name: sm
    spec:
      containers:
      - name: sm
        image: altorosbarrywilliams/spring-music:1.0
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: aks
        - name: MYSQL_USERNAME
          valueFrom:
            secretKeyRef:
              key: username
              name: mysql-secret
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              key: password
              name: mysql-secret
        volumeMounts:
        - mountPath: /app/config
          name: sm-config-vol
        ports:
        - containerPort: 8080
          name: http
          protocol: TCP
      volumes:
      - configMap
          name: sm-config
        name: sm-config-vol

Here’s the response from the console.

Deploying the Spring Music app to AKS using Azure Cloud Shell

 
Abstractions due to ConfigMaps and Secrets

There are some common differences between environments, including sensitive information (such as passwords) and possibly environment-specific optimizations (such as JVM heap size). It is recommended to store the configuration in Secrets and ConfigMaps, as appropriate, and use the same name for each Secret and ConfigMap across all environments. By doing this, you do not need to modify config-object names across environments, only their contents. Pods are automatically wired to the right configuration for a particular environment, because it efficiently looks up those objects by name.

The approach should be applied to naming other objects, as well, such as mysqldb for the database service. This way, you can confidently promote an application across environments, knowing that it will not have an issue at the Kubernetes object level.

 

Alternative ways to configure the Spring Music app

 
Expose with just ENV vars

Spring allows you to represent all configuration found in a properties file as environment variables. For example, setting the environment variable SPRING_DATASOURCE_USERNAME is the same as using spring.datasource.usernamespring.datasource.username in the properties file. This means that you would also need to set boilerplate variables, such as SPRING_DATASOURCE_VALIDATIONQUERY. In other words, using just environment variables could become rather tedious and complex.

 
Expose with just a file

Providing that the file contains all data, including sensitive information, and excluding the use of environment variables, it is possible to expose configuration directly in the file. However, there are some disadvantages to this approach. As now we have a single configuration file that contains sensitive data, it should be stored in a Secret.

When an object is stored as a Secret, the information remains opaque, which means only those with privileges can directly view the Secret’s contents. This seems overbearing, as users without privilege will be unable to view or edit any of the less sensitive data. Even still, with privileges, using kubectl get secret my-secret -o yaml brings back a base64-encoded version of the data, which is not very handy to work with for all the information we are placing in the Secret. Ultimately, throwing all configuration in a Secret may be very inconvenient.

 

Observations on AKS and Cloud Foundry

A major advantage of Cloud Foundry is that Spring can benefit from how the platform exposes configuration. The VCAP_SERVICES environment variable exposes service configuration that Spring uses to bind itself to services at runtime. Kubernetes does not use this type of mechanism for exposing configuration, and a user has to set up the exposure of configuration through Secrets and ConfigMaps. In Kubernetes, however, the service discovery provided to each pod, allowing us to automatically wire some configuration.

Cloud Foundry is modeled with a focus on applications, which is great for new apps, but it is difficult to run existing software. Kubernetes is modeled similar to running traditional servers, which keeps things familiar for both new and existing applications. Moreover, Kubernetes allows you to describe complex workloads, some of which would be difficult to describe in Cloud Foundry.

Spring Boot allows many ways to provide configuration (we’ve only covered a couple in this article). This configurability allows Spring Boot applications to fit in virtually any environment. As we have seen, configuration on Cloud Foundry is rather simple—due to the ability of Spring Boot to match the opinions of Cloud Foundry. There are still many improvements to be done in Kubernetes, but once configured, it can be nearly as simple to maintain as Cloud Foundry. With the ability of Cloud Foundry and Kubernetes following similar workflows, engineers can easily build new apps, as well as support legacy software, by reusing existing artifacts.

For the source code used in this tutorial, refer to our GitHub repository containing all the artifacts, including a Docker image and Kubernetes objects.

 

Further reading

 

About the authors

Barry Williams is a Cloud Solutions Architect with 17+ years of experience in IT and software development. He has designed and implemented architectures for various microservices-based applications, focusing on all things automation across cloud platforms. Technologies Barry works with include Kubernetes, Cloud Foundry, Docker, Linux, Concourse, ELK, Shell Script, Python, Java, Go, Ruby, etc. He is a Certified Kubernetes Administrator and a Google Cloud Certified Professional Cloud Architect.

Bob Combs is a Cloud-Native Solutions Architect with 30+ years in the software development industry—as a developer, solutions architect, and engineering manager. He has focused the majority of his career on industries spanning nearly all market segments, including financial services, IT forensics, healthcare, and advertising. Some of the companies he has delivered solutions for include Oracle, Expedia, Boeing, IBM, EMC, and Visa.

Tim Flagg is a Cloud Solutions Architect with 30+ years in software engineering and consulting. He founded multiple companies working in the IT automation space. Tim has a strong background in Kubernetes, Linux, cloud-native, and networking, having taught classes in the U.S., Asia, and Latin America. Some of the companies he has consulted to are AT&T, CitiCorp, IBM, Hong Leong Bank, General Electric, and Ford Motor Company. Tim holds a number of patents in networking and software.

 


The post was written by Barry Williams, Bob Combs, and Tim Flagg;
edited by Alex Khizhniak.