Azureの小ネタ (改)

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

Azure ARM API 事始め

この記事はAzure Advent Calender 22日目の記事です。ここ最近、Azure API を呼び出すことが多かったので、備忘録としてまとめておきたいと思います。

概要

APIには、Azure Management API(ASM)と、Azure Resource Manager(ARM)がありますが、この記事はARMを対象としています。

APIを呼び出すおおよその流れは、

  1. Azure ADにアプリを登録する。
  2. アプリからAzure ADで認証を行いトークンを取得する。
  3. トークンを元にAPIを呼び出す。

のような感じです。

1.は、一部ポータルから行えないため、Azure PowerShellで行う必要があります。2,3 については、Azure SDKが各言語向けに用意されているため、それらを利用します。

Azure ADへのアプリ登録

Add-AzureRmAccount でログインして、Select-AzureRmSubscription で既定のサブスクリプションを設定してください。

以下のスクリプトで、Azure ADへのアプリケーション登録ができます。New-AzureRmADApplication/New-AzureRmServicePrincipal でAzure ADへアプリの登録をします。アプリ名とURLは任意で構いません。登録するとクラシック ポータルのAzure ADのページから見られると思いますが、アプリの選択部分で「自分の会社が所有するアプリケーション」を選択しないと表示されないかもしれません。この辺りよく分かってませんのであしからず。

パスワードは適当でかまいません。有効期間を省略するとデフォで1年となります。ちなみに、ポータルから登録するとGUIDのパスワードが強制されます。

$role = "Owner"
$name = "ApiSample"
$url  = "http://localhost/ApiSample"
$pswd = "<<enter-your-password>>"

# Azure ADにアプリを登録
$app = New-AzureRmADApplication -DisplayName $name -HomePage $url -IdentifierUris $url -Password $pswd
New-AzureRmADServicePrincipal -ApplicationId $app.ApplicationId

# 少し間を置かないと以下でエラーになることがあった
New-AzureRmRoleAssignment -RoleDefinitionName $role -ServicePrincipalName $app.ApplicationId

最後のNew-AzureRmRoleAssignmentで、サブスクリプションへ権限をあたえます。ここでは、"Owner"としていますので、なんでも出来ちゃいます。このあたりは、Role Base Access Controlを参照してください。 これらRBACは新しいポータルで設定できますが、アプリに対しては設定できないようです。設定済みなものに関しては、新しいポータルから確認できます。サブスクリプション → 全ての設定 → ユーザー と進めば確認できるでしょう。

アプリを操作するCmdletですが、New/Remove-AzureRmADApplicationはありますが、Get系がありませんのでApplicationIdの取得に注意してください。この件に賛同されるかたは、https://github.com/Azure/azure-powershell/issues/1165 に +1してください。

Role

Get-AzureRmRoleDefinitionでロール一覧を取得できます。以下、Nameだけ表示した実行結果です。

Name : API Management Service Contributor
Name : Application Insights Component Contributor
Name : Automation Operator
Name : BizTalk Contributor
Name : Classic Network Contributor
Name : Classic Storage Account Contributor
Name : Classic Virtual Machine Contributor
Name : ClearDB MySQL DB Contributor
Name : Contributor
Name : Data Factory Contributor
Name : Data Lake Analytics Developer
Name : DevTest Labs User
Name : DNS Zone Contributor
Name : DocumentDB Account Contributor
Name : Intelligent Systems Account Contributor
Name : Network Contributor
Name : New Relic APM Account Contributor
Name : Owner
Name : Reader
Name : Redis Cache Contributor
Name : Scheduler Job Collections Contributor
Name : Search Service Contributor
Name : Security Manager
Name : SQL DB Contributor
Name : SQL Security Manager
Name : SQL Server Contributor
Name : Storage Account Contributor
Name : Traffic Manager Contributor
Name : User Access Administrator
Name : Virtual Machine Contributor
Name : Web Plan Contributor
Name : Website Contributor

トークンを取得する

APIを呼び出すためにTokenが必要ですが、TokenはAADに認証して発行してもらいます。

.NETでは以下のライブラリが必要です。

Tokenを取得するには、以下のようなコードで取得できます。AADにテナントID、クライアントID(ポータルから確認できます)、クライアントキー(PowerShellに指定したパスワード)

    public class AuthHelper
    {
        const string ManagementUri = "https://management.core.windows.net/";
        const string ArmAadUrl = "https://login.windows.net/";

        public static string GetAccessToken(string tenantId, string clientId, string clientKey)
        {
            var authenticationContext = new AuthenticationContext(ArmAadUrl + tenantId);
            var creds = new ClientCredential(clientId, clientKey);
            var result = authenticationContext.AcquireToken(ManagementUri, creds);

            Debug.WriteLine(result.AccessToken);
            Debug.WriteLine(result.ExpiresOn);
            Debug.WriteLine(result.AccessTokenType);

            return result.AccessToken;
        } 
    }

実行すると長いToken文字列が取得できますが、これらは以下のようなJSON Web Token(JWT)をデコードするページで参照できますので、お試しください。

トークンの有効時間は1時間です。長いプロセスでトークンをずっと保持しているとExpireしてしまうので注意してください。NETのAAD SDKは適切にキャッシュ&リフレッシュしてくれるみたいですので、呼び出す度に取得しても問題ないかもしれません。

APIを呼び出す

Tokenが取得できたところでARM APIを呼び出します。ARM SDKは、Compute/Network/Storageなどリソース別に分かれていますので、仮想マシンとか触りたい場合は、Computeを使います。

ちなみに、ASM SDK も配布されていてるので、間違わないようにしてください。ASM SDKは、"WindowsAzure"が名前空間になってます。

例えばVM一覧は以下のソースで取得できます。サブスクリプションIDとTokenを元に、TokenCloudCredentialsオブジェクトを作成し、それを元にComputeManagementClientオブジェクトを作ります。このクライアントをベースに色々操作ができますが、ここでは割愛します。

    var token = AuthHelper.GetAccessToken(tenantId, clientId, clientKey);
    var tokenCreds = new TokenCloudCredentials(subscriptionId, token);

    var client = new ComputeManagementClient(tokenCreds);

    var vms = client.VirtualMachines.ListAllAsync(null).Result;
    foreach(var vm in vms.VirtualMachines)
    {
        Console.WriteLine(vm.Name);
    }

仮想マシン情報の取得

ListAll で仮想マシン一覧を取得できますが、仮想マシンの稼働状況を取得してみます。個別の仮想マシンの情報取得には、ResourceGroup/ResourceNameが必要で、逐一APIを叩かないといけません。また、GetメソッドではVMの稼働状況は取得できないので、GetWithInstanceViewメソッドを使います。多分、API的に分かれている気がしました。

     var vms = client.VirtualMachines.ListAllAsync(null).Result;
     foreach(var vm in vms.VirtualMachines)
     {
        var resouceGroupName = vm.Id.Split(new char[] {'/'})[4];
        var resorceName = vm.Id.Split(new char[] {'/'})[8];
        var detail = client.VirtualMachines.GetWithInstanceView(resouceGroupName, resorceName).VirtualMachine;
        Console.WriteLine(vm.Id);
        Console.WriteLine(resouceGroupName + "/" + resorceName);
        Console.WriteLine(detail.InstanceView.Statuses[1].DisplayStatus);
    }

以下、実行結果です。IDはURLのパスみたい感じで表されます。次のリソースを取得するために、いちいちIdをParseしないといけないのは面倒臭いです。C#なら拡張メソッでごまかせそうですが、Javaだとそうもいかなくてうっとうしいです。

どうせなら、Idもそのまま受け付けて欲しいし、Resourceの基底クラスにResourceGroupName/ResourceNameプロパティくらい欲しい気もします。

/subscriptions/xxxxxxxx-c1c7-47e6-a66a-yyyyyyyyyyyy/resourceGroups/TESTRG/providers/Microsoft.Compute/virtualMachines/myUbuntu1
TESTRG/myUbuntu1
VM deallocated
/subscriptions/xxxxxxxx-c1c7-47e6-a66a-yyyyyyyyyyyy/resourceGroups/TESTRG/providers/Microsoft.Compute/virtualMachines/myUbuntu2
TESTRG/myUbuntu2
VM deallocated

同期API使用時の注意

更新でタグ付けなど軽いAPI呼び出しでも、同期APIを呼び出すとめちゃ時間がかかるときがあります。これは既定の動作として、

  1. タグ付けAPIを呼び出して、リクエストIDを受け取る
  2. リクエストIDの処理が終わったかAPIで確認する
  3. 終わってなかったら、30秒待ち再度確認する(以下、繰り返し)

みたいな実装になっているからです。非同期APIを駆使してもいいのですが、場合によっては同期APIを使う場面もあるでしょう。そのような場合は、以下のプロパティ(秒)を設定するとよいです。一回目を確認する時間と、リトライする間隔をそれぞれ指定できます。

     client.LongRunningOperationInitialTimeout = 2;
     client.LongRunningOperationRetryTimeout = 5;

Java SDKを使う

Java用のSDKも用意されていますので、ここではJavaでの例を紹介します。詳細は、以下のGitHubを参照してください。

適当にEclipseなどでMavenプロジェクト作成して、pom.xmlのDependencyに以下を記述します。ADALと、UtilityおよびComputeのライブラリを使います。

    <dependencies>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>adal4j</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-mgmt-utility</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-mgmt-compute</artifactId>
            <version>0.9.0</version>
        </dependency>
    </dependencies>

Javaにリライトしたものが以下になります。C#と同じことをしていますので詳細は省略。AuthHelperは、utilに入っている物を使っています。あと、clientインスタンスを生成する辺りが微妙にNETと異なっています。近い将来、NETと同一になっていくのではないかと思ってます。

package jp.co.pnop.moris.azure.api.sample;

import java.net.URI;

import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.azure.management.compute.ComputeManagementClient;
import com.microsoft.azure.management.compute.ComputeManagementService;
import com.microsoft.azure.management.compute.models.VirtualMachine;
import com.microsoft.azure.utility.AuthHelper;
import com.microsoft.windowsazure.Configuration;
import com.microsoft.windowsazure.management.configuration.ManagementConfiguration;

public class App {
    final static String MANAGEMENT_URL = "https://management.core.windows.net/";
    final static String ARM_ADAL_URL = "https://login.windows.net/";
    final static String armUrl = "https://management.azure.net/";

    public static void main(String[] args) throws Exception {

        String subscriptionId = "<<your subscriptoion>>";
        String tenantId = "<<your tenant>>";

        String clientId = "<<your client key>>";
        String clientKey = "<<your password>>";

        // Util の AuthHelperを使う
        AuthenticationResult creds = AuthHelper.getAccessTokenFromServicePrincipalCredentials(
                MANAGEMENT_URL,
                ARM_ADAL_URL,
                tenantId, clientId, clientKey);

        Configuration config = ManagementConfiguration.configure(
                null,
                new URI(armUrl),
                subscriptionId,
                creds.getAccessToken());

        ComputeManagementClient client = ComputeManagementService.create(config);

        for (VirtualMachine vm : client.getVirtualMachinesOperations().listAll(null).getVirtualMachines()) {
            String resourceGroupName = vm.getId().split("/")[4];
            String resourceName = vm.getId().split("/")[8];
            VirtualMachine detail = client.getVirtualMachinesOperations().getWithInstanceView(resourceGroupName, resourceName).getVirtualMachine();
            System.out.println(resourceGroupName + "/" + resourceName);
            System.out.println(detail.getId());
            System.out.println(detail.getInstanceView().getStatuses().get(1).getDisplayStatus());
        }        
    }
}

JavaのADALライブラリには、トークンをキャッシュしたまま使い続けるバグがあるので、1時間を超えるとExpireで例外が発生してしまうので注意してください。回避方法は、 https://github.com/Azure/azure-sdk-for-java/issues/557 に書いてあります。

ほぼ同じ構造なので、NET/Java共に同じように使えると思います。

まとめ

ARM SDKはAPI呼び出し後のJSONをデシリアライズしているにすぎない感じなので、どのみちJSONを理解していないとハマるときはハマりますし、REST APIを理解していたほうが良い気がします。そのときは、以下のResource Explorer が役立つと思います。

ARM SDKは、メタデータ(Swagger)から自動生成しているみたいです。したがって、個別のソースにPull Requestを送っても無意味です。以下のツールでやっているみたいですが、まだ追っかけられてません。使いにくい部分の要望を何処に出せば良いのが、責任の所在がよく分からない状態です。

Java版はNET版に比べて周回遅れっぽい感じがするので、来年にまたざっくり変わりそうです。ただNETと同じレベルになったとして、Javaの要望をJavaにのみ送っても無視されそうでやっぱり誰に言えば良いのか(ry

来年は、もう少し追っかけてみたいと思います。