Migration from Standard to Enterprise Tier should emphasize the key changes on IaC
Migration from Standard to Enterprise Tier should emphasize the key changes on IaC, including :
- Monitoring: not only the API is different than 'Microsoft.AppPlatform/Spring/monitoringSettings@2022-09-01-preview' but also the properties with bindingType: 'ApplicationInsights' and connection_string: appInsights.properties.ConnectionString
resource azureSpringAppsMonitoringSettings 'Microsoft.AppPlatform/Spring/buildServices/builders/buildpackBindings@2022-11-01-preview' = if (azureSpringAppsTier=='Enterprise') {
name: '${azureSpringApps.name}/${buildServiceName}/${builderName}/${monitoringSettingsName}' // /default (for Build Service ) /default (Builder) /default (Build Pack binding name)
properties: {
bindingType: 'ApplicationInsights'
launchProperties: {
properties: {
sampling_percentage: '10'
connection_string: appInsights.properties.ConnectionString // /!\ ConnectionString for Enterprise tier , InstrumentationKey for Standard Tier
}
}
}
dependsOn: [
appInsights
buildService
]
}
- The Config Server must be replaced by configuration Services which has a different API :
resource appconfigservice 'Microsoft.AppPlatform/Spring/configurationServices@2022-11-01-preview' = if (azureSpringAppsTier=='Enterprise') {
name: applicationConfigurationServiceName
parent: azureSpringApps
properties: {
settings: {
gitProperty: {
repositories: [
{
name: gitRepoName
label: configServerLabel
patterns: [
'application'
]
//searchPaths: [
// '/'
//]
uri: gitConfigURI
}
]
}
}
}
}
- The App must now include the addonConfigs for applicationConfigurationService and serviceRegistry :
resource customersserviceapp 'Microsoft.AppPlatform/Spring/apps@2022-11-01-preview' = {
name: 'customers-service'
location: location
parent: azureSpringApps
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${customersServicedentity.id}': {}
}
}
properties: {
addonConfigs: {
azureMonitor: {
enabled: true
}
applicationConfigurationService: {
resourceId: '${azureSpringApps.id}/configurationServices/${applicationConfigurationServiceName}'
}
serviceRegistry: {
resourceId: '${azureSpringApps.id}/serviceRegistries/${serviceRegistryName}'
}
}
httpsOnly: false
public: true
temporaryDisk: {
mountPath: '/tmp'
sizeInGB: 5
}
}
dependsOn: [
appconfigservice
]
}
- The App bindings must be be added :
resource customersbinding 'Microsoft.AppPlatform/Spring/apps/bindings@2022-11-01-preview' = if (azureSpringAppsTier=='Enterprise') {
name: 'customers-service-binding'
parent: customersserviceapp
properties: {
bindingParameters: {}
resourceId: customersserviceapp.id
key: 'customers-service' // There is no API Key for MySQL
}
dependsOn: [
serviceRegistry
]
}
- serviceRegistry must be created:
resource serviceRegistry 'Microsoft.AppPlatform/Spring/serviceRegistries@2022-11-01-preview' = if (azureSpringAppsTier=='Enterprise') {
name: serviceRegistryName
parent: azureSpringApps
}
output serviceRegistryId string = serviceRegistry.id
- API portal should be created
resource apiPortal 'Microsoft.AppPlatform/Spring/apiPortals@2022-11-01-preview' = if (azureSpringAppsTier=='Enterprise') {
name: apiPortalName
parent: azureSpringApps
sku: {
name: azureSpringAppsSkuName
capacity: any(1) // Number of instance ?
tier: azureSpringAppsTier
}
properties: {
gatewayIds: [
'${azureSpringApps.id}/gateways/${gatewayName}'
]
httpsOnly: false
public: true
ssoProperties: {
clientId: apiPortalSsoClientId
clientSecret: apiPortalSsoClientSecret
issuerUri: apiPortalSsoIssuerUri
scope: [
'openid'
'profile'
'email'
]
}
}
}
output apiPortalId string = apiPortal.id
output apiPortalUrl string = apiPortal.properties.url
output gatewayIds array = apiPortal.properties.gatewayIds
- gateway should be created
resource gateway 'Microsoft.AppPlatform/Spring/gateways@2022-11-01-preview' = if (azureSpringAppsTier=='Enterprise') {
name: gatewayName
parent: azureSpringApps
sku: {
name: azureSpringAppsSkuName
capacity: any(1)
tier: azureSpringAppsTier
}
properties: {
corsProperties: {
allowCredentials: false
allowedOrigins: [
'*'
]
allowedMethods: [
'GET'
]
allowedHeaders: [
'*'
]
}
httpsOnly: false
public: true
// az spring gateway update --help
resourceRequests: {
cpu: '1' // CPU resource quantity. Should be 500m or number of CPU cores.
memory: '1Gi' // Memory resource quantity. Should be 512Mi or #Gi, e.g., 1Gi, 3Gi.
}
ssoProperties: {
clientId: apiPortalSsoClientId
clientSecret: apiPortalSsoClientSecret
issuerUri: apiPortalSsoIssuerUri
scope: [
'openid'
'profile'
'email'
]
}
}
}
output gatewayId string = gateway.id
output gatewayUrl string = gateway.properties.url
- routeConfigs must be created :
resource CustomersGatewayRouteConfig 'Microsoft.AppPlatform/Spring/gateways/routeConfigs@2022-11-01-preview' = if (azureSpringAppsTier=='Enterprise') {
name: 'customers-service-gateway-route-config'
parent: gateway
properties: {
appResourceId: customersserviceapp.id
protocol: 'HTTP'
filters: [
'StripPrefix=0'
]
predicates: [
'/api/customer/**'
'/owners'
]
routes: [
{
description: 'customers-service'
title: 'customers-service'
uri: 'http://customers-service'
order: 1
ssoEnabled: apiPortalSsoEnabled
}
]
}
}
output CustomersGatewayRouteConfigId string = CustomersGatewayRouteConfig.id
output CustomersGatewayRouteConfigAppResourceId string = CustomersGatewayRouteConfig.properties.appResourceId
output CustomersGatewayRouteConfigRoutes array = CustomersGatewayRouteConfig.properties.routes
output CustomersGatewayRouteConfigIsSsoEnabled bool = CustomersGatewayRouteConfig.properties.ssoEnabled
output CustomersGatewayRouteConfigPredicates array = CustomersGatewayRouteConfig.properties.predicates
- we should provide a tool to automate this , importing routes from the OSS Spring Cloud Gateway config, see my sample :
spring:
cloud:
gateway:
discovery:
# make sure a DiscoveryClient implementation (such as Netflix Eureka) is on the classpath and enabled
locator: # https://cloud.spring.io/spring-cloud-gateway/reference/html/#the-discoveryclient-route-definition-locator
enabled: true # to configure Spring Cloud Gateway to use the Spring Cloud Service Registry to discover the available microservices.
routes:
- id: vets-service
uri: http://vets-service
predicates:
- Path=/api/vet/**
filters:
- StripPrefix=2
- id: visits-service
uri: http://visits-service
predicates:
- Path=/api/visit/**
filters:
- StripPrefix=2
- id: customers-service
uri: http://customers-service
predicates:
- Path=/api/customer/**
filters:
- StripPrefix=2
globalcors: # o allow CORS requests to our gateway. This can be helpful when you add a front-end that is not hosted on Azure Spring Apps.
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- All the build tools must be created :
resource buildService 'Microsoft.AppPlatform/Spring/buildServices@2022-11-01-preview' existing = if (azureSpringAppsTier=='Enterprise') {
//scope: resourceGroup('my RG')
name: '${azureSpringAppsInstanceName}/${buildServiceName}'
}
resource buildagentpool 'Microsoft.AppPlatform/Spring/buildServices/agentPools@2022-11-01-preview' = if (azureSpringAppsTier=='Enterprise') {
// '{your-service-name}/default/default' //{your-service-name}/{build-service-name}/{agenpool-name}
name: '${azureSpringAppsInstanceName}/${buildServiceName}/${buildAgentPoolName}'
properties: {
poolSize: {
name: 'S1'
}
}
dependsOn: [
azureSpringApps
]
}
// az spring build-service builder create --help
// https://learn.microsoft.com/en-us/azure/spring-apps/how-to-enterprise-build-service?tabs=azure-portal#default-builder-and-tanzu-buildpacks
resource builder 'Microsoft.AppPlatform/Spring/buildServices/builders@2022-11-01-preview' = if (azureSpringAppsTier=='Enterprise') {
name: builderName
parent: buildService
properties: {
buildpackGroups: [
{
buildpacks: [
{
id: 'tanzu-buildpacks/java-azure'
}
]
name: 'java'
}
]
// https://docs.vmware.com/en/VMware-Tanzu-Buildpacks/services/tanzu-buildpacks/GUID-full-stack-release-notes.html
//
stack: {
id: 'io.buildpacks.stacks.bionic' // io.buildpacks.stacks.bionic-base or tanzu-base-bionic-stack ? https://docs.pivotal.io/tanzu-buildpacks/stacks.html , OSS from https://github.com/paketo-buildpacks/java
version: 'base' // 1.2.35 https://network.tanzu.vmware.com/products/tanzu-base-bionic-stack#/releases/1218795/artifact_references
}
}
dependsOn: [
azureSpringApps
]
}
// https://github.com/Azure/Azure-Spring-Apps/issues/28
resource build 'Microsoft.AppPlatform/Spring/buildServices/builds@2022-11-01-preview' = if (azureSpringAppsTier=='Enterprise') {
name: buildName
parent: buildService
properties: {
agentPool: buildagentpool.id
builder: builder.id
env: {}
relativePath: '/'
}
dependsOn: [
azureSpringApps
]
}
Document Details
⚠ Do not edit this section. It is required for learn.microsoft.com ➟ GitHub issue linking.
- ID: c541b61f-c6e3-7bab-a58c-7499279aff25
- Version Independent ID: cd3fc689-4379-041f-c58d-b299232697fd
- Content: How to migrate an Azure Spring Apps Basic or Standard tier instance to Enterprise tier - Azure Spring Apps Enterprise tier
- Content Source: articles/spring-apps/how-to-migrate-standard-tier-to-enterprise-tier.md
- Service: spring-apps
- GitHub Login: @KarlErickson
- Microsoft Alias: xiading
@ezYakaEagle442
I've delegated this to content author @KarlErickson to review and share his valuable insights.
also the migration impacts the Deployment Pipelines : GH Workflows , I hit :
ERROR: '--runtime-version' doesn't support for Enterprise tier Spring instance.
See my Workflow deployment at https://github.com/ezYakaEagle442/azure-spring-apps-petclinic-mic-srv/blob/azure/.github/workflows/build-deploy-apps-staging-CLI.yml#L467
az spring app deployment create \
--name $DEPLOYMENT_NAME \
--app ${{ env.VETS_SERVICE }} \
--service ${{ env.AZURE_SPRING_APPS_SERVICE }} -g ${{ env.RG_APP }} \
--instance-count ${{ env.DEPLOYMENT_INSTANCE_COUNT }} \
--cpu ${{ env.DEPLOYMENT_CPU }} \
--memory ${{ env.DEPLOYMENT_MEMORY }} \
--version ${{ env.DEPLOYMENT_VERSION }} \
--runtime-version ${{ env.DEPLOYMENT_RUNTIME_VERSION }} \
--skip-clone-settings
Build reason(s): CONFIG
CONFIG:
- resources: ***
- source: ***
+ resources:
+ limits:
+ cpu: "1"
+ memory: 2Gi
+ services:
+ - apiVersion: v1
+ kind: Secret
+ name: 0-7default-7default-1
+ source:
+ blob:
+ url: https://1826c50187634f49aeeb1f26.file.core.windows.net/119e2834b4524cc1982393c691e5c73f/resources/2022121617-fba87097-5f50-41d0-b7a4-01e9fa602870?sv=2020-08-04&se=2023-04-15T17%3A27%3A35Z&sr=f&sp=r&sig=Rira6bvz2%2Bof%2FD3vR6WGd1o6e9ma%2BZ6QRYgPYAEbF5Y%3D
Loading secrets for "acr3da[318](https://github.com/ezYakaEagle442/azure-spring-apps-petclinic-mic-srv/actions/runs/3715175103/jobs/6300056129#step:10:319)443a37414d8.azurecr.io" from secret "docker-kpack"
Loading cluster credential helpers
Downloading 1826c50187634f49aeeb1f26.file.core.windows.net/119e2834b4524cc1982393c691e5c73f/resources/2022121617-fba87097-5f50-41d0-b7a4-01e9fa602870...
Successfully downloaded 1826c50187634f49aeeb1f26.file.core.windows.net/119e2834b4524cc1982393c691e5c73f/resources/2022121617-fba87097-5f50-41d0-b7a4-01e9fa602870 in path "/workspace"
Builder:
Image: acr3da318443a37414d8.azurecr.io/build-service-builder-119e2834b4524cc1982393c691e5c73f-default-default@sha256:a179e49eeaa9d4119a0be6917a701263983d2955570653aa40ad28fbf199830a
Name: default.default.1
Kind: Builder
Previous image with name "acr3da318443a37414d8.azurecr.io/build-service-result-image-119e2834b4524cc1982393c691e5c73f-default-vets-service-blue-vets-service:result" not found
7 of 41 buildpacks participating
paketo-buildpacks/ca-certificates 3.4.0
paketo-buildpacks/microsoft-openjdk 2.6.0
paketo-buildpacks/syft 1.10.1
paketo-buildpacks/executable-jar 6.5.0
paketo-buildpacks/dist-zip 5.4.0
paketo-buildpacks/spring-boot 5.19.0
paketo-buildpacks/azure-application-insights 5.8.0
Warning: Not restoring cached layer data, no cache flag specified.
$BPL_JAVA_NMT_ENABLED true enables Java Native Memory Tracking (NMT)
$BPL_JAVA_NMT_LEVEL summary configure level of NMT, summary or detail
Writing env/CLASSPATH.prepend
Process types:
executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
task: java org.springframework.boot.loader.JarLauncher (direct)
web: java org.springframework.boot.loader.JarLauncher (direct)
Paketo Buildpack for Spring Boot 5.19.0
https://github.com/paketo-buildpacks/spring-boot
Build Configuration:
$BP_SPRING_CLOUD_BINDINGS_DISABLED false whether to contribute Spring Boot cloud bindings support
Launch Configuration:
$BPL_SPRING_CLOUD_BINDINGS_DISABLED false whether to auto-configure Spring Boot environment properties from bindings
$BPL_SPRING_CLOUD_BINDINGS_ENABLED true Deprecated - whether to auto-configure Spring Boot environment properties from bindings
Creating slices from layers index
dependencies (84.8 MB)
spring-boot-loader (282.2 KB)
snapshot-dependencies (0.0 B)
application (105.3 KB)
Launch Helper: Contributing to layer
Creating /layers/paketo-buildpacks_spring-boot/helper/exec.d/spring-cloud-bindings
Spring Cloud Bindings 1.10.0: Contributing to layer
Reusing cached download from buildpack
Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
Web Application Type: Contributing to layer
Servlet web application detected
Writing env.launch/BPL_JVM_THREAD_COUNT.default
4 application slices
Image labels:
org.opencontainers.image.title
org.opencontainers.image.version
org.springframework.boot.version
There are other changes that should be documented :
the base image has some difference between enterprise/standard tier, and the some memories may allocated by buildpacks, which results in setting more memories to App deployments with Enterprise Tier.
with with Standard Tier :
DEPLOYMENT_JVM_OPTIONS: -Xms512m -Xmx1024m
DEPLOYMENT_RUNTIME_VERSION: Java_11
DEPLOYMENT_CPU: 500m
DEPLOYMENT_MEMORY: 1Gi
DEPLOYMENT_INSTANCE_COUNT: 1
az spring app deploy \
--name ${{ env.VETS_SERVICE }} \
--service ${{ env.AZURE_SPRING_APPS_SERVICE }} -g ${{ env.RG_APP }} \
--artifact-path ${{ env.VETS_SERVICE_PACKAGE_PATH }} \
--deployment $DEPLOYMENT_NAME \
--disable-probe true \
--env SPRING_CLOUD_AZURE_KEY_VAULT_ENDPOINT=${{ env.SPRING_CLOUD_AZURE_KEY_VAULT_ENDPOINT }} VETS_SVC_APP_IDENTITY_CLIENT_ID=${{ env.VETS_SVC_APP_IDENTITY_CLIENT_ID }} SPRING_CLOUD_AZURE_TENANT_ID=${{ env.SPRING_CLOUD_AZURE_TENANT_ID }} \
--jvm-options="${{ env.DEPLOYMENT_JVM_OPTIONS }}" \
--version ${{ env.DEPLOYMENT_VERSION }} \
--runtime-version ${{ env.DEPLOYMENT_RUNTIME_VERSION }}
with Enterprise Tier:
DEPLOYMENT_JVM_OPTIONS: -Xms1024m -Xmx2048m
DEPLOYMENT_CPU: 500m
DEPLOYMENT_MEMORY: 2Gi
DEPLOYMENT_INSTANCE_COUNT: 1
BP_JVM_VERSION: 11 # just '11' NOT Java_11
BUILD_ENV: BP_JVM_VERSION=11 # Space-separated environment variables in 'key[=value]' format: <key1=value1>, <key2=value2>
BUILD_CPU: 1 # CPU resource quantity. Should be 500m or number of CPU cores. Default: 1.
BUILD_MEMORY: 2Gi # Memory resource quantity. Should be 512Mi or #Gi, e.g., 1Gi, 3Gi. Default: 2Gi.
BUILDER: default # default or java-builder
CFG_FILE_PATTERNS: application # Config file patterns separated with ',' to decide which patterns of Application Configuration Service will be used. Use '""' to clear existing configurations.
az spring app deploy \
--name ${{ env.VETS_SERVICE }} \
--service ${{ env.AZURE_SPRING_APPS_SERVICE }} -g ${{ env.RG_APP }} \
--artifact-path ${{ env.VETS_SERVICE_PACKAGE_PATH }} \
--deployment $DEPLOYMENT_NAME \
--disable-probe true \
--env SPRING_CLOUD_AZURE_KEY_VAULT_ENDPOINT=${{ env.SPRING_CLOUD_AZURE_KEY_VAULT_ENDPOINT }} VETS_SVC_APP_IDENTITY_CLIENT_ID=${{ env.VETS_SVC_APP_IDENTITY_CLIENT_ID }} SPRING_CLOUD_AZURE_TENANT_ID=${{ env.SPRING_CLOUD_AZURE_TENANT_ID }} \
--jvm-options="${{ env.DEPLOYMENT_JVM_OPTIONS }}" \
--version ${{ env.DEPLOYMENT_VERSION }} \
--build-env ${{ env.BUILD_ENV }} \
--build-cpu ${{ env.BUILD_CPU }} \
--build-memory ${{ env.BUILD_MEMORY }} \
--builder ${{ env.BUILDER }} \
--config-file-patterns ${{ env.CFG_FILE_PATTERNS }}
Also, using CFG_FILE_PATTERNS= application and -Dspring.profiles.active was NOT enough to get my App configured using my application.yaml on my repo.
---
spring:
config:
activate:
on-profile: mysql
datasource:
schema: classpath*:db/mysql/schema.sql
data: classpath*:db/mysql/data.sql
username: mys_adm
url: jdbc:mysql://petcliasa.mysql.database.azure.com:3306/petclinic?useSSL=true&requireSSL=true&enabledTLSProtocols=TLSv1.2&verifyServerCertificate=true
initialization-mode: NEVER # NEVER ALWAYS
platform: mysql
#driver-class-name: com.mysql.jdbc.Driver
I had to use EXPLICTLY application-mysql on my repo:
spring:
datasource:
schema: classpath*:db/mysql/schema.sql
data: classpath*:db/mysql/data.sql
username: mys_adm
url: jdbc:mysql://petcliasa.mysql.database.azure.com:3306/petclinic?useSSL=true&requireSSL=true&enabledTLSProtocols=TLSv1.2&verifyServerCertificate=true
initialization-mode: NEVER # NEVER ALWAYS
platform: mysql
#driver-class-name: com.mysql.jdbc.Driver
And that was still NOT enough, I had then to EXPLICTLY set CFG_FILE_PATTERNS: application/mysql in the Workflow after having added a new repo named 'mysql' with profile application/mysql in the Application Configuration Service
DEPLOYMENT_JVM_OPTIONS: -Dspring.profiles.active=mysql,key-vault,cloud
CFG_FILE_PATTERNS: application/mysql
az spring app deploy \
--name ${{ env.VETS_SERVICE }} \
--service ${{ env.AZURE_SPRING_APPS_SERVICE }} -g ${{ env.RG_APP }} \
--artifact-path ${{ env.VETS_SERVICE_PACKAGE_PATH }} \
--deployment $DEPLOYMENT_NAME \
--disable-probe true \
--env SPRING_CLOUD_AZURE_KEY_VAULT_ENDPOINT=${{ env.SPRING_CLOUD_AZURE_KEY_VAULT_ENDPOINT }} VETS_SVC_APP_IDENTITY_CLIENT_ID=${{ env.VETS_SVC_APP_IDENTITY_CLIENT_ID }} SPRING_CLOUD_AZURE_TENANT_ID=${{ env.SPRING_CLOUD_AZURE_TENANT_ID }} \
--jvm-options="${{ env.DEPLOYMENT_JVM_OPTIONS }}" \
--version ${{ env.DEPLOYMENT_VERSION }} \
--build-env ${{ env.BUILD_ENV }} \
--build-cpu ${{ env.BUILD_CPU }} \
--build-memory ${{ env.BUILD_MEMORY }} \
--builder ${{ env.BUILDER }} \
--config-file-patterns ${{ env.CFG_FILE_PATTERNS }}
Dingmeng is publishing a document on this topic and he should double check.
We have represented this work in an Azure DevOps work item, and will investigate and fix.
#please-close