ラベル elb の投稿を表示しています。 すべての投稿を表示
ラベル elb の投稿を表示しています。 すべての投稿を表示

2014年3月7日金曜日

ELBってなんじゃ?(アクセスログがやってきた)



ELBにアクセスログ機能が搭載されたので、試してみました。

まず、AWSコンソールのELBのの画面をみてみますが、ここで、古いバージョンのコンソールだとアクセスログの設定ができないので、ヘッダ上部のお知らせアイコンから新しいバージョンに切り替えます。




任意のELBを選択して、下部ペインのDescriptionタブの最下段に、「Access Logs」という項目が追加されているのが分かります。





最初はDisabledになっています。Editのリンクを押下すると、以下のようなダイアログが表示されます。



ここで、「Enable Access Logs」にチェックを入れ、
  • Interval:ログ出力間隔(現状5分か60分)
  • S3 Location:ログ出力先のS3ロケーション(Create the location for meをチェックすると自動でつくられる)
を入力し、「Save」をクリックします。



しばらくすると、指定したS3のロケーションに、ログが出力されます。




中身をみてみると、以下のようになっています。
2014-03-06T23:50:37.819662Z manage 54.248.248.218:60123 10.156.231.23:80 0.00008 0.002195 0.000074 403 403 0 5039 "GET https://mng.cloudpack.jp:443/ HTTP/1.1"
2014-03-06T23:54:42.332009Z memory 219.117.233.241:43172 10.156.231.23:80 0.000068 2.234742 0.000065 200 200 0 129832 "GET https://memory.cloudpack.jp:443/cloudpack/admin/account HTTP/1.1"
2014-03-06T23:54:45.898722Z memory 219.117.233.241:43172 10.156.231.23:80 0.000069 0.142097 0.000058 200 200 0 766319 "GET https://memory.cloudpack.jp:443/cloudpack/admin HTTP/1.1"
2014-03-06T23:55:05.069736Z memory 219.117.233.241:43172 10.156.231.23:80 0.000082 0.767399 0.000083 200 200 0 129832 "GET https://memory.cloudpack.jp:443/cloudpack/admin/account HTTP/1.1"
2014-03-06T23:55:09.530026Z memory 219.117.233.241:43172 10.156.231.23:80 0.000078 0.701585 0.000067 200 200 0 133234 "GET https://memory.cloudpack.jp:443/cloudpack/admin/account/2 HTTP/1.1"
2014-03-06T23:55:11.639899Z memory 219.117.233.241:43172 10.156.231.23:80 0.000088 0.70764 0.000099 200 200 0 133341 "GET https://memory.cloudpack.jp:443/cloudpack/admin/account/3 HTTP/1.1"
2014-03-06T23:55:13.555633Z memory 219.117.233.241:43172 10.156.231.23:80 0.000076 0.698172 0.000095 200 200 0 131071 "GET https://memory.cloudpack.jp:443/cloudpack/admin/account/4 HTTP/1.1"
2014-03-06T23:55:15.414292Z memory 219.117.233.241:43172 10.156.231.23:80 0.000075 0.885828 0.00009 200 200 0 129831 "GET https://memory.cloudpack.jp:443/cloudpack/admin/account/1 HTTP/1.1"
2014-03-06T23:55:20.774540Z memory 219.117.233.241:43172 10.156.231.23:80 0.000075 0.68001 0.000083 200 200 0 23283 "GET https://memory.cloudpack.jp:443/cloudpack/admin/account/index?search=miura HTTP/1.1"
2014-03-06T23:55:25.478841Z memory 219.117.233.241:43172 10.156.231.23:80 0.000076 0.817105 0.000061 200 200 0 233574 "GET https://memory.cloudpack.jp:443/cloudpack/admin/account/doc/miura@cloudpack.jp HTTP/1.1"
2014-03-06T23:55:31.467146Z memory 219.117.233.241:43172 10.156.231.23:80 0.000072 0.142359 0.00011 200 200 0 766319 "GET https://memory.cloudpack.jp:443/cloudpack/admin HTTP/1.1"
2014-03-06T23:55:35.995594Z memory 219.117.233.241:43172 10.156.231.23:80 0.000077 0.768791 0.000101 200 200 0 129832 "GET https://memory.cloudpack.jp:443/cloudpack/admin/account HTTP/1.1"
2014-03-06T23:55:37.699927Z memory 54.248.248.218:36526 10.156.231.23:80 0.000064 0.001333 0.000062 403 403 0 5039 "GET https://memory.cloudpack.jp:443/ HTTP/1.1"


ログのフォーマットは、以下のような構成だそうです。
フィールド名説明
タイムスタンプクライアントにレスポンスを返したUTC時刻2014-02-15T23:39:43.945958Z
ELB名ELBの名前my-test-loadbalancer
クライアントポートリクエストしたクライアントのIP192.168.131.39:2817
バックエンドポートこのリクエストを処理したインスタンスのIP10.0.0.0.1
リクエスト処理時間ELBがリクエストをうけとってからインスタンスに渡すまでの経過時間(秒)0.000073
バックエンド処理時間インスタンスに渡してからレスポンスヘッダを返し始めるまでの経過時間(秒)0.001048
レスポンス処理時間ELBがインスタンスからレスポンスヘッダを受け取りクライアントにレスポンスヘッダを返し初めてからの経過時間(秒)
処理時間にはELBのキューイング時間とインスタンスへの接続要求時間も含まれています。
0.000057
ELBステータスコードELBからクライアントへ返されるHTTPステータスコード (HTTPのみ)200
バックエンドステータスコードインスタンスからELBへ返されるHTTPステータスコード (HTTPのみ)200
受信バイト数

クライアントからのリクエストのサイズ(バイト)
HTTPリクエストではボディのみでヘッダのサイズは含まれません
TCPリクエストではヘッダのサイズも含まれます
0
送信バイト数クライアントへのレスポンスのサイズ(バイト)
HTTPリクエストではボディのみでヘッダのサイズは含まれません
TCPリクエストではヘッダのサイズも含まれます
29
"リクエスト"
           
ダブルクォーテーションで囲まれたクライアントからのリクエスト行。フォーマットは
HTTPメソッド + プロトコル://ホスト:ポート + パス + HTTPバージョン

TPCリクエストでは"- - -"のようになります。
"GET http://www.example.com: 80/HTTP/1.1"


ELBは自動でスケールするので、漏れ無くロギングできるかどうかは怪しいところですが、簡単にアクセスログをとるには便利そうです。

以上です。

2013年5月25日土曜日

spiderってなんじゃ?(spider自身の冗長化)

いままではSpiderがひとつに対して、データノードが複数ある構成をとってきましたが、実戦ではSpiderが単一障害点になるため好ましくありません。ここではSpider自身の冗長化を行なってみます。

いままでのSpider x 1 + mroonga x 2 の構成に対してデータを入れ込むためのappノードを用意します。

SpoderのSPOF





mroonga

 CREATE TABLE doc (
  id INT PRIMARY KEY AUTO_INCREMENT,
  created_at datetime,
  content text,
  FULLTEXT INDEX (content)
 ) ENGINE = mroonga; 


spider

 CREATE TABLE doc (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  created_at datetime,
  content text,
  FULLTEXT INDEX (content)
 ) ENGINE = spider CHARSET utf8
 CONNECTION ' table "doc", user "memorycraft_user", password "memorycraft_pass" '
 PARTITION BY KEY() (
  PARTITION db1 comment 'host "10.0.1.136", port "3306"',
  PARTITION db2 comment 'host "10.0.1.137", port "3306"'
 ); 

app

# yum install -y php php-mbstring php-pdo php-mysql
$ vim links.php
<?php

//ランダム文字列
function getRandomString($size = 8){
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    mt_srand();
    $char = "";
    for($i = 0; $i < $size; $i++) {
        $char .= $chars{mt_rand(0, strlen($chars) - 1)};
    }
    return $char;
}

//XMLテンプレート
$tmpl =<<< END
<?xml version="1.0" encoding="UTF-8" ?>
<data>
  <info>
    <datetime>%s</datetime>
    <address>%s</address>
    <content>%s</content>
  </info>
</data>
END;

//XMLテンプレートに流しこむcontent要素の内容を読み込む
$contents = array();
for($i=0;$i<10;$i++){
  $contents[] = file_get_contents(dirname(__FILE__)."/dummy".$i.".txt");
}

//spiderホストは引数から取得する
$host = $argv[1];
//タイムゾーン
date_default_timezone_set('Asia/Tokyo');

//無限ループ
while(true){
  //XMLテンプレートに各要素を割り当てる
  $xml = sprintf($tmpl, date('Y/m/d H:i:s'), getRandomString(), $contents[rand(0,9)]);
  echo ".";
  //DB接続
  $pdo = new PDO(
    "mysql:dbname=memorycraft;host=$host;",
    "memorycraft_user",
    "memorycraft_pass",
    array(
     PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET `utf8`"
   ));
  $pdo->query("SET NAMES utf8;");
  //投入
  $st = $pdo->prepare("INSERT INTO doc VALUES(NULL,NOW(),?);");
  $rslt = $st->execute(array($xml));
}
?>
実行します
$ php links.php 10.0.1.20
.......................................


Spiderノードで投入されていることを確認します。

mysql> select id, created_at from doc limit 10;
+------+---------------------+
| id   | created_at          |
+------+---------------------+
|    1 | 2013-05-25 11:27:47 | |    2 | 2013-05-25 11:27:47 | |    3 | 2013-05-25 11:27:47 | |    4 | 2013-05-25 11:27:47 | |    5 | 2013-05-25 11:27:47 | |    6 | 2013-05-25 11:27:48 | |    7 | 2013-05-25 11:27:48 | |    8 | 2013-05-25 11:27:48 | |    9 | 2013-05-25 11:27:48 | |   10 | 2013-05-25 11:27:48 | +------+---------------------+ 10 rows in set (0.06 sec)


これで、spider x 1 + mroonga x 2 + app x 1になりました。


Spiderの冗長化


次は、Spider x 2 + mroonga x 2 + internal ELB + app x 4 にしてみます。




Spiderノードをもう1台(spider2)追加します。

テーブルはspider1と同じ内容にします。
/etc/my.cnfでauto_increment_increment = 100でoffsetを1,2でずらします。
これについては、別の記事で触れています。

mysqlってなんじゃ?(マルチマスタでauto_increment)
http://memocra.blogspot.jp/2013/05/mysqlautoincrement.html

また、spiderを複数にした場合、appからの接続は1つにまとめます。
Proxy系のソフトウェアを入れるのも1つの方法ですが、ここでは内部ELBを利用してみます。

ヘルスチェックは3306ではなく別のポート経由でMySQL監視プロセスに接続スべきですが、ここではとりいそぎhttpdを立ちあげ80番に対してヘルスチェックします。


それではappで投入プログラムを実行します。
対象のホストは内部ELBのDNSNameになります。

$ php links.php internal-spider-2040122974.ap-northeast-1.elb.amazonaws.com
........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................


spider1でデータを見てみます。

mysql> select id, created_at from doc limit 20;
+------+---------------------+
| id   | created_at          |
+------+---------------------+
|    1 | 2013-05-25 11:27:47 |
|  101 | 2013-05-25 11:27:47 |
|  201 | 2013-05-25 11:27:47 |
|  202 | 2013-05-25 11:27:47 |
|  601 | 2013-05-25 11:27:47 |
|  701 | 2013-05-25 11:27:48 |
| 1101 | 2013-05-25 11:27:48 |
| 1201 | 2013-05-25 11:27:48 |
| 1601 | 2013-05-25 11:27:48 |
| 1701 | 2013-05-25 11:27:48 |
| 2101 | 2013-05-25 11:27:49 |
| 2201 | 2013-05-25 11:27:49 |
| 2301 | 2013-05-25 11:27:49 |
| 2601 | 2013-05-25 11:27:49 |
| 2701 | 2013-05-25 11:27:49 |
| 2802 | 2013-05-25 11:27:49 |
| 3101 | 2013-05-25 11:27:49 |
| 3201 | 2013-05-25 11:27:49 |
| 3301 | 2013-05-25 11:27:49 |
| 3601 | 2013-05-25 11:27:50 |
+------+---------------------+

mysql> select count(*) from doc;
+----------+
| count(*) |
+----------+
|   111433 |
+----------+


順調に入っていってるみたいです。
あとはSpiderノードを必要に応じて追加していき、my.cnfでoffsetをずらして起動するだけです。
offsetずらしの処理はcloud-initなどで行うと、AutoScalingすることもできそうです。

今回は以上です。




2013年2月7日木曜日

Node.jsってなんじゃ?(Socket.IOとELBのまとめ)

過去の記事でnode.jsの話題をいくつか取り上げて来ましたが、node.jsではAmazonのELBと併用した時の問題があり、その注意点をまとめました。

Socket.IO


Socket.IOはnode.jsでwebsocketを使用するときのデファクトといっていいライブラリです。
他のSocket.IOが人気になったのは、以下のような利点のためです。

  • websocketをサポートしていないブラウザでは、自動的にxhrなどのポーリング使い通信できる
  • 接続に失敗しても再接続などを自動的に行う


ここではSocket.IOを使用する前提で、ELBを経由して複数のnodeサーバーをホストする場合についてまとめてみました。

インストールに関しては以下に書いてありますので割愛します。
WebSocketってなんじゃ?(Node編2 Socket.IOでプッシュ通信)

nodeサーバーには以下のようにファイルを配置します。上記の記事などで使用したチャットアプリです。
publicをhttpdのドキュメントルートにしておきます。
app
├── node
│   └── server.js
└── public
    ├── assets
    │   └── js
    │       └── client.js
    ├── health.txt
    └── index.html

public/health.txtはELB用のヘルスチェックファイルで、中身はありません。
その他の各ファイルの内容は以下の通りです。

server.js
index.html
client.js



ポートは3000番を利用します。
ここで、httpdを起動しておきます。
また、
node server.js

などで、nodeを起動しておきます。


AWS


単体構成



まず最初の例として、AWSは例として以下のような構成だとします。
nodeサーバーのEC2インスタンスはhtmlのホストとwebsocketの両方を行うので、80と3000のポートをセキュリティグループで開放しておきます。

画面を開いてみます。

普通に成功します。
通信をみてみると、websocketで通信されていることがわかります。


ELB


次に、nodeサーバーのインスタンスをもう一台追加し、新規作成したELB配下に2つのインスタンスをおきます。



ELBのリスナーを以下のように80番と3000番を設定します。


そしてELBのエンドポイントのURLをブラウザで開きます。


すると、xhr-pollingになり、接続と切断が繰り返されます。
また、画面を2つ開いてメッセージを送信しても相手に届かない場合があります。

注意点1:ELBはhttpではなくtcpでポートを設定


websocket通信ではクライアントとサーバーの間のハンドシェイクにUpgradeヘッダを送信します。
しかし、ELBはhttpリスナーの場合Upgradeヘッダを削ってしまうようです。
RFC6455 — The WebSocket Protocol 日本語訳
ELBでHTTPリスナーだとWebSocketは使えない

そのため、Socket.IOはwebsocketが使えないと判断し、次善策の一つとして通常のhttp通信でxhr-pollingで接続することになります。これはajaxの通信と同じです。
そこで、ELBでは上記リンクの通り、websocket用のリスナーはhttpの3000番ではなくtcpの3000番を設定する必要があります。



注意点2:Redisでセッション共有を行う


また、接続や切断が繰り返されるのはxhrのポーリングのたびにハンドシェイクが確立したサーバーとは別のサーバーに接続にいくからのようです。これはELBのリスナー設定をtcpにした場合も同様で、一度websocket通信が確立したかのように見えても、次に接続した時に別のサーバーにつながると、接続が切れたもしくはwebsocketに失敗したと判断し xhr-pollingなど他の方法で通信しようとするようです。

また、そもそもの問題として2つのnodeサーバーの間で接続情報(セッション)が共有されないので、ELBを通してnode1とnode2にそれぞれ接続したクライアント間ではメッセージのやりとりができません。

そこで、nodeサーバーのバックエンドとして、redisを利用してセッション共有を行う方法が有効です。
socket.ioはセッションを保持する方式としてローカルメモリを使用するMemoryStoreを使用しますが、オプションでRedisにを使用RedisStoreが選べます
Configuring Socket.IO

これを使います。
redisサーバーを追加し、redisを起動しておきます。
インスタンスにはセキュリティグループなどでredisで使用するポートを開放しておきます。



そして、以下のようにserver.jsを変更します。

server.js

これで、redisサーバーでnode1,node2のセッションを共有できます。


何度か試しましたが、うまく通信できているようです。


まとめ


注意点としては、ELBをつかった場合はtcpでリッスンすることと、ELBにかぎらずnodeサーバーをスケールする場合は、redisサーバーでセッション共有する必要があります。

以上です。