azure container apps
170 TopicsAnnouncing a flexible, predictable billing model for Azure SRE Agent
Billing for Azure SRE Agent will start on September 1, 2025. Announced at Microsoft Build 2025, Azure SRE Agent is a pre-built AI agent for root cause analysis, uptime improvement, and operational cost reduction. Learn more about the billing model and example scenarios.571Views0likes0CommentsObserve Quarkus Apps with Azure Application Insights using OpenTelemetry
Overview This blog shows you how to observe Red Hat Quarkus applications with Azure Application Insights using OpenTelemetry. The application is a "to do list" with a JavaScript front end and a REST endpoint. Azure Database for PostgreSQL Flexible Server provides the persistence layer for the app. The app utilizes OpenTelemetry to instrument, generate, collect, and export telemetry data for observability. The blog guides you to test your app locally, deploy it to Azure Container Apps and observe its telemetry data with Azure Application Insights. Prerequisites An Azure subscription. If you don't have an Azure subscription, create a free account before you begin. Prepare a local machine with Unix-like operating system installed - for example, Ubuntu, macOS, or Windows Subsystem for Linux. Install a Java SE implementation version 17 - for example, Microsoft build of OpenJDK. Install Maven, version 3.9.8 or higher. Install Docker for your OS. Install the Azure CLI to run Azure CLI commands. Sign in to the Azure CLI by using the az login command. To finish the authentication process, follow the steps displayed in your terminal. For other sign-in options, see Sign into Azure with Azure CLI. When you're prompted, install the Azure CLI extension on first use. For more information about extensions, see Use and manage extensions with the Azure CLI. Run az version to find the version and dependent libraries that are installed. To upgrade to the latest version, run az upgrade. This blog requires at least version 2.65.0 of Azure CLI. Prepare the Quarkus app Run the following commands to get the sample app app-insights-quarkus from GitHub: git clone https://github.com/Azure-Samples/quarkus-azure cd quarkus-azure git checkout 2024-11-27 cd app-insights-quarkus Here's the file structure of the application, with important files and directories: ├── src/main/ │ ├── java/io/quarkus/sample/ │ │ └── TodoResource.java │ └── resources/ │ ├── META-INF/resources/ │ ├── application.properties ├── pom.xml The directory src/main/resources/META-INF/resources contains the front-end code for the application. It's a Vue.js front end where you can view, add, update, and delete todo items. The src/main/java/io/quarkus/sample/TodoResource.java file implements the REST resource for the application. It uses the Jakarta REST API to expose the REST endpoints for the front end. The invocation of the REST endpoints is automatically instrumented by OpenTelemetry tracing. Besides, each REST endpoint uses the org.jboss.logging.Logger to log messages, which are collected by OpenTelemetry logging. For example, the GET method for the /api endpoint that returns all todo items is shown in the following code snippet: package io.quarkus.sample; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.validation.Valid; import jakarta.ws.rs.*; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; import java.util.List; import org.jboss.logging.Logger; @Path("/api") public class TodoResource { private static final Logger LOG = Logger.getLogger(TodoResource.class); @Inject TodoRepository todoRepository; @GET public List<Todo> getAll() { List<Todo> todos = todoRepository.findAll(); LOG.info("Found " + todos.size() + " todos"); return todos; } } The pom.xml file contains the project configuration, including the dependencies for the Quarkus application. The application uses the following extensions to support OpenTelemetry: <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-opentelemetry</artifactId> </dependency> <dependency> <groupId>io.opentelemetry.instrumentation</groupId> <artifactId>opentelemetry-jdbc</artifactId> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-logging</artifactId> </dependency> The src/main/resources/application.properties file contains the configuration for the Quarkus application. The configuration includes database connection properties for production, such as the JDBC URL and username. The configuration also includes the OpenTelemetry properties, such as enabling OpenTelemetry including logs and JDBC instrumentation at build time, using logging as exporter in development mode, and specifying the endpoint for the OpenTelemetry Protocol (OTLP) exporter in production mode. The following example shows the configuration for the OpenTelemetry: quarkus.otel.enabled=true quarkus.otel.logs.enabled=true quarkus.datasource.jdbc.telemetry=true %dev.quarkus.otel.logs.exporter=logging %dev.quarkus.otel.traces.exporter=logging %prod.quarkus.otel.exporter.otlp.endpoint=${OTEL_EXPORTER_OTLP_ENDPOINT} Run the Quarkus app locally Quarkus supports the automatic provisioning of unconfigured services in development mode. For more information, see Dev Services Overview in the Quarkus documentation. Now, run the following command to enter Quarkus dev mode, which automatically provisions a PostgreSQL database as a Docker container for the app: mvn clean package quarkus:dev The output should look like the following example: 2025-03-17 11:14:32,880 INFO [io.qua.dat.dep.dev.DevServicesDatasourceProcessor] (build-26) Dev Services for default datasource (postgresql) started - container ID is 56acc7e1cb46 2025-03-17 11:14:32,884 INFO [io.qua.hib.orm.dep.dev.HibernateOrmDevServicesProcessor] (build-4) Setting quarkus.hibernate-orm.database.generation=drop-and-create to initialize Dev Services managed database __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2025-03-17 11:14:36,202 INFO [io.ope.exp.log.LoggingSpanExporter] (JPA Startup Thread) 'quarkus' : 80437b598962f82bffd0735bbf00e9f1 aa86f0553056a8c9 CLIENT [tracer: io.opentelemetry.jdbc:2.8.0-alpha] AttributesMap{data={db.name=quarkus, server.port=59406, server.address=localhost, db.connection_string=postgresql://localhost:59406, db.statement=set client_min_messages = WARNING, db.system=postgresql}, capacity=128, totalAddedValues=6} 1970-01-01T00:00:00Z INFO ''quarkus' : 80437b598962f82bffd0735bbf00e9f1 aa86f0553056a8c9 CLIENT [tracer: io.opentelemetry.jdbc:2.8.0-alpha] AttributesMap{data={db.name=quarkus, server.port=59406, server.address=localhost, db.connection_string=postgresql://localhost:59406, db.statement=set client_min_messages = WARNING, db.system=postgresql}, capacity=128, totalAddedValues=6}' : 00000000000000000000000000000000 0000000000000000 [scopeInfo: io.quarkus.opentelemetry:] {code.lineno=-1, log.logger.namespace="org.jboss.logmanager.Logger", thread.id=122, thread.name="JPA Startup Thread"} 2025-03-17 11:14:36,236 INFO [io.ope.exp.log.LoggingSpanExporter] (JPA Startup Thread) 'DROP table quarkus' : 6b732661c29a9f0966403d49db9e4cff d86f29284f0d8eac CLIENT [tracer: io.opentelemetry.jdbc:2.8.0-alpha] AttributesMap{data={db.operation=DROP table, db.name=quarkus, server.port=59406, server.address=localhost, db.connection_string=postgresql://localhost:59406, db.statement=drop table if exists Todo cascade, db.system=postgresql}, capacity=128, totalAddedValues=7} 1970-01-01T00:00:00Z INFO ''DROP table quarkus' : 6b732661c29a9f0966403d49db9e4cff d86f29284f0d8eac CLIENT [tracer: io.opentelemetry.jdbc:2.8.0-alpha] AttributesMap{data={db.operation=DROP table, db.name=quarkus, server.port=59406, server.address=localhost, db.connection_string=postgresql://localhost:59406, db.statement=drop table if exists Todo cascade, db.system=postgresql}, capacity=128, totalAddedValues=7}' : 00000000000000000000000000000000 0000000000000000 [scopeInfo: io.quarkus.opentelemetry:] {code.lineno=-1, log.logger.namespace="org.jboss.logmanager.Logger", thread.id=122, thread.name="JPA Startup Thread"} 2025-03-17 11:14:36,259 INFO [io.ope.exp.log.LoggingSpanExporter] (JPA Startup Thread) 'CREATE table quarkus' : 54df3edf9f523a71bc85d0106a57016c bb43aa63ec3526ed CLIENT [tracer: io.opentelemetry.jdbc:2.8.0-alpha] AttributesMap{data={db.operation=CREATE table, db.name=quarkus, server.port=59406, server.address=localhost, db.connection_string=postgresql://localhost:59406, db.statement=create table Todo (completed boolean not null, ordering integer, id bigint generated by default as identity, title varchar(?) unique, url varchar(?), primary key (id)), db.system=postgresql}, capacity=128, totalAddedValues=7} 1970-01-01T00:00:00Z INFO ''CREATE table quarkus' : 54df3edf9f523a71bc85d0106a57016c bb43aa63ec3526ed CLIENT [tracer: io.opentelemetry.jdbc:2.8.0-alpha] AttributesMap{data={db.operation=CREATE table, db.name=quarkus, server.port=59406, server.address=localhost, db.connection_string=postgresql://localhost:59406, db.statement=create table Todo (completed boolean not null, ordering integer, id bigint generated by default as identity, title varchar(?) unique, url varchar(?), primary key (id)), db.system=postgresql}, capacity=128, totalAddedValues=7}' : 00000000000000000000000000000000 0000000000000000 [scopeInfo: io.quarkus.opentelemetry:] {code.lineno=-1, log.logger.namespace="org.jboss.logmanager.Logger", thread.id=122, thread.name="JPA Startup Thread"} 2025-03-17 11:14:36,438 INFO [io.quarkus] (Quarkus Main Thread) quarkus-todo-demo-app-insights 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.16.3) started in 7.409s. Listening on: http://localhost:8080 1970-01-01T00:00:00Z INFO 'quarkus-todo-demo-app-insights 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.16.3) started in 7.409s. Listening on: http://localhost:8080' : 00000000000000000000000000000000 0000000000000000 [scopeInfo: io.quarkus.opentelemetry:] {code.function="printStartupTime", code.lineno=109, code.namespace="io.quarkus.bootstrap.runner.Timing", log.logger.namespace="org.jboss.logging.Logger", thread.id=112, thread.name="Quarkus Main Thread"} 2025-03-17 11:14:36,441 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 1970-01-01T00:00:00Z INFO 'Profile dev activated. Live Coding activated.' : 00000000000000000000000000000000 0000000000000000 [scopeInfo: io.quarkus.opentelemetry:] {code.function="printStartupTime", code.lineno=113, code.namespace="io.quarkus.bootstrap.runner.Timing", log.logger.namespace="org.jboss.logging.Logger", thread.id=112, thread.name="Quarkus Main Thread"} 2025-03-17 11:14:36,443 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-validator, jdbc-postgresql, narayana-jta, opentelemetry, rest, rest-jackson, smallrye-context-propagation, vertx] 1970-01-01T00:00:00Z INFO 'Installed features: [agroal, cdi, hibernate-orm, hibernate-validator, jdbc-postgresql, narayana-jta, opentelemetry, rest, rest-jackson, smallrye-context-propagation, vertx]' : 00000000000000000000000000000000 0000000000000000 [scopeInfo: io.quarkus.opentelemetry:] {code.function="printStartupTime", code.lineno=115, code.namespace="io.quarkus.bootstrap.runner.Timing", log.logger.namespace="org.jboss.logging.Logger", thread.id=112, thread.name="Quarkus Main Thread"} -- Tests paused Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options> The output shows that the Quarkus app is running in development mode. The app is listening on http://localhost:8080. The PostgreSQL database is automatically provisioned as a Docker container for the app. The OpenTelemetry instrumentation for Quarkus and JDBC is enabled, and the telemetry data is exported to the console. Access the application GUI at http://localhost:8080. You should see a similar Todo app with an empty todo list, as shown in the following screenshot: Switch back to the terminal where Quarkus dev mode is running, you should see more OpenTelemetry data exported to the console. For example, the following output shows the OpenTelemetry logging and tracing data for the GET method for the /api endpoint: 2025-03-17 11:15:13,785 INFO [io.qua.sam.TodoResource] (executor-thread-1) Found 0 todos 1970-01-01T00:00:00Z INFO 'Found 0 todos' : 7cf260232ff81caf90abc354357c16ab c48a4a02e74e1901 [scopeInfo: io.quarkus.opentelemetry:] {code.function="getAll", code.lineno=25, code.namespace="io.quarkus.sample.TodoResource", log.logger.namespace="org.jboss.logging.Logger", parentId="c48a4a02e74e1901", thread.id=116, thread.name="executor-thread-1"} 2025-03-17 11:15:13,802 INFO [io.ope.exp.log.LoggingSpanExporter] (vert.x-eventloop-thread-1) 'GET /api' : 7cf260232ff81caf90abc354357c16ab c48a4a02e74e1901 SERVER [tracer: io.quarkus.opentelemetry:] AttributesMap{data={http.response.status_code=200, url.scheme=http, server.port=8080, server.address=localhost, client.address=0:0:0:0:0:0:0:1, user_agent.original=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0, url.path=/api/, code.namespace=io.quarkus.sample.TodoResource, http.request.method=GET, code.function=getAll, http.response.body.size=2, http.route=/api}, capacity=128, totalAddedValues=12} 1970-01-01T00:00:00Z INFO ''GET /api' : 7cf260232ff81caf90abc354357c16ab c48a4a02e74e1901 SERVER [tracer: io.quarkus.opentelemetry:] AttributesMap{data={http.response.status_code=200, url.scheme=http, server.port=8080, server.address=localhost, client.address=0:0:0:0:0:0:0:1, user_agent.original=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0, url.path=/api/, code.namespace=io.quarkus.sample.TodoResource, http.request.method=GET, code.function=getAll, http.response.body.size=2, http.route=/api}, capacity=128, totalAddedValues=12}' : 00000000000000000000000000000000 0000000000000000 [scopeInfo: io.quarkus.opentelemetry:] {code.function="export", code.lineno=65, code.namespace="io.opentelemetry.exporter.logging.LoggingSpanExporter", log.logger.namespace="org.jboss.logmanager.Logger", thread.id=126, thread.name="vert.x-eventloop-thread-1"} Then return to the web browser and interact with the Todo app, try to add a new todo item by typing in the text box and pressing ENTER, selecting the checkbox to mark the todo item as completed, or selecting Clear completed to remove all completed todo items. You can also delete a todo item by selecting the x icon when you hover over it. The app should work as expected. Finally, switch back to the terminal and press q to exit Quarkus dev mode. Create the Azure resources The steps in this section show you how to create the following Azure resources to run the Quarkus sample app on Azure: Azure Database for PostgreSQL Flexible Server Azure Container Registry Azure Container Apps environment Azure Application Insights First, define the following variables in your bash shell by replacing the placeholders with your own values. They will be used throughout the example: UNIQUE_VALUE=<your unique value> LOCATION=eastus2 RESOURCE_GROUP_NAME=${UNIQUE_VALUE}rg DB_SERVER_NAME=${UNIQUE_VALUE}db DB_NAME=demodb REGISTRY_NAME=${UNIQUE_VALUE}reg ACA_ENV=${UNIQUE_VALUE}env APP_INSIGHTS=${UNIQUE_VALUE}appinsights ACA_NAME=${UNIQUE_VALUE}aca Next, create the resource group to host Azure resources: az group create \ --name $RESOURCE_GROUP_NAME \ --location $LOCATION Then, create the Azure resources in the resource group by following the steps below. Create an Azure Database for PostgreSQL flexible server instance: az postgres flexible-server create \ --name $DB_SERVER_NAME \ --resource-group $RESOURCE_GROUP_NAME \ --database-name $DB_NAME \ --public-access None \ --sku-name Standard_B1ms \ --tier Burstable \ --active-directory-auth Enabled Create the Azure Container Registry and get the login server: az acr create \ --resource-group $RESOURCE_GROUP_NAME \ --location ${LOCATION} \ --name $REGISTRY_NAME \ --sku Basic LOGIN_SERVER=$(az acr show \ --name $REGISTRY_NAME \ --query 'loginServer' \ --output tsv) Create the Azure Container Apps environment: az containerapp env create \ --resource-group $RESOURCE_GROUP_NAME \ --location $LOCATION \ --name $ACA_ENV Create an Azure Application Insights instance: logAnalyticsWorkspace=$(az monitor log-analytics workspace list \ -g $RESOURCE_GROUP_NAME \ --query "[0].name" -o tsv | tr -d '\r') az monitor app-insights component create \ --resource-group $RESOURCE_GROUP_NAME \ --location $LOCATION \ --app $APP_INSIGHTS \ --workspace $logAnalyticsWorkspace Use the created Application Insights instance as the destination service to enable the managed OpenTelemetry agent for the Azure Container Apps environment: appInsightsConn=$(az monitor app-insights component show \ --app $APP_INSIGHTS \ -g $RESOURCE_GROUP_NAME \ --query 'connectionString' -o tsv | tr -d '\r') az containerapp env telemetry app-insights set \ --name $ACA_ENV \ --resource-group $RESOURCE_GROUP_NAME \ --connection-string $appInsightsConn \ --enable-open-telemetry-logs true \ --enable-open-telemetry-traces true When you deploy the Quarkus app to the Azure Container Apps environment later in this blog, the OpenTelemetry data is automatically collected by the managed OpenTelemetry agent and exported to the Application Insights instance. Deploy the Quarkus app to Azure Container Apps You have set up all the necessary Azure resources to run the Quarkus app on Azure Container Apps. In this section, you containerize the Quarkus app and deploy it to Azure Container Apps. First, use the following command to build the application. This command uses the Jib extension to build the container image. Quarkus instrumentation works both in JVM and native modes. In this blog, you build the container image for JVM mode, to work with Microsoft Entra ID authentication for Azure Database for PostgreSQL flexible server. TODO_QUARKUS_IMAGE_NAME=todo-quarkus-app-insights TODO_QUARKUS_IMAGE_TAG=${LOGIN_SERVER}/${TODO_QUARKUS_IMAGE_NAME}:1.0 mvn clean package -Dquarkus.container-image.build=true -Dquarkus.container-image.image=${TODO_QUARKUS_IMAGE_TAG} Next, sign in to the Azure Container Registry and push the Docker image to the registry: az acr login --name $REGISTRY_NAME docker push $TODO_QUARKUS_IMAGE_TAG Then, use the following command to create a Container Apps instance to run the app after pulling the image from the Container Registry: az containerapp create \ --resource-group $RESOURCE_GROUP_NAME \ --name $ACA_NAME \ --image $TODO_QUARKUS_IMAGE_TAG \ --environment $ACA_ENV \ --registry-server $LOGIN_SERVER \ --registry-identity system \ --target-port 8080 \ --ingress 'external' \ --min-replicas 1 Finally, connect the Azure Database for PostgreSQL Flexible Server instance to the container app using Service Connector: # Install the Service Connector passwordless extension az extension add --name serviceconnector-passwordless --upgrade --allow-preview true az containerapp connection create postgres-flexible \ --resource-group $RESOURCE_GROUP_NAME \ --name $ACA_NAME \ --target-resource-group $RESOURCE_GROUP_NAME \ --server $DB_SERVER_NAME \ --database $DB_NAME \ --system-identity \ --container $ACA_NAME \ --yes Wait for a while until the application is deployed, started and running. Then get the application URL and open it in a browser: QUARKUS_URL=https://$(az containerapp show \ --resource-group $RESOURCE_GROUP_NAME \ --name $ACA_NAME \ --query properties.configuration.ingress.fqdn -o tsv) echo $QUARKUS_URL You should see the similar Todo app when you ran the app locally before. Interact with the app by adding, completing and removing todo items, which generates telemetry data and sends it to Azure Application Insights. Observe the Quarkus app with Azure Application Insights Open the Azure Portal and navigate to the Azure Monitor Application Insights resource you created earlier. You can monitor the application with different views backed by the telemetry data sent from the application. For example: Investigate > Application map: Shows the application components and their dependencies. Investigate > Failures: Shows the failures and exceptions in the application. Investigate > Performance: Shows the performance of the application. Monitoring > Logs: Shows the logs and traces of the application. You may notice that metrics are not observed in the Application Insights in this blog, that's because the Application Insights endpoint used in the managed OpenTelemetry agent doesn't accept metrics yet, which is listed as a known limitation. This is also the reason why Quarkus metrics is not enabled in the configuration file with quarkus.otel.metrics.enabled=true above. Alternatively, you can consider using Quarkus Opentelemetry Exporter for Microsoft Azure in your Quarkus apps to export the telemetry data directly to Azure Application Insights. Clean up resources To avoid Azure charges, you should clean up unneeded resources. When the resources are no longer needed, use the az group delete command to remove the resource group and all Azure resources within it: az group delete \ --name $RESOURCE_GROUP_NAME \ --yes \ --no-wait Next steps In this blog, you observe the Quarkus app with Azure Application Insights using OpenTelemetry. To learn more, explore the following resources: OpenTelemetry on Azure Collect and read OpenTelemetry data in Azure Container Apps (preview) Application Insights overview Using OpenTelemetry Deploy a Java application with Quarkus on an Azure Container Apps Secure Quarkus applications with Microsoft Entra ID using OpenID Connect Deploy a Java application with Quarkus on an Azure Kubernetes Service cluster Deploy serverless Java apps with Quarkus on Azure Functions Jakarta EE on Azure688Views2likes3CommentsEnhancing Performance in Azure Container Apps
Azure Container Apps is a fully managed serverless container service that enables you to deploy and run applications without having to manage the infrastructure. The Azure Container Apps team has made improvements recently to the load balancing algorithm and scaling behavior to better align with customer expectations to meet their performance needs.6.4KViews3likes1CommentAccelerating Azure Container Apps with the Azure CLI and Compose Files
I’d like to introduce the containerapp-compose Azure CLI extension. This extension adds a command group to the containerapps extension, allowing you to start with a Compose file and a resource group in Azure and end up with a deployed Azure Container Apps environment and running services.11KViews1like9CommentsAzure Container Apps with Application Gateway and custom domain: hostname mismatch
Introduction Azure Container Apps offers a robust platform for deploying microservices and containerized applications. When integrating with Azure Application Gateway, an internal container app environment can be accessed via public internet. Users often bind custom domains to enhance accessibility and user experience. A common challenge arises when we bind the custom domain on Application Gateway and try to access container app. Container app is acting as a middleware service and needs to forward request to another API server or finish authentication process, users may encountered HTTP 403 forbidden error which is caused by hostname/redirect URL mismatch. What's more, you definitely don't want to expose your backend service default domain. This blog explores these challenges and offers practical solutions. Why do we encounter this kind of issue: By following our documentation Protect Azure Container Apps with Application Gateway and Web Application Firewall (WAF) | Microsoft Learn, we put the application gateway in front of internal container app, custom domain was resolved to application gateway public IP, and we use default domain of container app as backend pool. When application gateway receives the custom domain request, it will route the request to container app via its default domain. So far, everything seems normal, and users can successfully access the internal container app through the Internet via the custom domain name. However, if the container app is a middleware service, or authentication is required, we will see that the container app use its default domain name to redirect, which often results in a 403 forbidden error due to hostname/redirect URL mismatch. Proposed Solutions 1 (Adding custom domain in container app) To resolve this issue and ensure seamless integration between Azure Container Apps and other services, consider the following steps: 1. Bind custom domain on container app as well. We need to go to container app portal-->Custom domains to add the same custom domain as application gateway. This is internal container app, so we don't need to worry about domain name duplication. 2. Modify Backend setting in application gateway. Navigate to application gateway-->backend settings-->we select override with specific domain name and put your custom domain in Host name. 3. Now, container app is able to reach another service or finish authentication with custom domain. Proposed Solutions 2 (Modifying container app auth configs) For passing Azure AD authentication, Application Gateway uses a header called 'X-Original-Host' to let container app adapt the original hostname. We can use this REST API to update 'forwardProxy' in container app auth configs. Container Apps Auth Configs - Create Or Update - REST API (Azure Azure Container Apps) | Microsoft Learn Reference: Protect Azure Container Apps with Application Gateway and Web Application Firewall (WAF) | Microsoft Learn Host name preservation - Azure Architecture Center | Microsoft Learn Deploying a secure, resilient site with a custom domain - Azure App Service What Is Application Gateway Integration? - Azure App Service | Microsoft Learn781Views0likes0CommentsRunning Self-hosted APIM Gateways in Azure Container Apps with VNet Integration
With Azure Container Apps we can run containerized applications, completely serverless. The platform itself handles all the orchestration needed to dynamically scale based on your set triggers (such as KEDA) and even scale-to-zero! I have been working a lot with customers recently on using Azure API Management (APIM) and the topic of how we can leverage Azure APIM to manage our internal APIs without having to expose a public IP and stay within compliance from a security standpoint, which leads to the use of a Self-Hosted Gateway. This offers a managed gateway deployed within their network, allowing a unified approach in managing their APIs while keeping all API communication in-network. The self-hosted gateway is deployed as a container and in this article, we will go through how to provision a self-hosted gateway on Azure Container Apps specifically. I assume there is already an Azure APIM instance provisioned and will dive into creating and configuring the self-hosted gateway on ACA. Prerequisites As mentioned, ensure you have an existing Azure API Management instance. We will be using the Azure CLI to configure the container apps in this walkthrough. To run the commands, you need to have the Azure CLI installed on your local machine and ensure you have the necessary permissions in your Azure subscription. Retrieve Gateway Deployment Settings from APIM First, we need to get the details for our gateway from APIM. Head over to the Azure portal and navigate to your API Management instance. - In the left menu, under Deployment and infrastructure, select Gateways. - Here, you'll find the gateway resource you provisioned. Click on it and go to Deployment. - You'll need to copy the Gateway Token and Configuration endpoint values. (these tell the self-hosted gateway which APIM instance and Gateway to register under) Create a Container Apps Environment Next, we need to create a Container Apps environment. This is where we will create the container app in which our self-hosted gateway will be hosted. Using Azure CLI: Create our VNet and Subnet for our ACA Environment As we want access to our internal APIs, when we create the container apps environment, we need to have the VNet created with a subnet available. Note: If we’re using Workload Profiles (we will in this walkthrough), then we need to delegate the subnet to Microsoft.App/environments. # Create the vnet az network vnet create --resource-group rgContosoDemo \ --name vnet-contoso-demo \ --location centralUS \ --address-prefix 10.0.0.0/16 # Create the subnet az network vnet subnet create --resource-group rgContosoDemo \ --vnet-name vnet-contoso-demo \ --name infrastructure-subnet \ --address-prefixes 10.0.0.0/23 # If you are using a workload profile (we are for this walkthrough) then delegate the subnet az network vnet subnet update --resource-group rgContosoDemo \ --vnet-name vnet-contoso-demo \ --name infrastructure-subnet \ --delegations Microsoft.App/environments Create the Container App Environment in out VNet az containerapp env create --name aca-contoso-env \ --resource-group rgContosoDemo \ --location centralUS \ --enable-workload-profiles Deploy the Self-Hosted Gateway to a Container App Creating the environment takes about 10 minutes and once complete, then comes the fun part—deploying the self-hosted gateway container image to a container app. Using Azure CLI: Create the Container App: az containerapp create --name aca-apim-demo-gateway \ --resource-group rgContosoDemo \ --environment aca-contoso-env \ --workload-profile-name "Consumption" \ --image "mcr.microsoft.com/azure-api-management/gateway:2.5.0" \ --target-port 8080 \ --ingress 'external' \ ---env-vars "config.service.endpoint"="<YOUR_ENDPOINT>" "config.service.auth"="<YOUR_TOKEN>" "net.server.http.forwarded.proto.enabled"="true" Here, you'll replace <YOUR_ENDPOINT> and <YOUR_TOKEN> with the values you copied earlier. Configure Ingress for the Container App: az containerapp ingress enable --name aca-apim-demo-gateway --resource-group rgContosoDemo --type external --target-port 8080 This command ensures that your container app is accessible externally. Verify the Deployment Finally, let's make sure everything is running smoothly. Navigate to the Azure portal and go to your Container Apps environment. Select the container app you created (aca-apim-demo-gateway) and navigate to Replicas to verify that it's running. You can use the status endpoint of the self-hosted gateway to determine if your gateway is running as well: curl -i https://aca-apim-demo-gateway.sillytreats-abcd1234.centralus.azurecontainerapps.io/status-012345678990abcdef Verify Gateway Health in APIM You can navigate in the Azure Portal to APIM and verify the gateway is showing up as healthy. Navigate to Deployment and Infrastructure, select Gateways then choose your Gateway. On the Overview page you’ll see the status of your gateway deployment. And that’s it! You've successfully deployed an Azure APIM self-hosted gateway in Azure Container Apps with VNet integration allowing access to your internal APIs with easy management from the APIM portal in Azure. This setup allows you to manage your APIs efficiently while leveraging the scalability and flexibility of Azure Container Apps. If you have any questions or need further assistance, feel free to ask. How are you feeling about this setup? Does it make sense, or is there anything you'd like to dive deeper into?1.2KViews3likes2CommentsAnnouncing Azure Command Launcher for Java
Optimizing JVM Configuration for Azure Deployments Tuning the Java Virtual Machine (JVM) for cloud deployments is notoriously challenging. Over 30% of developers tend to deploy Java workloads with no JVM configuration at all, therefore relying on the default settings of the HotSpot JVM. The default settings in OpenJDK are intentionally conservative, designed to work across a wide range of environments and scenarios. However, these defaults often lead to suboptimal resource utilization in cloud-based deployments, where memory and CPU tend to be dedicated for application workloads (use of containers and VMs) but still require intelligent management to maximize efficiency and cost-effectiveness. To address this, we are excited to introduce jaz, a new JVM launcher optimized specifically for Azure. jaz provides better default ergonomics for Java applications running in containers and virtual machines, ensuring a more efficient use of resources right from the start, and leverages advanced JVM features automatically, such as AppCDS and in the future, Project Leyden. Why jaz? Conservative Defaults Lead to Underutilization of Resources When deploying Java applications to the cloud, developers often need to fine-tune JVM parameters such as heap size, garbage collection strategies, and other tuning configurations to achieve better resource utilization and potentially higher performance. The default OpenJDK settings, while safe, do not take full advantage of available resources in cloud environments, leading to unnecessary waste and increased operational costs. While advancements in dynamic heap sizing are underway by Oracle, Google, and Microsoft, they are still in development and will be available primarily in future major releases of OpenJDK. In the meantime, developers running applications on current and older JDK versions (such as OpenJDK 8, 11, 17, and 21) still need to optimize their configurations manually or rely on external tools like Paketo Buildpacks, which automate tuning but may not be suitable for all use cases. With jaz, we are providing a smarter starting point for Java applications on Azure, with default configurations designed for cloud environments. The jaz launcher helps by: Optimizing resource utilization: By setting JVM parameters tailored for cloud deployments, jaz reduces wasted memory and CPU cycles. Improve first-deploy performance: New applications often require trial and error to find the right JVM settings. jaz increases the likelihood of better performance on first deployment. Enhance cost efficiency: By making better use of available resources, applications using jaz can reduce unnecessary cloud costs. This tool is ideal for developers who: Want better JVM defaults without diving deep into tuning guides Develop and deploy cloud native microservices with Spring Boot, Quarkus, or Micronaut Prefer container-based workflows such as Kubernetes and OpenShift Deploy Java workloads on Azure Container Apps, Azure Kubernetes Service, Azure Red Hat OpenShift, or Azure VMs How jaz works? jaz sits between your container startup command and the JVM. It will: Detect the cloud environment (e.g., container limits, available memory) Analyzes the workload type and selects best-fit JVM options Launches the Java process with optimized flags, such as: Heap sizing GC selection and tuning Logging and diagnostics settings as needed Example Usage Instead of this: $ JAVA_OPTS="-XX:... several JVM tuning flags" $ java $JAVA_OPTS -jar myapp.jar" Use: $ jaz -jar myapp.jar You will automatically benefit from: Battle-tested defaults for cloud native and container workloads Reduced memory waste Better startup and warmup performance No manual tuning required How to Access jaz (Private Preview) jaz is currently available through a Private Preview. During this phase, we are working closely with selected customers to refine the experience and gather feedback. To request access: 👉 Submit your interest here Participants in the Private Preview will receive access to jaz via easily installed standalone Linux packages for container images of the Microsoft Build of OpenJDK and Eclipse Temurin (for Java 8). Customers will have direct communication with our engineering and product teams to further enhance the tool to fit their needs. For a sneak peek, you can read the documentation. Our Roadmap Our long-term vision for jaz includes adaptive JVM configuration based on telemetry and usage patterns, helping developers achieve optimal performance across all Azure services. ⚙️ JVM Configuration Profiles 📦 AppCDS Support 📦 Leyden Support 🔄 Continuous Tuning 📊 Share telemetry through Prometheus We’re excited to work with the Java community to shape this tool. Your feedback will be critical in helping us deliver a smarter, cloud-native Java runtime experience on Azure.345Views0likes0CommentsUnderstanding Idle Usage in Azure Container Apps
Introduction Azure Container Apps provides a serverless platform for running containers at scale, and one of the big benefits is that you can easily scale workloads to zero when they are not getting any traffic. Scaling to zero ensures you only pay when your workloads are actively receiving traffic or performing work. However, for some workloads, scaling to zero might not be possible for a variety of reasons. Some workloads must always be able to respond to requests quickly, and the time it takes to scale from 0 to 1 replicas, while short, is too long. Some applications need to be able to always respond to health checks, and so removing all replicas is not possible. In these scenarios, there may still be time periods where there is no traffic, or the application isn't doing any work. While you can't reduce costs to zero in these scenarios, you can reduce them through the concept of "Idle" usage charges. Where it is possible to scale your application to zero, it is recommended that this is where you focus your effort and optimise for scaling down when idle. What Are Idle Charges? When using the consumption plan (and idle charges only apply to the consumption plan), you are paying per second for vCPU and memory. In the central US region, this is currently $0.000024 per vCPU per second and $0.000003 per MB per second for Pay as You Go pricing. This cost is based on the resources you’ve requested and that have been allocated to your container, which may not be what you are actually consuming at any point in time. If your container qualifies for idle billing, the CPU cost drops to $0.000003 per vCPU per second, which is a fairly significant drop. Memory costs remain the same. Eligibility for Idle Pricing To be eligible to receive idle pricing, your container needs to meet several criteria: Consumption Plan - Idle pricing is only applicable to containers in the consumption plan. Resources in a dedicated plan are charged for the underlying compute resources and do not receive idle pricing. Not GPU Apps - Idle pricing does not apply to containers that have GPUs allocated. Scaled to Minimum - To be eligible for idle pricing, the app must be scaled to the minimum replica count. This does not have to be one replica; you can still have multiple replicas to support availability zone deployments or similar, but your app needs to be at whatever the minimum number you set is. All Containers Are Running - All containers in an app must be running to get idle charges, so if any are in a failed state, you will not see this pricing. Not a Container App Job - Jobs are stopped once the job completes, and so do not get charged. Meet the Required Resource Usage - Containers must have the following resource usage to be eligible: Not processing any HTTP requests Using less than 0.01 vCPU cores (this is actual usage) Receiving less than 1000 bytes/s of inbound network traffic You should see on your bill that your usage during the time when all of the above is true is shown as idle usage. Monitoring Idle Usage There is no single metric or counter that will show you if a container is in a state where it will get idle billing; instead, you need to look at a few different things. Things like being on the consumption plan and not using GPUs are static and something that you can check on your container at any point. The metrics that will vary are scale, HTTP requests, vCPU cores, and network traffic. We can view all of these metrics in Azure Monitor under the following counters: Counter Name Details Expected Value Replica Count Shows the number of replicas currently running The value set for "Min Replicas" in the scale rule settings CPU Usage The amount of CPU cores in use (ensure this is "CPU Usage" and not "CPU Usage Percentage") Less than 0.01 Network in Bytes The amount of network traffic being received Less than 1000 bytes/s Requests The amount of HTTP requests 0 Ensure that your granularity is set to the lowest level (1 minute) to avoid skewed results due to averaging. Common Pitfalls Health Probes Health probes from the ACA environment to confirm that containers are running and healthy are not counted when it comes to HTTP requests or network traffic. However, health checks that come from outside the container environment are treated the same way as any other inbound traffic and will cause your container to no longer be "idle". If your container is behind Azure Front Door, API Manager, or Application Gateway, you will likely have this configured to check the health of the service, and this will be causing the container to show as active. For some services, like Azure Front Door, the amount of health probes will be high and may stop your container from ever dropping into an idle state. For some applications, this real-time health reporting at the ingress layer is vital for ensuring the health of the application and being able to failover or heal from an issue. If that is the case, then you may not be able to take advantage of idle pricing. However, if there is some flexibility possible in this report, there are some techniques you can use to reduce the number of probes that reach the container app: Reduce probe frequency – Tools like Azure Front Door will query a health probe endpoint every 30 seconds by default, and this will be done by every point of presence in the network, resulting in a lot of requests. Most services have the option to reduce the frequency of requests, making it more likely that you will meet the requirements for idle billing. Aggregating and Caching – Rather than your ingress solution querying every application directly, you could use tools like Azure API Manager (APIM) to create a health endpoint that aggregates health data from the appropriate endpoints and caches it. Your ingress solution then queries APIM, and APIM only sends requests to the backend container apps when needed, and only to the applications that need to be queried to understand the overall application health. ARM Health APIs – As mentioned, the health queries done by the Container App Environment to ensure the Container Apps are healthy are not counted when it comes to idle billing. This health data is used to indicate whether the Container App is healthy and is reported in the ARM APIs for the Container App. You could have your ingress solution make a call to the ARM APIs to get this status, rather than querying the application directly. External Services Many applications will make calls out to external services, which could include: Monitoring services to record metrics and logs Messaging services (Service Bus, Storage Queues) to check for messages to process Background tasks to write data to external datastores While these tasks are mostly outbound data, they will often result in some level of inbound response to the Container App, which could push you over the 1000 bytes/s limit. Again, some of this may be unavoidable, but where possible, you will need to limit the amount of communication with external services while idle. For example: Avoid polling external services – Polling queues like Service Bus, Storage Queues, or APIs can generate frequent inbound responses. Where possible, use event-driven patterns to eliminate polling entirely. Throttle background tasks – If your app writes to external datastores or sends telemetry, ensure these tasks are batched or delayed. Avoid frequent writes that trigger acknowledgements or status responses. Limit monitoring and logging traffic – Sending metrics or logs to external monitoring platforms (e.g., Prometheus, Datadog) can result in inbound handshakes or status codes. Use buffering and reduce the frequency of exports. Use lightweight protocols – Where possible, prefer protocols or formats that minimize response size (e.g., gRPC over HTTP, compressed JSON). CPU Usage The amount of CPU usage allowed when in an idle state is low, at 0.01 vCPU/s. It is fairly easy for a container to breach that threshold even when not handling any inbound requests. Some reasons might include: Background threads that are running at all times Inefficient idle code not using proper async patterns Polling external services for messages Garbage collection or other framework services Additional services running within your container, but not used To avoid unintentionally breaching the 0.01 vCPU/s limit and losing idle billing benefits, consider the following strategies: Use proper async patterns – Avoid tight loops or background threads that run continuously. Instead, use async/await, Task.Delay(), or similar non-blocking patterns to yield CPU time. This helps ensure your container doesn’t exceed the 0.01 vCPU/s idle threshold. Throttle or eliminate background activity – If your app polls external services (e.g., queues, APIs), increase the polling interval or switch to event-driven triggers like KEDA. This reduces unnecessary CPU usage during idle periods. Tune framework and runtime settings – Some frameworks (like .NET or Java) may perform garbage collection or diagnostics even when idle. Configure these features to run less frequently or disable them if not needed. Audit container contents – Remove any unnecessary services or agents bundled in your container image. Background daemons, telemetry exporters, or cron jobs can all contribute to idle CPU usage. Monitor and profile – Use Azure Monitor to track CPU usage per replica. Set alerts for unexpected spikes and use profiling tools to identify hidden sources of CPU consumption. Summary Idle usage pricing is a good way to reduce your Container App bill if you are unable to scale to zero but do have periods where your applications are idle. Scaling to zero will always provide the lowest and most predictable cost, and I recommend using this wherever possible. However, when that is not feasible, idle pricing may be applicable. Being eligible for idle pricing does require meeting some fairly low resource limits. If your application is likely to spend a good amount of time idling, it is worth doing some work to optimize your containers to ensure they are using as few resources as possible when they are not servicing requests.800Views1like0Comments