かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Azure App Configuration を試してみよう on Azure Functions

アプリ設定を WebApps の構成のアプリケーション設定とは別に管理できる App Configuration を試してみました!

docs.microsoft.com

最近は ARM Template 書いてみるのがブームなので、以下のように ARM Template 作って下準備しました。

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "appName": {
            "type": "string"
        }
    },
    "variables": {
        "storageAccountName": "[concat(parameters('appName'), 'storage')]",
        "storageAccountid": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
        "appInsightsName": "[concat(parameters('appName'), '-insights')]",
        "functionAppName": "[concat(parameters('appName'), '-func')]",
        "hostingPlanName": "[concat(parameters('appName'), '-plan')]",
        "appConfigurationName": "[concat(parameters('appName'), 'config')]",
        "appConfigurationId": "[concat(resourceGroup().id,'/providers/','Microsoft.AppConfiguration/configurationStores/', variables('appConfigurationName'))]"
    },
    "resources": [
        {
            "type": "Microsoft.Storage/storageAccounts",
            "name": "[variables('storageAccountName')]",
            "apiVersion": "2019-04-01",
            "location": "[resourceGroup().location]",
            "kind": "StorageV2",
            "sku": {
                "name": "Standard_LRS"
            }
        },
        {
            "apiVersion": "2015-05-01",
            "name": "[variables('appInsightsName')]",
            "type": "Microsoft.Insights/components",
            "kind": "web",
            "location": "[resourceGroup().location]",
            "tags": {
                "[concat('hidden-link:', resourceGroup().id, '/providers/Microsoft.Web/sites/', variables('functionAppName'))]": "Resource"
            },
            "properties": {
                "Application_Type": "web",
                "ApplicationId": "[variables('appInsightsName')]"
            }
        },
        {
            "type": "Microsoft.Web/serverfarms",
            "apiVersion": "2016-09-01",
            "name": "[variables('hostingPlanName')]",
            "location": "[resourceGroup().location]",
            "properties": {
                "name": "[variables('hostingPlanName')]",
                "computeMode": "Dynamic"
            },
            "sku": {
                "name": "Y1",
                "tier": "Dynamic",
                "size": "Y1",
                "family": "Y",
                "capacity": 0
            }
        },
        {
            "apiVersion": "2016-08-01",
            "type": "Microsoft.Web/sites",
            "name": "[variables('functionAppName')]",
            "location": "[resourceGroup().location]",
            "kind": "functionapp",
            "dependsOn": [
                "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
                "[resourceId('Microsoft.Insights/components', variables('appInsightsName'))]",
                "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
                "[resourceId('Microsoft.AppConfiguration/configurationStores', variables('appConfigurationName'))]"                
            ],
            "properties": {
                "siteConfig": {
                    "appSettings": [
                        {
                            "name": "AzureWebJobsStorage",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
                        },
                        {
                            "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
                        },
                        {
                            "name": "WEBSITE_CONTENTSHARE",
                            "value": "[toLower(variables('functionAppName'))]"
                        },
                        {
                            "name": "FUNCTIONS_WORKER_RUNTIME",
                            "value": "dotnet"
                        },
                        {
                            "name": "FUNCTIONS_EXTENSION_VERSION",
                            "value": "~3"
                        },
                        {
                            "name": "WEBSITE_RUN_FROM_PACKAGE",
                            "value": 1
                        },
                        {
                            "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
                            "value": "[reference(resourceId('microsoft.insights/components/', variables('appInsightsName')), '2015-05-01').InstrumentationKey]"
                        },
                        {
                            "name": "AppConfigurationConnectionString",
                            "value": "[ListKeys(variables('appConfigurationId'), '2019-10-01').value[2].connectionString]"
                        }
                    ]
                }
            }
        },
        {
            "name": "[variables('appConfigurationName')]",
            "type": "Microsoft.AppConfiguration/configurationStores",
            "apiVersion": "2019-10-01",
            "location": "[resourceGroup().location]",
            "properties": {
            },
            "sku": {
                "name": "standard"
            }
        }
    ],
    "outputs": {
    },
    "functions": [
    ]
}

ARM Template Viewer で見ると以下のような感じです。

f:id:okazuki:20200304132936p:plain

App Configuration の接続文字列は ListKeys 関数でプライマリー読み書き、セカンダリー読み書き、プライマリー読み込み専用、セカンダリー読み込み専用の順番で入った値が返ってくるので 3 番目の要素の connectionString で取れます。

App Configuratoin の ARM Template は以下のドキュメントを参考にしました。

docs.microsoft.com

接続文字列をとってくる関数は、以下のドキュメントを見て ListKeys なんだと確認しました。

docs.microsoft.com

とりあえず App Configuration を使わないケース

Azure Functions のプロジェクトを作って以下の NuGet パッケージを入れます。DIしたいので。

  • Microsoft.Azure.Functions.Extensions

local.settings.json を以下のようにして…

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet",
        "Sample:Message": "Hello from local.settings.json"
    }
}

そして、Startup.cs で SampleObject というクラスに Sample:Message が設定されるようにします。

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(AppConfigFunc.Startup))]

namespace AppConfigFunc
{
    public class Startup : FunctionsStartup
    {
        private IConfiguration Configuration { get; }

        public Startup()
        {
            var config = new ConfigurationBuilder()
                .AddEnvironmentVariables()
                .AddJsonFile("local.settings.json", true);

            // 後で App Configuration から取り込む予定

            Configuration = config.Build();       
        }

        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.Configure<SampleObject>(Configuration.GetSection("Sample"));
        }
    }

    public class SampleObject
    {
        public string Message { get; set; }
    }
}

適当に関数で使ってみましょう。SampleObject を DI して Message を返すだけの関数を作ります。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.Extensions.Options;

namespace AppConfigFunc
{
    public class SayHello
    {
        private readonly SampleObject _sampleObject;
        public SayHello(IOptions<SampleObject> sampleObject)
        {
            _sampleObject = sampleObject.Value;
        }

        [FunctionName("SayHello")]
        public IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
            ILogger log)
        {
            return new OkObjectResult(_sampleObject);
        }
    }
}

実行して SayHello 関数を呼び出すと…

f:id:okazuki:20200303215506p:plain

思った通りですね。

App Configuration 対応

では、App Configuration 対応してみます。以下の NuGet パッケージを追加します。

  • Microsoft.Extensions.Configuration.AzureAppConfiguration

Startup.cs を以下のようにして本番のときだけ App Configuration から値を取るようにしてみました。 今回はローカルでは local.settings.json を使う想定で

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(AppConfigFunc.Startup))]

namespace AppConfigFunc
{
    public class Startup : FunctionsStartup
    {
        public IConfiguration Configuration { get; }

        public Startup()
        {
            var config = new ConfigurationBuilder()
                .AddEnvironmentVariables()
                .AddJsonFile("local.settings.json", true);

            // App Configuration から取り込む
            var builtConfig = config.Build();
            if (builtConfig["AZURE_FUNCTIONS_ENVIRONMENT"] != "Development")
            {
                config.AddAzureAppConfiguration(builtConfig["AppConfigurationConnectionString"]);
            }

            Configuration = config.Build();       
        }

        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.Configure<SampleObject>(Configuration.GetSection("Sample"));
        }
    }

    public class SampleObject
    {
        public string Message { get; set; }
    }
}

コードは安定のしばやんの blog を参考にしました。

blog.shibayan.jp

そして、App Configuration に以下のような値を設定します。

f:id:okazuki:20200303222007p:plain

ARM Template で作った Function Apps にデプロイして実行すると、以下のような値になりました!やったね!

f:id:okazuki:20200303222042p:plain

App Configuration の設定を動的に変更したい

その時用のメモとしてドキュメントへのリンクをぺたり。

docs.microsoft.com

ソースコード

github.com