木曜日, 7月 30, 2009

【Google App Engine】 RESTfulアプリのCRUDサンプル -Servlet編- このエントリーを含むはてなブックマーク


 今回は、RESTfulアプリのServlet部分について説明する。例によって、請求書アプリを題材にしている。使用しているReflexGaeライブラリは=>ココ

検索(GET)

 検索ではRequestパラメータを受け取り、それを条件にdatastoreを検索して、結果をJSONもしくはXMLで返す。

 1. ReflexServletをextendsしてRESTful機能をもたせる
 2. RequestパラメータをEntity(Invoice)にセットする
 3. パラメータInvoiceを与えてdatastoreを検索
 4. 結果をJSONもしくはXMLにして返す
 5. エラー発生時はエラー内容をJSONもしくはXMLにして返す




登録(POST)

 登録では、クライアントからPOSTで受け取ったXMLもしくはJSONをEntityオブジェクトに変換した後、datastoreへのINSERTを実行する。クライアントには登録成功でHTTPStatus201(created)を返す。


 1. クライアントから受け取ったXML(JSON)をEntityオブジェクトに変換
 2. すでに登録されている場合エラー
 3. 登録されていなければinsert
 4. エラー発生時はエラー内容をJSONもしくはXMLにして返す




更新(PUT)

 更新では登録と同様に、クライアントからPOSTで受け取ったXML、JSONをEntityオブジェクトに変換してUpdateを実行する。更新オブジェクトのRevision番号が同一であれば更新実行、異なれば楽観的ロック失敗とする。



削除(delete)

 削除では、リクエストパラメータをもとに削除したいオブジェクトを取得し、deleteを実行する。該当するオブジェクトが存在しない場合に、HTTPStatus 204(no content)を返すようにしているが、これは特になくてもよい処理かもしれない。



嵌りどころと注意点

 1. POSTの場合、Requestに対し、getInputStreamを実行する前に、getParameterを実行してしまうとInputStreamの値が読み取れない。(PUTの場合はなぜかうまく動作する)
 2. HTTP Status 200,201以外をセットする場合、ResponseのOutputStreamに内容が出力されない。具体的には以下のような現象となる。


(HTTP Status) : (現象)
* 204 : 内容なし
* 409 : java.io.IOException
* 404 : java.io.FileNotFoundException


 最後に一言。GAEにおけるアプリケーション開発で特に重要だと思うのはテストである。テストコードを書けとか、TDDをやれとかいう意味ではなく、実際にテストして動作を確認することが重要である。(私は自分が作ったアプリのテストコードはほとんど書かないが、他人が作ったものは実際に検証しないと何も信じないタチである。)実際に現象を確認しないで、SPECやDocumentを見て判断してはいけないし、やみくもに信じてはいけない。ある程度予想できる結果であったとしても、必ずテストして確認すること。GAEは特にそうしないと危ない。石橋を叩いて渡るぐらいの心構えでちょうどよいと感じている。

<関連>
AJAX CRUDサンプルとJDO代替ライブラリ
RESTfulアプリのCRUDサンプル -Modeling編-

火曜日, 7月 28, 2009

【Google App Engine】 Pagingをどうやって実現するか このエントリーを含むはてなブックマーク


 JDOをそのまま使うとPagingができないことは前記事で述べたとおりだが、DatastoreAPIを使えば可能なので、今回はその方法について述べたいと思う。前記事と同様、請求書アプリを元に説明する。

KeyとCounter


 まず、基本となるEntityのInvoiceBaseとInvoiceの構造から。



 InvoiceBaseは、Invoiceレコードを保持するする親のEntityだ。InvoiceBase(親)に紐づくInvoice(子)は1:nのOwnedな関連である。また、Invoiceレコードの件数を格納するcounterプロパティをもつ。InvoiceのKeyは、トランザクションで括る必要があってKeyを連結させているが、拙作ライブラリReflex GAEのAPI、keyUtils.getChildKey()を使って、連結されたInvoiceのKeyを取得している。(ReflexGaeライブラリは=>ココ
 レコードの件数counterは、Pagingのために必要な連番を振るためにも使用される。Datastoreは自動で連番を付けられないため、InvoiceBaseの自身でもつより他ない。

 一応、JDOのドキュメントで以下のようなものがあったので試してみたが案の定できなかった。(追記:最新版SDK1.2.6では、Statics APIで件数が取れるようになっている。こちらの記事の参考に=>Keyとカウンタは別々に考えるといいかも


【コード】
 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.INCREMENT)
 private Long uid;

【エラー内容】
 Message : There is no available value generator for strategy "increment" for this datastore. Please consult the documentation for details of which generators are available.


 そもそも、親子関係をもつようなEntityを扱うには、Long型ではなくKey型を使わなくてはならない。Googleのドキュメントにも記述されているが、Keyの中身が以下のようなツリー構造をしていることからもその理由は想像できる。


子要素までaddしたときのキー(実際はBase64でエンコードされている)

 j reflexworksr,  InvoiceBase" InvoiceBase Invoice"ID2
 j reflexworksr,  InvoiceBase" InvoiceBase Invoice"ID1


 よく、KeyをStringとしてもたせるような記述、「@Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")」を見かけるが、階層型の場合、連結させたDatastoreのKeyがそのままStringに変換されてしまうのでおもしろくない。例えば、先のEntityのKeyをStringにするとその中身は、「j reflexworksr,  InvoiceBase" InvoiceBase Invoice"ID1 」となってしまう。業務アプリで意識すべきは、業務アプリのKeyであって、DatastoreのKeyではない。DatastoreのKeyはむしろ隠蔽させて、ID1という業務アプリのキーだけを意識させる方がわかりやすいだろう。なので、Reflexでは、JDO KeyをStringとしてもたせる記述はせず、アプリのPKをKeyに変換するという方法をとっている。以下はアプリのPKであるinvoiceNoをJDOのKeyに変換する例である。(keyUtilsはReflexGaeのAPI)


 Key childKey = keyUtils.getChildKey(Invoice.class, param.invoiceNo);


Keyによる検索

 このPKを使った検索の例は次のとおり。pm.detachCopy()をすることで、Persistentではない、自由に扱えるEntityのコピーを得ることができる。Persistentなものは、まだDatastoreにAttachされていて、コネクションが張られている状態のような感じ。実際に値を書き換えるだけでDatastoreの中身も変更される。



Paging検索

 Paging検索では、以下のように、idで開始点を与えて、かつ、withLimitで範囲を絞るといった感じになる。JDOでは6千件でフリーズしてしまったが、DatastoreAPIを使っているため理論的には無限で、このサンプルではcounterの最大値(longの2147483647件)までは扱えることになる。とりあえず1万件でテストしてみたが非常に高速にレスポンスが返ってきたので問題なく使えると思う。

 1) filterで開始点となるidの値をセットして、それ以上のものを検索対象とする

query.addFilter("id", Query.FilterOperator.GREATER_THAN , Long.parseLong(nextId));

 2) FetchOptionsでwithLimit()をつけることで、検索結果の件数を絞る

FetchOptions fetchOptions = FetchOptions.Builder.withLimit(limit);




指定可能なfilter条件

 DataStoreAPIでは、GREATER_THANなどのinequality filter(<,>など)が1つと、equality filter(=)を複数指定できる。連番でinequality filterを使ってしまっているため、あとはequality filterだけが使えることになる。Reflex Gaeライブラリでは、QueryUtilsというものを用意していて、検索条件を格納したParameterBeanからequality filterを自動的に追加できるようにしている。

 // 検索項目(Indexがあるもの) を指定してnewする
 QueryUtils queryUtils = new QueryUtils(new String[]{"invoiceNo","companyName","job","issuedDate"});

// paramの検索項目がnullでなければaddFiler(FilterOperator.EQUAL)される
queryUtils.setParam(param, query);


EntityConverterによる変換

 DatastoreAPIで検索すると、MAPで返ってくるのでEntityのPropertiesからListに変換する必要がある。DatastoreAPIには、DataTypeTranslatorというのがあったが、使い方がよくわからなかったので自前で作成した。それがEntityConverterである。

List result = entityConverter.convert(Invoice.class, resultIterable,null,condition);


 filterで指定できる条件はequality filterだけなので、Like検索など複雑なことをやりたい場合には、Entityを取得した後で別途Java側で処理しなければならない。EntityConverterにソート機能や条件抽出機能をもたせたているのはこういった理由からだ。
 comparatorをEntityConverterの第三パラメータに与えることでEntityをソートして返すことができる。また、EntityConverterの第4パラメータにconditionを与えることで、conditionに合致するものだけを変換対象にするといったことができる。
 
Indexについて

 上記ソースのQueryはDatastoreAPIのものであってJDOQLではないので注意が必要だ。JDOQLでは自動的にIndexが作成されるが、DatastoreAPIではindexがないとエラーになるので自分で作成する必要がある。(これはむしろ好都合)
 作成するには、すべての検索パターン分のindexを定義した、datastore-indexes-auto.xmlを所定の位置(WEB-INF/appengine-generated)の下に置いてDeployするだけだ。(datastore-indexes.xmlではダメだった。また、index作成には結構時間がかかるので辛抱づよく待つ必要がある。管理画面のDatastore=>Indexesで、StatusがServingになればOKだ。とにかく待とう)
 すべての検索パターン分のindexとは、検索条件の組み合わせをすべて定義しなければならないという意味である。例えば、請求書アプリでは、"id"と"companyName"の2つの条件で検索したい場合もあれば、"invoiceNo","companyName","invoiceNo","issuedDate"の4つで検索したい場合もある。検索する可能性の組み合わせすべてを定義しなければならない。



登録処理 

 登録では次のような感じになる。



大規模なデータをもつ子要素に対してInsertして問題ないか

 まず、pm.getObjectById()でinvoiceBaseを取得する。存在しなかったら新規作成して、pm.makePersistent(invoiceBase)を実行する。これは更新時には必要ないため新規作成時のみ実行するようにする。Invoiceレコードを追加するたびに、InvoiceBaseのcounterをインクリメントする。
 InvoiceレコードはinvoiceBaseの子要素であるが、いわゆる、eager loadではないため、pm.getObjectById()しただけでは、Invoiceの全レコードがメモリに乗ることはない。getter/setterでアクセスしたものだけが実際のDatastoreへのアクセス対象となるので、膨大なレコード数が対象であってもこれでOKだ。(実際に1万件に対して実行しても大丈夫だった)

パフォーマンス

 パフォーマンスへの影響を考えた場合、EntityGroupは最小化すべきとよく言われる。実際、この例はテーブルロックのイメージに近いため、RDB設計に携わってこられた方は、どうしても気になるところだろう。counterとレコードの関係を考えると、トランザクションは切り離せない部分であるため、どうしてもこのような実装になってしまうが、それでも、コンテンションが起きた場合は、JDOCanRetryExceptionを拾ってリトライできるので、スループットは出せるのではないかと思う。実際に測定してみたところ、遅くなる要因としては、EntityGroupの大きさというより、トランザクション処理の件数の方がインパクトが大きかったので、TaskQueueとMemcacheを駆使して、複数のトランザクションを1つにまとめて非同期に一括処理するようなものを介すことでパフォーマンスを改善できると思われる。このあたりの話は深いのでまた別の機会に詳しく述べたいと思う。
 ちなみに、上記コード中で、JDOCanRetryException内で都度rollbackしているのは、そうしないとうまく動作しなかったからだ。(Documentどおりだと動作しないと思われるので注意)

楽観的ロック

 同一レコードの書き換えでコンテンションが起きてしまう場合は楽観的ロックが有効である。@Version(strategy = VersionStrategy.VERSION_NUMBER)を使用する方法があるようだが、この程度のものは潰しがきくので自前で実装する方がよい。具体的には、Updateの際にrevisionを比較して同じであれば更新実行してrevisionを+1するだけ。比較して違う場合にエラーとすればよい。

<関連>
Entityとトランザクション
JDOから直接JSON、XML
JDOから直接SOAP、ATOM。それからDeep Copy
AJAX CRUDサンプルとJDO代替ライブラリ
RESTfulアプリのCRUDサンプル -Servlet編-
RESTfulアプリのCRUDサンプル -Modeling編-

土曜日, 7月 25, 2009

【Google App Engine】 AJAX CRUDサンプルとJDO代替ライブラリ このエントリーを含むはてなブックマーク

RESTfulなWebサービスとワンソースマルチビュー

 先日行ったプレゼンの請求書DEMOでは、以下のように、HTMLの請求書を直接編集後、PDFを表示させるというところをお見せした。(以下のHTMLを表示させて右クリック、Searchで何かを選択してデータを読み込む、さらに右クリックでPDFを選択して生成)

 請求書デモ








 これは一見、統合されたアプリのように思えるかもしれないが、単なる疎結合なサービスの組み合わせであるにすぎない。つまり、JSONやXMLでCRUDできる機能だけが実装されたRESTfulなGAE上のサービスと、そのサービスに対してAJAXでアクセスするHTMLとJavaScript。そして、GAEのサービスからXMLを取得してPDFを生成するReflex iTextサービスの3つの組み合わせで成り立っているものである。特筆させていただきたいのは、ブラウザで表示させたHTMLとPDF生成で使ったテンプレートHTMLは全く同じものを使用しているという点。GAEのサービスから取得したものであるから、データはもちろん同じものである。このように、Reflexは、ブラウザやPDFなど様々なViewに対して、同じEntityを与えさえすれば、同じように表示させることができる。これをワンソース・マルチビューという。(そもそも、1つのEntityから複数の写像をとれるという意味で、名前をReflex(写像)としている)
 このように、ブラウザ以外にも、携帯やiPhone、Android、Flex2などの異なるメディアや端末に対象を広げられれば、コミュニケーションの幅を簡単に広げることができるため、ワンソース・マルチビューは今非常に重要視されている概念となっている。また、 Chrome OS戦略とIT世界の対立軸 でも述べているとおり、Googleは、RESTfulなWebサービスを基本とすることで、コミュニケーションに革命をもたらすとまでいいきっている。

Entityをそのまま表示させる

 これまで説明したように、Reflexではサーバで画面を作ることはしない。JSPも使わない。こうしてしまうと、どうしても密結合なアプリになってしまう。これは非常に大きなデメリットである。Reflexでは、JSONやXMLのCRUDだけが行えるサービスを基本とするが、JSPも使わないとなると具体的にどのようにブラウザで表示させればよいかが問題となる。この点について説明しよう。そんなに難しく考えないことがポイントである。

 まず、HTMLでモックを作り、表示させたいタグにIDを振る。画面が出来たら、IDを全部抽出して並べてルートタグで括る。これが表示用エンティティ。あとはそのエンティティをJavaでも実装して、AJAXを使って受け取れるようにする。JSONだとeval()できるので楽である。あとは、evalした個々のデータをHTMLのidに対してJavaScriptで挿入すればよい。具体的には、DEMOのinvoice.htmlとdisplay.jsを見ていただくのがよいと思う。ちなみに、Reflex iTextのデータ挿入もほぼ同じやりかたで行っている。また、高速化プロジェクト その1で述べたように、パフォーマンスも特に心配することはない。

 ただ、何でもかんでもこのスタイルがよいわけではないことも付け加えておく。SEO対策などで、サーバが出力する最初の段階から完全なHTMLでないとまずい場合などがあるからだ。そういう場合は、Reflex iTextのように、テンプレートとXMLから完全なHTMLを生成するようなサービスを介すとよいと思われる。いずれにしても、画面をベタ書きして疎結合を損なう設計になることだけは避けたいところである。

JDOとスケーラビリティ

 ここからが本当に言いたいところ。

 我々が検証した限り、JDOは大規模データを処理できないことがわかっている。正確には、JDOQLであるが、たった6千件のデータでさえ検索することができないのである。Indexが勝手に作成されてIndex爆発が起きる問題など、いくつか致命的なものもあるが、最も酷いのは、検索対象を絞るページングなどの範囲指定ができないことである。実は、JDOQLは5千件ぐらいまでは何とか検索できる。あれ?上限は千件までじゃなかったっけ?と不思議に思う方はGAEをよく知っている方。そう、一度にFetchできるのは千件までなのだが、それはDatastoreAPIの話。上位実装であるJDOではなんと5千件一括検索ができてしまうのだ。余計なことに、そんなことができる反面、6千件だともうフリーズしたように無反応になる。setRange()を指定してもダメ。getObjectById()やequalの検索条件を指定してやっと1件取得できるのみ。keyの複数件検索も未サポートなので、もう、これは使い物になりません。(※ただし、トランザクションや更新系はJDOのPMで問題なく使えている)
 そこで、私たちはJDOQLを諦め、低レベルのDatastoreAPIだけを使った検索ユーティリティを作成することにした。これを使えばPagingもできて大規模データの操作も問題ない。先の請求書DEMOも使用している。次回はこの中身について説明していきたいと思う。

<関連>
Reflex GAE
Entityとトランザクション
JDOから直接JSON、XML
AXIS2のデータバインディング能力
JDOから直接SOAP、ATOM。それからDeep Copy
Pagingをどうやって実現するか
RESTfulアプリのCRUDサンプル -Servlet編-

金曜日, 7月 17, 2009

【Google App Engine】 TaskQueue API for Javaが数週間以内にリリース!? このエントリーを含むはてなブックマーク


AppEngine SDK1.2.2にTaskQueue APIが含まれていなかったことで残念に思っている方も多かっただろう。MLではgoogleにかなり圧力がかけられていたようで、今朝、ようやくリリースの見通しについて口を割ってくれた。それによると、数週間以内にリリースされるだろうとのこと。やったー!\(^o^)/

TaskQueue API for Java ETA

Marcel,

ASAP means that it is one of our top priorities. Rather than give you a specific date and disappoint you if we don't meet it, we're working hard to release it as soon as we feel it has reached a quality level people can be happy with it. We typically provide new SDK releases on the order of weeks, so you can do some extrapolation from there.


火曜日, 7月 14, 2009

【Reflex iText】 Google Spread SheetからPDF生成するBookmarklet このエントリーを含むはてなブックマーク


 マイクロソフトがネット版オフィスを発売するそうだ。ネット化により、軽量化され、どこからでもアクセスできるという利便性もあるが、それ以上に、他のサービスやアプリと協調できるメリットの方が大きいと私は思う。例えば、Reflex iTextは、ネット上の様々なデータを元にPDF化できるが、GoogleのSpread Sheetと連携することもできる。ネット上でデータを作成してPDF帳票を出力する、といったBIツールのようなことが個々のサービスをマッシュアップさせることで可能になるのである。


* GoogleSpreadSheetCsvParser
http://csv2xml.latest.reflex-itext.appspot.com/

* GoogleSpreadSheet > Shre > Get the link to share
1. Allow anyone with the link to view (no sign-in required) にチェック
2. Also allow them to edit にチェック
3. Save&Close
4. スプレッドシート共有URLをコピー
5. http://csv2xml.latest.reflex-itext.appspot.com/csvparserservlet のGETパラメータとして
row=record
url=スプレッドシート共有URL
を指定


詳細については以下にまとめてあるのでぜひ参照いただきたい。

Google Spread Sheetから帳票作成

木曜日, 7月 09, 2009

【雑記】 Chrome OS戦略とIT世界の対立軸 このエントリーを含むはてなブックマーク

Chrome OS戦略


 昨日、Google Chrome OSの発表があった。Google Chrome OS のご紹介

Google Chrome OS の重要な要素は、スピードと使いやすさ、安全性です。
ユーザーが数秒でコンピューターを立ち上げてウェブにアクセスできるように、非常に高速で軽量の OS を設計中です。ユーザーインターフェイスはユーザーの妨げにならないよう最小限に抑えられ、ユーザーエクスペリエンスのほとんどはウェブ上で提供されます。


 Chrome OSは、「Webがすべて」という、これまでのGoogle戦略の延長線上にあるのは間違いない。これは、要は、クラウドとシンクライアントだけの世界をめざすものである。また、オープンソースの軽量オペレーティング・システムでLinuxを使うとしていることから、実際にはChrome(メッキ)のような薄いものを被せるだけのようだ。WindowsのようなファットなOSを開発するつもりは毛頭ないのだろう。というわけで、Chrome OSの上に高速なレンダリング機能をもつブラウザを載せてアプリを動かすことが中心となる。クライアントが入力したデータは、タイムスタンプのタグがつけられて、サーバに蓄えられる。そして、それがそのままクライアントに送り返される。あとは、それを受け取ったクライアントが自由にレンダリングする、といった感じのアプリケーションになっていくだろう。これはRESTfulなWebサービスを基本とするものである。RESTfulなWebサービスによる疎結合がスケーラビリティをもたらし、また、コミュニケーションにも革命をもたらすということだ。

携帯とのすみわけ

 ただ、Androidでも、そこで開発されているアプリのほとんどは、ブラウザを利用したWebアプリではない点には注意が必要だ。携帯とPCとは、異なるメディアである。そもそも画面サイズという根本的な違いがある。RIA的に進化したブラウザもアリかもしれないが、今の段階では、RIA的な濃さがPCと携帯では違うという認識は重要だろう。ネットブックには、Androidも搭載可能といわれてきたが、GoogleはAndroidに統一するのではなく、Chrome OSを登場させてきた。その大きな理由は、携帯とPCとでレンダリング方法の根本的な違いがあるのかもしれない。
 ただ、携帯アプリがブラウザではないにせよ、先に述べたRESTfulなWebサービスを解釈し、レンダリングするアプリという位置づけであることは変わらない。PCを含め、今後のブラウザはデータを処理できるビジネスロジックを備えたRIA化の方向で進化していくから、ブラウザとRIAとの違いの差はなくなっていくと思われる。なので、携帯がブラウザ中心でないことをそれほど気にするする必要はないと私は感じている。

IT世界の対立軸

 インターネットビジネスにおける「オセロの四隅」は、PC、Enterprise、Cloud、Mobileである。(と、私が勝手に妄想している)
 現在、これらを抑えているのが、Microsoft、IBM、Oracle、Apple、Google、Amazonなど。今一番元気なのがGoogleで、それに対抗しようとしているのがMicrosoft。ディフェンディングチャンピオンに立ち向かう挑戦者の構図は、一昔前であれば、IBM vs Microsoftであった。もちろん、今もIBMは健在なのだが、なにぶんGoogleからは遠すぎる。当然、Googleもエンタープライズにいきたいと思っているけれどもIBMやMicrosoftのように顧客をもっていない。これらは営業が汗水流して長年培ってきた財産であり、そんなに簡単には手に入らない。Googleがすぐにエンタープライズにいけない理由は、Webでしか商売をしない「Webがすべて」という戦略の裏返しでもある。なので、Googleの戦略からいくと、まずはコンシューマで、次にエンタープライズという順番なのだろう。コンシューマですぐ隣にいるのがMicrosoftなのだから当然ガチンコになるというわけだ。









 

水曜日, 7月 08, 2009

【おしらせ】 7/22(日食の日)次世代Web活用部会 セミナーでReflex iTextについて話します このエントリーを含むはてなブックマーク


2009年7月22日(水)日食の日、第1回次世代Web活用部会(XMLコンソーシアム) にて、Reflex iTextの概要を話します。

・『Reflex iTextサービスについて』
~クラウドならではの無限のスケーラビリティを簡単に体験

デモ中心であんまり濃い話はしないつもり。

受講参加するには事務局にメールすればいいじゃないかなあ。たぶん。

土曜日, 7月 04, 2009

【Amazon EC2】 GAEからEC2を管理するツール、CloudWatchなど このエントリーを含むはてなブックマーク




 Scalable PDFでは、EC2インスタンスの起動/終了やCloudWatchを使ったリソースのモニターなどを、Google App Engineからできるようになっている。わざわざGAEを使っているのは、利用料削減とレイテンシーの改善のためである。EC2インスタンスをずっと起動しているとチャージされてもったいないので、必要なときに必要な分だけのインスタンスを起動する、というオンデマンド的な利用がおすすめだ。インスタンス起動は数十秒で可能であり、また、Reflex iTextが揮発性で疎結合であるため、このような経済的なシステム利用(グリーンIT?)が可能になっている。レイテンシーについては、前記事にも書いたとおり、GAEを介す方が50%も改善できるという結果もある。

 さて今回は、このScalable PDFでも採用しているテクニックをいくつかご紹介したいと思う。まずはCloudWatchから。

GAEでCloudWatch

【Amazon CloudWatchとは】
 ・Amazon EC2のインスタンス(仮想サーバ)の状態を監視するサービス。
 ・インスタンスのCPU使用率やネットワークI/O、ディスクI/Oをチェックすることができる。
 ・$0.015 per Amazon EC2 instance hour
 ・インスタンスを落としても2週間は参照可能。
 ・AWS Management Consoleからは2009-07-02現在使用できないため、利用開始時にはEC2コマンドラインツールの「ec2-monitor-instances」を実行する必要がある。
 ・モニターしたインスタンスの情報はCloudWatchコマンドラインツールの「mon-get-stats」で参照できる。

【リンク】
Amazon CloudWatch on GAE/J (beta)デモ
ソース

【このツールの内容】
 ・CloudWatchの監視対象としたインスタンスの情報をGAE/J上から閲覧する。
 ・公開版は過去に使用したインスタンスの情報のみ参照できるようにしている
 ・Firefox3.0とIE6では動作確認済み。

【このツールの仕組み】
 基本的に、CloudWatchAPIの実行結果XMLをReflexを使って一旦パースし、それをJSONに変換してブラウザに渡して、JavaScriptを使って表示している。

 1.GAE/J→CloudWatch
  CloudWatchAPIのListMetricsを叩いて、参照可能な監視情報一覧を取得(json)。
 2.GAE/J
  参照する情報を指定。
 3.GAE/J→CloudWatch
  CloudWatchAPIのGetMetricStatisticsを叩いてインスタンスの情報を取得(json)。
 4.GAE/J
  取得した情報を表示。

【項目の説明】
  MeasureName
  参照する監視内容。
    CPU使用率、ネットワークI/O、ディスクI/O。
  DimensionName
    監視対象の分類。
    インスタンスIDごと、イメージIDごと、インスタンス種別ごと。
  DimensionValue
    監視対象の詳細。
    インスタンスID、イメージID、インスタンス種別。
  From
    参照開始日時。
  To
    参照終了日時。
  Period
    抽出する間隔(秒)。
    60なら1分ごとのデータ。
    3600なら60分ごとのデータ。

【使用例】
  1.Amazon CloudWatch on GAE/J (beta) > Menu > CloudWatch > Control
  2.-MeasureNameでCPUUtilizationを選択。
  3.-DimensionNameでInstanceIdを選択。
  4.-DimensionValueでi-b3ac89daを選択。
  5.Fromを2009-06-22T00:00にセット。
  6.Toを2009-06-24T00:00にセット。
  7.Periodを3600にセット。
  8.GetStatisticsMetricsボタンをクリック。
  9.結果テーブルのタイトル行をクリックするとソート可能(TinyTable JavaScript Table Sorterを利用)

  ※参照可能なデータが無い場合や表示データ上限数を超えている場合などは「Error!」と表示される。

【リンク】
  Amazon CloudWatch
  AWS Management Console(EC2)
  TinyTable JavaScript Table Sorter - 2.5KB

GAEでEC2インスタンスを管理する

 
 次に、EC2インスタンスの起動や終了をGAEに通知する方法について説明したいと思う。まずは、EC2インスタンス作成から。

【EC2インスタンスを作成】

 【初期操作】
  AWS Management Console等からAWSのデフォルトAMIを指定して新規インスタンスを作成。
    ↓
   インスタンスに接続してカスタマイズ。
    ↓
   AMIをS3に登録・保存
    S3に保存することで次回からも同じ設定のインスタンスを使用できる。

 【Amazon EC2のAMIをS3に登録する】
    インスタンスへsshして保存用イメージファイル(AMI)を作成→S3へアップロード→AMI登録

   * 保存イメージファイル(AMI)作成
     /mntディレクトリに移動して「ec2-bundle-vol」を実行。

    #cd /mnt/
    #ec2-bundle-vol -d /mnt --privatekey /path/to/pk-XXXX.pem --cert /path/to/cert-xxxx.pem --user YOUR-ACCOUNT-NUMBER --fstab /etc/fstab

    ** pk-XXXX.pem: Secret Access Key File
    ** cert-xxxx.pem: X.509 Certificate File
    ** YOUR-ACCOUNT-NUMBER: Account Number

    * S3へアップロード
    「ec2-upload-bundle」コマンドをバケット名を指定して実行。

    #ec2-upload-bundle --bucket /path/to/BUCKET_DIRECTORY_NAME --manifest image.manifest.xml --access-key XXXX --secret-key xxxx

    ** BUCKET_DIRECTORY_NAME: S3側のBucket Directory
    ** XXXX: Access Key ID
    ** xxxx: Secret Access Key

    * AMI登録
     インスタンスからログアウトして、「ec2-register」を実行。

     C:\>ec2-register /path/to/BUCKET_DIRECTORY_NAME/image.manifest.xml

【起動時にGAEに伝える仕組み】

 EC2インスタンスの起動終了時に、GAEのWebサービスプロバイダに対してステータスを通知することで、GAE上で利用可能なEC2インスタンスを管理することができるようになる。

  【仕組み】
   インスタンス起動時のnetworkが立ち上がる箇所で、自身のエンドポイント(ホスト名、インスタンスID)をec2nodeに登録するスクリプト(post_endpoint_to_gae.sh)を実行。
   インスタンス終了時にec2nodeから登録を削除するスクリプト(delete_endpoint_from_gae.sh)を実行。削除キーはインスタンスID。

  【/etc/rc.d/ini.d/networkの変更】
   インスタンス起動・終了時にエンドポイントの登録・削除を行うための設定。

    startコマンド最終行に以下を追加。(エンドポイント登録用)
     /bin/sleep 5
     /path/to/post_endpoint_to_gae.sh

     stopコマンド先頭行に以下を追加(エンドポイント削除用)
      /path/to/delete_endpoint_from_gae.sh
      /bin/sleep 20
      ※sleepをかけないとdelete_endpoint_from_gae.shの実行前にネットワークが切断されてしまい削除できない。

【エンドポイント登録・削除用スクリプト】

    登録(post_endpoint_to_gae.sh)
     エンドポイント情報を取得し、ec2nodeへPOSTする。
     「http://169.254.169.254/latest/」はAWS側で用意されているインスタンスID等の問い合わせ先。



     #!/usr/bin/php
      <?php
        $publicHostname = trim(file_get_contents("http://169.254.169.254/latest/meta-data/public-hostname"));
        $instanceId = trim(file_get_contents("http://169.254.169.254/latest/meta-data/instance-id"));
        $status = "IDLE";

        $data = array(
            "ec2record" =<
                  array(
                      "ec2node" => "{$instanceId}",
                      "endpoint_url" => "{$publicHostname}",
                      "status" => "{$status}"
                  )
            );

        $json = json_encode($data);

        $url = "http://www.example.com/ec2node";
        $options = array(
          "http" => array(
               "method" => "POST",
               "header" => "Content-type: text/plain",
               "content" => $json
            )
        );

        $context = stream_context_create($options);
        $fp = fopen($url, "r", false, $context);
        fpassthru($fp);
        fclose($fp);
        exit("\n");
       ?>


     削除(delete_endpoint_from_gae.sh)
      インスタンスID(削除キー)を取得し、ec2nodeへDELETEする。

      #!/usr/bin/php
      <?php
        $instanceId = trim(file_get_contents("http://169.254.169.254/latest/meta-data/instance-id"));
        $url = "http://www.example.com/ec2node?ec2node={$instanceId}";
        $options = array(
          "http" => array(
               "method" => "DELETE",
               "header" => "Content-type: text/plain"
            )
        );

        $context = stream_context_create($options);
        $fp = fopen($url, "r", false, $context);
        fpassthru($fp);
        fclose($fp);

        exit("\n");
      ?>


 
© 2006-2015 Virtual Technology
当サイトではGoogle Analyticsを使ってウェブサイトのトラフィック情報を収集しています。詳しくは、プライバシーポリシーを参照してください。