This article is the follow-up on Refreshing configuration at Runtime with Spring Cloud Bus and Azure Service Bus that covers the basics of Spring Cloud Config Server for config management for microservices.
Spring Cloud Config Server leveraging the concept of "Configuration as a code" is very powerful, so it has become the most popular project in the entire Spring Cloud family. However, when it comes to production, Config Server becomes an additional resource to manage that requires security (ex, network access control, authentication/authorization), high availability, patching, etc. In the previous article, I intentionally chose Azure Spring Apps as a platform, so I don't need to deal with provisioning a Config server myself. But if you're a developer using Kubernetes or AKS, you don't have luxury other than running it independently.
What if Azure has a similar Paas service so that it could reduce all the management overhead described above and also supports Spring natively? I hope this sounds exciting enough for you to read the rest of the article. It's relatively unknown among developers, but Azure has a PaaS service for configuration management called Azure App Configuration that you can use with any application platform on Azure like App Service, AKS, ARO, Spring Apps, and Container Apps. Here is the basic introduction from official Microsoft documentation.
"Azure App Configuration provides a service to centrally manage application settings and feature flags. Modern programs, especially programs running in a cloud, generally have many components that are distributed in nature. Spreading configuration settings across these components can lead to hard-to-troubleshoot errors during application deployment. Use App Configuration to store all the settings for your application and secure their accesses in one place."
Since it's a first-party product in Azure, it has certain advantages over running your own Spring Cloud Config instances on your platform. For example:
- Polyglot language support — Java, .NET, .NET core, Python, Javascript
- Feature Management — Not only configuration but also feature flag
- Private Endpoint Support
- Encryption using CMK
- Managed Identity support
It ticks all the boxes of usual Azure requirements like MI, Private Endpoint, etc., but it has a significant difference compared to Spring Cloud Config, which is the core construct of "Configuration as a Code" Spring Cloud Config Server uses git, svn, Vault as a backing store for configuration but Azure App Configuration doesn't use any separate backing store. To practice the "Configuration as a Code" with App Configuration, your CI/CD pipeline should handle the "synchronization" of your configuration to Azure. Azure provides the actions for Github and Azure DevOps. Otherwise, you should be able to do it with az CLI in the pipeline. https://docs.microsoft.com/en-us/azure/azure-app-configuration/howto-best-practices#configuration-as-code
In this article, you will see the basics of App Configuration, @Profile
integration, access with managed identity, and configuration refresh. I will use Azure Container Apps for the target platform.
Create Azure App Configuration
I will create an instance with the name"appconf-java
"using az CLI. Needless to say, you should choose a unique name. Creating an instance takes only a few minutes to complete.
$ az appconfig create -g sandbox-rg -n appconf-java -l westus
Go to Azure Portal -> Azure App Configuration -> appconf-java, and open Configuration explorer.
There are two ways to create a key/value pair, "Key-value" and "Key Vault Reference" As the name indicates, the first is to supply key-value pairs manually, whereas "Key Vault Reference" is to refer to the secrets on Azure Key Vault. In the demo, I will use the first one to supply key-value pairs manually. Note that the key name starts with'/application/
', and the label is empty. I will save the explanation for the next chapter.
Create Sample Spring Boot Apps
Generate a project using Spring initilizr with a few simple dependencies, Web
, Actuator
, and Azure Support
.
Open the project with your favorite IDE, and manually add the following dependency in the pom.xml
. This is the very dependency that has not been updated to Spring Cloud Azure 4.
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>azure-spring-cloud-appconfiguration-config</artifactId>
<version>2.8.0</version>
</dependency>
NOTE: Spring dependency for Azure App Configuration has not been updated to a new Spring Cloud Azure 4 yet, hence need to add dependency manually.
Then create a property file, bootstrap.yml
. Since managed identity is not configured yet, connection-string
is required for connection. I'm using the environment variable APP_CONFIGURATION_CONNECTION_STRING
to supply the connection-string
.
spring:
cloud:
azure:
appconfiguration:
enabled: true
stores[0]:
connection-string: ${APP_CONFIGURATION_CONNECTION_STRING}
management:
endpoints:
web:
exposure:
include: "*"
We have enabled Actuator endpoints so that we can verify the sample app easily. Run the sample app locally and open the URL, http://localhost:8080/actuator/env
propertySources
showing that one of the sources is Azure App Configuration, and there is one property "message
".
spring.application.name and @Profile with Azure App Configuration
If you have sharp eyes, you must have noticed that the key of our message in App Configuration is "/application/message
" but actual property presented is "message
". This is the predefined behavior by Azure Spring Cloud AppConfiguration library. This library reads all the properties that start with "/application/
" and present them to the client. This is a critical detail to remember for Spring developers as this is the way to separate the different applications where we used to use "spring.application.name
" in Spring Cloud Config Server. Here is an example.
spring:
cloud:
azure:
appconfiguration:
enabled: true
stores[0]:
connection-string: ${APP_CONFIGURATION_CONNECTION_STRING}
selects[0]:
key-filter: /${spring.application.name}/
application:
name: appconfig-java
management:
endpoints:
web:
exposure:
include: "*"
I set the "spring.application.name
" as "appconfig-java
" and use it for key-filter.
As you might imagine, key-filter
is to filter the list of keys to get only the ones of interest that can serve the perfect needs of Config Server-like features. I will add a few more properties with the keys starting with "/appconfig-java/
".
Restart the application and open the Actuator endpoint again.
This time with key-filter
, it only shows the properties that I newly added. Not as smooth as Spring Config Server, but it just works fine. Let's move on to @Profile. It's always a combination of spring.application.name
and @Profile
which differentiate the different configurations per app and environment. Azure Spring Cloud App configuration has a concept called "label
" which is directly mapped to Spring Profile, which makes our life much easier. Let's try it with an app. This time, I'll add a profile to the bootstrap.yml
.
spring:
cloud:
azure:
appconfiguration:
enabled: true
stores[0]:
connection-string: ${APP_CONFIGURATION_CONNECTION_STRING}
selects[0]:
key-filter: /${spring.application.name}/
application:
name: appconfig-java
profiles:
active: dev
management:
endpoints:
web:
exposure:
include: "*"
You might have noticed that the URL in the bootstrapProperties is changing by the value of spring.profiles.active
. It was '/u0000' (null) before, but now it is '/dev’
. Also, it doesn't show any properties from App Configuration. As there are no keys with the label 'dev
', it doesn't return anything. Let's add a few values and see how they would change.
As expected, it returns the properties with the label 'dev'. I really like this seamless integration between the two.
Run sample app on Azure Container Apps with Managed Identity
As most of the basics are covered and the sample app is ready, let's deploy this to the platform. Then we will switch the access keys to managed identity as access keys wouldn't be a recommended approach for the production use case.
I'm going to make a few changes on bootstrap.yml.
I will create two profiles, dev
and containerapps
to easily switch between the local environment and Azure Container Apps. And I enable managed identity to use System assigned managed identity.
spring:
cloud:
azure:
appconfiguration:
stores[0]:
endpoint: https://appconf-java.azconfig.io
selects[0]:
key-filter: /${spring.application.name}/
credential:
managed-identity-enabled: true
enabled: true
application:
name: appconfig-java
config:
activate:
on-profile: containerapps
---
spring:
cloud:
azure:
appconfiguration:
enabled: true
stores[0]:
connection-string: ${APP_CONFIGURATION_CONNECTION_STRING}
selects[0]:
key-filter: /${spring.application.name}/
application:
name: appconfig-java
config:
activate:
on-profile: dev
I created another file application.yml
to store the common configuration.
management:
endpoints:
web:
exposure:
include: "*"
logging:
level:
com.azure: DEBUG
I will use my all-time favorite tool Cloud Native Buildpack which takes care of containerizing my Spring apps without much hassle. If you're unfamiliar with it, I have an excellent article you can start with. Creating Spring Boot container in a minute with Cloud Native Buildpacks for Azure Container Platform
$ pack build eggboy/appconfig-java:0.0.1 — builder paketobuildpacks/builder:base — buildpack paketo-buildpacks/java-azure — env BP_JVM_VERSION=17 — env BP_MAVEN_BUILD_ARGUMENTS=”-T4 -Dmaven.test.skip=true — batch-mode package” --publish
All set to deploy on Azure Container Apps. I will use CLI to deploy this container image. I set `--system-assigned’
so it will create a system-managed identity. I also set the active profile as 'containerapps
'.
$ az containerapp create -n appconfig-java-system -g containerapps-rg \
--image eggboy/appconfig-java:0.0.1 --environment jay-containerapps-env \
--ingress external --target-port 8080 \
--cpu 1 --memory 2.0Gi \
--system-assigned \
--env-vars SPRING_PROFILES_ACTIVE=containerapps \
--query properties.configuration.ingress.fqdn
...
Container app created. Access your app at https://appconfig-java.victoriousocean-1bfe0788.westus.azurecontainerapps.io/"appconfig-java.victoriousocean-1bfe0788.westus.azurecontainerapps.io"
The app is deployed successfully but won't be accessible as managed identity configuration is missing. Go to Azure App Configuration -> Access Control -> Add role assignment -> Choose ‘App Configuration Data Owner’ -> select managed identity from System assigned managed identity. You should be able to see the identity generated for your container app.
Once role assignment takes effect, the app will start up and be accessible. Container Apps like normal Kubernetes will restart the failing application, so you will be able to see an app running in some time. Or you could restart an app manually using az CLI.
$ az containerapp revision list -n appconfig-java -g containerapps-rg -o table
Command group 'containerapp' is in preview and under development. Reference and support levels: https://aka.ms/CLI_refstatus
CreatedTime Active TrafficWeight Name
------------------------- -------- --------------- -----------------------
2022-07-21T03:31:01+00:00 True 100 appconfig-java--6f47omf
$ az containerapp revision restart --revision appconfig-java--6f47omf -n appconfig-java -g containerapps-rg
Add one quickly and restart the app.
Refresh Configuration at runtime
App Configuration provides two ways to refresh the configuration from apps, poll-based and push-based models. Poll-based model is literally polling changes by a predefined interval. In contrast, the push-based model uses Azure Event Grid to send the change event down to the application using a webhook. Just like Spring Cloud Config, App Configuration exposes endpoint in actuator like /actuator/appconfiguration-refresh, /actuator/appconfiguration-refresh-bus
that can be used with webhook. I will explore the poll-based model in this article for a quick test.
Refresh requires one additional dependency.
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>azure-spring-cloud-appconfiguration-config-web</artifactId>
<version>2.8.0</version>
</dependency>
App Configuration refresh works differently than you might have imagined looking at the option "refresh-interval" or even Spring Cloud Kubernetes. Instead of using scheduling, it relies on the ApplicationEvent
. I personally doubt the logic behind this decision. Anyway, here is the explanation from the official documentation.
azure-spring-cloud-appconfiguration-config-web
's automated refresh is triggered based on activity, specifically Spring Web's ServletRequestHandledEvent
. If a ServletRequestHandledEvent
is not triggered, azure-spring-cloud-appconfiguration-config-web
's automated refresh will not trigger a refresh even if the cache expiration time has expired.
It requires a bit more configuration as below. The critical part here is triggers[0]
where it defines the key and label. To trigger the refresh, you should change the value of the trigger key with the proper label.
spring:
cloud:
azure:
appconfiguration:
enabled: true
stores[0]:
monitoring:
enabled: true
refresh-interval: 5s
triggers[0]:
key: refresh-key
label: dev
connection-string: ${APP_CONFIGURATION_CONNECTION_STRING}
selects[0]:
key-filter: /${spring.application.name}/
application:
name: appconfig-java
config:
activate:
on-profile: dev
Run the app, open the actuator endpoint, and look at the DEBUG logs on the console. AppConfigurationRefresh checks the interval and decides to skip or refresh the configuration by checking the value of triggers[0].key
2022-07-21 15:13:18.698 DEBUG 90978 --- [ task-4] c.a.s.c.config.AppConfigurationRefresh : Skipping configuration refresh check for https://appconf-java.azconfig.io
Wrapping Up
Azure App Configuration is a relatively unknown service among Azure developers, but hopefully, this article shows its potential to you developers and makes you keen to try it out. Its integration with Spring Boot and Cloud with Azure SDK ensures an easier transition from Spring Cloud Config to Azure App Configuration. I can't wait to see the full integration of App Configuration SDK into Spring Cloud Azure 4.
The sample source code is at https://github.com/eggboy/appconfig-java
If you like my article, please leave some claps here or maybe even start following me. You can hit me up on Linkedin. Thanks!