2011 fall Openness

さかのぼること1か月弱、2011 fall Opennessというイベントがあったのでござるが、何からどう書いたものやら。書いていいのやら。

このイベントの趣旨は一言でいうと「世界最強の”Azure Openness”チームの結成」(原文ママ)なわけであります。

具体的には、オープンソースなアプリケーションやサービスをWindows Azureで開発・運用しようぜ!ノウハウ共有しようぜ!
でもってちゃんとビジネスとしても対応可能なOSS on Azureなアジュらーを育成しようぜ!という目的のために集まった仮想チームの決起集会&勉強会でした!

わお!もしかしてそんな活動、世界初!?(って言ってみた

普段からAzureな話題に触れてて、多少視野が広ければAzureでも(もっと言えばWindowsでも)PHPやRuby、Javaでもなんでも大体のOSSは動くじゃない、って分かっていただけるかと思いますが、やっぱりWindowsな世界の外から見るとそんな風には見えないみたいですね。
そのへんある程度動くだろうなーと思われないとそもそも選択肢にすらなりゃしない。これはもったいない!

ここが大事なポイントですが、普通にRubyもPHPも動くんです!そうじゃなくてもバイナリさえあればだいたい動くんです!動かすためのコツはそりゃーありますけどね!だからそのコツを共有したり、使ってみて嵌りそうなところ回避したり、フィードバックしてもっとAzureいいものにしようぜ!未来のブルーオーシャンは俺たちのものだ!Cool Japan!ってのがAzure Opennessなわけです。(ちょっと誇張気味)

Windowsって、Azureって、意外とできる子なんですよ!認知すらされてなかったり10年以上前のイメージで語られたりするけど!
そういうのってもったいないなーとか思うわけで。目的に応じて取捨選択すればいいだけなので、できるだけ選択肢は広げておきましょう的な。

で、ビジネス的な要因もあるでしょうけど、ほらそのあたりはOSSやDeveloperを大事に考えるMSだから。まじめに使ってもらえることを考えてると思うのですよ。ここ1年ぐらいのOSSへの力の入れよう見ればそのあたりわかるかなと思います。

あとこれは副産物ですが、Azure Opennessをやったことで新しいアイデアも出たし、フィードバックできそうな内容も新しい知見も得られたり。面白い取り組みも何個か走りそうな感じですよ~!こうご期待!?

とまぁ業界の端っこに存在するBlogで言ってもあまり説得力ありませんが…興味がある人はどんどん試して、Twitterで(#azurejp や #jazug ハッシュタグ付けて)発言したりするといいと思います!
最近だとInstallManixが対Azureな感じになってるので、たくさんのOSSが動いてる&ドキュメントがそろってると思います。
そういうのに参加したり、参加してる人に絡んだりするといいんじゃないでしょうか!

取り留めがありませんが(取り留める気もあまりなかったけど)、こんな感じの超レアで熱いイベントでしたー!いじょー

In-place UpdateがUpdateされました

唐突なアナウンスですがWindows AzureのIn-place Update機能が更新されたようです。まぁWindows Azure側の話なので、こちらで何か修正等があるわけではありませんし、既に展開されてるのでみんな恩恵にあずかれます。

元ネタ:Announcing Improved In-place UpdatesOverview of Updating a Windows Azure Service

変更内容ですが、簡単にいうと以下のケースにおいて今までIn-place Updateが出来なかったのですが、それができるようになりました
( ゚Д゚ノノ"☆パチパチパチパチ

  • 仮想マシンのサイズ変更(スケールアップ/スケールダウン) ※たとえばSサイズからLサイズ、SサイズからXSとか
  • ローカルストレージの増加
  • デプロイされてるロールの追加・削除
  • エンドポイントの数や種類の変更

今まで再デプロイとかVIPスワップで対応しないと行けなかったのがIn-place Updateでもいけるのは嬉しいですね!

※ちなみにIn-place UpgradeなのかIn-place Updateなのかははっきりしてほしいですがw(管理ポータルとか見る限りUpgradeだとは思いますけど元ネタのBlogに合わせてます。どっちでもいいんじゃないですかね)

変更できる項目が元ネタのところにまとまってるのでそのまま持ってこようと思います。

変更内容 In-place Update VIP Swap 再デプロイ
Guest OSのバージョン OK OK OK
.NET Trustレベル OK OK OK
仮想マシンのサイズ OK
注意:仮想マシンのサイズを変更するとローカルデータが破棄されます。
Management APIを使用してこの変更を行う場合は強制フラグが必要です
メモ:Azure SDK 1.5以上が必要
OK OK
ローカルストレージの設定 OK(ただし増加のみ)
メモ:Azure SDK 1.5以上が必要
OK OK
ロールの追加または削除 OK OK OK
特定ロールのインスタンス数 OK OK OK
サービスエンドポイントの数や種類の変更 OK
メモ:Azure SDK 1.5以上が必要
エンドポイントが更新されるので、一時的に可用性が損なわれる(接続できなくなる瞬間がある)
NG OK
ConfigurationSettingsの名前と値 OK OK OK
ConfigurationSettingsの値 OK OK OK
証明書の追加 OK OK OK
既存の証明書の変更 OK OK OK
新しいコードのデプロイ OK OK OK

再デプロイは基本的にまっさらな状態でデプロイになるので、まぁこの辺の制約はないわけで。
VIP SwapもIPアドレス付け替えと考えると、サービスエンドポイントが変わらなければOK(つまりそこだけNG)なので納得ですね。

In-place Updateで仮想マシン(インスタンス)のサイズやロール数を変更する場合は、アップグレードダイアログの「VMサイズまたはロール数の更新を許可する」にチェックを付けましょう。付けずに更新しようとするとエラーになります。(Management APIでやる場合も同様に強制フラグをOnにして実行しましょう)

さて他にも幾つか追加された機能がありまして。

In-place Updateをキャンセル/ロールバックなど細かくコントロールすることができます。
アップグレードモードが自動の場合はこれらはよしなにしてくれますが、マニュアルモードの場合、管理ポータルやManagement APIを使って細かく制御できます。

このへん詳しく載っていますので参照ください↓

 

まとめ

ということで、アップグレード手法をまとめるとこんな感じですかね(コピペですけど)

更新方法 説明 メリット デメリット
In-place 新しいパッケージを今稼働してるインスタンスやサービス上に適用する方法 1つのデプロイで済む
各ロールで2インスタンス以上あれば可用性を維持したまま更新できる
更新してる間、更新対象のロールはダウンするのでサービス提供能力が落ちます。
全インスタンスが更新されるまでの間、新旧2つのバージョンのサービスコードが稼働するので動作に差がでてしまいます
VIP Swap 新しいパッケージを別の領域(ステージング)に展開し、IPアドレスを付け替える方法(サービスをスワップする) サービスダウンタイムや提供機能のロスが無い 少なくとも2つデプロイ(プロビジョニングとステージング)が実行されてる必要がある(スワップするのに必要)
再デプロイ 稼働してるサービスを削除して新しいパッケージをデプロイする方法 1つのデプロイで済む サービス削除するので、提供していたサービスはダウンする。デプロイが変わるとIPアドレスも変わるので、DNSの伝達に時間がかかるケースもある

CSEncrypt コマンドでパスワードの暗号化

Windows Azure SDK 1.5でリモートデスクトップ接続時に使用するパスワードやらを暗号化するのに使える、CSEncryptコマンドラインツールが追加されました。

Azure SDK 1.5が出る以前はどうだったかというと…

にある通り、パスワード文字列をバイト配列にして証明書読み込んで読み込んだ証明書を使って暗号化して、できあがったものをBase64でエンコードするという超絶めんどい作業をする必要がありました。

[Reflection.Assembly]::LoadWithPartialName("System.Security")
$pass = [Text.Encoding]::UTF8.GetBytes("<Password>")
$content = new-object Security.Cryptography.Pkcs.ContentInfo ?argumentList (,$pass)
$env = new-object Security.Cryptography.Pkcs.EnvelopedCms $content
$env.Encrypt((new-object System.Security.Cryptography.Pkcs.CmsRecipient(gi cert:\CurrentUser\My\<Thumbprint>)))
[Convert]::ToBase64String($env.Encode())

こんな感じですね。

さてこれがAzure SDK 1.5に付属するCSEncryptコマンドだとどうなるかというと。

csencrypt Get-PasswordEncryptionCertificate
コマンドを実行して証明書一覧を表示して拇印をコピーして…

csencrypt Encrypt-Password -CopyToClipboard -Thumbprint "<さっきの拇印>"
コマンドを実行してパスワードを入力するだけ!

あとは出力された文字列を(改行除いて)コピーすればOK!ほら簡単でしょ?(

ちなみに -CopyToClipboard オプションを付けておけば勝手に結果をクリップボードにコピーしてくれます。
※Outputオプションでファイルに出力することもできます。(やっぱり改行はされてますけど)
そのほかにもNew-PasswordEncryptionCertificateオプションで証明書の生成もできるのでお手軽ですね。

ちなみにパスワードを書いたテキストファイルを標準入力として読み込ませれば手打ちしなくてもイイです。

オプションや内容についてはこちらを参照。

というわけでコマンドでやるにも便利な感じになりましたとさ。

Windows AzureはAutoScaleの夢を見るか?

ちょっと前の話題ではありますが、Microsoft patterns & practicesが提供するEnterprise Library 5.0にWindows Azure Integration Packが追加されています。(現状はPreview版のようですが)

Integration PackとしてはWindows AzureのほかにもSilverlightなどがありますね。

さてこのWindows Azure Integration Packとやらは何ができるんでしょう?

EntLib vNext teaserUsing Enterprise Library 5.0 in Windows Azure(PDF) を見るとEnterprise Libraryで提供されているロギングや暗号化、データアクセス、コンフィグレーションなどのアプリケーションブロックがAzure上でも利用できるようになっているようです。

でEnterprise Libraryなので簡単なコンフィグレーションで環境の差異が吸収できるようになってたりするっぽいですね。なのでこのIntegration Packを使うとOnPremiseからAzureへ、AzureからOnPremiseへの移行とかがしやすくなりそうです。(というかその辺が目的でしょうか)

で、それはそれでいいとして。

目玉としてはWindows Azureに特化した部分としてWindows Azure Autoscaling Application Block(WASABi)が追加されたことです。

わお!素晴らしいですね!こいつを使うとCPU負荷などの利用状況や時間帯などルールを評価してアクション(つまりオートスケール等)ができるようです。

WASABi

まぁカスタムアクションやルールの定義も自由度高いので、そのあたりは柔軟にできそうですね。というかそのためのライブラリだし。

image

想定されるMaxとMinを指定してインスタンス数が予想外に増えないように・下がらないようにルール作ったり、スパイクに対応させたり。
判定材料はCPU負荷などいろいろ構成できますと。

ということでWindows Azureは公式標準機能ではありませんがオートスケールを手に入れたようです。まだPreviewなので、要望がある方は是非Voteってください。

実際に試したい方はNuGetにあがっているのでそちらからもどうぞ。

以下余談です。

このWASABiを使うと確かにオートスケールできそうです。でもWASABiを使ってるアプリケーションのみですよね。単発のLOBなアプリケーションとかならいいんでしょうけど、適用する場所、やり方、運用、ITのあり方なんかは別でちゃんと考えたほうがいいと思います。似たようなことはSystemCenterでもできるわけですし、なんでそっちでも出来るかとかは考える価値はあるかと思います。

まぁ釈迦に説法と思われますが。

あとオートスケールいうならゼロインスタンス機能くれよーーこればっかりはAzure側で対応してくれなきゃ無理!!

Windows Azure AppFabric ACS でカスタムのサインイン画面を作る

Windows Azure AppFabric ACS使ってフェデレーション認証するのはいいけど、味気ないサインイン画面で辟易してる方も多いんじゃないでしょうか。

↓味気ないサインイン画面

というわけで今日はこいつのカスタマイズをしたいと思います。

ゴールとして今回はASP.NET MVC3のログイン画面内で上記のサインイン画面(というかセレクタ画面)を埋め込んでアプリケーションでちゃんと(?)制御できるようにしたいと思います。

続きを読む

Windows Azure SDK 1.5

というわけでWindows Azure SDK 1.5がでましたね。

さくっとインストールしてみましょう。(※Visual Studio “11”やWindows 8ではTools for Visual Studioがインストールできませんでした。Visual Studio 2010が入ってたら行けそうですけど .NET Framework 3.5と4が必要なところでこけそうです・・・)

インストールはWeb Platform Installerからするのが楽ちんなのでそのようにします。
※念のために古いSDKやTools for VSはアンインストールしました。

注意点:Storage EmulatorのDBが更新されるので、必要なデータいれてる人はバックアップしておきましょう。古いDB削除されます。

Azureで検索したら Azure AppFabric SDK 1.5とWindows Azure Tools for Microsoft Visual Studio 2010 – 2011年8月(リリース日が2011/09/14)の2つがでるので追加しましょう。

依存関係はこんな感じです。

日本語期待した人はサヨウナラ・・・

でインストールするとAzure SDK 1.5とAzure AppFabric SDK 1.5、Azure Tools for VSが入ります。
※ASP.NET MVC3 Tools Update Installerが入ってなかったようなので一緒にはいりました。

Visual Studioで見るとちゃんと更新されてますね。

さて、インストールが終わったらStorage Emulatorを起動して初期化しましょう。

更新後はDBこんな感じに。

DevelopmentStorageDb20110816 が今回使ってるDBです。以前のは消えてますので注意。

Compute Emulatorもちゃんと新しくなってます。

あとAzure SDK 1.5ではパスワード(文字列)の暗号化用ツールが追加されました。

今まで手動で証明書指定したり暗号化用のコマンド叩いたり超面倒だったのが一発で!!!(

以前のバージョンで作ったプロジェクトを移行する場合は Microsoft.WindowsAzure.StorageClient.dll を 1.1.0.0 に更新しましょう。

またWindows Azure Connectのプラグインで利用している以下の設定が無くなりましたのでコンフィグから削除する必要があるようです。

   <Setting name="Microsoft.WindowsAzure.Plugins.Connect.Diagnostics" value="" />
   <Setting name="Microsoft.WindowsAzure.Plugins.Connect.DNSServers" value="" />

元ネタ:Windows Azure SDK Release Notes (September 2011)

どう変わったか等細かいところはMSDNにドキュメントがありますが、おいおいということで。

Windows AzureのWeb Roleでセッションを共有する

紆余曲折ありましたが(?)、Windows Azure上のWeb Roleでセッションを共有する方法を簡単に(いい加減に)まとめたいと思います。

  ASP.NET標準 ASP.NET標準 MSDN Code Gallaryのセッションプロバイダ ASP.NET Univarsal Providers High Performance Session State Provider Windows Azure AppFabric Caching
ストア メモリ(InProc) SQL Azure Azure Storage Table SQL Azure SQL Azure Windows Azure AppFabric Caching
作成者 MS MS MS? MS @takepara
@kazuk
MS
SLA 無(ストアはある) 無(ストアはある) 無(ストアはある) 無(ストアはある)
特徴 冗長化・負荷分散しない漢向け 削除をなんとかしたら使えないことはない Table使うので安い 新入り。そつなく利用可。 自作上等。ASP.NET Universal Providerの残念なところを解消 大規模で本気出すなら商用サービスだよね
メリット お手軽高速 お手軽 お手軽で安い? お手軽。RoleやMembershipも使える お手軽 高速。一番早い。
デメリット 冗長化されない・負荷分散されない セッションが削除されない セッションが削除されない。遅い。 セッション削除に難あり 自家製 お金。
追加コスト 無し DB分必要 低(SQL Azureよりは安い) DB分必要 DB分必要 高め(128MB/月で3,933.45円)
パフォーマンス 中の上
その他 パフォーマンスカウンタが付く セッション数の把握はDB見れば可 よくわからない セッション数の把握はDB見れば可 セッション数の把握はDB見れば可 ざっくりした合計セッションサイズがある程度わかる(ただしリアルタイムでは把握できない?)

 

という感じで役に立つかどうかわかりませんがまとめました。いろいろ要件にあわせて選んでくださいませ。

冗長化いらないならInProcとか。
最低限負荷分散したいけど小規模だしコストかけたくないなぁとかならAzure Storage Tableとか。
それなりにちゃんと使いたいとかならHighPerformanceSessionStateProviderとか。
いやー自分で面倒みるの大変だよとかならASP.NET Univarsal Providersとか。
大規模で性能いるんだよお金はあるぜとかならAzure AppFabric Cachingとか。

まぁ他にもMemcached使ったり、自前で負荷分散も対応したInMemoryなプロバイダ作ってもいいでしょうし。

お好みでどうぞ!

ASP.NET Univarsal Providers のセッションプロバイダを使ってみる (4)

性懲りもなく4回目。

@kazuk さんが「無駄な処理をしない事と、重い処理をバックグラウンドにする事」ということで、
削除処理を毎回するんじゃなくてある程度間隔あけて+別スレッドでやるべきだろJKとおっしゃられました。
というわけで @takepara さんのHighPerformanceSessionStateProviderに追加してみました。

↓結果

少しパフォーマンスよくなりました。ただMAXも2秒とかなので、平均して落ち着いた感。エラーもなくなってますね。
クライアントは↓

多少ばらつきありますがネットワーク的に遠いところ(日本)なのでまぁこんな感じですかね。ゴミセッションの削除がはしったと思われるタイミングでもそんなに重くなってないのが特徴です。

とまぁこんな感じでかなりまともに使えるセッションプロバイダになった気がします。
ロールやメンバシップの部分はASP.NET Univarsal Providersのが良しなに利用できると思うので組み合わせるといいんではないでしょうか。

最終的なソースはこちら↓

// create: 2011.08.23
// author: @takepara (http://tinyurl.com/3kebwlu) , @kazuk (http://tinyurl.com/3r5vjkq)

using System;
using System.Configuration;
using System.Data.Common;
using System.Reflection;
using System.Web;
using System.Web.Providers;
using System.Web.Providers.Entities;
using System.Threading;

namespace UniversalProviders
{
	public class HighPerformanceSessionStateProvider : DefaultSessionStateProvider
	{
		private string _connectionStringName;
		private bool _initialized = false;
		private DateTime _lastSessionExpireInvoked = DateTime.MinValue;
		private object _lockSessionExpire = new object();
		private bool _working = false; 

		public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
		{
			_connectionStringName = config["connectionStringName"];

			base.Initialize(name, config);
		}

		private void ExecuteSql(ConnectionStringSettings connectionStringSettings, Action<DbProviderFactory, DbCommand> functor)
		{
			var providerName = connectionStringSettings.ProviderName;
			var factory = DbProviderFactories.GetFactory(providerName);
			using (var connection = factory.CreateConnection())
			{
				connection.ConnectionString = connectionStringSettings.ConnectionString;
				connection.Open();

				var command = connection.CreateCommand();

				functor(factory, command);
				command.ExecuteNonQuery();
				connection.Close();
			}
		}

		private void CreateSessionIndex(ConnectionStringSettings connectionStringSettings)
		{
			ExecuteSql(connectionStringSettings, (factory, command) =>
			{
				command.CommandText = @"IF not EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Sessions]') AND name = N'IX_Sessions')
begin
CREATE NONCLUSTERED INDEX [IX_Sessions] ON [dbo].[Sessions] 
(
	[Expires] ASC
)
end
";
			});
		}

		private void RemoveExpiredSessions( 
			ConnectionStringSettings connectionStringSettings) 
		{ 
			if( _working ) return; // やってる人が居るならやらんでいいでしょ 
			_working = true; 
			try { 
				ThreadPool.QueueUserWorkItem( (object state)=> 
				{  
					try 
					{ 
						ExecuteSql(connectionStringSettings, (factory, command) => 
						{  
							command.CommandText = "delete Sessions where Expires < @0"; 
							var parameter = factory.CreateParameter(); 
							parameter.ParameterName = "@0"; 
							parameter.Value = DateTime.UtcNow; 
							command.Parameters.Add(parameter); 
						}); 
					} 
					finally { _working = false; } 
				}); 
			} catch { _working = false; throw; } 
		}

		private MethodInfo GetCreateSessionEntities()
		{
			var modelHelper =
				Assembly.GetAssembly(typeof(Session)).GetType("System.Web.Providers.Entities.ModelHelper");
			return modelHelper.GetMethod("CreateSessionEntities", 
				BindingFlags.NonPublic | BindingFlags.Static);
		}

		public override void InitializeRequest(HttpContext context)
		{
			var connectionStringSettings = ConfigurationManager.ConnectionStrings[_connectionStringName];
			if (!_initialized)
			{
				var initializer = GetCreateSessionEntities();
				initializer.Invoke(null, new object[] { connectionStringSettings });
				CreateSessionIndex(connectionStringSettings);
				_initialized = true;
			}

			if( _lastSessionExpireInvoked < DateTime.UtcNow - TimeSpan.FromSeconds(30) ) 
			{ 
				if( Monitor.TryEnter( _lockSessionExpire ) ) { 
					// ロックに入れたスレッドでだけ処理する、入れなかったスレッドでは処理しない 
					DateTime old = _lastSessionExpireInvoked;
					_lastSessionExpireInvoked = DateTime.UtcNow; 
					try 
					{ 
						try 
						{ 
							RemoveExpiredSessions(connectionStringSettings); 
						} 
						catch 
						{ 
							// 失敗した時はタイマ値を戻すことで別のスレッドにやってもらう 
							_lastSessionExpireInvoked = old; 
							throw; 
						} 
					} 
					finally 
					{ 
						// 絶対にMonitorを出る 
						Monitor.Exit( _lockSessionExpire ); 
					} 
			   } 
			}
		}
	}
}