動的にモジュールを読み込む

Windows Azure Blog Storage上にアセンブリを置いて動的に読み込んでみましょう。

何の変哲もないインターフェース用意して

image

適当に2つほど実装して個別のアセンブリを作っておきます。

imageimage

MarshalByRefObjectがポイントですね。

ビルドしたアセンブリはBlobにUpしておきましょう。

一応Privateコンテナです。

さて今回はサンプルなのでWorkerRole内でアセンブリをロードして定期的に実行するようにします。

で、実際にアセンブリをロードするわけです。

		private void LoadAssembly(string AssemblyUrl,string AssemblyTypeName)
		{
			if (SampleAppDomain != null)
			{
				AppDomain.Unload(SampleAppDomain);
			}

			PermissionSet trustedLoadFromRemoteSourceGrantSet
				= new PermissionSet(PermissionState.Unrestricted);

			AppDomainSetup trustedLoadFromRemoteSourcesSetup = new AppDomainSetup();
				trustedLoadFromRemoteSourcesSetup.ApplicationBase =
					AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

			SampleAppDomain = AppDomain.CreateDomain("SampleDomain", null, trustedLoadFromRemoteSourcesSetup, trustedLoadFromRemoteSourceGrantSet);
			var webpermission = new WebPermission(
						NetworkAccess.Connect,
						AssemblyUrl
					);
			SampleAppDomain.PermissionSet.SetPermission(webpermission);
			SampleAppDomain.PermissionSet.Demand();
			SampleAssembly = (ISample.ISample)SampleAppDomain.CreateInstanceFromAndUnwrap(AssemblyUrl, AssemblyTypeName);
		}

 

こんな感じですね。リモートホスト上にアセンブリがあるのでWebPermissionで権限を付けてます。それ以外はいたって普通。

あとBlobをプライベートコンテナに置いているので、共有アクセス署名(SAS: Shared Access Signature)を付けないとアクセスできません。

		private string GetAssemblyUrl()
		{
			var assemblyName = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobName");
			var assemblyContainerName = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobContainerName"); ;
			var connectionString = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobStorage"); ;

			var account = CloudStorageAccount.Parse(connectionString);
			var client = account.CreateCloudBlobClient();

			var assemblyBlobContainer = client.GetContainerReference(assemblyContainerName);

			var policy = new SharedAccessPolicy
			{
				Permissions = SharedAccessPermissions.Read,
				SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-5),
				SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromMinutes(10)
			};

			CloudBlob blob = assemblyBlobContainer.GetBlobReference(assemblyName);
			var accessurl = blob.Uri.ToString() + blob.GetSharedAccessSignature(policy);
			Trace.WriteLine(accessurl);

			return accessurl;
		}

 

SAS付けた状態のURLを返してもらって、そいつをCreateInstanceFromAndUnwrapに渡します。

ポイントはアクセス期限の開始時刻を現在時刻からマイナス5分してるところぐらいですかね。。。Blob Storage上の時刻よりローカルPCのほうが進んでたために、アクセスした時刻が署名で許可してる期間外になってFileNotFoundExceptionになるというヒドイ目を見ました。ふぅ。

最後にロードしたいアセンブリの情報をサービス設定ファイルに書いておきましょう。

image

で、サービス設定ファイルが更新されたらアセンブリをロードしなおすようにします。

		public override bool OnStart()
		{
			LoadAssembly(GetAssemblyUrl(), RoleEnvironment.GetConfigurationSettingValue("AssemblyTypeName"));

			RoleEnvironment.Changed += (s, e) =>
			{
				if (e.Changes.Any(chg => chg is RoleEnvironmentConfigurationSettingChange))
				{
					LoadAssembly(GetAssemblyUrl(), RoleEnvironment.GetConfigurationSettingValue("AssemblyTypeName"));
				}
			};

			return base.OnStart();
		}

とりあえずこんな感じ。

じゃ実行してみましょう。

起動した時はSample1.dllがロードされて、サービス設定ファイルをSample2.dllに変更してUpdateしたらちゃんとロードしなおされてますね!

あとは気が向いたらBlob上のDLLを入れ替えるなりしてロードしなおせばいいわけです。

もちろんロードした後はローカルのキャッシュで動作してるので、SASの期限切れても問題ないですしBlob上からファイルがなくなっても問題ないです。

拡張モジュールの実装とかいろいろ出来そうですね。AppDomainを工夫したらマルチテナントで遊べると思います。

 

おまけ。

using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
namespace WorkerRole1
{
public class WorkerRole : RoleEntryPoint
{
private ISample.ISample SampleAssembly;
private AppDomain SampleAppDomain;
public override void Run()
{
while (true)
{
Trace.WriteLine(SampleAssembly.Execute());
Thread.Sleep(5000);
}
}
public override void OnStop()
{
if (SampleAppDomain != null)
{
AppDomain.Unload(SampleAppDomain);
}
base.OnStop();
}
public override bool OnStart()
{
LoadAssembly(GetAssemblyUrl(), RoleEnvironment.GetConfigurationSettingValue("AssemblyTypeName"));
RoleEnvironment.Changed += (s, e) =>
{
if (e.Changes.Any(chg => chg is RoleEnvironmentConfigurationSettingChange))
{
LoadAssembly(GetAssemblyUrl(), RoleEnvironment.GetConfigurationSettingValue("AssemblyTypeName"));
}
};
return base.OnStart();
}
private void LoadAssembly(string AssemblyUrl,string AssemblyTypeName)
{
if (SampleAppDomain != null)
{
AppDomain.Unload(SampleAppDomain);
}
PermissionSet trustedLoadFromRemoteSourceGrantSet
= new PermissionSet(PermissionState.Unrestricted);
AppDomainSetup trustedLoadFromRemoteSourcesSetup = new AppDomainSetup();
trustedLoadFromRemoteSourcesSetup.ApplicationBase =
AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
SampleAppDomain = AppDomain.CreateDomain("SampleDomain", null, trustedLoadFromRemoteSourcesSetup, trustedLoadFromRemoteSourceGrantSet);
var webpermission = new WebPermission(
NetworkAccess.Connect,
AssemblyUrl
);
SampleAppDomain.PermissionSet.SetPermission(webpermission);
SampleAppDomain.PermissionSet.Demand();
SampleAssembly = (ISample.ISample)SampleAppDomain.CreateInstanceFromAndUnwrap(AssemblyUrl, AssemblyTypeName);
}
private string GetAssemblyUrl()
{
var assemblyName = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobName");
var assemblyContainerName = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobContainerName"); ;
var connectionString = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobStorage"); ;
var account = CloudStorageAccount.Parse(connectionString);
var client = account.CreateCloudBlobClient();
var assemblyBlobContainer = client.GetContainerReference(assemblyContainerName);
var policy = new SharedAccessPolicy
{
Permissions = SharedAccessPermissions.Read,
SharedAccessStartTime = DateTime.UtcNow.AddMinutes(5),
SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromMinutes(10)
};
CloudBlob blob = assemblyBlobContainer.GetBlobReference(assemblyName);
var accessurl = blob.Uri.ToString() + blob.GetSharedAccessSignature(policy);
Trace.WriteLine(accessurl);
return accessurl;
}
}
}

view raw
gistfile1.cs
hosted with ❤ by GitHub

Microsoft Endpoint Protection for Windows Azure CTP

Windows Azureのセキュリティが心配で心配で夜も眠れない人に朗報。 クライアントPC等で企業向けに提供されているMicrosoft Endpoint Protection(以下MEP)がWindows Azureでも利用できるようになりました。(※2012年3月19日現在ではCTP)

続きを読む

Azure上の非.NET アプリケーションからRoleEnvrionmentを触る

たんたかさんが PHPでRoleEnvironmentを使う というへんたい素晴らしいPostをしてたのでもっと手軽にできないかなーと。

もともとWindows Azure SDK 1.5以上でサービス定義ファイルにxPathを使用してRoleEnvironmentの値を環境変数にセットできるのですが、これがちょっと曲者。

というのもサービス定義ファイルのEnvironment要素に指定した環境変数はプロセス環境変数で、つまりRoleEntryPointがあるプロセスでしか見れません。

これはどういうことかというと、別プロセスとして動作するFull IISや、たとえばPHPなどからはせっかくの値が見れないってことですね。
これは不便極まりない!ということで冒頭のたんたかさんのBlogにあるようなHackが必要なわけですが、もっとお手軽にできるんじゃない?ということで試してみました。

まずサンプルということで、ASP.NET MVCで環境変数を出力するようなアプリを作っておきます。

<h1>Machine</h1>

<table>

@foreach (System.Collections.DictionaryEntry env in Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine)) {

         <tr>

                 <td>@env.Key</td>

                 <td>@env.Value.ToString()</td>

         </tr>

}

</table>

</p>

<p>

<h1>Process</h1>

<table>

@foreach (System.Collections.DictionaryEntry env in Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process)) {

         <tr>

                 <td>@env.Key</td>

                 <td>@env.Value.ToString()</td>

         </tr>

}

</table>

</p>

<p>

<h1>User</h1>

<table>

@foreach (System.Collections.DictionaryEntry env in Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User)) {

         <tr>

                 <td>@env.Key</td>

                 <td>@env.Value.ToString()</td>

         </tr>

}

</table>

</p>

 

適当にViewにはっておきましょう。

さてやること1つ目。サービス定義ファイルに環境変数を追加します。

<ServiceDefinition name="WindowsAzureProject3" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="MvcWebRole1" vmsize="ExtraSmall">
    <Sites>
      <Site name="Web">
        <Bindings>
          <Binding name="Endpoint1" endpointName="Endpoint1" />
        </Bindings>
      </Site>
    </Sites>
    <Endpoints>
      <InputEndpoint name="Endpoint1" protocol="http" port="80" />
    </Endpoints>
    <Runtime>
      <Environment>
        <Variable name="EnvironmentTest1">
          <RoleInstanceValue xpath="/RoleEnvironment/CurrentInstance/Endpoints/Endpoint[@name='Endpoint1']/@address" />
        </Variable>
        <Variable name="EnvironmentTest2" value="EnvironmentTest2Value" />
      </Environment>
    </Runtime>
    <Startup>
      <Task commandLine="env.bat" executionContext="elevated" taskType="simple">
        <Environment>
        <Variable name="ENVIRONMENT3">
              <RoleInstanceValue xpath="/RoleEnvironment/CurrentInstance/Endpoints/Endpoint[@name='Endpoint1']/@address" />
           </Variable>
        </Environment>
      </Task>
    </Startup>
  </WebRole>
</ServiceDefinition>

 

上記のEnvrionmentTest1、EnvrionmentTest2、Envrionment3はそれぞれテスト用です。後で見たらわかりますが、これらの値はASP.NETなアプリ(別プロセス)からは参照できません。

で、ここでのポイントはStartup要素以下のEnvrionmentですね。ここでスタートアップタスクに環境変数を渡してるのですがこのままだとStartupTaskのプロセスが終わったら消えてしまいます。

ということで、StartupTask内でプロセス環境変数をシステム環境変数につけかえましょう。

@echo off

setx ENVIRONMENT4 %ENVIRONMENT3% /M

Setxコマンドに/M引数つけてシステム環境変数に設定してるだけです。簡単!

注意点はシステム環境変数に設定するのでStartupTaskを管理者権限(Elevated)で動作させてるのと、システム環境変数を参照するには設定終わった後にプロセスが起動しないといけないのでtaskTypeをsimpleにして他のプロセスより先に実行されるようにしてるところでしょうか。

もし他にもStartupTaskがあるのであれば、優先順位を付けたりするといいかもです。

さて実際にデプロイして確かめてみましょう。

他の環境変数は取れてませんが、StartupTaskで付け替えたシステム環境変数はバッチリ取れてますね。

これで非.NETアプリ等でもシステム環境変数さえ触れればRoleEnvrionmentの値を触ることができますね!

あの日デプロイしたパッケージのReadyを僕たちはまだ知らない

Windows Azure でデプロイするとぐるぐる祭りになることは非常によくあることだと思います。

というわけで今日はぐるぐる祭りの嵌りどころなどを少し。

OnStartで起動時の処理に失敗したりしてFalseを返すと再試行されます。このときStartup Taskなども再度走りますので、リトライが考えられてないと変なことになってしまいます。
※StartupTaskで嵌った場合はそもそもOnStartまで来なかったりしますけど(Simpleの場合)
※1回しか走らない想定のアプリケーションのインストール処理とかが該当するかと

ちなみにローカルPCのCompute Emulatorでデバッグ実行などを行うとデバッガが終わって再試行されないので気付きにくいですね。但し、CSRunコマンドから実行した場合はちゃんとエミュレートしてくれます。

OnStartが繰り返し呼ばれてるのがわかるかと思います。

そんなわけでOnStartのエラー処理はちゃんと考えて適切にしましょう。ただ変なまま起動するのがいいのか、このケースみたいにずっとビジーがいいのかは要件次第ですね。(というあたりが難しいところ)

あと、Windows Azureの管理ポータルで表示されるインスタンスのメニューですが、「再起動」はOSの再起動じゃなくて配置をもう一度最初からする的な意味の再起動です。

つまりStartup Taskも走ればOnStartも走るということで。OS上の再起動とは異なるので注意。
※エラートラップちゃんとしてなくて安易にやられるとOnStartでFalseかえってぐるぐる祭り開催!になります。
対処方法としては「初期状態にリセット」でReImageするとかですね。。

という感じで今日は自分が嵌った点をお伝えしました。とほほ。

 

※ぐるぐる祭りとは

ビジーやAbortのままReady状態にならない時の管理ポータルの見た目(アイコン)の事

例:

こんな感じでぐるぐる繰り返します。。。切ない。

Azure のインスタンスの状態を弄ろう

今日も平和についったー。

たまたま目についたツイートがこちら

image

※もともとの発端は原発の話だったと思われます

このツイートだけ切り出すのもどうかと思ったのですが。。で、TLとしてはたとえば例外全キャッチせずに本当の想定外ならアプリしんじゃえーとかにするとかそういうこと考えないといけないよねという話に。

で一例としてたとえば安全側に倒すなら、サービスがちゃんと生きてる間にアプリをサービスから切り離すとか(例:ロードバランサのクラスタから切り離す、サービスのみオフラインにする等)すればいいんじゃないですかね、Azureなら簡単にロードバランサから切り離せますよという話になりました。というかそんなネタを振ってみました。

幸いAzureにはインスタンスの状態をBusyに設定してロードバランサから切り離してくれる機構があるので、今日はそのへんを見てみようと思います。

以上前ふり。

続きを読む

Windows Azure は Auto Scale の夢を見るか? ひとりでもやってやるです編

※この投稿は Windows Azure Advent Calendar jp: 2011 の17日目です。 ただいま17日の27時過ぎですキリッ(ごめんなさい・・・)
しょっぱなからぶっ飛んだネタが展開されたAdvent Calendarですが、ネタ方向ではいいのが思いつかなかったので淡々と機能紹介でもすることにしますw

ということで Microsoft Enterprise Library 5.0 Integration Pack for Windows Azure – Autoscaling Application Block 、通称WASABiを簡単に紹介した前回に続き、実際に触ってみてAuto Scaleはどんなものか見てみようと思います。正式リリースしましたしね。
今のところ英語情報しかありませんし、全部把握できてるわけではないので細かいところで間違いがあるかもしれませんのであくまで参考まで。
ちゃんと元ドキュメント見て実装等は自己責任で宜しくお願いします。

という逃げ口上もしたうえで早速。

続きを読む

CSEncryptで$が入力できなかったり

ふとWindows Azure SDK 1.5に付属するCSEncryptコマンドを使って暗号化されたパスワードな文字列を生成して、RDPのユーザーパスワードに設定して配置後、接続しようとするとどうも認証ではじかれる。

管理ポータルから再設定したらちゃんと入れるし、Visual Studioを使用してCSCFGファイル作れば問題なし=CSEncryptが何かおかしい。という状況に陥りました。

打ち間違いもなく、何ならコピペでやってるから絶対同じなのにおかしいなぁ~と思ってよくよく、ゆっくりと入力してみると…

あああ! $ が入力されなイイイイイイ!!

タイプしても、ペーストしてもスルー!!!/(^o^)\

というわけで原因はCSEncryptで(というかPowerShellで?)$が入力できない、ということでした。ちゃんちゃん。

 

いやいやいや、入力できないと困ります。

ということでどうするかといいますと。リダイレクトすればいいわけですねー。

こんなファイルを用意しまして、

という感じで標準入力にリダイレクトしてあげるといいようです。

ちなみにちゃんと$込で暗号化されてるのか不安ですよね。ちょっと復号してみてみましょう。

[Reflection.Assembly]::LoadWithPartialName("System.Security")
$source = "<Base64な暗号化された文字列>"
$thumbprint = "<証明書の拇印>"
$store = new-object System.Security.Cryptography.X509Certificates.X509Store My,CurrentUser
$store.Open("ReadOnly")
$cert = $store.Certificates.Find(0,$thumbprint,0)
$env = new-object Security.Cryptography.Pkcs.EnvelopedCms
$env.Decode([Convert]::FromBase64String($source))
$env.Decrypt($cert)
[Text.Encoding]::UTF8.GetString($env.ContentInfo.Content)

こんな感じのコマンドをPowerShell上で実行してあげると復号して元文字列を取り出せます。
結果は同じ、$もOKですね!

※ちなみに暗号化には公開鍵を、復号には秘密鍵を利用しているので暗号化する際に使用した秘密鍵付証明書が個人用証明書ストアに保存されてないとダメです。逆に言えば秘密鍵付証明書(とパスワード)があれば簡単に復号できちゃいますということで。

今日はこの挙動に悩まされた一日でした…とほほ。