今日も平和についったー。
たまたま目についたツイートがこちら
※もともとの発端は原発の話だったと思われます
このツイートだけ切り出すのもどうかと思ったのですが。。で、TLとしてはたとえば例外全キャッチせずに本当の想定外ならアプリしんじゃえーとかにするとかそういうこと考えないといけないよねという話に。
で一例としてたとえば安全側に倒すなら、サービスがちゃんと生きてる間にアプリをサービスから切り離すとか(例:ロードバランサのクラスタから切り離す、サービスのみオフラインにする等)すればいいんじゃないですかね、Azureなら簡単にロードバランサから切り離せますよという話になりました。というかそんなネタを振ってみました。
幸いAzureにはインスタンスの状態をBusyに設定してロードバランサから切り離してくれる機構があるので、今日はそのへんを見てみようと思います。
以上前ふり。
さてさて、具体的にロードバランサから切り離す(アプリを安全にサービスから除外する)にはどうしたらいいでしょう?
答えは (RoleEntryPoint内の)RoleEnvironment.StatusCheck イベントで SetBusy メソッドを呼ぶ、です。
まぁ簡単!
まずStatusCheck イベントですが、これはロールインスタンスの状態をチェックするために15秒間隔で発火するイベントです。(※呼び出しはWaHostBootStrapperが行います)
このイベント内で担うべき処理は、アプリが(ロールインスタンスが)健全かどうか判断し、問題なしなら「Ready」を、問題ありなら「Busy」に状態を設定して自分自身がおかしいですよ、クライアントから接続させないでください、とAzureのFabric Controllerやロードバランサに伝えることです。
具体的にはOKなら何も処理しない、問題ありなら RoleInstanceStatusCheckEventArgs の SetBusy メソッドを呼ぶ、だけです。(もちろん何を持ってOKかどうかは各アプリ次第ですので独自で処理を実装してください)
StatusCheck イベントは15秒おきに呼び出されるので、ReadyまたはBusyが次のStatusCheck イベントが発生する15秒間続く感じになります。つまり毎度毎度判断するわけ。ずっとBusyにしたければ15秒おきに毎回SetBusy メソッドを呼びます。
具体的にはこんな感じのコードになるかと思います。(MSDNのドキュメント見たらわかりますが)
public class WebRole : RoleEntryPoint { public override bool OnStart() { // For information on handling configuration changes // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357. return base.OnStart(); } private bool IsBusy = false; public override void Run() { RoleEnvironment.StatusCheck += (sender, args) => { if (IsBusy) { args.SetBusy(); } }; while (true) { var account = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString")); CloudQueueClient qc = account.CreateCloudQueueClient(); var q = qc.GetQueueReference("busytest"); q.CreateIfNotExist(); var m = q.GetMessage(TimeSpan.FromMinutes(1)); if (m != null) { IsBusy = !IsBusy; q.DeleteMessage(m); } System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); } } }
今回の例だと単純にBusyかどうかの自前内部フラグを見てBusyだったらSetBusyを呼ぶ感じにしました。フラグそのものはAzure Storage Queueにメッセージが入ってたらフラグをセットまたはリセットする感じですね。程よく手抜きしているので適宜読み替えてください。
さてさて、実際にデプロイして確認してみましょう。今回は3インスタンスで見てみました。
ではQueueにメッセージを放り込んでBusyにしてみます。
こうやって放り込んで
2個放り込んで2つのインスタンスをBusyにしました。
インスタンスの内部状態はBusyですが、管理ポータル上で見ると「Role is offline」となっています。この状態になれば対象インスタンスはロードバランサから切り離されてますので、サービスポートへの接続はReadyな(接続完了な)インスタンス、画面例だとWebRole1_IN_0にしかいきません。
実際どれだけ接続しても他のOfflineなインスタンスには接続しませんでした。
ちなみにRole is Offlineな状態になるのはFabric ControllerやロードバランサがBusyと認識してからなので、若干ラグがありますので注意。即時じゃないです。
さて全インスタンスがロードバランサから切り離されるとどうなるでしょうか。
ロールのステータスがビジーになってる通り、サービス(今回だとWebアプリ)には接続できません。
ここで問題です。この状態でリモートデスクトップ接続は繋がるでしょうか?
答え:大丈夫だ、問題ない。
ステータスとしてはロードバランサから切り離されただけで、インスタンスがBusyに(初期化されてなかったりとかほんとにおかしい状態)なってないので繋がります。あとRDP接続するときにインスタンスを指定しているからロードバランスされませんしね。(厳密にいうと違う気がしますが(内部でフォワードしてる気がするので)そのあたりは良しなにしてくれているんでしょうきっと。実際つながるし(投げやり))
あと微妙に動作がおかしい気がするロールのインスタンス数が1インスタンスだけの場合ですが、、、ビジーなのに繋がります。ロードバランサ通ってないというか1インスタンスだと振りようがないからかな?ちょっと挙動が怪しいです。(意図しない状況になりえるという意味で)
まぁSLA的にも2インスタンス以上必要なので、本番なサービス展開の時は大丈夫と思いますが…
まとめ
こんな感じで意外と簡単にWindows Azureではロードバランサから切り離したり戻したりできます。ゆーはぶこんとろーる!
ただ冒頭の話の通り、結局機構としてあるけど「だから?」という状況ではあると思います。
何をもってBusyにするのか、どうやったら安全に対応できるのかとかはアプリ毎に変わるんじゃないでしょうか。なので難しい話ですよね。(過負荷だからオフラインにしようとかいってインスタンス数減らすだけだと他に負荷が行って結局全部オフラインに、とかありえそうですね。オートスケールの話とも大きく関連しますねこのへん)
あとこの手法はロードバランサを経由しない、本当にバックグラウンド処理するためだけのWorker Roleなんかには使えません。違う方法考えましょう。
VM RoleはRoleEntryPoint作れた気がするので大丈夫かな?