我有一个非常简单的.NET Core应用程序:
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
builder.AddAzureKeyVault("https://MyKeyVault.vault.azure.net");
var stopwatch = new Stopwatch();
stopwatch.Start();
var configuration = builder.Build();
var elapsed = stopwatch.Elapsed;
Console.WriteLine($"Elapsed time: {elapsed.TotalSeconds}");
}
csproj文件如下:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
</ItemGroup>
</Project>
我的问题是,在连接了调试器的情况下,应用程序大约需要10秒钟才能执行(没有调试器的情况下,则大约需要5秒钟)。如果我使用 AddAzureKeyVault 删除该行,则该应用程序将在不到一秒钟的时间内执行。我知道 AddAzureKeyVault 将使应用程序连接到Azure并从密钥库中读取值,但是我希望这样做会更快。
这是预期的行为吗?我可以做些什么来加快速度吗?
答案 0 :(得分:5)
是的,将AzureServiceTokenProvider
显式配置为使用az cli
进行身份验证。您可以通过设置一个名为AzureServicesAuthConnectionString
的环境变量来做到这一点。
重击:
export AzureServicesAuthConnectionString="RunAs=Developer; DeveloperTool=AzureCli"
PowerShell:
$Env:AzureServicesAuthConnectionString = "RunAs=Developer; DeveloperTool=AzureCli"
请注意,您需要在运行应用的任何会话中设置环境变量。
问题的根源在MS docs on authentication中指出,状态为“默认情况下,AzureServiceTokenProvider
使用多种方法来检索令牌。”
在使用的多种方法中,az cli
身份验证是列表中的一种方法。因此,在最终使用AzureServiceTokenProvider
作为令牌源之前,az cli
需要花费一些时间来尝试其他以更高的顺序进行身份验证的身份验证方法。在环境变量中设置连接字符串可以节省您等待其他身份验证方法失败的时间。
此解决方案优于硬编码clientId
和clientSecret
不仅是为了方便,而且还因为az cli
身份验证不需要硬编码clientSecret
或将其存储在纯文本。
答案 1 :(得分:1)
您可以尝试使用clientId和clientSecret获取Azure密钥库,并且它可能运行得更快。
builder.AddAzureKeyVault("https://yourkeyvaultname.vault.azure.net", clientId,clinetSecret);
我对此进行了测试,它花了3秒钟。
有关更多详细信息,您可以参考此article。
答案 2 :(得分:0)
之前建议的带有 clientId 和 AzureServiceTokenProvider 的解决方案对已弃用的数据包 Microsoft.Azure.KeyVault 有影响。但是有了新数据包 Azure.Security.KeyVault.Secrets,我的测量中不再需要这些解决方案。
我的解决方案是从 Azure KeyVault 缓存配置并在本地存储该配置。使用此解决方案,您将能够在开发期间使用 Azure KeyVault 并且仍然具有出色的性能。以下代码显示了如何执行此操作:
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
namespace ConfigurationCache
{
public class Program
{
private static readonly Stopwatch Stopwatch = new Stopwatch();
public static void Main(string[] args)
{
Stopwatch.Start();
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((ctx, builder) =>
{
builder.AddAzureConfigurationServices();
})
.ConfigureServices((hostContext, services) =>
{
Stopwatch.Stop();
Console.WriteLine($"Start time: {Stopwatch.Elapsed}");
Console.WriteLine($"Config: {hostContext.Configuration.GetSection("ConnectionStrings:MyContext").Value}");
services.AddHostedService<Worker>();
});
}
public static class AzureExtensions
{
public static IConfigurationBuilder AddAzureConfigurationServices(this IConfigurationBuilder builder)
{
// Build current configuration. This is later used to get environment variables.
IConfiguration config = builder.Build();
#if DEBUG
if (Debugger.IsAttached)
{
// If the debugger is attached, we use cached configuration instead of
// configurations from Azure.
AddCachedConfiguration(builder, config);
return builder;
}
#endif
// Add the standard configuration services
return AddAzureConfigurationServicesInternal(builder, config);
}
private static IConfigurationBuilder AddAzureConfigurationServicesInternal(IConfigurationBuilder builder, IConfiguration currentConfig)
{
// Get keyvault endpoint. This is normally an environment variable.
string keyVaultEndpoint = currentConfig["KEYVAULT_ENDPOINT"];
// Setup keyvault services
SecretClient secretClient = new SecretClient(new Uri(keyVaultEndpoint), new DefaultAzureCredential());
builder.AddAzureKeyVault(secretClient, new AzureKeyVaultConfigurationOptions());
return builder;
}
private static void AddCachedConfiguration(IConfigurationBuilder builder, IConfiguration currentConfig)
{
//Setup full path to cached configuration file.
string path = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MyApplication");
string filename = System.IO.Path.Combine(path, $"configcache.dat");
// If the file does not exists, or is more than 12 hours, update the cached configuration.
if (!System.IO.File.Exists(filename) || System.IO.File.GetLastAccessTimeUtc(filename).AddHours(12) < DateTime.UtcNow)
{
System.IO.Directory.CreateDirectory(path);
UpdateCacheConfiguration(filename, currentConfig);
}
// Read the file
string encryptedFile = System.IO.File.ReadAllText(filename);
// Decrypt the content
string jsonString = Decrypt(encryptedFile);
// Create key-value pairs
var keyVaultPairs = JsonSerializer.Deserialize<Dictionary<string, string>>(jsonString);
// Use the key-value pairs as configuration
builder.AddInMemoryCollection(keyVaultPairs);
}
private static void UpdateCacheConfiguration(string filename, IConfiguration currentConfig)
{
// Create a configuration builder. We will just use this to get the
// configuration from Azure.
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
// Add the services we want to use.
AddAzureConfigurationServicesInternal(configurationBuilder, currentConfig);
// Build the configuration
IConfigurationRoot azureConfig = configurationBuilder.Build();
// Serialize the configuration to a JSON-string.
string jsonString = JsonSerializer.Serialize(
azureConfig.AsEnumerable().ToDictionary(a => a.Key, a => a.Value),
options: new JsonSerializerOptions()
{
WriteIndented = true
}
);
//Encrypt the string
string encryptedString = Encrypt(jsonString);
// Save the encrypted string.
System.IO.File.WriteAllText(filename, encryptedString);
}
// Replace the following with your favorite encryption code.
private static string Encrypt(string str)
{
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(str));
}
private static string Decrypt(string str)
{
return System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(str));
}
}
}