この記事はAzure Advent Calender 22日目の記事です。ここ最近、Azure API を呼び出すことが多かったので、備忘録としてまとめておきたいと思います。
概要
APIには、Azure Management API(ASM)と、Azure Resource Manager(ARM)がありますが、この記事はARMを対象としています。
APIを呼び出すおおよその流れは、
- Azure ADにアプリを登録する。
- アプリからAzure ADで認証を行いトークンを取得する。
- トークンを元に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を呼び出すとめちゃ時間がかかるときがあります。これは既定の動作として、
- タグ付けAPIを呼び出して、リクエストIDを受け取る
- リクエストIDの処理が終わったかAPIで確認する
- 終わってなかったら、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
来年は、もう少し追っかけてみたいと思います。