Cloud & Engineering

Emile Dawalibi

Is It Time to Migrate to Isolated Azure Functions?

Posted by Emile Dawalibi on 23 November 2022

App Service Isolated, App Services, Azure, Azure Functions, Service Bus, ASP.NET Core, APIs, .NET, Isolated Azure Functions

Introduction 

On November 10, 2020, Microsoft released .NET 5. But at that point in time, the current version of the Azure Functions runtime host (version 3.x) couldn’t support any code targeting .NET 5. That’s because version 3 of the runtime utilises NET Core 3.1, and therefore all the functions you code had to match that target framework. So how were you meant to write functions in the latest-and-greatest C# code targeting the latest frameworks? You’d have to wait for Azure to release a new runtime host that runs on the new framework to support this.  

 

Four months later, on March 10, 2021, Microsoft released a way to decouple this tight integration between the class library function projects that developers code, and the Functions host runtime. They released what are called ‘out-of-process’, or ‘isolated’ Functions. It’s a different way of running function code as opposed to the traditional ‘in-process’ way Azure Functions have operated until now. In fact, Microsoft is going all-in on isolated Functions, planning to completely phase out in-process Functions from the release of .NET 7 onwards.  

This is what the roadmap for Functions looks like: 

Timeline

Description automatically generated

 

So why are Microsoft going down this route? In this blog, I aim to explore this question by diving deep into how Azure Functions work behind the scenes, what running functions ‘in-process’ means, and how running out-of-process can mitigate their limitations. More importantly, I’ll answer whether you should look to begin migrating to isolated Functions. 

Running In-Process 

Isolated Functions are not a new concept; it’s how Microsoft allows developers to program their functions in other languages such as Python and JavaScript. So, running functions in-process is a phenomenon specific to functions written in C# targeting .NET.  

 

Functions running in-process mean that they are running in the same process as the Function Host runtime. For that to make more sense, let’s dive into a few terms.  

Component diagram of an in-process function.

When I say ‘Functions’, here I refer specifically to the class library projects that we as developers create to program our function classes, logic, models, etc. Yes, the Visual Studio project templates you use to create Function projects are technically class library projects! That is why you can run a function without any start-up code (although a very limited one that can only support primitive input bindings like strings). The FunctionName attribute on the class name tells the underlying runtime that this is the entry point for a function execution. 

public static class TestSBTopicTrigger 
{ 
    [FunctionName("TestSBTopicTrigger")] 
    public static async Task RunAsync([ServiceBusTrigger("test-queue",
    Connection = "ServiceBusConnection")]
    string mySbMsg, ILogger log)
    { 
        log.LogInformation($"C# ServiceBus topic trigger function processed message: {mySbMsg}"); 
    } 
}

This attribute is part of the WebJobs SDK (Microsoft.Azure.Webjobs), which the Azure Functions host’s runtime builds upon to supply the hosting platform. The host runs an implementation of IHost (Version 3.x onwards) or JobHost (Version 2.x).  

 

Therefore, when you are running a Function in-process, it means you are effectively tying your function to this host spun up by the service. This has the following implications: 

  • You do not have full control over the host, as the Azure Functions service is what creates it. Instead, it can be configured (albeit limited) by passing parameters into a host.json file. 
  • There is deep integration between the class library functions and the host. That means the host’s dependencies are your function’s dependencies. For example, the v2 host’s Newtonsoft.Json dependency, which it uses to deserialise JSON payloads in the background, is how it supports POCO inputs into functions. If you wish to use a different version of the package in your class library (i.e., your function), then package conflicts like the below can occur: 
Error NU1107 Version conflict detected for Newtonsoft.Json. Install/reference Newtonsoft.Json 12.0.1 directly to project <YOUR_PROJECT_NAME> to resolve this issue…	 

This was a problem, particularly in v2 of Functions, and a good write-up of this issue can be found here 

  • In the same way, your Functions need to be compatible with the same .NET framework the host is targeting. 
  • You cannot incorporate custom middleware (for instance, to deal with authentication or error handling) into the invocation pipeline. 

Running Out-Of-Process 

Clearly running Functions in-process has a few limitations as a result of this coupling. Hence Microsoft’s decision to begin moving away from in-process as part of future .NET releases. 

 

Isolated Functions are those that run in separate worker instances created by the host. 

Component diagram for an isolated function

The runtime host spinning up these separate worker instances effectively addresses all the limitations above. Because your functions are now no longer coupled to the host runtime, you now get full control over the host initialisation and configuration. This brings functions into a similar development experience as traditional web applications, requiring a Program.cs file that incorporates dependency injection, middleware injection, and starts the worker. As such, Functions can target whichever .NET framework and use whichever packages and versions as desired, as the worker process is isolated from the host. However, isolated functions need to make use of a completely new Functions worker SDK and its extensions.  

using Microsoft.Extensions.Hosting; 
 
var host = new HostBuilder() 
    .ConfigureFunctionsWorkerDefaults() 
    .Build(); 
 
host.Run();

A Program.cs file for an isolated Function. Only 5 lines to build an implementation of IHost, configure and run! Like a web application, a full request pipeline is available and supports the injection of middleware into it. 

 

Each function host can create up to 10 worker processes, each of which can handle a single invocation of a function, allowing up to 10 concurrent invocations. This is controlled by setting the FUNCTIONS_WORKER_PROCESS_COUNT app setting. Note that this is NOT scaling; scaling involves adding and reducing the number of host instances dependent on event invocations. A consumption-plan Function might scale up to its maximum limit of 200 instances, with each instance having up to 10 worker processes, allowing a possibility of 2000 concurrent function invocations. 

Seems like the way to go, right? 

Well, it depends on your use case. The new worker SDK is still new and therefore not quite fully mature yet. The list of isolated and in-process features can be found here 

Lack of Rich Bindings 

The rich binding input and output Service SDK types present in the Webjobs SDK are unavailable in isolated Functions. Instead, only simple types, POCOs, and arrays/enumerations are supported. While this may be sufficient for some scenarios, it bars other use cases completely. 

 

For example, while Service Bus triggers can be implemented, the lack of rich types such as ServiceBusReceivedMessage and ServiceBusMessageActions mean being unable to access message properties and metadata, along with finer-grained control of dead lettering, completing, and abandoning messages. I would therefore recommend sticking with in-process apps if you need to use service SDK types that complement triggers off infrastructures such as Service Bus and Blob. The GitHub issue tracking can be found here. 

 

Headaches with App Insights Logging 

Furthermore, adding logging to Application Insights has been a struggle for a lot of developers, as can be seen from this GitHub issue. Notably, the out-of-the-box logging event correlation seen in App Services and in-process functions has had a lot of mixed results in isolated versions. A preview package has very recently been released, and the documentation (and by documented I mean a PR description) purportedly resolves a lot of the issues seen. I have gotten it to work myself, although with a few workarounds: 


  var host = new HostBuilder() 
    .ConfigureFunctionsWorkerDefaults(builder => 
    { 
        builder 
            .AddApplicationInsights() 
            .AddApplicationInsightsLogger(); 
    }) 
    .ConfigureLogging(builder =>  
    { 
        builder.AddConsole(); //Microsoft.Azure.Functions.Worker.ApplicationInsights 1.0.0-preview3 
                              //w. Microsoft.Azure.Functions.Worker 1.11.0-preview2 requires this in order 
                              //to log to console 
        builder.Services.Configure(options => options.Rules.Clear()); // Needed at the moment to log to App Insights 
    }) 
    .Build(); 
 
host.Run(); 
  

App insights snippet of in process

App insights snippet of isolated process

In-process (top) v isolated (bottom) App Insights view. 

 

For people used to the OOB logging of in-process, this code isn’t a welcome sight. An overall word of caution then is to tread carefully. It would be prudent to wait for the SDKs to mature and release stable versions. 

 

Durable Functions? 

With the new release of .NET 7, Durable Functions are now supported in an isolated process. If the limitations above aren’t a blocker, it can be worth exploring how fit-for-purpose they are at this stage. I would love to hear about your experiences with them in the comments! 

 

HTTP Triggers – An alternate to Web APIs? 

Despite this common theme of new tech ‘teething issues’, HTTP triggers are well supported in the isolated model. The HTTPRequestData and HTTPResponseData objects can be used to route, receive requests, and return various responses. On a recent client project, we utilised them to build APIs where the ability to add custom middleware to authenticate requests really highlighted the potential of isolated functions. In a future blog, I will explore the particularly interesting question of whether you should use Function Apps for APIs, compared to the traditional controller-based MVC ASP.NET web API and the new .NET 6 Minimal APIs.  

 

Conclusion 

I hope that this guide has helped explain the fundamental differences between in-process and isolated Functions, and the limitations with the former that Microsoft has attempted to resolve by introducing the latter. This new way of running Function apps is not thoroughly documented and can be somewhat confusing to understand. Like a lot of new technologies, there are still some teething issues, and I would recommend holding off on migrating production apps to the isolated model until some of the points addressed above get resolved, most importantly if your functions rely heavily on the complex input types provided by the Webjobs SDK. Nonetheless, the Microsoft overlords have deemed isolated functions the Azure Functions of the future, so we’d better get used to them.

 

 

If you like what you read, join our team as we seek to solve wicked problems within Complex Programs, Process Engineering, Integration, Cloud Platforms, DevOps & more!

 

Have a look at our opening positions in Deloitte. You can search and see which ones we have in Cloud & Engineering.

 

Have more enquiries? Reach out to our Talent Team directly and they will be able to support you best.

Leave a comment on this blog: