はじめに
サーバーレスアプリの開発といえば、皆さん大好き「Azure Functions/Azure App Service」ですよね。
特に便利な機能の一つで、ダウンタイムを発生させずにデプロイやフォールバックができる「デプロイスロット」があります。このデプロイスロットを最近完全に理解した←ので備忘録です。
実験
今回紹介するのはデプロイスロットの挙動と環境変数を使う時の注意点です。手を動かせる人は以下、一緒にハンズオンでやってみてください。
1.準備(アプリの作成)
まずはFunctionsアプリを準備します。今回はこのチュートリアルに沿ってVSCodeでC#のAPIを作りました。
Visual Studio Code: v1.72.0
Azure Functions拡張機能: v1.8.1
チュートリアルの説明は割愛するとして、手順通りに進めると以下のようにレスポンスを返すHTTPトリガーのAPIができました。(プログラムや名前空間の名前をDeploySlotTest
としています)

今回は環境変数を使ったアプリでデプロイスロットを試したいので、 local.settings.json
やC#のコードを以下のように修正して環境変数 VERSION
の値を返すようにしました。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"VERSION": "1.0.0"
}
}
// using省略
[FunctionName("DeploySlotTest")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
string version = Environment.GetEnvironmentVariable("VERSION");
string responseMessage = $"Hello World. Version is {version}";
return new OkObjectResult(responseMessage);
}

2.デプロイ
アプリができたらFunctionsにリソースを作成し、デプロイします。
デプロイが完了したら[関数のURLの取得]をコピーしてブラウザで開いてみてください。すると、まだ環境変数 VERSION
を準備していないので、「Hello World. Version is」と表示されるはずです。


[構成]から[新しいアプリケーション設定]で VERSION
を作成し、保存するとバージョンの値も表示されるようになります。

3.デプロイスロット
それでは本題のデプロイスロットを試してみましょう。
[デプロイメント] > [デプロイ スロット]から[スロットの作成]を実行して「slot1」を作成します。このときの[構成]は現在の運用のと全く同じものが出来上がります。
(ただし、スロットの作成時は関数が空になっているので、再度スロットにもデプロイをして動作確認してみましょう)
ここで、本番の方の[構成]にあるVERSION
を1.0.0
から1.1.0
に更新します。もちろん「運用」のAPIを再度叩くと表示は1.1.0
に変わっています。それでは「slot1」の方はどうでしょう。1.0.0
のままですね。(チェックポイント1)

次は「slot1」に新しい環境変数NEW_ENV
を参照するようにアプリケーションを改修してみます。
[FunctionName("DeploySlotTest")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
string version = Environment.GetEnvironmentVariable("VERSION");
string newEnv = Environment.GetEnvironmentVariable("NEW_ENV");
string responseMessage = $"Hello World. Version is {version}\nNew environment variable added on {newEnv}";
return new OkObjectResult(responseMessage);
}
再度「slot1」にデプロイし、「slot1」の[構成]でVERSION
を2.0.0
に変更し、2022/10/14
という値の環境変数NEW_ENV
を追加します。これをスワップすると「運用」と「slot1」の状態が入れ替わります。今度は「slot1」の[構成]を見ると、先ほど追加した新しい環境変数NEW_ENV
がないことが確認できます。(チェックポイント2)

最後に、環境変数の編集画面にある[デプロイ スロットの設定]オプションについて少し触れます。スロット同士をスワップすると環境変数の値も入れ替わりますが、このオプションを有効にしている環境変数の値は現在のスロットに固定されます。確認しておきましょう。
「運用」のVERSION
を編集して[デプロイ スロットの設定]を有効にします。この状態で再々度スワップします。すると、バージョンは2.0.0
のままアプリケーションの中身は前に戻りました。(チェックポイント3)


ここまでの流れを図示すると、こんな感じです↓
- スロットの作成時は、現在の運用スロットの状態(環境変数など)がそのままコピーされる
- 以後、[構成]で編集/追加した環境変数はそのスロットにのみ反映される
- スワップするとアプリケーションも[構成]の値も入れ替わる([デプロイ スロットの設定]を有効にした環境変数は除く)

まとめ
アプリケーションの改修とは別に[構成]の内容を更新する場合は、「どのスロットを触っているか」を慎重に確認しつつ、「環境変数の追加漏れ」がないかの見直しはかなり重要だと思いました。(自戒)