2013年4月24日水曜日

AWS whitepaper “Using AWS for Disaster Recovery” を読み解く

山本です。
AWS のホワイトペーパー Using AWS for Disaster Recovery を読み解いてみます。
(注:このホワイトペーパーは既存のクラウド上でないシステムの存在を前提にしているフシがある)

キーワード

一般用語

Recovery Time Objective, RTO
災害から回復までの所要時間とサービスレベルの目標。 objective(目標)であって mandate(指令)ではないところがポイント。 RTOを満足しない作戦をとることもありうるし、その場合でもRTOは目標として堅持すべき。
Recovery point objective, RPO
許容可能なデータロス期間の上限目標。 例えば、RPOとして4時間を設定したとすると、日次バックアップは適切な手段ではないことになる。

AWS用語

リージョン
国くらいの粒度の地域単位。AWSでは往々にしてリージョンをまたぐような操作に壁がある。
アベイラビリティ・ゾーン, AZ
リージョンの中に複数個存在。地理的に離れていて独立性が高い、ということになっている。 普段目にするのは論理AZであって物理AZではないため、アカウントをまたぐとゾーン名が一緒でも物理的には違うなどということがあるらしい。 (ソース)
CloudFormation
インスタンスの作成などをテンプレート化して自動化するための仕掛け。(今のところ、UVでこれを活用したことはない)

災害復旧シナリオ例

Backup and Restore

昔なら定期的にテープにフルダンプをとってオフサイト送り、となるところ。
S3がテープメディアの代わりに。EBSのスナップショットも有効。

バックアップで話は終わりじゃない!

リストアまでがシナリオです。
  • バックアップに使うツールは適切か?
  • データ保持(期間)のポリシーは適切か?
  • セキュリティ対策は?
  • 作ったバックアップ からの復元のテストを定期的に!

Pilot Light(種火)

最重要なコア要素をAWSで動かすようにする。
災害時にはこのコア要素に取り巻きを立て直すことで復旧とする。

準備

データはパイロットライト系統にレプリケーション。
OSのようなあまり更新頻度の高くないようなものは定期的にAMI(マシンイメージ)を更新しておく。

復旧

水平展開によるスケーリングの方がオススメ。インスタンスタイプを上位に変更してのスケールアップ手法もとれないことはない。
復旧が一段落したら、冗長性を速やかに取り戻すべし。

キーポイント

  • アプリケーションサーバのAMIを作って、そこからすぐ立ち上げられるように
  • 必要に応じてスケールアップ
  • 災害時はDNSをいじってAWS側を向くように
  • AMIベースでない要素の構成を忘れずに、理想的には自動化

Warm Standby

Pilot Light 方式の拡張。 インスタンスタイプその他を必要最小限に抑えた一式をAWSに用意する方式。
災害時にはこちらをスケールアップして負荷を捌く。

Multi-Site

平時はオンプレミスとAWS上のシステムを同時に動かしておく方式。

データレプリケーションの方式

  • 同期(Multi-AZな AWS RDB はこの構成になる)
  • 非同期(バックアップ用や参照系のユースケースならこれで充分なことが多い)
レプリケーション方式について理解しておくことが推奨されている。

災害復旧プランを向上するには

テスト!

いざ障害という時に復旧プロセスが可能な限りシンプルにできるように、復旧マニュアルが不足ないかを見る。

モニタリングとアラート(監視系)

バックアップは継続的に

アクセス制御

自動化

2013年4月6日土曜日

AndroidでのRESTクライアント(GETのみ)


高瀬です。

今回はAndroidアプリを作ってみる。目指すものは、Webサーバからデータを取得し、それを画面に表示する、という機能の実現とする。

アプリとWebサーバとの通信はRESTの形で行う。アプリ側でのRESTの実装にはSpring for Androidを利用。このSpring for Androidを試してみることが本題なのだが、RESTのサーバを準備したり、JUnitでAndroidアプリのテストなどもやってみる。

ソースコードはGitHubのandrestリポジトリを参照。


RESTについて


自分はRESTが何なのか分かっていないので、まず用語辞典を読んでみる。

RESTとは
http://e-words.jp/w/REST.html

やっぱり何だかよく分からないが、自分の認識を書くと以下のとおり。

  • URLが一つのリソースを表す。
  • リソースへの操作の種類をHTTPメソッド(GET=取得、POST=新規追加、PUT=更新、DELETE=削除)で表す。
  • レスポンスはJSON形式で送信する。

上記の用語辞典のページに書かれているとおり、RESTは別にHTTP通信に限ったものでもないし、レスポンスがJSONでなくてはいけないわけでもないが、今のところはこのくらいの内容で理解しておくことにする。

ここではサーバからのデータの取得のみを実装する。


サーバ側


サーバ側はPHPのYiiフレームワークで用意する。RESTでの実装を補助してくれるエクステンションがあるので、これを利用してみる。

RESTFullYii
http://www.yiiframework.com/extension/restfullyii/

このエクステンションでは、ERestControllerクラスの派生クラスとしてコントローラを作成することで、GETやPOSTに対応する処理を書くことができるようになっている。また、URLとリソースを結びつけるURLフォーマットも用意されている。

作成したのはPostControllerクラス。エクステンションのソースコードに含まれているREADME.mdの説明にしたがって、doRestList()、doRestView()メソッドを記述した。

doRestList()は、ID指定なしでGETメソッドによりアクセスされた場合に呼び出される。ここでは8件の記事のデータを返すようにした。

  $data = array(
    array(
      "post_id" => 1,
      "time" => "3月3日 12時42分",
      "title" => "Evernoteが不正アクセス被害"
    ),
    array(
      "post_id" => 2,
      "time" => "3月4日 18時3分",
      "title" => "警戒区域で初 ストビュー撮影"
    ),
    :
  );

  $this->renderJson($data);

記事の内容は2013年3月4日のYahoo!JAPANのニュースから拝借。

doRestView()は、ID指定ありでGETメソッドによりアクセスされた場合に呼び出される。ここでは、8件の記事のうち、IDが指している番号の記事の内容を返すようにした。上記の一覧取得では各記事の内容をpost_id、time、titleとしているが、こちらではこれらに加えてcontentを返すようにしている。例えば、1が指定された場合は以下の内容を返す。

  array(
    "post_id" => 1,
    "time" => "3月3日 12時42分",
    "title" => "Evernoteが不正アクセス被害、全ユーザーのパスワードをリセット",
    "content" => "米Evernoteは2日、同社のシステムに何者かが不正アクセスしたことを公表した。 ..."
  ),

アプリ側ではこれらのデータをJSON形式で受け取り、解析する。

なお、この後登場するSpring for AndroidのRestTemplateが、HTTPレスポンスのタイプがapplication/jsonであることを期待しているので、HTTPヘッダのContent-Typeにapplication/jsonをセットしている。


アプリで通信をする際の準備


通信を行うアプリを開発する場合、以下の2点に注意する必要がある。

  1. パーミッションandroid.permission.INTERNETを許可する。
  2. 通信処理はUIスレッド(メインスレッド)で実行してはならない。

参考:
Androidアプリでインターネット接続する為に必要な設定(android.permission.INTERNET)
http://feedyomi.blog32.fc2.com/blog-entry-181.html

android.os.NetworkOnMainThreadExceptionエラーへの対応方法
http://garnote.com/2012/10/android-os-networkonmainthreadexception.html

パーミッションの許可を指定しないと、実行時に以下の例外が発生した。

I/O error: socket failed: EACCES (Permission denied);
nested exception is java.net.SocketException: socket failed: EACCES (Permission denied)

UIスレッドで通信をしようとすると、上記参考ページのとおりNetworkOnMainThreadExceptionが発生する。

パーミッションについては、参考ページのとおりAndroidManifest.xmlでパーミッションの指定をすればよい。

通信処理を行うスレッドについては、AsyncTaskクラスなどを使用してワーカースレッドを作成する必要がある。

参考:
AsyncTask を利用した非同期処理
http://android.keicode.com/basics/async-asynctask.php

AsyncTaskLoaderを利用した非同期処理を行う
http://techbooster.org/android/application/13492/

時代は AsyncTask より AsyncTaskLoader
http://archive.guma.jp/2011/11/-asynctask-asynctaskloader.html

AsyncTaskLoaderの方が便利だが、対象OSバージョンがAndroid 3.0以降であること、コールバックメソッドが必ずActivityになければならないことから、少々使いにくい印象がある。そこで、AsyncTaskを使用して、非同期処理完了時に呼び出されるハンドラを指定できるようにしてみた。

ハンドラ用にOnFetchListenerクラスを定義。インスタンスを生成したら、ハンドラとなるメソッドを定義する。そして、AsyncTaskクラスのインスタンス生成時にハンドラを渡しておく。

これなら、一つのActivityで複数の非同期処理を実行したい場合に、ハンドラを個別に指定することができる。

非同期処理の準備ができたら、いよいよ通信処理を実装していく。


Spring for Android


ここからが本題。AndroidでのHTTP通信はorg.apache.http.client.HttpClientを使用してもよいが、RESTを簡単に実装できるという、Spring for Androidを使ってみることにする。

Spring for Android | SpringSource.org
http://www.springsource.org/spring-android

Jackson JSON Processor
http://jackson.codehaus.org/

Spring for Androidでは、HTTPメソッドGET、POST、PUT、DELETEにそれぞれ対応する、getForObject()、postForObject()、put()、delete()というメソッドが用意されている。使い方は、delete()ならURLのみ、それ以外はURLとパラメータを指定して呼び出すだけである。

マニュアルにしたがって、まずはインストールから。

Spring for Androidのライブラリ(spring-android-rest-template-{version}.jarとspring-android-core-{version}.jar)をダウンロードしたら、以下の手順(Spring for Android Reference Documentation から抜粋)でプロジェクトに組み込む。

  1. Refresh the project in Eclipse so the libs/ folder and jars display in the Package Explorer.
  2. Right-Click (Command-Click) the first jar.
  3. Select the BuildPath submenu.
  4. Select Add to Build Path from the context menu.
  5. Repeat these steps for each jar.

libsフォルダに各jarファイルをコピーしたら、Eclipse上で右クリックし、「Add to Build Path」でビルドパスに追加。これでRestTemplateが使用できるようになる。

ついでに、JSONの解析にJacksonというライブラリを使用するので、同様にプロジェクトに組み込んでおく。ダウンロードは上記の参照ページから。


一覧表示


記事の一覧を取得する。記事データの構造は前述のとおり、post_id、time、title、contentの4つの要素で構成されている。まずはこの構造に合わせたクラスを用意しておく。一覧取得ではcontentは使用しないが、単一記事と共用で使用できるクラスとするために含めている。


public class Post {
  private Long post_id;
  private String time;
  private String title;
  private String content;

  public String getPost_id() {
    return this.post_id.toString();
  }

  public void setPost_id(Long post_id) {
    this.post_id = post_id;
  }

  public String getTime() {
    return this.time;
  }

  public void setTime(String time) {
    this.time = time;
  }

  public String getTitle() {
    return this.title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getContent() {
    return this.content;
  }

  public void setContent(String content) {
    this.content = content;
  }
}


そして、AsyncTaskを継承したクラスPostListを作成。以下が記事一覧を取得する部分。

  private ArrayList<HashMap<String, String>> data;
    :

  protected Long doInBackground(String... params) {
    // TODO Auto-generated method stub
    RestTemplate rest;
    String url = this.context.getString(R.string.post_url);

    // Create a new RestTemplate instance
    rest = new RestTemplate();

    try {
      Log.i("App", "start");
      // Add the String message converter
      rest.getMessageConverters().add(new MappingJackson2HttpMessageConverter());

      Post posts[] = rest.getForObject(url,  Post[].class);

      this.data.removeAll(null);
      for (int i=0; i<posts.length; i++) {
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("post_id", posts[i].getPost_id());
        map.put("time", posts[i].getTime());
        map.put("title", posts[i].getTitle());
        this.data.add(map);
      }
      Log.i("App", "end");
    }
    catch (Exception e) {
      Log.e("App", e.getMessage());
    }
    return null;
  }

getForObject()でサーバへ記事の一覧を要求すると、結果がPostクラスの配列で得られる。これをListViewにセットできる形にするため、メンバー変数dataに保存した。


アクセスするURLはres/values/params.xmlファイルに定義している。


<resources>
  <string name="post_url">http://hostname/path/index.php/api/post/</string>
</resources>

api/post/ にGETでアクセスすることで、サーバ側のPostController、doRestList()が呼び出され、記事の一覧取得が行われる。


単一記事表示


単一記事を取得する場合も、一覧取得とほぼ同様の処理となる。ソースはPostDetailクラスを参照。

getForObject()の結果は、取得される記事は1件だけなので、Postクラスのインスタンスが1つだけ返される。

またアクセスするURLは api/post/1 などのように記事のIDを付加している。これによりサーバ側ではdoRestView()メソッドが呼び出され、該当の記事の取得が行われる。


JUnit


サーバからのデータ取得と画面表示ができたら、テストコードも書いてみる。Androidでのテストについては以下のページを参照。

Androidアプリ開発でテストを始めるための基礎知識
http://www.atmarkit.co.jp/fsmart/articles/androidtest01/01.html

Testing Fundamentals
http://developer.android.com/tools/testing/testing_android.html

さらに、非同期処理の部分については以下も参照。

AsyncTaskをJUnitでテストする方法
http://wavetalker.blog134.fc2.com/blog-entry-68.html

テストはプロジェクトを「Android Test Project」で作成する。ActivityのテストをするにはActivityInstrumentationTestCase2の派生クラスを作成する。

上記@ITの記事では見つけられなかったが、ActivityInstrumentationTestCase2の派生クラスを作る際、引数なしのデフォルトコンストラクタがないとテストが実行されなかった。デフォルトコンストラクタは自分で記述する必要がある。

AndrestcliActivityTestクラスを作成。以下のテストを行う。


  • 記事の一覧が取得できること。
  • 取得した記事の件数が一致すること。
  • リストビューのアイテム数が記事の数と一致すること。



  public void testGetPostList() throws Exception {
    // 非同期処理完了時のハンドラーを定義
    OnFetchListener handlerForTest = new OnFetchListener() {
      @Override
      public void onFetch(Context context, AsyncBase asyncTask) {
        // unlock testcase's thread.
        signal.countDown();
      }
    };
    // create subclass of test target asynctask.
    PostList posts = new PostList(activity, handlerForTest);
    // execute asynctask.
    // AndrestcliActivityのOnCreate()で実行されているものとは別にもう一度実行する
    posts.execute("");
    // wait for asynctask.
    signal.await(30, TimeUnit.SECONDS);
    PostList result = activity.getPostList();
    assertNotNull("記事の一覧が取得できること。", result);
    assertEquals("取得した記事の件数が一致すること。", 8, result.getData().size());
    ListView listView1 = (ListView)activity.findViewById(R.id.listView1);
    assertNotNull("リストビューが取得できること。", listView1);
    assertEquals("リストビューのアイテム数が記事の数と一致すること。", 8, listView1.getCount());
  }

サーバのモックをしていないので、サーバが動作していないとテストに失敗してしまうが、とりあえずこれでテストの実行を確認することができる。実施しているのは、記事の一覧が取得できることと、それをListViewで表示できていること、としている。

実は、テスト対象のAndrestcliActivityはOnCreate()で記事一覧の取得を行っており、テストコードでも記事一覧の取得を行っているので、一覧取得が2回動いてしまっている。テストをするには都合の悪い作りだっただろうか。


おわり


通信処理をUIスレッドで行ってはいけない、という制限がなんとも面倒だ。画面に記事を表示するというだけの機能しかないのに、ずいぶんと手間がかかった印象がある。

しかし、もっと多機能なアプリを作るにはどのみち非同期の処理は必要になってくるだろうし、一度非同期の処理を作ってしまえば後は使いまわすだけなので、実質的には取っ掛かりが少々面倒、というところだろうか。

Spring for Androidはそれほど苦労もなく使えたので、便利なライブラリだと思う。GET以外のメソッドについては別の機会に試してみる。

アプリが通信機能を持つことは多いだろう、覚えておいて損はないはず。