Sunday, August 30, 2020

Tutorial: Learn about advanced scenarios - ASP.NET MVC with EF Core

 

In the previous tutorial, you implemented table-per-hierarchy inheritance. This tutorial introduces several topics that are useful to be aware of when you go beyond the basics of developing ASP.NET Core web applications that use Entity Framework Core.

In this tutorial, you:

  • Perform raw SQL queries
  • Call a query to return entities
  • Call a query to return other types
  • Call an update query
  • Examine SQL queries
  • Create an abstraction layer
  • Learn about Automatic change detection
  • Learn about EF Core source code and development plans
  • Learn how to use dynamic LINQ to simplify code

Prerequisites

Perform raw SQL queries

One of the advantages of using the Entity Framework is that it avoids tying your code too closely to a particular method of storing data. It does this by generating SQL queries and commands for you, which also frees you from having to write them yourself. But there are exceptional scenarios when you need to run specific SQL queries that you have manually created. For these scenarios, the Entity Framework Code First API includes methods that enable you to pass SQL commands directly to the database. You have the following options in EF Core 1.0:

  • Use the DbSet.FromSql method for queries that return entity types. The returned objects must be of the type expected by the DbSet object, and they're automatically tracked by the database context unless you turn tracking off.

  • Use the Database.ExecuteSqlCommand for non-query commands.

If you need to run a query that returns types that aren't entities, you can use ADO.NET with the database connection provided by EF. The returned data isn't tracked by the database context, even if you use this method to retrieve entity types.

As is always true when you execute SQL commands in a web application, you must take precautions to protect your site against SQL injection attacks. One way to do that is to use parameterized queries to make sure that strings submitted by a web page can't be interpreted as SQL commands. In this tutorial you'll use parameterized queries when integrating user input into a query.

Call a query to return entities

The DbSet<TEntity> class provides a method that you can use to execute a query that returns an entity of type TEntity. To see how this works you'll change the code in the Details method of the Department controller.

In DepartmentsController.cs, in the Details method, replace the code that retrieves a department with a FromSql method call, as shown in the following highlighted code:

C#
public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
    var department = await _context.Departments
        .FromSql(query, id)
        .Include(d => d.Administrator)
        .AsNoTracking()
        .FirstOrDefaultAsync();

    if (department == null)
    {
        return NotFound();
    }

    return View(department);
}

To verify that the new code works correctly, select the Departments tab and then Details for one of the departments.

Department Details

Call a query to return other types

Earlier you created a student statistics grid for the About page that showed the number of students for each enrollment date. You got the data from the Students entity set (_context.Students) and used LINQ to project the results into a list of EnrollmentDateGroup view model objects. Suppose you want to write the SQL itself rather than using LINQ. To do that you need to run a SQL query that returns something other than entity objects. In EF Core 1.0, one way to do that is write ADO.NET code and get the database connection from EF.

In HomeController.cs, replace the About method with the following code:

C#
public async Task<ActionResult> About()
{
    List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
    var conn = _context.Database.GetDbConnection();
    try
    {
        await conn.OpenAsync();
        using (var command = conn.CreateCommand())
        {
            string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
                + "FROM Person "
                + "WHERE Discriminator = 'Student' "
                + "GROUP BY EnrollmentDate";
            command.CommandText = query;
            DbDataReader reader = await command.ExecuteReaderAsync();

            if (reader.HasRows)
            {
                while (await reader.ReadAsync())
                {
                    var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
                    groups.Add(row);
                }
            }
            reader.Dispose();
        }
    }
    finally
    {
        conn.Close();
    }
    return View(groups);
}

Add a using statement:

C#
using System.Data.Common;

Run the app and go to the About page. It displays the same data it did before.

About page

Call an update query

Suppose Contoso University administrators want to perform global changes in the database, such as changing the number of credits for every course. If the university has a large number of courses, it would be inefficient to retrieve them all as entities and change them individually. In this section you'll implement a web page that enables the user to specify a factor by which to change the number of credits for all courses, and you'll make the change by executing a SQL UPDATE statement. The web page will look like the following illustration:

Update Course Credits page

In CoursesController.cs, add UpdateCourseCredits methods for HttpGet and HttpPost:

C#
public IActionResult UpdateCourseCredits()
{
    return View();
}
C#
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
    if (multiplier != null)
    {
        ViewData["RowsAffected"] = 
            await _context.Database.ExecuteSqlCommandAsync(
                "UPDATE Course SET Credits = Credits * {0}",
                parameters: multiplier);
    }
    return View();
}

When the controller processes an HttpGet request, nothing is returned in ViewData["RowsAffected"], and the view displays an empty text box and a submit button, as shown in the preceding illustration.

When the Update button is clicked, the HttpPost method is called, and multiplier has the value entered in the text box. The code then executes the SQL that updates courses and returns the number of affected rows to the view in ViewData. When the view gets a RowsAffected value, it displays the number of rows updated.

In Solution Explorer, right-click the Views/Courses folder, and then click Add > New Item.

In the Add New Item dialog, click ASP.NET Core under Installed in the left pane, click Razor View, and name the new view UpdateCourseCredits.cshtml.

In Views/Courses/UpdateCourseCredits.cshtml, replace the template code with the following code:

CSHTML
@{
    ViewBag.Title = "UpdateCourseCredits";
}

<h2>Update Course Credits</h2>

@if (ViewData["RowsAffected"] == null)
{
    <form asp-action="UpdateCourseCredits">
        <div class="form-actions no-color">
            <p>
                Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
            </p>
            <p>
                <input type="submit" value="Update" class="btn btn-default" />
            </p>
        </div>
    </form>
}
@if (ViewData["RowsAffected"] != null)
{
    <p>
        Number of rows updated: @ViewData["RowsAffected"]
    </p>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Run the UpdateCourseCredits method by selecting the Courses tab, then adding "/UpdateCourseCredits" to the end of the URL in the browser's address bar (for example: http://localhost:5813/Courses/UpdateCourseCredits). Enter a number in the text box:

Update Course Credits page

Click Update. You see the number of rows affected:

Update Course Credits page rows affected

Click Back to List to see the list of courses with the revised number of credits.

Note that production code would ensure that updates always result in valid data. The simplified code shown here could multiply the number of credits enough to result in numbers greater than 5. (The Credits property has a [Range(0, 5)] attribute.) The update query would work but the invalid data could cause unexpected results in other parts of the system that assume the number of credits is 5 or less.

For more information about raw SQL queries, see Raw SQL Queries.

Examine SQL queries

Sometimes it's helpful to be able to see the actual SQL queries that are sent to the database. Built-in logging functionality for ASP.NET Core is automatically used by EF Core to write logs that contain the SQL for queries and updates. In this section you'll see some examples of SQL logging.

Open StudentsController.cs and in the Details method set a breakpoint on the if (student == null) statement.

Run the app in debug mode, and go to the Details page for a student.

Go to the Output window showing debug output, and you see the query:

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
    SELECT TOP(1) [s0].[ID]
    FROM [Person] AS [s0]
    WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
    ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]

You'll notice something here that might surprise you: the SQL selects up to 2 rows (TOP(2)) from the Person table. The SingleOrDefaultAsync method doesn't resolve to 1 row on the server. Here's why:

  • If the query would return multiple rows, the method returns null.
  • To determine whether the query would return multiple rows, EF has to check if it returns at least 2.

Note that you don't have to use debug mode and stop at a breakpoint to get logging output in the Output window. It's just a convenient way to stop the logging at the point you want to look at the output. If you don't do that, logging continues and you have to scroll back to find the parts you're interested in.

Create an abstraction layer

Many developers write code to implement the repository and unit of work patterns as a wrapper around code that works with the Entity Framework. These patterns are intended to create an abstraction layer between the data access layer and the business logic layer of an application. Implementing these patterns can help insulate your application from changes in the data store and can facilitate automated unit testing or test-driven development (TDD). However, writing additional code to implement these patterns isn't always the best choice for applications that use EF, for several reasons:

  • The EF context class itself insulates your code from data-store-specific code.

  • The EF context class can act as a unit-of-work class for database updates that you do using EF.

  • EF includes features for implementing TDD without writing repository code.

For information about how to implement the repository and unit of work patterns, see the Entity Framework 5 version of this tutorial series.

Entity Framework Core implements an in-memory database provider that can be used for testing. For more information, see Test with InMemory.

Automatic change detection

The Entity Framework determines how an entity has changed (and therefore which updates need to be sent to the database) by comparing the current values of an entity with the original values. The original values are stored when the entity is queried or attached. Some of the methods that cause automatic change detection are the following:

  • DbContext.SaveChanges

  • DbContext.Entry

  • ChangeTracker.Entries

If you're tracking a large number of entities and you call one of these methods many times in a loop, you might get significant performance improvements by temporarily turning off automatic change detection using the ChangeTracker.AutoDetectChangesEnabled property. For example:

C#
_context.ChangeTracker.AutoDetectChangesEnabled = false;

EF Core source code and development plans

The Entity Framework Core source is at https://github.com/dotnet/efcore. The EF Core repository contains nightly builds, issue tracking, feature specs, design meeting notes, and the roadmap for future development. You can file or find bugs, and contribute.

Although the source code is open, Entity Framework Core is fully supported as a Microsoft product. The Microsoft Entity Framework team keeps control over which contributions are accepted and tests all code changes to ensure the quality of each release.

Reverse engineer from existing database

To reverse engineer a data model including entity classes from an existing database, use the scaffold-dbcontext command. See the getting-started tutorial.

Use dynamic LINQ to simplify code

The third tutorial in this series shows how to write LINQ code by hard-coding column names in a switch statement. With two columns to choose from, this works fine, but if you have many columns the code could get verbose. To solve that problem, you can use the EF.Property method to specify the name of the property as a string. To try out this approach, replace the Index method in the StudentsController with the following code.

C#
 public async Task<IActionResult> Index(
     string sortOrder,
     string currentFilter,
     string searchString,
     int? pageNumber)
 {
     ViewData["CurrentSort"] = sortOrder;
     ViewData["NameSortParm"] = 
         String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
     ViewData["DateSortParm"] = 
         sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";

     if (searchString != null)
     {
         pageNumber = 1;
     }
     else
     {
         searchString = currentFilter;
     }

     ViewData["CurrentFilter"] = searchString;

     var students = from s in _context.Students
                    select s;
     
     if (!String.IsNullOrEmpty(searchString))
     {
         students = students.Where(s => s.LastName.Contains(searchString)
                                || s.FirstMidName.Contains(searchString));
     }

     if (string.IsNullOrEmpty(sortOrder))
     {
         sortOrder = "LastName";
     }

     bool descending = false;
     if (sortOrder.EndsWith("_desc"))
     {
         sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
         descending = true;
     }

     if (descending)
     {
         students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
     }
     else
     {
         students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
     }

     int pageSize = 3;
     return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), 
         pageNumber ?? 1, pageSize));
 }

Acknowledgments

Tom Dykstra and Rick Anderson (twitter @RickAndMSFT) wrote this tutorial. Rowan Miller, Diego Vega, and other members of the Entity Framework team assisted with code reviews and helped debug issues that arose while we were writing code for the tutorials. John Parente and Paul Goldman worked on updating the tutorial for ASP.NET Core 2.2.

Troubleshoot common errors

ContosoUniversity.dll used by another process

Error message:

Cannot open '...bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' because it is being used by another process.

Solution:

Stop the site in IIS Express. Go to the Windows System Tray, find IIS Express and right-click its icon, select the Contoso University site, and then click Stop Site.

Migration scaffolded with no code in Up and Down methods

Possible cause:

The EF CLI commands don't automatically close and save code files. If you have unsaved changes when you run the migrations add command, EF won't find your changes.

Solution:

Run the migrations remove command, save your code changes and rerun the migrations add command.

Errors while running database update

It's possible to get other errors when making schema changes in a database that has existing data. If you get migration errors you can't resolve, you can either change the database name in the connection string or delete the database. With a new database, there's no data to migrate, and the update-database command is much more likely to complete without errors.

The simplest approach is to rename the database in appsettings.json. The next time you run database update, a new database will be created.

To delete a database in SSOX, right-click the database, click Delete, and then in the Delete Database dialog box select Close existing connections and click OK.

To delete a database by using the CLI, run the database drop CLI command:

.NET Core CLI
dotnet ef database drop

Error locating SQL Server instance

Error Message:

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)

Solution:

Check the connection string. If you have manually deleted the database file, change the name of the database in the construction string to start over with a new database.

Get the code

Download or view the completed application.

Additional resources

For more information about EF Core, see the Entity Framework Core documentation. A book is also available: Entity Framework Core in Action.

For information on how to deploy a web app, see Host and deploy ASP.NET Core.

For information about other topics related to ASP.NET Core MVC, such as authentication and authorization, see Introduction to ASP.NET Core.

Next steps

In this tutorial, you:

  • Performed raw SQL queries
  • Called a query to return entities
  • Called a query to return other types
  • Called an update query
  • Examined SQL queries
  • Created an abstraction layer
  • Learned about Automatic change detection
  • Learned about EF Core source code and development plans
  • Learned how to use dynamic LINQ to simplify code

This completes this series of tutorials on using the Entity Framework Core in an ASP.NET Core MVC application. This series worked with a new database; an alternative is to reverse engineer a model from an existing database.

Saturday, August 29, 2020

8 5 6 Scale a HTTP Triggered app up and down in Kubernetes using KEDA and Prometheus


This blog post goes into how you can scale up and down your Http Triggered application in Kubernetes based on requests using KEDA and Prometheus.

The application here is an Azure Function app but it can be any app that exposes a Http interface. Where appropriate below I point out where you can use your app instead of an Azure Function app.

Among the major cloud providers, the FaaS implementation in Azure, Azure Functions, is unique that its runtime is open-source. The runtime and your code can therefore be deployed to a custom container or deployed on your own infrastructure including Kubernetes.

To enable scaling of a function app (or any other workload) in Kubernetes we at Azure (along with RedHat) built KEDA, Kubernetes Event Driven Autoscaling. With the combination of aforementioned runtime and KEDA you can run and scale your Azure Functions in your own Kubernetes cluster. Currently in KEDA we support more than twenty different message event sources including Kafka, RabbitMQ, NATS, Azure Queue, AWS SQS Queue, GCP Pub Sub etc. However, there is no support for Http request based scaling. This post outlines one approach on how you can scale a Http Trigerred function app in Kubernetes using the Prometheus KEDA scaled object and an Ingress Controller.

Overview

The basic idea is that we will deploy an Ingress Controller in this case the NGINX Ingress Controller and have all HTTP traffic to your function app go through it. We use Prometheus to track the incoming request metrics on the Ingress Controller. Finally, we use KEDA's Prometheus based scaler to scale up/down the function app deployment.

HttpScale

Walkthrough

Prerequisites

  1. A Kubernetes cluster which has the ability to install a Service with a Load Balancer (usually any cloud provider). The steps below were tested using an AKS cluster.
  2. kubectl pointing to your Kubernetes cluster
  3. Helm to install the artifacts. All of the artifacts below use Helm3
  4. Azure Functions core tools
  5. docker installed locally and a Docker Hub account.

Steps

  1. Create a namespace for your ingress resources

    kubectl create namespace ingress-nginx
    
  2. Install the NGINX-Ingress ingress controller

    Use Helm to deploy an NGINX ingress controller and enable metrics and set the right annotations for Prometheus. The ingress controller is installed as service with LoadBalancer type. In addition, a backend Service and a metrics Service are also deployed.

    helm install ingress-controller stable/nginx-ingress \     
        --namespace ingress-nginx \
        --set controller.replicaCount=2 \
        --set controller.metrics.enabled=true \
        --set controller.podAnnotations."prometheus\.io/scrape"="true" \
        --set controller.podAnnotations."prometheus\.io/port"="10254"
    
    kubectl -n ingress-nginx get svc
    NAME                                                  TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
    ingress-controller-nginx-ingress-controller           LoadBalancer   10.0.14.166    40.70.230.xxx   80:31036/TCP,443:32179/TCP   31s
    ingress-controller-nginx-ingress-controller-metrics   ClusterIP      10.0.240.199   <none>          9913/TCP                     31s
    ingress-controller-nginx-ingress-default-backend      ClusterIP      10.0.63.133    <none>          80/TCP                       31s
    

    The ingress controller is exposed via the EXTERNAL-IP 40.70.230.xxx above. Also have a look at the following page for instructions on how to install it for various configurations.

    Optionally - Create a DNS entry pointing to your ingress controller. For AKS, you can get a cloudapp.azure.com address using the procedure here. In the steps below the fqdn configured is "function-helloworld.eastus2.cloudapp.azure.com"

  3. Deploy Prometheus to monitor the NGINX ingress Controller

    kubectl apply --kustomize github.com/kubernetes/ingress-nginx/deploy/prometheus/    
    
    kubectl -n ingress-nginx get svc
    NAME                                                  TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
    ...
    prometheus-server                                     NodePort       10.0.38.50     <none>          9090:30860/TCP               34s
    
    kubectl -n ingress-nginx get pods
    NAME                                                              READY   STATUS    RESTARTS   AGE
    ..
    prometheus-server-86cd54f9d5-9xxh7                                1/1     Running   0          95s
    

    Note: If you happen to use any other namespace other than "ingress-nginx" then you need to go and change the namespace in the yaml files from here and then deploy.

  4. Deploy KEDA

    helm install keda kedacore/keda --namespace ingress-nginx     
    
    kubectl get pods -n ingress-nginx
    NAME                                                              READY   STATUS    RESTARTS   AGE
    ...
    keda-operator-697b98dcdd-8zdrk                                    2/2     Running   0          5d17h
    
  5. Deploy a function app to the Kubernetes cluster
    Create a Python Http Trigerred function app, generate the required docker file and finally deploy the function app to the cluster.

    func init --worker-runtime python
    func new --template "HttpTrigger" --name helloworld
    func init --docker-only
    func kubernetes deploy --name function-helloworld --namespace ingress-nginx --service-type ClusterIP --registry anirudhgarg
    

    Note that the authentication mode has to be changed to anonymous for now while we are working to support function keys. Navigate to the function app folder and open the function.json file. Find the following line: "authLevel": "function" and change the authLevel to "anonymous"

    --name is the name of your Deployment. --registry points to your DockerHub registry and you have to be logged in to docker and connected to your account locally. See more here

    Note: Instead of using a function app you can deploy your own app that listens to Http requests. Just make sure you create a k8s Cluster IP Service pointing to your deployment.

  6. Deploy an Ingress Resource pointing to the deployed function app Service.

    This is how the YAML looks like:

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: function-helloworld
      namespace: ingress-nginx
      annotations:
        kubernetes.io/ingress.class: nginx
        nginx.ingress.kubernetes.io/rewrite-target: /$2    
    spec:  
      rules:  
        - host: <replace-with-host-name-pointing-to-ingress-controller>
          http:
            paths:                
            - backend:
                serviceName: function-helloworld-http
                servicePort: 80
              path: /helloworld(/|$)(.*)
    

    You can find an example here

    The serviceName attribute is the name of the Service for the function app. host should point to the fqdn configured pointing to the Ingress Controller. You can also choose a random name here but a host has to be configured otherwise Prometheus monitoring of the Ingress Resource will not work. A path has also been configured with prefix "helloworld".

  7. Create an Ingress Resource with NGINX Ingress Controller annotations pointing to the Prometheus Service

    This is how the YAML looks like:

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: prometheus-service
      namespace: ingress-nginx
      annotations:
        kubernetes.io/ingress.class: nginx    
    spec:
      rules:  
        - http:
            paths:
            - backend:
                serviceName: prometheus-server
                servicePort: 9090
              path: /
    

    The serviceName attribute is the name of the Service for the Promtheus Server.

    kubectl apply -f "https://raw.githubusercontent.com/anirudhgarg/azurefunction-k8s-http/master/prom-ingress.yml"    
    
  8. Deploy the KEDA Prometheus Scaled object which inturn monitors the NGINX Ingress Controller

    kubectl apply -f "https://raw.githubusercontent.com/anirudhgarg/azurefunction-k8s-http/master/keda-prom.yml"    
    

    This is how the YAML looks like:

    apiVersion: keda.k8s.io/v1alpha1
    kind: ScaledObject
    metadata:
    name: prometheus-scaledobject
    namespace: ingress-nginx
    labels:
        deploymentName: function-helloworld-http
    spec:
    scaleTargetRef:
        deploymentName: function-helloworld-http
    pollingInterval: 15
    cooldownPeriod:  30
    minReplicaCount: 1
    maxReplicaCount: 10
    triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus-server.ingress-nginx.svc.cluster.local:9090
        metricName: access_frequency
        threshold: '1'
        query: sum(rate(nginx_ingress_controller_requests[1m]))
    

    deploymentName is the name of the function app Deployment, the pollingInterval is how frequently in seconds does KEDA poll Prometheus, we have a minimum of 1 pod (minReplicaCount) and the maximum scale out is 10 pods (maxReplicaCount). query is pointing to the prometheus query which tracks the metrics to incoming requests to the ingress controller in the last minute. Since the threshold is '1' the function app will scale as long as the number of requests/minute > 60.

  9. Test !
    The function app is now listening on:
    http://function-helloworld.eastus2.cloudapp.azure.com/helloworld/api/helloworld?name=anirudh

Note that if you did not create a domain name pointing to your Ingress Controller then you might need to use curl --resolve or its equivalent to invoke the function app

```
 curl -v function-helloworld.eastus2.cloudapp.azure.com/helloworld/api/helloworld?name=anirudh
* Trying 40.70.230.199...
* TCP_NODELAY set
* Connected to function-helloworld.eastus2.cloudapp.azure.com (40.70.230.199) port 80 (#0)
> GET /helloworld/api/helloworld?name=anirudh HTTP/1.1
> Host: function-helloworld.eastus2.cloudapp.azure.com
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: openresty/1.15.8.2
< Date: Mon, 13 Jan 2020 01:47:30 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 14
< Connection: keep-alive
<
Hello anirudh!* Connection #0 to host function-helloworld.eastus2.cloudapp.azure.com left intact
```

Now you can use your favorite Http requests tool and send requests at a high enough rate to triger the scale out. (I used a tool called k6)

```
kubectl -n ingress-nginx get pods
NAME                                                              READY   STATUS              RESTARTS   AGE
function-helloworld-http-6ccd9c9bbf-6f6d7                         0/1     ContainerCreating   0          0s
function-helloworld-http-6ccd9c9bbf-98bzq                         1/1     Running             0          15s
function-helloworld-http-6ccd9c9bbf-dcdwc                         0/1     ContainerCreating   0          0s
function-helloworld-http-6ccd9c9bbf-fr7hq                         0/1     ContainerCreating   0          0s
function-helloworld-http-6ccd9c9bbf-k9lhn                         1/1     Running             0          6d20h
function-helloworld-http-6ccd9c9bbf-mfp4c                         1/1     Running             0          15s
function-helloworld-http-6ccd9c9bbf-v7g47                         0/1     ContainerCreating   0          0s
function-helloworld-http-6ccd9c9bbf-x9l2t                         1/1     Running             0          15s
ingress-controller-nginx-ingress-controller-6c9f7486d4-27vjq      1/1     Running             0          7d1h
ingress-controller-nginx-ingress-controller-6c9f7486d4-b8ddr      1/1     Running             0          7d1h
ingress-controller-nginx-ingress-default-backend-df57464f-lvqmj   1/1     Running             0          7d1h
keda-operator-697b98dcdd-8zdrk                                    2/2     Running             0          6d18h
prometheus-server-86cd54f9d5-9xxh7                                1/1     Running             0          7d1h
```

After a while if there are no further requests the function pods will scale back down to 1. Note that we are only scaling down to 1 here.

Hope you found this useful. Please try it out and let me know in the comments or send me a tweet if this approach worked for you or not. We are looking to streamline this process further as we go forward.

Acknowledgments for inspiration - Autoscaling Kubernetes apps with Prometheus and KEDA post by Abhishek Gupta, and to OpenFaaS which also uses Prometheus metrics for request based scaling.