Containerised Azure function (v4, dotnet-isolated) breaks when trying to use environment variables as config source

1.5k views Asked by At

This follows on from a previous question. I'm trying to build an Azure function to run as a Linux container image in an isolated process using .NET 6 and v4 of the functions runtime.

I have followed the Microsoft guide to get a working, running sample function with HTTP trigger. I have then begun the process of adding my code to the app and immediately become blocked. This is the Main method of my Program:

public static void Main()
{
    var host = new HostBuilder()
        .ConfigureFunctionsWorkerDefaults()
        .ConfigureAppConfiguration((context, builder) =>
        {
            builder.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
            builder.AddEnvironmentVariables();
        })
        .ConfigureServices((context, collection) => ConfigureServices(collection, context.Configuration))
        .Build();

    host.Run();
}

What I've discovered is that the function works fine until I add the line builder.AddEnvironmentVariables();. With this change only, the function suddenly craps out on startup with the exception:

System.InvalidOperationException at Microsoft.Azure.WebJobs.Script.Workers.Rpc.RpcFunctionInvocationDispatcherLoadBalancer.GetLanguageWorkerChannel

There's a message hidden in there saying "Did not find any initialized language workers" which I understand is Azure Function language for "something bad happened during startup but I'm not going to tell you what or why".

In my particular use case I'm depending on the function being able to use environment variables as a config source because my pipeline will be passing these to the ARM template as site config. So my question is, how is this possible in isolated mode?

1

There are 1 answers

0
Tom Troughton On

Well it turns out that environment variables are added automatically by the call to ConfigureFunctionsWorkerDefaults. You can see this here in WorkerHostBuilderExtensions. I have no idea why attempting to add environment variables as a config source in the traditional dotnet core way causes the whole thing to blow up, but at least I'm unblocked. Hope this helps someone else.

It's woth also pointing out that this will affect the order in which you use the ConfigureAppConfiguration and ConfigureFunctionsWorkerDefaults extensions. Since the latter adds console input and environment variables as config sources, if you also use an appsettings.json on disk it's likely you'll want environment variables to override its config so ConfigureFunctionsWorkerDefaults would need to be called after ConfigureAppConfiguration. Therefore my Program looks like this:

public class Program
{
    public static async Task Main()
    {
        var host = new HostBuilder()
            .ConfigureAppConfiguration((context, configurationBuilder) =>
            {
                configurationBuilder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

                if (context.HostingEnvironment.IsDevelopment())
                {
                    configurationBuilder.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true);
                }
                else
                {
                    configurationBuilder.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true);
                }
            })
            .ConfigureFunctionsWorkerDefaults()
            .ConfigureServices((context, collection) => ConfigureServices(collection, context.Configuration))
            .Build();

        await host.RunAsync();
    }

    private static void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
        services
            .AddLogging()
            .AddDomainLayer(configuration)
            .AddHttpLayer(configuration)
            .AddKeyVaultLayer(configuration);
    }
}