Cloud Native application deployment using a K8s based service or tool is now a common practice providing -
- Immense Flexibility
- Scalability
- Resiliency
- Reliability
This allows organizations to rollout Stable releases much faster. But there are downsides of this approach as well:
- Need to have a deep insights into K8s eco-system
- Managing the K8s Cluster - its security, performance, upgrades
- Additional effort needed for ensuring Application Isolation, Security, Multi-tenancy
- Solid implementation of Container Insight solutions for continuous monitoring of Pods, Services and Nodes
- Imperative to have some 3rd party solutions like Service Mesh to have better insights into application flow and integrations for complex systems with large no of granular Microservice
While managed services like AKS provides a lot of relief to the Organizations but they want to move towards an even more Managed solution that can take away the complexities of K8s eco-system and its subsequent management; yet they do not want to compromise on most of the K8s benefits.
Azure Container Apps is a service aimed at solving this problem and make Microservices deployment easier and quicker!
- A deep insights into Azure Container Apps
- Benefits
- Features
- How to Setup - Azure CLI and ARM
- [Connected Examples](#Connecting the Dots...)
Azure Container Apps enables users to run containerized applications in a completely Serverless manner providing complete isolation of Orchestration and Infrastructure. Few Common uses of Azure Container Apps include:
- Deploying API endpoints
- Hosting background processing applications
- Handling event-driven processing
- Running microservice
Applications built on Azure Container Apps can dynamically scale based on the various triggers as well as KEDA-supported scaler
Features of Azure Container Apps include:
- Run multiple Revisions of containerized applications
- Autoscale apps based on any KEDA-supported scale trigger
- Enable HTTPS Ingress without having to manage other Azure infrastructure like L7 Load Balancers
- Easily implement Blue/Green deployment and perform A/B Testing by splitting traffic across multiple versions of an application
- Azure CLI extension or ARM templates to automate management of containerized applications
- Manage Application Secrets securely
- View Application Logs using Azure Log Analytics
tenantId="<tenantId>"
subscriptionId="<subscriptionId>"
resourceGroup="<resourceGroup>"
monitoringResourceGroup="<monitoringResourceGroup>"
location="<location>"
logWorkspace="<logWorkspace>"
basicEnvironment="basic-env"
securedEnvironment="secure-env"
acrName="<acrName>"
registryServer="<container_registry_server>"
registryUserName="<container_registry_username>"
registryPassword="<container_registry_password>"
# Optional - NOT a requirement for Contyainer Apps but mostly for microservice applications
storageName="<storage_account_name>"
# Optional - Primary for Securing Container Apps
containerAppVnetName="containerapp-workshop-vnet"
containerAppVnetId=
# Optional - Subnet for Control plane of the Container Apps Infrastructure
controlPlaneSubnetName="containerapp-cp-subnet"
controlPlaneSubnetId=
# Optional - Subnet for hosting Container Apps
appsSubnetName="containerapp-app-subnet"
appsSubnetId=
# Both Control plane Subnet and Application Services Subnet should be in same VNET viz. $containerAppVnetName
# Add CLI extension for Container Apps
az extension add \
--source https://workerappscliextension.blob.core.windows.net/azure-cli-extension/containerapp-0.2.0-py2.py3-none-any.whl
# Register the Microsoft.Web namespace
az provider register --namespace Microsoft.Web
az provider show --namespace Microsoft.Web
# Hosting Container Apps
az group create --name $resourceGroup --location $location
# Hosting Log Analytics Workspace for Container Apps
az group create --name $monitoringResourceGroup --location $location
az monitor log-analytics workspace create --resource-group $monitoringResourceGroup --workspace-name $logWorkspace
# Retrieve Log Analytics ResourceId
logWorkspaceId=$(az monitor log-analytics workspace show --query customerId -g $monitoringResourceGroup -n $logWorkspace -o tsv)
# Retrieve Log Analytics Secrets
logWorkspaceSecret=$(az monitor log-analytics workspace get-shared-keys --query primarySharedKey -g $monitoringResourceGroup -n $logWorkspace -o tsv)
# Simple environment with no additional security for the underlying sInfrastructure
az containerapp env create --name $basicEnvironment --resource-group $resourceGroup \
--logs-workspace-id $logWorkspaceId --logs-workspace-key $logWorkspaceSecret --location $location
- A Containerized Application which responds to http Post requests
- The app is built with Azure Function for Http trigger
- Only returns some pre-formatted response message
httpImageName="$registryServer/httpcontainerapp:v1.0.0"
azureWebJobsStorage="<Storage Connection string as needed by Azure Function>"
# Deploy Container App
az containerapp create --name httpcontainerapp --resource-group $resourceGroup \
--image $httpImageName --environment $basicEnvironment \
--registry-login-server $registryServer --registry-username $registryUserName \
--registry-password $registryPassword \
#External Ingress - generates a Public FQDN
--ingress external --target-port 80 --transport http \
# Min/Max Replicas
--min-replicas 1 --max-replicas 5 \
# CPU/Memory specs; similar to resource quota requests oin K8s Deployment manifest
--cpu 0.25 --memory 0.5Gi \
# Secrets needed by Azure Function App; similar to K8s secrets
--secrets azurewebjobsstorage=$azureWebJobsStorage \
# Environment variables assigned from secrets created; similar to secretRef in K8s Deployment manifest
--environment-variables "AzureWebJobsStorage=secretref:azurewebjobsstorage"
-
Creates a simple Container App with External Ingress
-
Generates a Public FQDN
- The App can be accessed from anywhere
- No separate Load Balancer is needed to maintain; Azure does it automatically
-
--target-port indicates the Container Port; basically as exposed in Dockerfile and similar to containerPort in K8s Deployment manifest
-
This Deployment also ensures a minimum of 1 replica and maximum of 5 replicas for this App
-
Azure Container Registry credentials are passed as CLI arguments
- --registry-login-server
- --registry-username
- --registry-password
-
CPU and Memory is also specified - similar to resource quota in K8s Deployment manifest
-
Secrets are added as part of the Container App Deployment process
-
-
Manage Revisions
-
Get a list of Revisions
az containerapp revision list --name httpcontainerapp --resource-group $resourceGroup --query="[].name"
-
Deactivate/Activate Revisions
az containerapp revision deactivate --name "<revision_name>" --app httpcontainerapp \ --resource-group $resourceGroup az containerapp revision activate --name "<revision_name>" --app httpcontainerapp \ --resource-group $resourceGroup
-
-
Split Traffic
-
Split Traffic between two revisions by 50%
az containerapp update --traffic-weight "httpcontainerapp--rv1=50,httpcontainerapp--rv2=50" \ --name httpcontainerapp --resource-group $resourceGroup
-
Route all Traffic to latest revision
# Assuming httpcontainerapp--rv2 as the latest Revision az containerapp update --traffic-weight "httpcontainerapp--rv1=0,httpcontainerapp--rv2=100" \ --name httpcontainerapp --resource-group $resourceGroup
-
-
A Containerized Application which responds to http Post requests
-
The app is built with Azure Function for Http trigger
-
Only returns some pre-formatted response message
-
Application runs within a Secured Container App Environment
-
Create a Secured Environment for the Container App
az containerapp env create --name $securedEnvironment --resource-group $resourceGroup \ --logs-workspace-id $logWorkspaceId --logs-workspace-key $logWorkspaceSecret --location $location \ # Subnet for Control Plane Infrastructure --controlplane-subnet-resource-id $controlPlaneSubnetId \ # Subnet for Container App(s) --app-subnet-resource-id $appsSubnetId # Both Control plane Subnet and Application Services Subnet should be in same VNET viz. $containerAppVnetName
-
Create secured Container app injected into the Virtual Network
az containerapp create --name httpcontainerapp-secured --resource-group $resourceGroup \ # Secured Environment for the Container App --image $httpImageName --environment $securedEnvironment \ --registry-login-server $registryServer --registry-username $registryUserName \ --registry-password $registryPassword \ # Ingress: Internal; generates Private FQDN, no access from outside of the Virtual Network --ingress internal --target-port 80 --transport http \ --min-replicas 1 --max-replicas 5 \ --cpu 0.25 --memory 0.5Gi \ --secrets azurewebjobsstorage=$azureWebJobsStorage \ --environment-variables "AzureWebJobsStorage=secretref:azurewebjobsstorage"
- Application would run within a specified Virtual Network
- Internal/Private FQDN for the form - <APP_NAME>.internal.<UNIQUE_IDENTIFIER>.<REGION_NAME>.azurecontainerapps.io
- All Applicationds within the same Secured Environment would share same internal/Private IP address
-
A Containerized Application which responds to http Post requests
-
The app is built with Azure Function for Http trigger
-
Only returns some pre-formatted response message
-
Application running within a Virtual Network
-
External Ingress to accept calls from Outside of the Virtual Network
-
Would call httpcontainerapp-secured internally - since both exist within the same Virtual Network
az containerapp create --name httpcontainerapp-mult --resource-group $resourceGroup \ --image $httpImageName --environment $securedEnvironment \ --registry-login-server $registryServer --registry-username $registryUserName \ --registry-password $registryPassword \ --ingress external --target-port 80 --transport http \ --min-replicas 1 --max-replicas 5 \ --cpu 0.25 --memory 0.5Gi \ --secrets azurewebjobsstorage=$azureWebJobsStorage \ --environment-variables "AzureWebJobsStorage=secretref:azurewebjobsstorage"
-
A Containerized Application which responds toBlob events
-
The app is built with Azure Function for Blob trigger
az containerapp create --name blobcontainerapp --resource-group $resourceGroup \ --image $blobImageName --environment $basicEnvironment \ --registry-login-server $registryServer --registry-username $registryUserName \ --registry-password $registryPassword \ --min-replicas 1 --max-replicas 10 \ --secrets azurewebjobsstorage=$azureWebJobsStorage \ --environment-variables "AzureWebJobsStorage=secretref:azurewebjobsstorage"
-
Unlike previous apps, NO Ingress is specified here; since the application is listening to the Blob events which is an Outbound call
- No FQDN is generated as Ingress is disabled
- No InBound call is needed (or possible)
- Application responds to the Blob storage events Only
-
Deploy blobcontainerapp using ARM
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "containerappName": { "defaultValue": "blobcontainerapp", "type": "String" }, "location": { "defaultValue": "eastus", "type": "String" }, "environmentName": { "defaultValue": "basic-env", "type": "String" }, "imageName": { "defaultValue": "", "type": "String" }, "acrServer": { "defaultValue": "", "type": "String" }, "acrUsername": { "defaultValue": "", "type": "String" }, "acrPassword": { "defaultValue": "", "type": "String" }, "azureWebjobsStorage": { "defaultValue": "", "type": "String" } }, "variables": { "passwordSecretName": "passwordsecret", "storageSecretName": "azurewebjobsstorage" }, "resources": [ { "apiVersion": "2021-03-01", "type": "Microsoft.Web/containerApps", "name": "[parameters('containerappName')]", "location": "[parameters('location')]", "properties": { "kubeEnvironmentId": "[resourceId('Microsoft.Web/kubeEnvironments', parameters('environmentName'))]", "configuration": { "secrets": [{ "name": "azurewebjobsstorage", "value": "[parameters('azureWebjobsStorage')]" }, { "name": "passwordsecret", "value": "[parameters('acrPassword')]" }], "registries": [{ "server": "[parameters('acrServer')]", "username": "[parameters('acrUsername')]", "passwordSecretRef": "[variables('passwordSecretName')]" }] }, "template": { "containers": [ { "name": "blob-container", "image": "[parameters('imageName')]", "env": [ { "name": "AzureWebJobsStorage", "secretRef": "[variables('storageSecretName')]" } ], "resources": { "cpu": 0.5, "memory": "1Gi" } } ], "scale": { "minReplicas": 1, "maxReplicas": 10, "rules": [ { "name": "blob-scaling", "custom": { "type": "azure-blob", "metadata": { "blobContainerName": "blobcontainerapp", "blobCount": "3" }, "auth": [{ "secretRef": "azurewebjobsstorage", "triggerParameter": "connection" }] } }] } } } } ] }
blobImageName="$registryServer/blobcontainerapp:v1.0.0" azureWebJobsStorage="<Storage connection string as needed by Azure Function>" az deployment group create -f ./blob-deploy.json -g $resourceGroup \ --parameters imageName=$blobImageName acrServer=$registryServer \ acrUsername=$registryUserName acrPassword=$registryPassword azureWebjobsStorage=$azureWebJobsStorage
-
Secret values are passed to the Container Apps through secrets section in the template
"secrets": [{ "name": "azurewebjobsstorage", "value": "[parameters('azureWebjobsStorage')]" }, { "name": "passwordsecret", "value": "[parameters('acrPassword')]" }]
-
Scaling configuration is provided by the scale section of the template
- Refer Scale Triggers as supported by Container Apps
- Scale type and metadata are similar to what KEDA Scalers provie us with
"scale": { "minReplicas": 1, "maxReplicas": 10, "rules": [ { "name": "blob-scaling", "custom": { "type": "azure-blob", "metadata": { "blobContainerName": "blobcontainerapp", "blobCount": "3" }, "auth": [{ "secretRef": "azurewebjobsstorage", "triggerParameter": "connection" }] } }] }
-
-
Deploy httpcontainerapp using ARM
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "containerappName": { "defaultValue": "httpcontainerapp", "type": "String" }, "location": { "defaultValue": "eastus", "type": "String" }, "environmentName": { "defaultValue": "basic-env", "type": "String" }, "imageName": { "defaultValue": "", "type": "String" }, "acrServer": { "defaultValue": "", "type": "String" }, "acrUsername": { "defaultValue": "", "type": "String" }, "acrPassword": { "defaultValue": "", "type": "String" }, "azureWebjobsStorage": { "defaultValue": "", "type": "String" }, "revisionSuffix": { "defaultValue": "", "type": "String" } }, "variables": { "passwordSecretName": "passwordsecret", "storageSecretName": "azurewebjobsstorage" }, "resources": [ { "apiVersion": "2021-03-01", "type": "Microsoft.Web/containerApps", "name": "[parameters('containerappName')]", "location": "[parameters('location')]", "properties": { "kubeEnvironmentId": "[resourceId('Microsoft.Web/kubeEnvironments', parameters('environmentName'))]", "configuration": { "secrets": [{ "name": "azurewebjobsstorage", "value": "[parameters('azureWebjobsStorage')]" }, { "name": "passwordsecret", "value": "[parameters('acrPassword')]" }], "registries": [{ "server": "[parameters('acrServer')]", "username": "[parameters('acrUsername')]", "passwordSecretRef": "[variables('passwordSecretName')]" }], "ingress": { "external": true, "targetPort": 80, "allowInsecure": false, "traffic": [ { "latestRevision": true, "weight": 100 } // { // "revisionName": "httpcontainerapp--rv1", // "weight": 90 // }, // { // "revisionName": "httpcontainerapp--rv2", // "weight": 10 // } ] } }, "template": { "revisionSuffix": "[parameters('revisionSuffix')]", "containers": [ { "name": "blob-container", "image": "[parameters('imageName')]", "env": [ { "name": "AzureWebJobsStorage", "secretRef": "[variables('storageSecretName')]" } ], "resources": { "cpu": 0.5, "memory": "1Gi" } } ], "scale": { "minReplicas": 1, "maxReplicas": 10, "rules": [ { "name": "http-scaling", "http": { "metadata": { "concurrentRequests": "100" } } }] } } } } ] }
-
Traffic Splitting is handled by traffic section of the template
"traffic": [ // { // "latestRevision": true, // "weight": 100 // } { "revisionName": "httpcontainerapp--rv1", "weight": 50 }, { "revisionName": "httpcontainerapp--rv2", "weight": 50 } ]
-
Build a Logic App with basic request/response workflow - viz. LogicContainerApp
- Run and test this Logic app as docker container locally
- Deploy the Logic App container onto Azure as a Container App
- Host the Logic App inside a Virtual Network (Secured Environment)
- Expose the container app with Internal Ingress - blocking all public access
-
Build an Azure Function App with Http POST trigger - viz. HttpLogicContainerApp
- Azure Function would call the above logic app (i.e. LogicContainerApp) sending some Json as POST body
- Function would recieve the http rspons from Logic App and return back to the caller
- Run and test this function app as docker container locally
- Deploy the Function App container onto Azure as a Container App
- Host the Function App inside a Virtual Network (Secured Environment)
- Expose the container app with Internal Ingress - blocking all public access
-
Integrate both the Container Apps (Function App and Logic App) with Azure APIM
- Create an APIM instance on Azure with a Self-hosted Gateway
- Deploy Self-hosted APIM as Container App and in the same Secured Environment as above
- Add two Container Apps (as deployed above) as backend for the APIM
- Expose the APIM Container App with External Ingress thus making it the only public facing endpoint for the entire system
- APIM Container App (Self-hosted Gateway) would be able to call the internal Container Apps since being part of the same Secured Environment
-
Let us first Create and Deploy a Logic app as Docker Container
-
Logic App runs an Azure Function locally and hence few tools/extensions need to be installed
- Azure Function Core Tools - v3.x
- The abobve link is for macOS; please install the appropriate links in the same page for other Operating Systems
- At the time of writing, Core tools 3.x only supports the Logic App Designer within Visual Studio Code
- The current example has been tested with - Function Core Tools version 3.0.3904 on a Windows box
- Docker Desktop for Windows
- A Storage Account on Azure - which is needed by any Azure function App
- Logic App (aka Azure Function) would use this storage to cache its state
- VS Code Extension for Standard Logic App
- VS Code Extension for Azure Function
- VS Code extension for Docker
- This is Optional but recommended; it makes life easy while dealing with Dockerfile and Docker CLI commands
- Azure Function Core Tools - v3.x
-
Create a Local folder to host all files related Logic App - viz. LogicContainerApp
-
Open the folder in VS Code
-
Create a New Logic App Project in this Folder
-
Choose Stateful workflow in the process and name accordingly - viz. httperesflow
-
This generates all necessary files and sub-folders within the current folder
-
A folder named httpresflow is also added which contains the workflow.json file
-
This describes the Logic App Actions/triggers
-
This example uses a Http Request/Response type Logic App for simplicity
-
The Logic App would accept a Post body as below and would return back the same as response
{ "Zip": "testzip-2011.zip" }
-
Right click on the workflow.json file and Open the Logic App Designer - this might take few seconds to launch
-
Add Http Request trigger
-
Add Http Respoinse Action
-
Save the Designer changes
-
Right click on the empty area on the workspace folder structure and Open the Context menu
-
Select the menu options that says - Convert to Nuget-based Logic App project
-
This would generate .NET specific files - along with a LogicContainerApp.csproj file
-
Open the local.settings.json file
- Replace the value of AzureWebJobsStorage variable with the value from Storage Account Connection string created earlier
-
Add a Dockerfile in the workspace
FROM mcr.microsoft.com/azure-functions/node:3.0 ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ AzureFunctionsJobHost__Logging__Console__IsEnabled=true \ FUNCTIONS_V2_COMPATIBILITY_MODE=true \ AzureWebJobsStorage='' \ AZURE_FUNCTIONS_ENVIRONMENT=Development \ WEBSITE_HOSTNAME=localhost \ WEBSITE_SITE_NAME=logiccontainerapp COPY ./bin/Debug/netcoreapp3.1 /home/site/wwwroot
- WEBSITE_SITE_NAME - this is the name by which entries are created in Storage Account by the Logic App while caching its state
-
Build docker image
docker build -t <repo_name>/<image_name>:<tag> .
-
Create the Logic App Container
docker run --name logiccontainerapp -e AzureWebJobsStorage=$azureWebJobsStorage -d -p 8080:80 <repo_name>/<image_name>:<tag>
-
Let us now Run the logic app locally as a Docker container
-
Open the Storage account created earlier
-
Open POSTMAN or any Rest client of choice like curl
http://localhost:8080/runtime/webhooks/workflow/api/management/workflows/httpresflow/triggers/manual/listCallbackUrl?api-version=2020-05-01-preview&code=<master_key_value_from_storage_account>
-
This would return the Post callback Url for Http triggered Logic App
{ "value": "https://localhost:443/api/httpresflow/triggers/manual/invoke?api-version=2020-05-01-preview&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=<value>", "method": "POST", "basePath": "https://localhost/api/httpresflow/triggers/manual/invoke", "queries": { "api-version": "2020-05-01-preview", "sp": "/triggers/manual/run", "sv": "1.0", "sig": "<value>" } }
-
Copy the value of the value parameter from the json response
-
-
Make following Http call
http://localhost:8080/api/httpresflow/triggers/manual/invoke?api-version=2020-05-01-preview&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=<value>
-
Post Body
{ "Zip": "testzip-2011.zip" }
-
Check the response coming back from Logic App as below
{ "Zip": "testzip-2011.zip" }
-
-
Create Virtual Network to inject Container Apps
containerAppVnetId=$(az network vnet show -n $containerAppVnetName --resource-group $resourceGroup --query="id" -o tsv) controlPlaneSubnetId=$(az network vnet subnet show -n $controlPlaneSubnetName --vnet-name $containerAppVnetName --resource-group $resourceGroup --query="id" -o tsv) appsSubnetId=$(az network vnet subnet show -n $appsSubnetName --vnet-name $containerAppVnetName --resource-group $resourceGroup --query="id" -o tsv)
-
Create a Secured Environment for Azure Container Apps with this Virtual Network
az containerapp env create --name $securedEnvironment --resource-group $resourceGroup \ --logs-workspace-id $logWorkspaceId --logs-workspace-key $logWorkspaceSecret --location $location \ --controlplane-subnet-resource-id $controlPlaneSubnetId \ --app-subnet-resource-id $appsSubnetId
-
Let us now deploy the logic app container onto Azure as Container App
-
Push Logic App container image to Azure Container Registry
# If Container image is already created and tested, use Docker CLI docker push <repo_name>/<image_name>:<tag> OR # Use Azure CLI command for ACR to build and push az acr build -t <repo_name>/<image_name>:<tag> -r $acrName .
-
Create Azure Container App with this image
logicappImageName="$registryServer/logiccontainerapp:v1.0.0" azureWebJobsStorage="<storage_account_connection_string" az containerapp create --name logicontainerapp --resource-group $resourceGroup \ --image $logicappImageName --environment $securedEnvironment \ --registry-login-server $registryServer --registry-username $registryUserName \ --registry-password $registryPassword \ --ingress external --target-port 80 --transport http \ --secrets azurewebjobsstorage=$azureWebJobsStorage \ --environment-variables "AzureWebJobsStorage=secretref:azurewebjobsstorage"
-
Note down the Logic App ingress url
-
-
This function will be triggerred by a http Post call
-
This is going to invoke Logic App internally
-
Return the response back to the caller
-
Before we Deploy the function app, let us look at its code
using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; namespace HttpContainerApps { public static class HttpContainerApps { [FunctionName("container")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); var name = req.Query["name"]; var cl = new HttpClient(); var uri = $"http://httpcontainerapp-secured.internal.greensea-4ecd9ebc.eastus.azurecontainerapps.io/api/container?name={name}"; var res = await cl.GetAsync(uri); var response = await res.Content.ReadAsStringAsync(); log.LogInformation($"Status:{res.StatusCode}"); log.LogInformation($"Response:{response}-v1.0.4"); response = $"Hello, {response}-v1.0.4"; // var response = $"Secured, {name}-v1.0.3"; return new OkObjectResult(response); } } }
- Deploy Azure Function app as Container App
httpImageName="$registryServer/httplogiccontainerapp:v1.0.5" logicAppCallbackUrl="https://<logicontainerapp_internal_ingress_url>/runtime/webhooks/workflow/api/management/workflows/httpresflow/triggers/manual/listCallbackUrl?api-version=2020-05-01-preview&code=<master_key_value_from_storage_account>" logicAppPostUrl="https://<logicontainerapp_internal_ingress_url>/api/httpresflow/triggers/manual/invoke?api-version=2020-05-01-preview&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig={0}" az containerapp create --name httplogiccontainerapp --resource-group $resourceGroup \ --image $httpImageName --environment $securedEnvironment \ --registry-login-server $registryServer --registry-username $registryUserName \ --registry-password $registryPassword \ --ingress internal --target-port 80 --transport http \ --secrets azurewebjobsstorage=$azureWebJobsStorage,logicappcallbackurl=$logicAppCallbackUrl,logicappposturl=$logicAppPostUrl \ --environment-variables "AzureWebJobsStorage=secretref:azurewebjobsstorage,LOGICAPP_CALLBACK_URL=secretref:logicappcallbackurl,LOGICAPP_POST_URL=secretref:logicappposturl"
- This Container App is with Ingress type Internal so this would be at exposed publicly
-
Select gateway option in APIM in the Azure Portal
-
Get the Endpoint Url and Auth Token from the portal
-
Define ARM template for APIM Container App
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "containerappName": { "defaultValue": "apimcontainerapp", "type": "String" }, "location": { "defaultValue": "eastus", "type": "String" }, "environmentName": { "defaultValue": "secure-env", "type": "String" }, "serviceEndpoint": { "defaultValue": "", "type": "String" }, "serviceAuth": { "defaultValue": "", "type": "String" } }, "variables": {}, "resources": [ { "apiVersion": "2021-03-01", "type": "Microsoft.Web/containerApps", "name": "[parameters('containerappName')]", "location": "[parameters('location')]", "properties": { "kubeEnvironmentId": "[resourceId('Microsoft.Web/kubeEnvironments', parameters('environmentName'))]", "configuration": { "ingress": { "external": true, "targetPort": 8080, "allowInsecure": false, "traffic": [ { "latestRevision": true, "weight": 100 } ] } }, "template": { // "revisionSuffix": "revapim", "containers": [ { "name": "conainerapp-apim-gateway", "image": "mcr.microsoft.com/azure-api-management/gateway:latest", "env": [ { "name": "config.service.endpoint", "value": "[parameters('serviceEndpoint')]" }, { "name": "config.service.auth", "value": "[parameters('serviceAuth')]" } ], "resources": { "cpu": 0.5, "memory": "1Gi" } } ], "scale": { "minReplicas": 1, "maxReplicas": 3 } } } } ] }
- Deploy APIM as Container App
apimappImageName="mcr.microsoft.com/azure-api-management/gateway:latest" serviceEndpoint="<service_Endpoint>" serviceAuth="<service_Auth>" az deployment group create -f ./api-deploy.json -g $resourceGroup \ --parameters serviceEndpoint=$serviceEndpoint serviceAuth=$serviceAuth
-
Add Container Apps as APIM back end
-
The Web Service URL would be the Internal Ingress url of the Http Container App
-
Grab the FQDN of the APIM Container App from the portal
-
The FQDN can be obtained through Azure CLI as well
fqdn=$(az containerapp show -g $resourceGroup -n apimcontainerapp --query="configuration.ingress.fqdn")
-
Make a call to the API URL as below and receive the response back
curl -k -X POST --data '{"zip":"test.zip"}' https://$fqdn/container/api/logicapp/ .... {"zip":"test.zip"}
-
-