You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure Function中从KeyVault更新appsettings.json值失败排查

Azure Function配置KeyVault引用未替换的问题排查

问题根源

你的代码存在两个核心问题:

  • 未启用KeyVault配置引用的自动解析:AddAzureKeyVault方法默认仅将KeyVault中的密钥加载为独立配置源,不会自动扫描并替换appsettings中@Microsoft.KeyVault(...)格式的引用字符串。
  • 配置构建逻辑无效:第二次构建ConfigurationBuilder后,未将最终配置实例关联到Functions的配置体系中,后续添加的KeyVault配置源实际未生效。

修正方案(推荐方式)

针对In-Process模型的Azure Function,修改ConfigureAppConfiguration方法,通过密钥名称与配置路径的映射实现自动覆盖,无需保留@Microsoft.KeyVault(...)格式引用:

public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
    try
    {
        FunctionsHostBuilderContext context = builder.GetContext();

        // 1. 构建基础配置,获取KeyVault连接信息
        var baseConfig = builder.ConfigurationBuilder
            .SetBasePath(context.ApplicationRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{context.EnvironmentName}.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables()
            .Build();

        // 2. 配置托管标识凭据
        string userAssignedClientId = baseConfig["userAssignedClientId"];
        var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions 
        { 
            ManagedIdentityClientId = userAssignedClientId 
        });

        // 3. 重置配置构建器,添加所有配置源并启用密钥路径映射
        builder.ConfigurationBuilder.Clear();
        builder.ConfigurationBuilder
            .SetBasePath(context.ApplicationRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{context.EnvironmentName}.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables()
            .AddAzureKeyVault(new Uri(baseConfig["vaultUri"]), credential, new KeyVaultSecretManager());

        // 4. 构建最终配置
        _ = builder.ConfigurationBuilder.Build();
    }
    catch (Exception e)
    {
        throw new Exception($"{e.StackTrace} {e.Message}");
    }
}

// 自定义密钥管理器:将KeyVault密钥名称转换为配置路径(如MySection-MyUrl → MySection:MyUrl)
public class KeyVaultSecretManager : IKeyVaultSecretManager
{
    public bool Load(SecretProperties secret) => true;

    public string GetKey(KeyVaultSecret secret)
    {
        return secret.Name.Replace("-", ":");
    }
}

配套调整

  • 修改appsettings.json,将MyUrl的引用格式替换为占位值(最终会被KeyVault值覆盖):
    {
      "IsEncrypted": false,
      "Values": {
        "AzureWebJobsStorage": "",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet"
      },
      "MySection": {
        "MyUrl": ""
      },
      "vaultUri": "https://mykv.vault.azure.net/",
      "userAssignedClientId": "你的托管标识ClientId"
    }
    
  • 确保KeyVault中存在名称为MySection-MyUrl的密钥,且托管标识拥有该密钥的Get权限。

保留引用格式的兼容方案

如果必须保留@Microsoft.KeyVault(...)格式,可以在配置构建完成后手动解析替换:

public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
    try
    {
        FunctionsHostBuilderContext context = builder.GetContext();

        var baseConfig = builder.ConfigurationBuilder
            .SetBasePath(context.ApplicationRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{context.EnvironmentName}.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables()
            .Build();

        string userAssignedClientId = baseConfig["userAssignedClientId"];
        var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions 
        { 
            ManagedIdentityClientId = userAssignedClientId 
        });
        var secretClient = new SecretClient(new Uri(baseConfig["vaultUri"]), credential);

        // 解析所有配置中的KeyVault引用
        var overrideSettings = new Dictionary<string, string>();
        foreach (var kvp in baseConfig.AsEnumerable())
        {
            if (kvp.Value?.StartsWith("@Microsoft.KeyVault(") == true)
            {
                // 提取SecretUri
                var secretUriPart = kvp.Value.Split(new[] { "SecretUri=" }, StringSplitOptions.None)[1].TrimEnd(')');
                var secret = secretClient.GetSecretAsync(new Uri(secretUriPart)).GetAwaiter().GetResult();
                overrideSettings.Add(kvp.Key, secret.Value.Value);
            }
        }

        // 重置并构建最终配置
        builder.ConfigurationBuilder.Clear();
        builder.ConfigurationBuilder
            .SetBasePath(context.ApplicationRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{context.EnvironmentName}.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables()
            .AddInMemoryCollection(overrideSettings)
            .Build();
    }
    catch (Exception e)
    {
        throw new Exception($"{e.StackTrace} {e.Message}");
    }
}

关键注意事项

  • 本地测试时,DefaultAzureCredential会优先使用Azure CLI、Visual Studio等本地开发凭据;如需指定服务主体,需设置AZURE_CLIENT_ID、AZURE_CLIENT_SECRET、AZURE_TENANT_ID环境变量。
  • 确保托管标识/服务主体已被授予KeyVault的Secret Get权限,否则无法获取密钥值。

内容的提问来源于stack exchange,提问作者JakeUT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 16:55:21