LoginSignup
41
42

More than 5 years have passed since last update.

Azure Functions での環境変数の切り替え

Posted at

Azure Functions を使うときに、C# をつかうと、特に便利だ。世界最強の統合 IDE が Functions を使うときでも使うことができる。しかも、デバッグもできるし、デプロイも出来るので超便利だ。

さて、いま、Azure Functions を活用したプロジェクトにいるのだが、環境変数の切り替えみたいなものはどのようにマネージしたらいいだろうか?プロダクション環境、ステージング環境、テスト環境みたいなもの。

12 factors に従うと、環境変数ということになるが、それをどのようにマネージするのがよさげか?という話である。実はここらへんもうまい仕組みがあるので、それを活用していきたので、その知見をシェアしたい。

1. Visual Studio で Azure Functions を作成する

まずは簡単なプログラムを書いてみよう。 Azure Functions のテンプレートを使うと、デフォルトで、local.settings.json というファイルが生成される。こちらは、Azure Functions をローカル実行するときのコンフィグを書くことができる。

using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using System.Configuration;

namespace ConfigurationSpike
{
    public static class ConfigFunction
    {
        [FunctionName("ConfigFunction")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
        {
            log.Info("C# HTTP trigger function processed a request.");
            var env = ConfigurationManager.AppSettings.Get("Environment");
            var conn = ConfigurationManager.ConnectionStrings["EventHub"].ConnectionString;

            return req.CreateResponse(HttpStatusCode.OK, $"Current Environment: {env} Connection String: {conn}");

        }
    }
}

local.setting.json

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "AzureWebJobsDashboard": "",
    "Environment": "Test"
  },
  "ConnectionStrings": {
    "EventHub": "Endpoint=SomeConnection"
  }
}

このValue の中の項目は、Environment Variables になる。だから、Environment.GetEnvironmentVariable(string); のメソッドで取れるのだが、お勧めしない。お勧めは上記でつかわれているとおり、ConfigurationManager を使う方法だ。ConfigrationManager は、本来、app.config とかの内容を管理できるものだが、Azure Functions では、local.settings.json や、Azure Function を Azure 上にデプロイしたときの、App Settings の内容も取得できる。さらに、クラスになっているので、キーの一覧取得とか、必要そうなメソッドがすでに実装されている。

var env = ConfigurationManager.AppSettings.Get("Environment");
var conn = ConfigurationManager.ConnectionStrings["EventHub"].ConnectionString;

こちらをとって実際に、Azure Functions をローカルで動作させて、実行させてみる。
Config01.png

ブラウザから実行してみると、環境変数も、Connection String もちゃんととれているのがわかる。

Config02.png

ちなみに、変数名とかを間違えると、こんな例外がでるのでびっくりするかもしれないが、Object reference not set to an instance of an object. が出たときは、単に、コードから呼ばれる環境変数がみつからないだけだ。

<ApiErrorModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Azure.WebJobs.Script.WebHost.Models">
<Arguments xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" i:nil="true"/>
<ErrorCode>0</ErrorCode>
<ErrorDetails>
Microsoft.Azure.WebJobs.Host.FunctionInvocationException : Exception while executing function: Functions.ConfigFunction ---> System.NullReferenceException : Object reference not set to an instance of an object. at async ConfigurationSpike.ConfigFunction.Run(HttpRequestMessage req,TraceWriter log) at c:\users\tsushi\Source\Repos\ConfigurationSpike\ConfigurationSpike\ConfigFunction.cs : 19 at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at async Microsoft.Azure.WebJobs.Script.Description.FunctionInvokerBase.Invoke(Object[] parameters) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionInvoker`1.InvokeAsync[TReflected](Object[] arguments) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.InvokeAsync(IFunctionInvoker invoker,Object[] invokeParameters,CancellationTokenSource timeoutTokenSource,CancellationTokenSource functionCancellationTokenSource,Boolean throwOnTimeout,TimeSpan timerInterval,IFunctionInstance instance) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithWatchersAsync(IFunctionInstance instance,IReadOnlyDictionary`2 parameters,TraceWriter traceWriter,ILogger logger,CancellationTokenSource functionCancellationTokenSource) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(??) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(??) End of inner exception at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(??) at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.TryExecuteAsync(IFunctionInstance functionInstance,CancellationToken cancellationToken) at Microsoft.Azure.WebJobs.Host.Executors.ExceptionDispatchInfoDelayedException.Throw() at async Microsoft.Azure.WebJobs.JobHost.CallAsyncCore(MethodInfo method,IDictionary`2 arguments,CancellationToken cancellationToken) at async Microsoft.Azure.WebJobs.Script.ScriptHost.CallAsync(String method,Dictionary`2 arguments,CancellationToken cancellationToken) at async Microsoft.Azure.WebJobs.Script.WebHost.WebScriptHostManager.HandleRequestAsync(FunctionDescriptor function,HttpRequestMessage request,CancellationToken cancellationToken) at async Microsoft.Azure.WebJobs.Script.WebHost.Controllers.FunctionsController.ProcessRequestAsync(HttpRequestMessage request,FunctionDescriptor function,CancellationToken cancellationToken) at async Microsoft.Azure.WebJobs.Script.WebHost.Controllers.FunctionsController.<>c__DisplayClass3_0.<ExecuteAsync>b__0(??) at async Microsoft.Azure.WebJobs.Extensions.Http.HttpRequestManager.ProcessRequestAsync(HttpRequestMessage request,Func`3 processRequestHandler,CancellationToken cancellationToken) at async Microsoft.Azure.WebJobs.Script.WebHost.Controllers.FunctionsController.ExecuteAsync(HttpControllerContext controllerContext,CancellationToken cancellationToken) at async System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken) at async System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken) at async Microsoft.Azure.WebJobs.Script.WebHost.Handlers.SystemTraceHandler.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken) at async Microsoft.Azure.WebJobs.Script.WebHost.Handlers.WebScriptHostHandler.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken) at async System.Web.Http.HttpServer.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
</ErrorDetails>
<Id>26080856-a5b1-4715-8191-e65e8008ce60</Id>
<Message>
Exception while executing function: Functions.ConfigFunction -> Object reference not set to an instance of an object.
</Message>
<RequestId>9db538e4-0a0b-4fab-ab15-7cbcf30e74e4</RequestId>
<StatusCode>InternalServerError</StatusCode>
</ApiErrorModel>

Azure にデプロイして設定する

私のお勧めは、このような方法で、環境ごとに、この設定ファイルを作ることではない。Azure Functions は様々なサービスを連携させることが多いので、これらの設定ファイルを手でゴリゴリ書いていてはとても面倒だ。しかも、こういうシークレットをがっつりとリポジトリにぶち込むのも抵抗がある。じゃあ、どうするかというと、Azure 上に、このアプリをデプロイして、そこで環境を作ろう。

プロジェクトを右クリックして、Publish で簡単にデプロイできる。

Config03.png

こんな感じで、App Settings を Azure の Azure Functions 上で設定する。

Config04.png

これが、App Settings

Config05.png

実行してみて、動作確認する。

Config06.png

ローカル実行のために、App Settings を取得する

Azure の方で、実際の Azure Functions の動作確認をしたら、どうするか?というと、Azure Functions CLI というものがあるので、それをインストールしておく。

npm i -g azure-functions-cli

そして、このコマンドをうてばいい。

func azure functionapp fetch-app-settings AZURE_FUNCTION_NAME

すると、Azure 上の Azure Functions で設定している内容がごっそりファイルになって落ちてくる。local.settings.json は、暗号化にも、非暗号にも対応しているが、暗号化された結果が返ってくるので、中身がわからなくなっているが、ConfigrationManager はこれに対応している。こうしてあげると、ローカルで実行していても、Azure で、Azure Functions を動かしている感じで実行できる。これはいい感じだ。
だから、Azure 上で、環境構築したら、その分だけ、先のコマンドでファイルを落とせばよい。

Config07.png

セッティングの整理

このように、環境ごとに、Azure Functions を Azure に設定したら、ダウンロードして、リポジトリにぶち込めばよい。名前は、local.settings.ushio.json みたいに個人の名前をいれておくといい。それを、local.settings.json を、.gitignore に入れておけば完了で、使いたいlocal.settings.json を都度、local.settings.jsonにコピーして使うとよい。

Config08.png

これは、人の名前がついているので、「なんで!」と思うかもしれないが、理由は、先ほどの暗号化は、各PCのKeyを使うので、該当PCでしか解凍できないようになっているから。

もし、この程度の情報はプライベートのリポジトリだからいいやという人は、平文で、のこして、IsEncrypted を false にしておくとよい。プログラムに影響はない。

おわりに

私もこういう方法を思いつかなかったが、イケてる同僚がこの方法だったので、学んで試してブログにしてみました。

リソース

41
42
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
42