How to scale out web app base on custom metrics - JackyChiou/jackychiou.github.io GitHub Wiki
Scenario: How to scale out web app base on custom metrics. For example: request, response time and etc.
Currently, Web App Linux only CPU and memory are available.
Here is the end to end steps/screenshots:
**Step 1. Use the portal to create an Azure AD application and service principal that can access resources
-
Click “App registration”
-
“New registration”
-
Enter a Name and click Register
-
In Overview page, you can find the Application Id and Tenant Id.
-
Create a client secret.
-
Copy the client secret in Azure Portal
For more information: https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal **Step 2. Add app roles in your application and receive them in the token
-
Go to your resource group and “Access Control” and “Add a role assignment”
-
Select “Contributor” and select your app (Registered in Step1)
-
Go to your App Service Plan
-
Select “Reader” Role
-
Publish your function app.
-
Test Your Function App in Azure Portal
-
Verify the Instance Count in Azure Portal
-
Get the Function URI:
-
Test it in browser.
-
Verify it in Azure Portal
For more information: https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps
-
**Step 3. Setup an alert to change capacity
-
Create an alert and choose a custom metric
-
Create a action group and action type “Webhook”, the function URI is from step2.8
-
Save alert
-
Simulate a User Load and verify the Instance Count:
-
Instance Count changed to 5
-
using System;
using System.IO;
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;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Rest;
using System.Linq;
namespace ScaleWebAppFunction
{
public static class Function1
{
[FunctionName("ScaleWebApp")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
int capacity;
if (!string.IsNullOrEmpty(req.Query["capacity"]) && int.TryParse(req.Query["capacity"], out capacity))
{
log.LogInformation("You want to chagne the capacity to "+ capacity.ToString());
// Step1. How to: Use the portal to create an Azure AD application and service principal that can access resources
// https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal
// Step2. How to: Add app roles in your application and receive them in the token
// https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps
// For security concerns, I only give the resource group => contributor
// and the service plan read permission
var subscriptionId = "Your subscription id";
var appId = "e6f6d231-Your-App_id";
var secretKey = "2z]pj0vy2JRapjo:R@TJSoW1AmOCC=o8";
var tenantId = "72f988bf-Your-Tenant-Id";
var resourceGroup = "jackywebl1rg";
var servicePlanName = "ASP-jackywebl1rg-81ee";
// Step 3. change capacity
var context = new AuthenticationContext("https://login.windows.net/" + tenantId);
ClientCredential clientCredential = new ClientCredential(appId, secretKey);
var tokenResponse = context.AcquireTokenAsync("https://management.azure.com/", clientCredential).Result;
var accessToken = tokenResponse.AccessToken;
TokenCredentials credential = new TokenCredentials(accessToken);
var webSiteManagementClient = new Microsoft.Azure.Management.WebSites.WebSiteManagementClient(credential);
webSiteManagementClient.SubscriptionId = subscriptionId;
var servicePlan = webSiteManagementClient.AppServicePlans.ListByResourceGroupWithHttpMessagesAsync(resourceGroup).Result.Body.Where(x => x.Name.Equals(servicePlanName)).FirstOrDefault();
var appServicePlanRequest = await webSiteManagementClient.AppServicePlans.ListByResourceGroupWithHttpMessagesAsync(resourceGroup);
appServicePlanRequest.Body.ToList().ForEach(x => log.LogInformation($">>>{x.Name}"));
var appServicePlan = appServicePlanRequest.Body.Where(x => x.Name.Equals(servicePlanName)).FirstOrDefault();
if (appServicePlan == null)
{
log.LogError("Could not find app service plan.");
}
//scale up/down
//servicePlan.Sku.Family = "P";
//servicePlan.Sku.Name = "P1v2";
//servicePlan.Sku.Size = "P1v2";
//servicePlan.Sku.Tier = "PremiumV2";
servicePlan.Sku.Capacity = capacity; // scale out: number of instances
var updateResult = webSiteManagementClient.AppServicePlans.CreateOrUpdateWithHttpMessagesAsync(resourceGroup, servicePlanName, servicePlan).Result;
log.LogInformation("Completed!!");
return (ActionResult)new OkObjectResult($"Hello, {capacity} {updateResult.Response.StatusCode}");
}
else
{
return new BadRequestObjectResult("Please pass a capacity on the query string or in the request body");
}
}
}
}
HTH. By Jacky 2019-11-18