Migrating Spring Apps from Cloud Foundry to Azure Spring Apps for CF Developers

cf push

Cloud Foundry’s vision boils down to one powerful command, “cf push". This one command can handle a myriad of things like containerization of an app, ingress setup, service binding, limit and request, etc.

$ git clone https://github.com/cloudfoundry-samples/spring-music.git
$ cd spring-music
$ cat manifest.yml
---
applications:
- name: spring-music
memory: 1G
random-route: true
path: build/libs/spring-music-1.0.jar
env:
JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '{enabled: false}'
SPRING_PROFILES_ACTIVE: http2
$ ./gradlew clean build
$ cf push
Pushing app spring-music to org test-org / space dev as c**@gmail.com...
Applying manifest file /private/tmp/spring-music/manifest.yml...
Manifest applied
Packaging files to upload...
Uploading files...
49.83 MiB / 49.83 MiB [=================================================================================================] 100.00% 4s
Waiting for API to complete processing files...Staging app and tracing logs...
Downloading binary_buildpack...
Downloading r_buildpack...
Downloading php_buildpack...
Downloading ruby_buildpack...
Downloaded binary_buildpack
Downloading dotnet_core_buildpack...
Downloading nodejs_buildpack...
Downloaded ruby_buildpack
Downloading nginx_buildpack...
Downloaded nodejs_buildpack
...
Instances starting...
Instances starting...
Instances starting...
Instances starting...
Instances starting...
name: spring-music
requested state: started
routes: spring-music-grateful-pangolin-ck.apps.***
last uploaded: Sat 06 Aug 00:07:31 +08 2022
...
$ az spring app create --name spring-music --service asc-standard --resource-group asc-rg --memory 1Gi --runtime-version Java_17 --assign-endpoint true
This command usually takes minutes to run. Add '--verbose' parameter if needed.
[1/3] Creating app spring-music
[2/3] Creating default deployment with name "default"
[3/3] Updating app "spring-music" (this operation can take a while to complete)
App create succeeded
{
"id": "/subscriptions/.../resourceGroups/asc-rg/providers/Microsoft.AppPlatform/Spring/asc-standard/apps/spring-music",
"identity": null,
"location": "eastus",
"name": "spring-music",
"properties": {
"activeDeployment": {
"id": "/subscriptions/.../resourceGroups/asc-rg/providers/Microsoft.AppPlatform/Spring/asc-standard/apps/spring-music/deployments/default",
"name": "default",
...
"provisioningState": "Succeeded",
"public": true,
"temporaryDisk": {
"mountPath": "/tmp",
"sizeInGb": 5
},
"url": "https://asc-standard-spring-music.azuremicroservices.io",
"vnetAddons": null
},
"resourceGroup": "asc-rg",
...
}
$ az spring app deploy -n spring-music -s asc-standard -g asc-rg --source-path . --env SPRING_PROFILES_ACTIVE=http2 JBP_CONFIG_SPRING_AUTO_RECONFIGURATION="'{enabled: false}'"
This command usually takes minutes to run. Add '--verbose' parameter if needed.
[1/3] Requesting for upload URL.
[2/3] Uploading package to blob.
[3/3] Updating deployment in app "spring-music" (this operation can take a while to complete)
...
Previous image with name "acr9788ecdca02b433e8.azurecr.io/720555919a4f415dbd087e25f5ed09f8-spring-music" not found
9 of 23 buildpacks participating
paketo-buildpacks/ca-certificates 3.2.3
paketo-buildpacks/microsoft-openjdk 2.2.5
paketo-buildpacks/syft 1.11.3
paketo-buildpacks/gradle 6.4.5
paketo-buildpacks/executable-jar 6.2.4
paketo-buildpacks/apache-tomcat 7.3.6
paketo-buildpacks/liberty 1.1.1
paketo-buildpacks/dist-zip 5.2.4
paketo-buildpacks/spring-boot 5.11.0
/ Running ..
...
Spring Music running on Azure Spring Apps
  • Cloud Foundry no-route — There is no equivalent on ASA. ASA always creates a test endpoint by default which is protected by HTTP Basic Auth.
  • Cloud Foundry random-route — There is no equivalent on ASA. ASA creates a URL based on the given app name.
  • Cloud Foundry map-route —Not Available on ASA
  • Cloud Foundry custom domain — ASA supports the custom domain

cf logs

cf logs by default show not only application logs but also platform logs including Stemcell, GoRouter, CAPI server, etc. And it by default tails the log.

$ cf logs spring-music
$ az spring app logs -g asc-rg -s asc-standard -n spring-music -f
$ cf logs spring-music --recent
$ az spring app logs -g asc-rg -s asc-standard -n spring-music$ az spring app logs -g asc-rg -s asc-standard -n spring-music -i spring-music-default-13-6747564bd8-4mqx2

Auto Scaling

Cloud Foundry provides an app autoscaler to auto-scale Application Instances by two methods, 1. metrics based and 2. schedule based. ASA supports two models exactly the same as Cloud Foundry, but There is one difference in the support of custom metrics. Cloud Foundry supports custom metrics emitted by the application code, but ASA doesn’t. However, ASA supports using JVM level metrics with the help of the Spring Boot Actuator. Aside from basic metrics like CPU, memory, network, etc, it also supports metrics like JVM GC pause or Tomcat total thread count.

ASA Metrics for autoscaling
Schedule based autoscaling

Marketplace and Service Binding

Cloud Foundry way of integrating third-party services is to use Service Instances. The concept of service binding is from the 12factor backing service. (https://12factor.net/backing-services). Any service created in a form of Service Instance can be bound or unbound to an app without changing the code. That is the realization of the 12-factor app. Cloud Foundry marketplace is to help developers to pick&choose from the list of services available on the platform so that they can easily get going without the hassles of provisioning services on their own. Let's look at an example of MySQL.

$ cf create-service p.mysql db-medium test-db$ cf bind-service spring-music db-medium$ cf env spring-musc
Getting env variables for app spring-music in org test-org / space dev as cloudnativejay@gmail.com...
OK
System-Provided:
{
"VCAP_SERVICES": {
"p-mysql": [
{
"credentials": {
"hostname": "10.0.4.35",
"port": 3306,
"name": "cf_2e23d10a_8738_8c3c_66cf_13e44422698c",
"username": "8McHri7aKbuTEGCR",
"password": "J2BNJYkeXAH9idkG",
"uri": "mysql://8McHri7aKbuTEGCR:J2BNJYkeXAH9idkG@10.0.4.35:3306/cf_2e23d10a_8738_8c3c_66cf_13e44422698c?reconnect=true",
"jdbcUrl": "jdbc:mysql://10.0.4.35:3306/cf_2e23d10a_8738_8c3c_66cf_13e44422698c?user=8McHri7aKbuTEGCR&password=J2BNJYkeXAH9idkG"
},
"syslog_drain_url": null,
"volume_mounts": [],
"label": "p-mysql",
"provider": null,
"plan": "100mb",
"name": "mysql",
"tags": [
"mysql",
"relational"
....
  1. cf create-service create a MySQL instance running at 10.0.4.35, with username and password autogenerated.
  1. Azure App Configuraiton
  2. Spring Cloud Config Server
  3. Azure Key Vault
Create a connection through browsing
Creating connector with Username Password, store in Azure Key Vault
Service Connector

Spring Cloud Services(SCS)

This section is specifically for developers using Tanzu Application Service, which has a service called Spring Cloud Service for VMWare Tanzu. SCS is a marketplace service on TAS that developers can create a Spring Cloud Config Server and Spring Clod Eureka Discovery Server. Like other services, using SCS start with creating Service Instance.

$ cf create-service -c '{ "git": { "uri": "https://github.com/spring-cloud-samples/config-repo" }, "count": 3 }' p.config-server standard my-config-server
...
$ cf bind-service myapp my-config-server
Config Client (TAS), Service Registry (TAS)
Config Client, Eureka Discovery Client
Repository configuration on Azure portal
Checking Eureka Registry App Registration

APM Integration

APM Integration for popular services like New Relic, App Dynamics, Dynatrace, Datadog, Elastic, etc. on Cloud Foundry work by Service Instance and buildpack collaboration in tandem. Developers should create a User Provided Service type of Service Instance (cups) with an access key, etc., then bind it to their app so that buildpack detects the settings and injects the agent inside the droplet. Buildpack has a specific rule to detect APM integration by checking the name of the service. Let's look at the example of App Dynamics.

Detection Criterion used by Java Buildpack on Cloud Foundry
$ cf cups app-dynamics -p '{"account-access-key":"ACCESS-KEY", "account-name":"ACCOUNT", "application-name":"APP", "host-name":"HOST", "plan-description":"PLAN", "plan-name":"NAME", "port":"PORT", "ssl-enabled":TRUE|FALSE'$ cf service-bind spring-music app-dynamics
...
$ az spring app deploy \
--resource-group "<your-resource-group-name>" \
--service "<your-Azure-Spring-Apps-instance-name>" \
--name "<your-app-name>" \
--jar-path app.jar \
--jvm-options="-javaagent:/opt/agents/appdynamics/java/javaagent.jar" \
--env APPDYNAMICS_AGENT_APPLICATION_NAME=<your-app-name> \
APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY=<your-agent-access-key> \
APPDYNAMICS_AGENT_ACCOUNT_NAME=<your-agent-account-name> \
APPDYNAMICS_JAVA_AGENT_REUSE_NODE_NAME=true \
APPDYNAMICS_JAVA_AGENT_REUSE_NODE_NAME_PREFIX=<your-agent-node-name> \
APPDYNAMICS_AGENT_TIER_NAME=<your-agent-tier-name> \
APPDYNAMICS_CONTROLLER_HOST_NAME=<your-AppDynamics-controller-host-name> \
APPDYNAMICS_CONTROLLER_SSL_ENABLED=true \
APPDYNAMICS_CONTROLLER_PORT=443

Wrapping Up

I’ve touched upon the most frequently used features of Cloud Foundry and its equivalent on Azure Spring Apps for Cloud Foundry developers. As you see in the article, simple Spring Boot apps will run just fine on Azure Spring Apps without any refactoring, but some apps especially with heavy use of java-cfenv-boot or unique Service Binding usage would require a little bit of refactoring. I hope this lays a nice foundation of understanding for Cloud Foundry developers for future migration.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Jay Lee

Jay Lee

Cloud Native Enthusiast. Java, Golang, Spring, Kubernetes.