はじめに

第18回の「Azureをコマンドラインツールから操作してみよう」では、コマンドラインからAzureを操作する方法を解説しましたが、これ以外にもC#やJava、Pythonなど各種プログラミング言語からAzureを操作することができます。これから数回にわけて各言語にてでAzureを操作する方法を解説していきますが、今回はC#を利用してAzureを操作するアプリケーションを作成していきます。

本稿での開発環境は以下の通りです。C#によるプログラミング経験とAzureの概要を理解している方を対象としています。

・Visual Studio 2017
・NET Framework 4.5.2 以上
・Azure PowerShell コマンドレット

Azure REST APIとは

Azureには、「Azure REST API」と呼ばれるAzure自身を管理するためのAPI群が用意されており、リソースの作成、情報取得、更新、削除などの操作が提供されています。ただし、このREST APIを直接操作するのはあまりに煩雑です。したがって、これらをラップした形で言語毎にクライアントライブラリが公開されています。これらを総称して、Azure Management Library(マネジメントライブラリ)と呼んでいます。このマネジメントライブラリを利用することで、より簡単に各言語からAzureを操作するプログラムを記述できます。

資格情報を作成する

ポータル画面からAzureを利用する場合を考えてみますと、ブラウザからユーザーとパスワードを入力して目的のサブスクリプションにアクセスできる権限を取得できますが、自分が作成したアプリケーションやサービス、自動化ツールからサブスクリプションを操作するためにはどのようにしたらよいでしょうか。Azureではサービスプリンシパルと呼ばれる資格情報をあらかじめ作成しておき、その資格情報に対してサブスクリプションを操作する権限を割り当てAzureを操作します。

サービスプリンシパルオブジェクトを作成する

サービスプリンシパルオブジェクトは、サブスクリプションが紐付くAzure Active Directory(AAD)上に作成されます。このため、AADに適切なアクセス許可を持っている必要があります。組織アカウントにサブスクリプションを付与されている場合など、作成できない場合があります(*)。

*)権限の確認は、「リソースにアクセスできる Azure Active Directory アプリケーションとサービス プリンシパルをポータルで作成する」を参照してください。

前々回の記事を参考に、Azure PowerShellにて該当のサブスクリプションに接続します。サブスクリプションに接続すると、サブスクリプションIDとテナントIDが表示されるので、これをメモしておきます(リスト1)。

[リスト1]サブスクリプション一覧の取得

PS C:\> Get-AzureRmSubscription
...
Name     : Visual Studio Ultimate with MSDN (MVP)
Id       : aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
TenantId : bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee
State    : Enabled

次にサービスプリンシパルオブジェクトとそのコンテナであるアプリケーションオブジェクトを作成します(リスト2)。

[リスト2]アプリケーションオブジェクトとサービスプリンシパルオブジェクトの作成

$app = New-AzureRmADApplication -DisplayName "mynaviapp" -IdentifierUris "http://localhost/mynaviapp"
$password = [Guid]::NewGuid().ToString()
$secure   = ConvertTo-SecureString -AsPlainText -Force -String $password
New-AzureRmADServicePrincipal -ApplicationId $app.ApplicationId -Password $secure
Write-Host $password

New-AzureRmADApplicaitonコマンドでアプリケーションオブジェクトを作成します。引数はサンプル通りでかまいませんが、IdentifierUrisはテナント毎に一意の必要があります。重複する可能性は少ないと思いますがエラーが発生したら変更してください。New-AzureRmADServicePrincipalにてサービスプリンシパルオブジェクトを作成します。PasswordオプションにはGUIDから生成したパスワード(クライアントシークレットと呼ぶ)をセキュア文字列化して渡します。

これらを実行するとリスト3の結果が得られます。ここでは、ApplicationIdと表示されたクライアントID、Idと表示されたサービスプリンシパルID、最後に表示されたクライアントシークレットをメモしておきます。

[リスト3]サービスプリンシパルオブジェクトの作成結果

ServicePrincipalNames : {caf33856-2ca1-4234-90c3-42bb4d3fd2d0, http://localhost/mynaviapp}
ApplicationId         : caf33856-2ca1-4234-90c3-42bb4d3fd2d0
DisplayName           : mynaviapp
Id                    : 93dd18d6-d42c-48b3-a736-69a74af4704c
Type                  : ServicePrincipal

98317aff-03e1-49f9-a717-19d44349cbcb

サブスクリプションにアクセス権限を付与する

サブスクリプションにサービスプリンシパルによるアクセス権限を付与します(リスト4)。

[リスト4]サブスクリプションに権限を割り当てる

PS C:\> New-AzureRmRoleAssignment -ObjectId 93dd18d6-d42c-48b3-a736-69a74af4704c -RoleDefinitionName Owner -Scope /subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee

RoleAssignmentId   : /subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/providers/Microsoft.Authorization/roleAssignments/3056cea3-9233-446f-b3c8-4c483a6d20ae
Scope              : /subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
DisplayName        : mynaviapp
SignInName         : 
RoleDefinitionName : Owner
RoleDefinitionId   : 8e3af657-a8ff-443c-a75c-2fe8c4bcb635
ObjectId           : 93dd18d6-d42c-48b3-a736-69a74af4704c
ObjectType         : ServicePrincipal
CanDelegate        : False

New-AzureRmRoleAssignmentコマンドでサブスクリプションへのアクセス権限を付与します。各オプションの詳細は以下の通りです(表1)。

引数の意味

引数 概要
ObjectId サービスプリンシパルIDを指定します。
RoleDefinitionName アクセス権限を指定します。Ownerを指定すると所有者権限を与えたことになります。権限一覧は、Get-AzureRmRoleDefinitionコマンドで取得できます。
Scope アクセス権限を与える範囲を指定します。/subscriptions/につづいて、サブスクリプションIDを指定します。

ポータルから適切な権限が付与されたか確認してみます。ポータルから「サブスクリプション」一覧を開き該当のサブスクリプションを選択します。メニューから「アクセス制御 (IAM)」を選択すると、このサブスクリプションにアクセス可能なアイテム一覧が表示されます。この中に、「mynaviapp」が表示されていれば正しく権限が設定されています(図1)。

  • 図1:権限の割り当て確認

    図1:権限の割り当て確認

さて、ここまで操作すると最終的に以下の情報が取得できているはずです。


・サブスクリプションID (aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)
・テナントID(bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee)
・クライアントID(caf33856-2ca1-4234-90c3-42bb4d3fd2d0)
・クラアイントシークレット(98317aff-03e1-49f9-a717-19d44349cbcb)(*)

括弧内は、ここまでの例での値です。*アクセスキーや、パスワードとも呼ばれます。機密情報ですので第三者に渡さないようにしてください。

今回のサンプルでは、所有者権限をサブスクリプション全体に割り当てていますが、実際には権限とあたえる範囲は最小にします。たとえば、特定のリソースグループ内のリソースのみ参照する必要がありならば、アクセス権は読み取り専用にし、範囲は特定のリソースグループに絞ることもできます。

C#からAzureを操作する

それではファーストステップとして、これらの資格情報を利用してサブスクリプションに定義されているリソースグループ一覧を取得してみましょう。

コンソールアプリ作成とマネジメントライブラリをインストールする

Visual Studioを起動し、「ファイル」―「新規作成」―「プロジェクト」からコンソールアプリを作成します(図2)。

  • 図2:コンソールアプリの作成

    図2:コンソールアプリの作成

次に必要なライブラリをNuget経由でインストールします。「ツール」-「Nugetパッケージマネージャー」―「パッケージマネージャーコンソール」を選択するとパッケージマネージャーコンソールウィンドウが開くので、リスト5のコマンド実行します。

[リスト5]マネジメントライブラリのインストール

Install-Package Microsoft.Azure.Management.Fluent -Version 1.16.0

Azureのコンポーネント毎にライブラリは分割されていますが、関連するAzureライブラリはすべてインストールされます。

リソースグループ一覧を取得する

APIを通してリソースを作成する前に、疎通確認を兼ねてリソースグループ一覧を取得するプログラムを実行してみましょう。まずは、Program.csを以下にように書き換えて実行してみてください(リスト6)。

(1)では、先ほどメモしておいた各情報を実際の値に書き換えてください。
(2)では、それらの情報より、AzureCredentialsオブジェクトを取得します(*)。
(3)では、資格情報を元に、WithSubscriptionメソッドで操作するサブスクリプションを指定し、IAzureインタフェースを取得します。IAzureインタフェースは、Azureのリソースを操作する基点のインタフェースとなります。
(4)では、IAzureインタフェースのResourceGroupプロパティからListメソッドを呼び出し、リソースグループ一覧を取得しています

(*)最後に指定した、AzureEnvironment.AzureGlobalCloud は通常のAzureを使うという指示です。米政府、中国向けなど特定顧客向けのデータセンターを利用する場合は値を変更を変更します。

[リスト6]リソースグループの取得

using System;
using Microsoft.Azure.Management.Compute.Fluent;
using Microsoft.Azure.Management.Compute.Fluent.Models;
using Microsoft.Azure.Management.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Authentication;
using Microsoft.Azure.Management.ResourceManager.Fluent.Core;

namespace ApiSample
{
    class Program
    {
        // (1)
        private const string SubscriptionId = " サブスクリプション ID";
        private const string TenantId = " テナント ID";
        private const string ClientId = " クライアント ID";
        private const string ClientSecret = " クライアントシークレット ";
        static void Main(string[] args)
        {
            AzureCredentials creds = new AzureCredentialsFactory().FromServicePrincipal(ClientId, ClientSecret, TenantId, AzureEnvironment.AzureGlobalCloud); // (2)
            IAzure azure = Azure.Authenticate(creds).WithSubscription(SubscriptionId); // (3)
            foreach (var rg in azure.ResourceGroups.List()) // (4)
            {
                Console.WriteLine($"{rg.Name} : {rg.RegionName}");
            }
        }
    }
}

正しく実行され、リソースグループの一覧が表示されたでしょうか?例外が発生する場合は、設定を再度確認してみてください。さて、前述したとおりIAzureインタフェースにAzureの操作の基点になります。定義されている代表的なプロパティは下表の通りです。

表2:IAzureインタフェースの代表的なプロパティ

プロパティ 概要
AppServices Appサービスを管理するためのエントリポイント
Networks 仮想ネットワークを管理するためエントリポイント
ResourceGroups リソースグループを管理するためのエントリポイント
RedisCaches Redisキャッシュを管理するためのエントリポイント
SqlServers SQLデータベースを管理するためのエントリポイント
VirtualMachines 仮想マシンを管理すつためのエントリポイント

仮想マシンを作成する

正しくAzureの情報を取得できたところで、次に仮想マシンの作成、操作をしてみましょう。あらかじめ作成する仮想マシンのパラメーターを決めておきます(表3)。

表3 仮想マシンのパラメーター一覧

リソース 名前
ロケーション 東日本
リソースグループ名 mynavi
仮想ネットワーク mynavivnet 10.0.0.0/16
サブネット subnet 10.0.0.1/24
仮想マシン名 mynavi-vm
仮想マシンのOS Windows Server 2012R2 Datacenter
仮想マシンのサイズ Basic_A1
仮想マシンのDNS名 mynavi-vm(Azureリージョンで一意)

次に、プログラムをリスト7に書き換えて実行してみましょう。ただし、WithNewPrimaryPublicIPAddressの引数は最終的にDNS名の一部となるため、リージョンで一意な名前が必要です。またWithAdminPasswordの引数には、要件にあったパスワードを設定してから実行してください。

[リスト7]仮想マシンの作成

 using System;
 using Microsoft.Azure.Management.Compute.Fluent;
 using Microsoft.Azure.Management.Compute.Fluent.Models;
 using Microsoft.Azure.Management.Fluent;
 using Microsoft.Azure.Management.ResourceManager.Fluent;
 using Microsoft.Azure.Management.ResourceManager.Fluent.Authentication;
 using Microsoft.Azure.Management.ResourceManager.Fluent.Core;

 namespace ApiSample
 {
     class Program
     {
         private const string SubscriptionId = " サブスクリプション ID";
         private const string TenantId = " テナント ID";
         private const string ClientId = " クライアント ID";
         private const string ClientSecret = " クライアントシークレット ";

         static void Main(string[] args)
         {
             AzureCredentials creds = new AzureCredentialsFactory().FromServicePrincipal(ClientId, ClientSecret, TenantId,AzureEnvironment.AzureGlobalCloud);
             IAzure azure = Azure.Authenticate(creds).WithSubscription(SubscriptionId);

             var resourceGroup = azure.ResourceGroups
                 .Define("mynavi")
                 .WithRegion(Region.JapanEast)
                 .Create();

             // (2) 仮想ネットワークの作成
             var network = azure.Networks
                 .Define("mynavivnet")
                 .WithRegion(Region.JapanEast)
                 .WithExistingResourceGroup(resourceGroup)
                 .WithAddressSpace("10.0.0.0/16")
                 .WithSubnet("subnet", "10.0.1.0/24")
                 .Create();

             // (3) 仮想マシンの作成 
             var vm = azure.VirtualMachines
                 .Define("mynavi-vm")
                 .WithRegion(Region.JapanEast)
                 .WithExistingResourceGroup(resourceGroup)
                 .WithExistingPrimaryNetwork(network)
                 .WithSubnet("subnet")
                 .WithPrimaryPrivateIPAddressDynamic()
                 .WithNewPrimaryPublicIPAddress("mynavi-vm") // 環境毎に書き換えが必要
                 .WithPopularWindowsImage(KnownWindowsVirtualMachineImage.WindowsServer2012R2Datacenter)
                 .WithAdminUsername("azureuser")
                 .WithAdminPassword("serow225!!") // 要件にあったパスワード文字列を設定
                 .WithSize(VirtualMachineSizeTypes.BasicA1)
                 .Create();
         }
     }
 }

しばらく時間はかかりますが、仮想マシンが作成されますので、ポータルで確認してみましょう(図3)。

  • 図3:仮想マシン作成後のポータル

    図3:仮想マシン作成後のポータル

リスト7では、リソースグループの作成、仮想ネットワークの作成、仮想マシンの作成と3つにわかれていますが、いずれもDefineメソッドからCreateメソッドまでメソッド呼び出しを連鎖させているのがわかると思います。このようなメソッドを連鎖させて呼び出すAPIを一般的にFluent APIと呼びますが、初見でもなんとなく処理の流れが理解できるのではないかと思います。

細かい点を見ていきますと、リソースグループの作成では、Defineメソッドでmynaviというリソースグループ名を定義したのち、後続のWithRegionメソッドでリソースグループのリージョンを東日本に設定し、最後のCreateメソッドでリソースグループの作成を行います。仮想ネットワークの作成も同様で、Defineメソッドで名前を定義し、リージョンを設定、アドレス空間等を設定し、最後のCreateメソッドで仮想ネットワークを作成します。

仮想マシンの作成も同様です。一点注意してほしいのは、WithNewPrimaryPublicIPAddressメソッドに指定する名前は、DNS名の一部に使われるため、Azureリージョン内で一意な必要があるのと、要件にあったパスワードを設定しないと例外が発生します。

情報取得、停止、開始などの操作を行う

仮想マシンが作成できたところで、仮想マシンの情報を取得したり、停止や開始などの操作を行ってみたいと思います。
前述したとおりazure.VirtualMachinesが仮想マシンを管理するエントリポイントとなります。例えば、リソースグループ名と仮想マシン名を指定してGetByResourceGroupメソッドを呼び出すと、目的の仮想マシンのインスタンスを取得できます。まずは、このインスタンスから代表的なプロパティを表示してみましょう(リスト8)。

[リスト8]仮想マシンの情報取得

using System;
using Microsoft.Azure.Management.Compute.Fluent;
using Microsoft.Azure.Management.Compute.Fluent.Models;
using Microsoft.Azure.Management.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Authentication;
using Microsoft.Azure.Management.ResourceManager.Fluent.Core;

namespace ApiSample
{
    class Program
    {
        private const string SubscriptionId = " サブスクリプション ID";
        private const string TenantId = " テナント ID";
        private const string ClientId = " クライアント ID";
        private const string ClientSecret = " クライアントシークレット ";

        static void Main(string[] args)
        {
            AzureCredentials creds = new AzureCredentialsFactory().FromServicePrincipal(ClientId, ClientSecret, TenantId,AzureEnvironment.AzureGlobalCloud);
            IAzure azure = Azure.Authenticate(creds).WithSubscription(SubscriptionId);

            var vm = azure.VirtualMachines.GetByResourceGroup("mynavi", "mynavi-vm");

            Console.WriteLine($"コンピュータ名 : {vm.OSProfile.ComputerName}");
            Console.WriteLine($"管理者ユーザ名 : {vm.OSProfile.AdminUsername}");
            Console.WriteLine($"VMサイズ    : {vm.Size}");
            Console.WriteLine($"DISKサイズ     : {vm.StorageProfile.OsDisk.DiskSizeGB}GB");
            Console.WriteLine($"ステータス0    : {vm.InstanceView.Statuses[0].Code} {vm.InstanceView.Statuses[0].DisplayStatus} {vm.InstanceView.Statuses[0].Level} {vm.InstanceView.Statuses[0].Message}");
            Console.WriteLine($"ステータス1    : {vm.InstanceView.Statuses[1].Code} {vm.InstanceView.Statuses[1].DisplayStatus} {vm.InstanceView.Statuses[1].Level} {vm.InstanceView.Statuses[1].Message}");
            Console.WriteLine($"OS Publisher   : {vm.StorageProfile.ImageReference.Publisher}");
            Console.WriteLine($"OS Offer       : {vm.StorageProfile.ImageReference.Offer}");
            Console.WriteLine($"OS Sku         : {vm.StorageProfile.ImageReference.Sku}");
            Console.WriteLine($"OS Version     : {vm.StorageProfile.ImageReference.Version}");
        }
    }
}

以下のような結果が得られると思います。プロパティの意味については後度紹介するAPIリファレンスで確認できます。

コンピュータ名 : mynavi-vm
管理者ユーザ名 : azureuser
VMサイズ    : Basic_A1
DISKサイズ     : 127GB
ステータス0    : ProvisioningState/succeeded Provisioning succeeded Info
ステータス1    : PowerState/running VM running Info
OS Publisher   : MicrosoftWindowsServer
OS Offer       : WindowsServer
OS Sku         : 2012-R2-Datacenter
OS Version     : latest

次にDeallocateメソッドを呼び出してみましょう。これで仮想マシンを停止(課金されない状態)することができます(リスト9)。ポータルで仮想マシンの状態を確認し、停止済み(割り当て解除)と表示されていることが確認できます。

[リスト9]仮想マシンの停止

var vm = azure.VirtualMachines.GetByResourceGroup("mynavi", "mynavi-vm");
  vm.Deallocate();

開始するには、Startメソッドを呼びます(リスト10)

[リスト10]仮想マシンの開始

var vm = azure.VirtualMachines.GetByResourceGroup("mynavi", "mynavi-vm");
  vm.Start();

このほかにも、おおよそポータルから取得できる項目や操作をAPIを通して実行可能です。

さらに詳しい情報が欲しい場合は、「APIリファレンス」を参考にしてください。

まとめ

簡単なプログラム例でしたが、AzureをC#から操作する方法が理解いただけたかと思います。これらを活用し専用のAzureツールを作成することで、開発や運用効率を高めていくことができると思います。

WINGSプロジェクト 森島政人 著/山田祥寛監修
<WINGSプロジェクトについて>テクニカル執筆プロジェクト(代表山田祥寛)。海外記事の翻訳から、主にWeb開発分野の書籍・雑誌/Web記事の執筆、講演等を幅広く手がける。一緒に執筆をできる有志を募集中