Skip to content

Azure Functions .NET 8 Isolated での appsettings.json 読み込み問題

問題概要

Azure Functions を .NET 6 インプロセスモデルから .NET 8 Isolated ワーカーモデルに移行する際、appsettings.json ファイルの構成設定がローカル環境では正常に読み込まれるものの、Azure 環境(特に Linux)では読み込まれないという問題が発生します。具体的な症状は以下の通りです:

  • ローカル実行時:appsettings.json の設定値が正常に読み取られる
  • Azure デプロイ時:appsettings.json の設定値が無視され null が返る
  • 環境変数からの設定は正常に適用される
  • ファイルはデプロイ先に存在することを確認済み

根本的な原因は、Azure 環境(特に Linux)での アプリケーションベースパスの差異にあります。インプロセスモデルとは異なり、Isolated モデルでは実行コンテキストが変わります。

解決策

方法 1: ベースパスの明示的指定 (Linux 環境向け)

Azure Linux 環境では、アプリケーションファイルは /home/site/wwwroot に配置されます。ConfigureAppConfiguration でベースパスを明示的に設定します。

cs
.ConfigureAppConfiguration((context, builder) =>
{
    // Azure 環境ではベースパスを固定
    if (!context.HostingEnvironment.IsDevelopment())
    {
        builder.SetBasePath("/home/site/wwwroot");
    }
    
    builder
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();
})

特徴:

  • Linux 環境で確実に動作
  • ホスティングプラン(Consumption/App Service)を問わない
  • 開発環境と本番環境を自動切り替え

注意点

/home/site/wwwroot パスは Azure の Linux ホスティング環境に依存します。将来のプラットフォーム変更に備え、他の方法との併用を推奨します。

方法 2: アセンブリ位置を基準としたパス設定 (クロスプラットフォーム)

現在のアセンブリの場所をベースパスとして使用する方法です。プラットフォーム非依存で動作します。

cs
internal static class Config
{
    public static void AddAppSettings(
        HostBuilderContext hostContext, IConfigurationBuilder builder)
    {
        // 現在のアセンブリ位置をベースパスに設定
        string assemblyPath = Assembly.GetExecutingAssembly().Location;
        string assemblyDir = Path.GetDirectoryName(assemblyPath)
                         ?? Directory.GetCurrentDirectory();
                         
        builder.SetBasePath(assemblyDir)
               .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
               .AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true)
               .AddEnvironmentVariables();
    }
}
cs
.ConfigureAppConfiguration(Config.AddAppSettings)

特徴:

  • Windows/Linux の両方で動作
  • Azure の内部パス変更に影響されない
  • 信頼性が高い

方法 3: 現在の作業ディレクトリを使用 (App Service Plan 向け)

Directory.GetCurrentDirectory() を使用する簡易的な方法ですが、Consumption プランでは動作しない場合があります。

cs
.ConfigureAppConfiguration((context, builder) =>
{
    // 現在の作業ディレクトリを取得
    var currentDirectory = Directory.GetCurrentDirectory();
    
    builder.SetBasePath(currentDirectory)
           .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
           .AddEnvironmentVariables();
})

ホスティングプラン別の挙動

  • App Service Plan: 正常動作
  • Consumption Plan: 動作しない場合あり

ファイル配置の設定 (csproj)

すべての解決策で共通して必要な appsettings.json の配置設定:

xml
<ItemGroup>
    <!-- appsettings.json のコピー設定 -->
    <None Update="appsettings.json">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    
    <!-- 開発環境用設定(デプロイ除外) -->
    <None Update="appsettings.Development.json">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
    
    <!-- local.settings.json はデプロイしない -->
    <None Update="local.settings.json">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
</ItemGroup>

根本原因の解説

Azure Functions の Isolated ワーカーモデルでは、.NET アプリケーションが別プロセスで実行されるため、パスの解決方法がインプロセスモデルとは異なります。特に Linux 環境では、以下のパス差異が問題を引き起こします:

環境ContentRootPath実際のアプリパス
ローカルプロジェクトルートプロジェクトルート
Azure (Windows)アプリケーションフォルダアプリケーションフォルダ
Azure (Linux)/azure-functions-host/home/site/wwwroot

この差異により、Linux 環境ではデフォルトの ContentRootPath が間違った場所を参照し、appsettings.json が見つからなくなります。

ベストプラクティス

  1. 環境ごとの設定管理

    cs
    .AddJsonFile("appsettings.json")
    .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
  2. 設定値のバリデーション

    cs
    services.AddOptions<DummyOption>()
            .Bind(configuration.GetSection(nameof(DummyOption)))
            .ValidateDataAnnotations();
  3. 構成ソースの優先順位

    cs
    .AddJsonFile("appsettings.json") // 基本設定
    .AddJsonFile($"appsettings.{env}.json", optional: true) // 環境別設定
    .AddEnvironmentVariables() // 環境変数(上書き用)

最終的な Program.cs 例

cs
var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureAppConfiguration((context, builder) =>
    {
        // 方法1: Linux環境用ベースパス指定
        if (!context.HostingEnvironment.IsDevelopment())
        {
            builder.SetBasePath("/home/site/wwwroot");
        }
        
        // 方法2: アセンブリ基準パス(クロスプラットフォーム)
        var assemblyPath = Assembly.GetExecutingAssembly().Location;
        var assemblyDir = Path.GetDirectoryName(assemblyPath);
        if (!string.IsNullOrEmpty(assemblyDir))
        {
            builder.SetBasePath(assemblyDir);
        }

        builder
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
    })
    .ConfigureServices((context, services) =>
    {
        var configuration = context.Configuration;
        services.Configure<DummyOption>(configuration.GetSection(nameof(DummyOption)));
    })
    .Build();

host.Run();

まとめ

Azure Functions の .NET 8 Isolated モデルで appsettings.json を確実に読み込むには、プラットフォーム環境に応じたベースパスの明示的指定が不可欠です。特に Linux 環境では:

  1. Azure のファイル配置パス (/home/site/wwwroot) を直接指定するか
  2. アセンブリの位置を基準としたパス解決を行う

上記いずれかの方法を実施し、csproj ファイルでの配置設定を適切に行うことで、ローカルと Azure 環境の両方で一貫した構成管理が可能になります。