Loading...

Use Logic App to monitor Application Gateway Backend Health

Image

Problem statement:

While Azure Application Gateway probes backend health and reports the status, it does not log failed health probe requests. These logs if stored can help you to diagnose problems with the backend. 

Even more, if the backend is failing intermittently, it would be difficult to diagnose without the failure logs.

Solution:

Trigger an Azure Logic App whenever there is an alert for Unhealthy Backend Host count from Metrics and use the Azure Application Gateway Backend Health REST API to the ingest logs into a custom table in Log Analytics workspace.

SayanRoy_0-1712164071691.png

Please find below steps to configure the above flow

  1. Create Logic App in Consumption Plan
  2. Add a Managed Identity for the Logic App

    SayanRoy_1-1712164204553.png

     

  3. Use Manual Trigger.
  4. The following steps are used in logic App
    1. Initialize the variables – Affected Resources, Final Response , Response Body, Sub ID, App Gateway name and Location (the header value in response incase we get 201/202, will explain this down the line).
    2. Add action ‘HTTP’  to send the HTTP request for backend health API , use Managed Identity for authentication.
      SayanRoy_2-1712164390277.png

       

      Note - Azure Application Gateway Backend Health REST API will run asynchronously with 202 response code. Among the header values, you will see:
      Location: https://management.azure.com/subscriptions/{subscription-id}/providers/Microsoft.Network/locations/{region}/operationResults/{operation-id}?api-version=2023-09-01

      To check the status of the asynchronous operation, send another request to that URL:
      GET https://management.azure.com/subscriptions/{subscription-id}/providers/Microsoft.Network/locations/{region}/operationResults/{operation-id}?api-version=2023-09-01

    3.  If the response is 200, get set final response as yes and set the response body from the response.
    4. If the response is 202, you must send the request again with the header value in ‘location’ until you get 200 as explained above in the Note.
    5. Once you get the proper response, we must use ‘parse JSON’ action to format and fetch required information from the response.
      Note - You can create a schema by using a sample response.
    6. There might be multiple backends which are failing, so start for loop for each unhealthy backend
      • Send the details to log analytics workspace custom table using “Send Data” action which uses log analytics connector.
        SayanRoy_3-1712164564181.png

 

The below JSON can be used in code view to get started:

 

 

 

{ "definition": { "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", "actions": { "Condition": { "actions": { "Set_Response_body": { "inputs": { "name": "ResponseBody", "value": "@body('HTTP')" }, "runAfter": { "Set_final_response_yes": [ "Succeeded" ] }, "type": "SetVariable" }, "Set_final_response_yes": { "inputs": { "name": "finalResponse", "value": true }, "runAfter": {}, "type": "SetVariable" } }, "else": { "actions": { "Set_location": { "inputs": { "name": "location", "value": "@{outputs('HTTP')['headers']?['Location']}" }, "runAfter": {}, "type": "SetVariable" }, "Until": { "actions": { "Condition_2": { "actions": { "Set_ResponseBody": { "inputs": { "name": "ResponseBody", "value": "@body('HTTP_2')" }, "runAfter": { "Set_final_response": [ "Succeeded" ] }, "type": "SetVariable" }, "Set_final_response": { "inputs": { "name": "finalResponse", "value": true }, "runAfter": {}, "type": "SetVariable" } }, "expression": { "and": [ { "equals": [ "@outputs('HTTP_2')['statusCode']", 200 ] } ] }, "runAfter": { "HTTP_2": [ "Succeeded" ] }, "type": "If" }, "HTTP_2": { "inputs": { "authentication": { "identity": "", "type": "ManagedServiceIdentity" }, "method": "GET", "uri": "@variables('location')" }, "runAfter": {}, "type": "Http" } }, "expression": "@equals(variables('finalResponse'), true)", "limit": { "count": 60, "timeout": "PT1H" }, "runAfter": { "Set_location": [ "Succeeded" ] }, "type": "Until" } } }, "expression": { "and": [ { "equals": [ "@outputs('HTTP')['statusCode']", 200 ] } ] }, "runAfter": { "HTTP": [ "Succeeded" ] }, "type": "If" }, "For_each": { "actions": { "For_each_2": { "actions": { "For_each_3": { "actions": { "Send_Data": { "inputs": { "body": "{ \"Application Gateway\": \"@{variables('AppGWName')}\", \"Subscription\": \"@{variables('SubID')}\", \"Resource Group\":\"@{variables('ResourceGroup')}\", \"Backend HTTP Setting\":\"@{items('For_each_2')?['backendHttpSettings']?['id']}\", \"Backendpool\": \"@{items('For_each')?['backendAddressPool']?['id']}\", \"Server\":\"@{items('For_each_3')?['address']}\", \"Health\": \"@{items('For_each_3')?['health']}\" , \"Health Probe Log\":\"@{items('For_each_3')?['healthProbeLog']}\"}", "headers": { "Log-Type": "AppGWBackendHealth" }, "host": { "connection": { "name": "@parameters('$connections')['azureloganalyticsdatacollector']['connectionId']" } }, "method": "post", "path": "/api/logs" }, "runAfter": {}, "type": "ApiConnection" } }, "foreach": "@items('For_each_2')?['servers']", "runAfter": {}, "type": "Foreach" } }, "foreach": "@items('For_each')?['backendHttpSettingsCollection']", "runAfter": {}, "type": "Foreach" } }, "foreach": "@body('Parse_JSON')?['backendAddressPools']", "runAfter": { "Parse_JSON": [ "Succeeded" ] }, "type": "Foreach" }, "HTTP": { "inputs": { "authentication": { "identity": "", "type": "ManagedServiceIdentity" }, "method": "POST", "uri": "https://management.azure.com/subscriptions/@{variables('SubID')}/resourceGroups/@{variables('ResourceGroup')}/providers/Microsoft.Network/applicationGateways/@{variables('AppGWName')}/backendhealth?api-version=2023-09-01" }, "runAfter": { "Initialize_Location_Header": [ "Succeeded" ] }, "type": "Http" }, "Initialize_AffectedResources": { "inputs": { "variables": [ { "name": "AffectedResources", "type": "array", "value": "@split(triggerBody()?['data']?['essentials']?['alertTargetIDs'][0], '/')" } ] }, "runAfter": {}, "type": "InitializeVariable" }, "Initialize_App_GW_Name": { "inputs": { "variables": [ { "name": "AppGWName", "type": "string", "value": "@{variables('AffectedResources')[8]}" } ] }, "runAfter": { "Initialize_Subscription_ID": [ "Succeeded" ] }, "type": "InitializeVariable" }, "Initialize_Location_Header": { "inputs": { "variables": [ { "name": "location", "type": "string" } ] }, "runAfter": { "Initialize_App_GW_Name": [ "Succeeded" ] }, "type": "InitializeVariable" }, "Initialize_Resource_Group": { "inputs": { "variables": [ { "name": "ResourceGroup", "type": "string", "value": "@{variables('AffectedResources')[4]}" } ] }, "runAfter": { "Initialize_Response_Body": [ "Succeeded" ] }, "type": "InitializeVariable" }, "Initialize_Response_Body": { "inputs": { "variables": [ { "name": "ResponseBody", "type": "object" } ] }, "runAfter": { "Initialize_finalResponse": [ "Succeeded" ] }, "type": "InitializeVariable" }, "Initialize_Subscription_ID": { "inputs": { "variables": [ { "name": "SubID", "type": "string", "value": "@{variables('AffectedResources')[2]}" } ] }, "runAfter": { "Initialize_Resource_Group": [ "Succeeded" ] }, "type": "InitializeVariable" }, "Initialize_finalResponse": { "inputs": { "variables": [ { "name": "finalResponse", "type": "boolean", "value": false } ] }, "runAfter": { "Initialize_AffectedResources": [ "Succeeded" ] }, "type": "InitializeVariable" }, "Parse_JSON": { "inputs": { "content": "@variables('ResponseBody')", "schema": { "properties": { "body": { "properties": { "name": { "type": "string" }, "value": { "properties": { "backendAddressPools": { "items": { "properties": { "backendAddressPool": { "properties": { "id": { "type": "string" } }, "type": "object" }, "backendHttpSettingsCollection": { "items": { "properties": { "backendHttpSettings": { "properties": { "id": { "type": "string" } }, "type": "object" }, "servers": { "items": { "properties": { "address": { "type": "string" }, "health": { "type": "string" }, "healthProbeLog": { "type": "string" }, "ipConfiguration": { "properties": { "id": { "type": "string" } }, "type": "object" } }, "required": [ "address", "ipConfiguration", "health", "healthProbeLog" ], "type": "object" }, "type": "array" } }, "required": [ "backendHttpSettings", "servers" ], "type": "object" }, "type": "array" } }, "required": [ "backendAddressPool", "backendHttpSettingsCollection" ], "type": "object" }, "type": "array" } }, "type": "object" } }, "type": "object" } }, "type": "object" } }, "runAfter": { "Condition": [ "Succeeded" ] }, "type": "ParseJson" } }, "contentVersion": "1.0.0.0", "outputs": {}, "parameters": { "$connections": { "defaultValue": {}, "type": "Object" } }, "triggers": { "manual": { "inputs": { "schema": { "properties": { "properties": { "properties": { "data": { "properties": { "properties": { "properties": { "alertContext": { "properties": { "properties": { "properties": {}, "type": "object" }, "type": { "type": "string" } }, "type": "object" }, "essentials": { "properties": { "properties": { "properties": { "alertContextVersion": { "properties": { "type": { "type": "string" } }, "type": "object" }, "alertId": { "properties": { "type": { "type": "string" } }, "type": "object" }, "alertRule": { "properties": { "type": { "type": "string" } }, "type": "object" }, "alertTargetIDs": { "properties": { "items": { "properties": { "type": { "type": "string" } }, "type": "object" }, "type": { "type": "string" } }, "type": "object" }, "description": { "properties": { "type": { "type": "string" } }, "type": "object" }, "essentialsVersion": { "properties": { "type": { "type": "string" } }, "type": "object" }, "firedDateTime": { "properties": { "type": { "type": "string" } }, "type": "object" }, "monitorCondition": { "properties": { "type": { "type": "string" } }, "type": "object" }, "monitoringService": { "properties": { "type": { "type": "string" } }, "type": "object" }, "originAlertId": { "properties": { "type": { "type": "string" } }, "type": "object" }, "resolvedDateTime": { "properties": { "type": { "type": "string" } }, "type": "object" }, "severity": { "properties": { "type": { "type": "string" } }, "type": "object" }, "signalType": { "properties": { "type": { "type": "string" } }, "type": "object" } }, "type": "object" }, "type": { "type": "string" } }, "type": "object" } }, "type": "object" }, "type": { "type": "string" } }, "type": "object" }, "schemaId": { "properties": { "type": { "type": "string" } }, "type": "object" } }, "type": "object" }, "type": { "type": "string" } }, "type": "object" } }, "kind": "Http", "type": "Request" } } }, "parameters": { "$connections": { } } }

 

 

 

 

 

Save and then edit the following to make the Logic App working.

  • Update authentication in all HTTP action to Managed Identity of logic app.
  • Delete the Send data and add again. This will help to create a log Analytics connector by giving the Log analytics workspace ID and key.
  • Either add all the log details in header of send data as below screenshot or edit the logic app in code view to copy paste the Header value from the above sample JSON.  

 

Configure Azure monitor Alert to trigger the Logic App

Configure alert for signal unhealthy host count,

In the actions, create action group and select Action type as logic app, select the logic app created.  

SayanRoy_1-1712164899975.png

 

SayanRoy_2-1712164918625.png
Enable automatically resolve , else it will be triggered multiple times.

The logs generated would look like below: 

SayanRoy_3-1712165198723.png

 

Learn more
Author image

Azure Networking Blog articles

Azure Networking Blog articles

Share post:

Related

Stay up to date with latest Microsoft Dynamics 365 and Power Platform news!

* Yes, I agree to the privacy policy