Azure Functions を使った Azure Storage の増分バックアップ

先日作成した Azure Storage のコンテナをバックアップするプログラムは、高速化しても完了までに 5 時間弱かかる。

tnakamura.hatenablog.com

こいつを毎日実行するのは気が引ける。コンテナの完全バックアップは週一回にしておき、増分バックアップを毎日取る方針に切り替えた。

最初、増分バックアップも完全バックアップ同様にコンソールアプリケーションで作成し、夜間に実行しようと思ったけど、ふと閃いた。Azure Functions を使えば良いのでは、と。Azure Functions はブロブが登録されたのをトリガーに起動できるから、都度コピーすれば増分バックアップの出来上がりってことになる。

早速 Azure Functions を書いてみた。ソースコードを Git で管理したかったので、Visual Studio で Azure Functions プロジェクトを作成している。コードは次の通り。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;

namespace AzureStorageIncrementalBackup
{
    // Azure Functions を使って増分バックアップ
    public static class AzureStorageIncrementalBackup
    {
        // ブロブが登録されると起動する
        [FunctionName("AzureStorageIncrementalBackup")]
        public static void Run([BlobTrigger("mycontainer/{name}", Connection = "AzureWebJobsStorage")]CloudBlockBlob myBlob, TraceWriter log)
        {
            CopyBlobAsync(myBlob, log).GetAwaiter().GetResult();
        }

        // 日本標準時の現在時間を取得する
        static DateTimeOffset JstNow => DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(9));

        // ブロブをバックアップ用のストレージアカウントにコピーする
        static async Task CopyBlobAsync(CloudBlockBlob myBlob, TraceWriter log)
        {
            var destConnectionString = "<コピー先のストレージアカウントの接続文字列>";
            var destStorageAccount = CloudStorageAccount.Parse(destConnectionString);
            var destBlobClient = destStorageAccount.CreateCloudBlobClient();

            // 今日の日付のコンテナが無ければ作る
            var destContainerName = $"mycontainer-incremental-{JstNow.ToString("yyyyMMdd")}";
            var destContainer = destBlobClient.GetContainerReference(destContainerName);
            try
            {
                await destContainer.CreateIfNotExistsAsync();
            }
            catch (Exception e)
            {
                log.Error(e.Message);
            }

            try
            {
                var destBlockBlob = destContainer.GetBlockBlobReference(myBlob.Name);
                log.Info($"{destBlockBlob.Name} にバックアップします。");

                using (var stream = await myBlob.OpenReadAsync())
                {
                    await destBlockBlob.UploadFromStreamAsync(stream);
                }

                log.Info("バックアップ成功");

            }
            catch (Exception ex)
            {
                log.Error(ex.Message);
                log.Info("バックアップ失敗");
            }
            finally
            {
                log.Info("バックアップ完了");
            }
        }
    }
}

Microsoft Azure ポータルから Function App を作成し、Visual Studio から発行すればデプロイ完了。今回の要件は Azure Functions にピッタリだった。