Hatena::ブログ(Diary)

niwaka diary このページをアンテナに追加 RSSフィード

2011-09-06

ひとつ前に打ったコマンドの一部を置換して実行

$ cat test.txt 
hogehoge
fugafuga
$ 
$ grep hoge test.txt
hogehoge
$ 
$ ^hoge^fuga
grep fuga test.txt
fugafuga

便利だなぁと思いつつまだ使いこなせてなかったので、今日練習した。

2011-08-23

ソケットインターフェースと3-way handshake

今更ですが基本の基本

新人エンジニアが一番最初に学ぶべき基礎中の基礎、押さえていて当然なTCP/IPですが、プログラムからの視点だと若干理解が薄い部分があったので、、実際コード書いて流れの勉強をしてみました。


参考URL

今回は下記サイトを参考に、と言うよりコピペさせて頂いて、Cで書かれたTCPサーバ/クライアントプログラムを用意しました。

Geekなぺーじ : TCPを使う


TCPサーバのソース(server.c)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int
main()
{
int sock0;
struct sockaddr_in addr;
struct sockaddr_in client;
int len;
int sock;

/* socket関数でソケット作成 */
sock0 = socket(AF_INET, SOCK_STREAM, 0);

/* sockaddr_in構造体に、アドレス/ポート情報を設定 */
addr.sin_family = AF_INET;
addr.sin_port = htons(12345);
addr.sin_addr.s_addr = INADDR_ANY;

/* 作成したソケットに、sockaddr_in構造体の情報を紐付け */
bind(sock0, (struct sockaddr *)&addr, sizeof(addr));

/* ソケットをListenしてコネクションを受け付ける状態に */
listen(sock0, 5);

/* クライアントからのアドレス情報をもとにacceptして新しいソケットを取得する */
len = sizeof(client);
sock = accept(sock0, (struct sockaddr *)&client, &len);

/* ソケットに文字列を書き込む */
write(sock, "HELLO", 5);

/* ソケットを閉じる */
close(sock);

/* ソケットを閉じる */
close(sock0);

return 0;
}

TCPクライアントのソース(client.c)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int
main()
{
struct sockaddr_in server;
int sock;
char buf[32];
int n;

/* ソケットを作成する */
sock = socket(AF_INET, SOCK_STREAM, 0);

/* 接続しに行くサーバのアドレス、ポート情報を設定 */
server.sin_family = AF_INET;
server.sin_port = htons(12345);
server.sin_addr.s_addr = inet_addr("127.0.0.1");

/* 用意したソケットを使用して、connectして接続しに行く */
connect(sock, (struct sockaddr *)&server, sizeof(server));

/* 接続を確立したソケットから指定バイト読み込む */
memset(buf, 0, sizeof(buf));
n = read(sock, buf, sizeof(buf));

/* 端末に出力 */
printf("%d, %s\n", n, buf);

/* ソケットを閉じる */
close(sock);

return 0;
}

とりあえず繋いでみる

サーバプログラムを起動させてみます。

$ ./server

$ netstat -an
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address               Foreign Address             State     
tcp        0      0 0.0.0.0:12345               0.0.0.0:*                   LISTEN 

IP anyでListenしてることがnetstatの結果から分かります。

クライアントプログラムから接続させてみます。

$ ./client
5, HELLO

読み込まれたバイト数と文字列が返ってきました。正常終了です。


パケットキャプチャ

パケットキャプチャをとってみました。想定通りの動きです。

〜〜 ここから3-way handshake 〜〜
01:45:58.863579 IP 127.0.0.1.38579 > 127.0.0.1.12345: Flags [S], seq 2441469373, win 32792, options [mss 16396,sackOK,TS val 743698124 ecr 0,nop,wscale 6], length 0
01:45:58.863600 IP 127.0.0.1.12345 > 127.0.0.1.38579: Flags [S.], seq 2446647646, ack 2441469374, win 32768, options [mss 16396,sackOK,TS val 743698124 ecr 743698124,nop,wscale 6], length 0
01:45:58.863618 IP 127.0.0.1.38579 > 127.0.0.1.12345: Flags [.], ack 1, win 513, options [nop,nop,TS val 743698124 ecr 743698124], length 0
〜〜 ここまで3-way handshake 〜〜

〜〜 データをPush 〜〜
01:45:58.863770 IP 127.0.0.1.12345 > 127.0.0.1.38579: Flags [P.], seq 1:6, ack 1, win 512, options [nop,nop,TS val 743698124 ecr 743698124], length 5

〜〜 データ送信が終了したのでFin Ack 〜〜
01:45:58.863786 IP 127.0.0.1.12345 > 127.0.0.1.38579: Flags [F.], seq 6, ack 1, win 512, options [nop,nop,TS val 743698124 ecr 743698124], length 0
01:45:58.864428 IP 127.0.0.1.38579 > 127.0.0.1.12345: Flags [.], ack 6, win 513, options [nop,nop,TS val 743698124 ecr 743698124], length 0
01:45:58.864533 IP 127.0.0.1.38579 > 127.0.0.1.12345: Flags [F.], seq 1, ack 7, win 513, options [nop,nop,TS val 743698124 ecr 743698124], length 0
01:45:58.864543 IP 127.0.0.1.12345 > 127.0.0.1.38579: Flags [.], ack 2, win 512, options [nop,nop,TS val 743698124 ecr 743698124], length 0

SYN_SENTの状態にしてみる

ここで寄り道。Synを送り、そのまま相手からのAckを待っている送りっぱなしの状態にしてみます。SYN_SENT状態に遷移することを見たいのですね、一応。

connect後にsleepするように、ソースを書き換えます。

client.c

〜〜〜省略〜〜〜
server.sin_addr.s_addr = inet_addr("111.111.111.111");

connect(sock, (struct sockaddr *)&server, sizeof(server));
sleep(600);
〜〜〜省略〜〜〜

IP層で接続できない相手に対してconnectし、600秒間sleepするようにしてみました。


クライアントプログラム実行後のパケットキャプチャ
01:52:10.787558 IP 10.146.1.28.59648 > 111.111.111.111.12345: Flags [S], seq 3992126992, win 5840, options [mss 1460,sackOK,TS val 743791105 ecr 0,nop,wscale 6], length 0
01:52:13.789851 IP 10.146.1.28.59648 > 111.111.111.111.12345: Flags [S], seq 3992126992, win 5840, options [mss 1460,sackOK,TS val 743791856 ecr 0,nop,wscale 6], length 0
01:52:19.805851 IP 10.146.1.28.59648 > 111.111.111.111.12345: Flags [S], seq 3992126992, win 5840, options [mss 1460,sackOK,TS val 743793360 ecr 0,nop,wscale 6], length 0

IP層でパケットが到達できないので、TCPの再送を繰り返しています。


netstatで確認してみる
$ netstat -an
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address               Foreign Address            
State     
tcp        0      1 10.146.1.28:59648           111.111.111.111:12345       SYN_SENT  

SYN_SENT状態になっていることが確認できました。connectシステムコールが発行される時、Synが投げられているのですね。


Syn + Ack はいつ投げるのか?

今までちょっとした疑問だったのが、最初のSynが来た後、Syn + Ackってプログラム的にはいつ投げてるの?ということ。

これは、acceptしなくてもlistenさえしてれば Syn + Ack を返すようです。で、すぐにAckが返ってきてEstablished状態に。

accept前にsleepするように、ソースを書き換えます。

server.c
listen(sock0, 5);
len = sizeof(client);
sleep (600);
sock = accept(sock0, (struct sockaddr *)&client, &len);

サーバクライアント実行後、netstat実行結果
$ netstat -an | grep 12345
tcp        0      0 0.0.0.0:12345               0.0.0.0:*                   LISTEN     
tcp        0      0 127.0.0.1:12345             127.0.0.1:34187             ESTABLISHED
tcp        0      0 127.0.0.1:34187             127.0.0.1:12345             ESTABLISHED 

まだacceptを実行していないのにESTABLISHEDになりました。

やっぱりlistenさえしていればSyn + Ackが投げれる状態になっているのですね。


ソケットがファイルインターフェースであることを確認する

最後におまけ。ソケットがファイルインターフェースと同じように、ファイルディスクリプタのように取り扱われていることを確認してみます。


straceでシステムコールをトレース
$ strace ./server
execve("./server", ["./server"], [/* 29 vars */]) = 0
〜〜〜省略〜〜〜
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(12345), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 5)                            = 0
accept(3,
(サーバはこの時点でListenで待ち受ける)

(clientプログラム起動)
{sa_family=AF_INET, sin_port=htons(57041), sin_addr=inet_addr("127.0.0.1")}, [16]) = 4
write(4, "HELLO", 5)                    = 5
close(4)                                = 0
close(3)                                = 0
exit_group(0)                           = ?

確かに クライアントがconnectしてきた後は fd 4 を取得し、そこにwriteしているのが見て分かります。

また、サーバがソケットを作成した時点で、fdを確認してみます。サーバがListenしてる時点では fdは3を取得しているはずです。

$ ps -ef | grep server
ec2-user 17866 17865  0 03:02 pts/1    00:00:00 ./server

$ ls -ltr /proc/17866/fd
total 0
lrwx------ 1 ec2-user ec2-user 64 Aug 23 03:03 2 -> /dev/pts/1
lrwx------ 1 ec2-user ec2-user 64 Aug 23 03:03 3 -> socket:[2426838]
lrwx------ 1 ec2-user ec2-user 64 Aug 23 03:03 1 -> /dev/pts/1
lrwx------ 1 ec2-user ec2-user 64 Aug 23 03:03 0 -> /dev/pts/1

なにやら/proc/[PID]/fdの下にできています。確かにソケットはファイルインターフェースと同じように取り扱われていることが分かります。


まとめ

何をやったというわけではないですが...実際サーバクライアントをソース用意して動かすと理解が深まりますねw 勉強になったのでした。 TCP/IP おもしろい!

2011-06-19

Amazon EC2 マイクロインスタンス起動

東京リージョンができたこともあり、今更ながらAmazon EC2に初挑戦してみました。

今まで検証機として使用していた自宅サーバLeenoの監視/バックアップサーバとして運用し始めたので、簡単に使える検証サーバがなくなってしまったので。。いつでも気軽にソフト入れたり壊したりできる検証サーバがないとなかなか勉強進まないですよね。w

無料ティアでマイクロインスタンス立ち上げ

今回は検証用だし、お金もかけていられないので無料ティアのマイクロインスタンスを使うことにしました。

下記のような流れで。。

これだけ。なんと簡単なことか。。。


今回は無料ティアだということと、初めてのOSを使ってみたいということもありAMIAmazon Linuxを使ってみました。

とりあえず普段使ってるソフトをyumインストールしてバージョンを確認。

Apache
  • httpd-2.2.16-1.11.amzn1.x86_64
MySQL
  • mysql-server-5.1.52-1.6.amzn1.x86_64
nginx
  • nginx-0.8.53-1.2.amzn1.x86_64

CentOSベースっぽい。検証で使う分には全く問題なさそう。

無料で使う分には、それこそスペック低めのVPS使うのと同じような感覚で使えそうです。でもEC2の本当のメリットを享受できるのはやっぱりスケールしていってからなんだろうなぁ。。自分のやってるLeenoで使うって手もアリですが、要件が生まれない限りはまだ手が出せないかな(コスト的に)

無料ティアのデータ転送量の制限

よく分かっていないのは、無料ティアの「データ転送30GBまで」の制限。これって、1ヶ月に累計でこれ以上のサイズのデータ転送したら課金されるってことなのかなぁ。。負荷試験やベンチマークとってみたかったけど、それもデータ転送に計上されちゃったら場合によっては課金されちゃいそうですね。。。


と、こんな感じのEC2初挑戦でした。

2011-06-12

Leeno構築で学んだ技術/ノウハウを晒してみる

自分が運営に携わってるラクガキコミュニケーションサービスLeenoですが、おかげさまで5/15の公開以降アクセスも増えてきて、なんと週刊アスキーさん*1に紹介されたりもしました。新機能だって、HTML5のweb strageを使った履歴機能が追加されたり、少しずつ前進してますよ。

f:id:happs:20110612235343j:image:w360

さて、オープンしてからそろそろ1ヶ月も経つことですし、このあたりで今回はLeenoの裏側について少しずつ話していこうと思います。


告白

まず最初に正直にバラしちゃいますと、私はLeenoのコード1行も書いていません。

自分はインフラ構築担当だったということもあり、短い開発期間の中、自分の今できる役割をきっちり果たすことに専念しました。もちろんRails3/Ruby1.9/Javascript/HTML5など、Leenoで使われている各要素技術の概要くらいは勉強しましたけどね。

プログラムに関してはほんと、うちのエースプログラマid:soplanaが凄すぎて、分からないことがあればいつも教えてもらってます。


今までやってきたこと

で、私の担当したこと。ざっとこんなところです。

いくら本業でサーバエンジニアをやってるとはいえ、各フェーズが初めての経験ばかり。OSSだって仕事で使うことほとんどないので....。学ぶところは相当多かったです。今回はそんな経験の中から、技術的にここは工夫しましたよーってところを数点紹介。初歩的なテクですがドヤ顔で書いてみました。


画像ファイルのブラウザキャッシュ

ラクガキサービスLeenoのアプリケーションの性質上、どうしても気になってしまうのは画像データのI/Opngファイルのリクエストに対して毎回I/Oが発生していては、サーバの負荷も高くなるわ帯域も食うわであんまりいいことありません。

Leenoはこの点を、Leenoのアプリの性質を利用して上手いこと解決しています。行った設定はおおむね下記のような感じ。

  1. webサーバ側でexpiresヘッダを返却するよう設定
  2. サーバからのレスポンスにて取得したデータがクライアントブラウザキャッシュされる
  3. 新たなリクエストに対しては、画像/CSS/javascriptなど更新されないファイルは既にブラウザキャッシュにのっかっているのでサーバ側にリクエストすることなく取得

このexpiresヘッダ、とっても便利なんですが気にしなきゃいけないのは、データが更新された場合はブラウザキャッシュを参照せずにサーバ側に最新データを取得しに行かせること。その点、Leenoの場合そのアプリの性質上、画像(canvas)は更新されることがありません。画像は上書きされず、常に新しいものが新しいパスを持ってPOSTされるので、誤って古い情報のキャッシュを返してしまう、ってことは起こり得ないのです。いわゆるREST的なアレ*2

このアプリの動作のおかげで体感速度的にはほぼ問題なくアクセスできているかと思います。


静的ファイルはリバースプロキシで返す

上記の通り画像データの扱いには気を使いましたので、実際サーバにリクエストが来た時の挙動もちょこっと手を加えました。下記のような感じです。

Web/APサーバが大体1プロセス50MB程度、結構メモリ食っちゃうので、画像リクエスト程度ではわざわざメモリ食うプロセスは使わず軽いリバースプロキシ(1プロセス3MB程度)のみで返しちゃう設定にしてます。

このあたりのノウハウは全てこの本に教えてもらいましたw


MySQLレプリケーション

LeenoはMySQLを使ってて、レプリケーションバックアップとってます。DBについては自分がほとんど無知だったということもあり、、調べること沢山ありました。

ストレージエンジンはActive Recordとトランザクションの制限でinnodb1択。となると、バックアップどうしよ?という話が出てきます。MySQLのオンラインバックアップというと色々方法はあると思いますが....

MyISAMを使わない以上、mysqlhotcopyとmysqlsnapshotは使えない。mysqldumpはテーブルにロック掛かっちゃうので使いたくない。とすると、結局のところレプリケーション1択になっちゃいます。

ググりググり、なんとか別のサーバレプリケーション構成を組んで現在運用中です。レプリケーションサーバに参照リクエストを振り分けたりはしてません。今後の負荷次第かなぁ。。


監視(ZABBIX)

あとは監視。こいつも自分はそんなに知見があるわけじゃなく....*3でもサービス障害はほっとくわけにはいかないので、今回はZABBIXを監視プロダクトとして採用しました。決め手は下記。

  • GUI操作なので、他のメンバも操作を覚えれば監視しやすい
  • グラフ作成が楽
  • qpstudy/hbstudyクラスタで、よく話題に上る印象(よく使われているということか)

構成的にはオーソドックスに、ZABBIXエージェントを本番環境のサーバインストールして、ZABBIXサーバは別のサーバに用意してリモート監視してます。死活監視/性能監視/プロセス監視/ポート監視/ログ監視 等々いろいろ設定してますが、今のところそれほど大きな障害もなく安定運用できています。


こだわりポイントはまだまだありますが、今回はこれくらいで。

まだまだインフラも整っていない部分が多々あるので、しばらくは忙しくなりそうです。ではまた。

*1:2011/6/7号

*2:LeenoアプリというよりはRailsの性質とも言えるのか

*3:今回のプロジェクトでつくづく感じたのは、俺OSしか知らねーじゃん、っていうこと

2011-05-22

ラクガキを描いてみんなで描き直して遊べるサービス「Leeno」を公開しました

久しぶりのブログ投稿。

さて、Twitterではもう何度も何度もつぶやいて宣伝していますが、この度、仲間と「Leeno(りーの)」というWebサービスを公開しました。

http://leeno.jp/

f:id:happs:20110522222929p:image:w360


Leeno?何それうまいの?

小学校、中学校の頃…机にラクガキをしたら、いつの間にか他の誰かがラクガキしてて面白い絵になってた…とか、ラクガキで伝言ゲームをした…という思い出は誰にでもあるではないでしょうか?

「あの頃の楽しさをWebで実現できないか??」

Leenoはそんなコンセプトで生まれ、作られたサービスです。


何ができるの?

できることはシンプル。


ラクガキを描ける

Leenoではラクガキを「canvas」と呼びます。誰でも、匿名でラクガキを描いてcanvasを作ることができます。

f:id:happs:20110522222932p:image:w360


誰でも描き直せる

Leenoでは公開されているcanvasを自由に描き変えることができます。

これを「ReDraw」と呼びます。

例えば、このかわいい女の子のイラストに、私が「ポニテにしてくだしあ><」と書き込んでみました。

f:id:happs:20110522222933p:image:w360

すると、しばらくしたら見事このリクエストに反応がww 俺歓喜ww

f:id:happs:20110522222934p:image:w360

さてReDrawされたcanvasは「history」と呼ばれます。

canvasを直接書き換えるのではなく、親canvasを残したまま新しい子としてhistoryが作られます。*1

f:id:happs:20110522222935p:image:w360

このように、過去のhistoryは一覧になっていますし、それぞれのhistoryから親history, 子historyをたどることもできます。

f:id:happs:20110522222938p:image:w360

f:id:happs:20110522222937p:image:w360


他にも色々

他にもパラパラマンガ(Flip)を作れたり、HTML5で作られているのでiPad/iPhoneでもそれなりの絵が描けたり....色々と遊べます。


どんな風に遊べるの?

例えば4コマ漫画をみんなで描いたり、ラクガキしりとりをしたり...遊び方は無限にあると思っています。

f:id:happs:20110522222936p:image:w360

f:id:happs:20110522222931p:image:w360

ありがたいことに、「プロの犯行ww」と言いたくなるような絵も含め、もうすでに800枚以上のラクガキが投稿されています。

f:id:happs:20110522222930p:image:w360


あそんでみてください

まだまだ機能は足りない部分がありますが、運営メンバー3人で知恵を搾って楽しく遊べるサービスに育てていこうと思います。

ぜひ、遊んでみてください!

使い方よく分からないけど気になるとか、こんな機能ほしい!ってのがあったらコメントお願いします><


IEごめんなさい

Leenoは HTML5/CSS3 を使っているので、Internet Explorerは残念ながら対応していません>< Firefox/Chrome/Opera/Safari で見てね。

*1インフラエンジニアとしてはforkを連想せざるを得ないシステムですw