Azureの小ネタ (改)

~Azureネタを中心に、色々とその他の技術的なことなどを~

Azure Batchのリソースファイルをより簡単にアップロードする

前回の記事で、UploadFileToContainer を使って手動でBLOBにリソースファイルをアップロードしていましたが、SDKにそういう機能をもったAPIが定義されていました。

関連するのは以下のクラス

  • IFileStagingProvider
  • FileToStage
  • StagingStorageAccount

です。FileToStageが、IFileStagingProviderを実装した具象クラスで、StagingStorageAccountは、ストレージアカウント情報を保持するクラス。

MSDN的には以下をたどればよいでしょう。

FileToStage Class (Microsoft.Azure.Batch.FileStaging) | Microsoft Docs

具体的なコードとしては、

var filesToStage = new List<IFileStagingProvider>();
...
fileToStage.Add(ファイルのフルパス);
..
task.FilesToStage = fileToStage;

とすれば、勝手に指定したストレージアカウントに任意のコンテナ作成して、Uploadしてくれます。めっちゃ便利ですね。

またこのままだと、不要なリソースファイルが蓄積されてしまいますので、削除したくなると思います。そういうときは、

    var artifacts = batchClient.JobOperations.AddTask(jobId, task);

Taskを投入するときに、IFileStagingArtifact が格納されたDictionaryが返ってきますので、中身を除くとUpload時につかったコンテナが格納されていると思います。

と長々書きましたが、以下のコードを読んだほうが早いと思います。

github.com

サンプルコード読むの大事ですね。

Azure BatchでちょっとしたProgramを実行する

Azure Batch でプログラムを実行しようとすると、かつてのクラウドサービスを彷彿とさせるインダイレクト感がします。フレームワークとしてはキチンとしていても、アドホックに挙動を確認したいコードをさくっと実行できなかったり。

というわけでVSから気軽(?)に実行できるようにしたのが以下のコード。

Azure Batch では固有の固有の変数が設定されるので、設定されていればBATCH側コードを実行し、そうでなければ、自分自身のDLL群をTask+リソースファイルとして登録してTaskとして実行します。

確認はPortal側からしましたが、ちゃんとやればBATCH側の出力も取ってこれると思います。

というわけで、自分自身の備忘録として。

   class Program
    {
        static void Main(string[] args)
        {
            if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AZ_BATCH_JOB_ID")))
            {
                 new BatchUtil().CreateTask();
            }
            else
            {
                Console.WriteLine("******************************************");
                Console.WriteLine("Hello Azure Batch");
                Console.WriteLine("******************************************");
            }
        }
    }

    public class BatchUtil
    {
        private const string TaskIdPrefix = "TestTask";

        public void CreateTask()
        {
            var s = new BatchSettings();

            BatchSharedKeyCredentials cred = new BatchSharedKeyCredentials(s.BatchAccountUrl, s.BatchAccountName, s.BatchAccountKey);
            using (BatchClient batchClient = BatchClient.Open(cred))
            {
                var job = batchClient.JobOperations.GetJob(s.BatchJobId);
                CloudStorageAccount storageAccount = new CloudStorageAccount(new StorageCredentials(s.StorageAccountName, s.StorageAccountKey), true);
                CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
                blobClient.GetContainerReference(s.BlobContainer).CreateIfNotExists();

                List<ResourceFile> inputFiles = new List<ResourceFile>();

                foreach (var file in new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.GetFiles())
                {
                    if (file.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)) continue;
                    inputFiles.Add(UploadFileToContainer(blobClient, s.BlobContainer, file.FullName));
                }

                var taskId = "TestTask" + DateTime.Now.Ticks;
                var task = new CloudTask(taskId, $"cmd /c WebAppsBatchHybride.exe")
                {
                    ResourceFiles = inputFiles
                };

                batchClient.JobOperations.AddTask(s.BatchJobId, task);

                var btask = batchClient.JobOperations.GetTask(s.BatchJobId, taskId);
                TaskStateMonitor taskStateMonitor = batchClient.Utilities.CreateTaskStateMonitor();
                taskStateMonitor.WhenAll(new List<CloudTask> {btask}, TaskState.Completed, TimeSpan.FromMinutes(120)).Wait();
            }
        }

        private static ResourceFile UploadFileToContainer(CloudBlobClient blobClient, string containerName, string filePath)
        {
            Debug.WriteLine("Uploading file {0} to container [{1}]...", filePath, containerName);
            string blobName = Path.GetFileName(filePath);
            CloudBlobContainer container = blobClient.GetContainerReference(containerName);
            CloudBlockBlob blobData = container.GetBlockBlobReference(blobName);
            blobData.UploadFromFile(filePath);
            SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy
            {
                SharedAccessExpiryTime = DateTime.UtcNow.AddHours(2),
                Permissions = SharedAccessBlobPermissions.Read
            };
            string sasBlobToken = blobData.GetSharedAccessSignature(sasConstraints);
            string blobSasUri = $"{blobData.Uri}{sasBlobToken}";
            return new ResourceFile(blobSasUri, blobName);
        }
    }

Azure Batch に NET 4.7.1をインストールする

Azure Batch にNET 4.7.1を仕込む備忘録。

NET4.7.1を仕込むには、まずインストーラを取得する必要がありますが、既定の状態だとCMDベースでCURLとかが使えないとかちょっと不自由。

Application Package

ただ、Azure Batchには、Application Packageと呼ばれるアプリケーションを仕込んでおく仕組みがあって、そこにNET4.7.1のパッケージを配置しておきます。

NET4.7.1のオフラインインストーラーは以下から取得します。

https://docs.microsoft.com/ja-jp/dotnet/framework/deployment/deployment-guide-for-developers

New Application から、EXEをZIP化してUploadしておくと所定のストレージに格納されます。

f:id:StateMachine:20180315105822p:plain

いちどタスクにApplication Packageを紐づけて環境変数を参照すると以下のように展開されています。

AZ_BATCH_APP_PACKAGE_netfx=D:\batch\tasks\apppackages\netfx4712018-03-15-01-59
AZ_BATCH_APP_PACKAGE_netfx#471=D:\batch\tasks\apppackages\netfx4712018-03-15-01-59

DIRすると

 Directory of D:\batch\tasks\apppackages\netfx4712018-03-15-01-59

03/15/2018  02:02 AM    <DIR>          .
03/15/2018  02:02 AM    <DIR>          ..
03/15/2018  02:02 AM        68,742,112 NDP471-KB4033342-x86-x64-AllOS-ENU.exe
               1 File(s)     68,742,112 bytes
               2 Dir(s)  18,570,358,784 bytes free

こうなります。Docsでは以下のあたりを参照のこと。

コンピューティング ノードへのアプリケーション パッケージのインストール - Azure Batch | Microsoft Docs

Pool

PoolにApplication Packageを紐づけて、ノードをリブートします。

f:id:StateMachine:20180315111241p:plain

Start Task

Start Taskでインストーラーを起動します。

f:id:StateMachine:20180315111549p:plain

最初、/norestartをつかたら、3010でエラーになりました。Rebootが必要なのに、/norestart指定したからっぽいです。

MsiExec.exe and InstMsi.exe Error Messages (Windows)

今回使っているBatchノードはVMなので、不完全ながらインストールされてしまうと再度テストできないので、ノード数を 1->0->1 のように変更してさいどセットアップされるようにしてみます。

最終的には以下のコマンドライン。

cmd /c "%AZ_BATCH_APP_PACKAGE_netfx%\NDP471-KB4033342-x86-x64-AllOS-ENU.exe /q"

最後、RDPでのぞいて KB4033393が入っていればOK。

f:id:StateMachine:20180315124655p:plain

これで、ノードをスケールアップ、ダウンさせても各ノードにNET4.7.1が展開されるでしょう。

余談

DocsのAzure BatchサンプルのPortalは日本語なのに、現時点で英語になってたよくわからない。