月別: 2012年12月 Page 1 of 2

VirtualDocumentRootで開発サーバーを楽に運用

ApacheのVirtualDocumentRoot機能について。

弊社では開発の為にグローバル環境に置かれた開発サーバーを用意しています。
Apacheの名前ベースVirtualHostを利用しているのですが
開発用という事もあって変更が多く、変更の度に以下のような作業が必要となります。

1,開発用のドメインサブドメインを追加し、開発サーバーへ向ける
2,VirtualHostディレクティブを追加
3,Apacheの再起動

非常に面倒だったのですが「DNSワイルドカード機能」と「VirtualDocumentRoot」を使うと上記の作業を自動化できます。

事前準備

上記の機能を使用する為には、apacheがconfigureオプションに
–enable-vhost-alias」をつけてコンパイルされている必要があります。
すでにApacheが動作している場合は上記オプションをつけて再インストールする必要があります。

また、DNSレコードを編集できるドメインを一つ取得している必要があります。
今回は例として example.com というドメインが完全に使用できると仮定して進めます。

DNSワイルドカード機能

ドメインのレコードを以下のように設定する事でサブドメインワイルドカードとして設定できます。
[code]
* IN A xxx.xxx.xxx.xxx
[/code]

この場合、test.example.com も hoge.example.com も、
すべてIP「xxx.xxx.xxx.xxx」へ向かうサブドメインと設定する事ができます。

VirtualDocumentRoot

Apacheのconfで以下のような設定を行います。
[code]
NameVirtualHost *:80

    <VirtualHost *:80>
ServerName          example.com
ServerAlias         *.example.com
VirtualDocumentRoot /home/%0/public_html
ErrorLog /usr/local/apache2/logs/error_log
TransferLog /usr/local/apache2/logs/access_log
<Directory /home/*/puclic_html>
Options FollowSymLinks ExecCGI
AllowOverride All
        Order deny,allow
Allow from all
</Directory>
</VirtualHost>

[/code]

この設定の勘所は
・DocumentRoot が VirtualDocumentRoot となりパスの部分に%0 といった変数になっている
・ServerAliasにワイルドカードを使用
ディレクトリにワイルドカードを使用

上記の設定だと
/home/test.example.com/public_html
というディレクトリを作成するだけで
test.example.com で接続できる状態となります。

VirtualDocumentRootの%0の部分はtest.example.comに置き換えられているのですが
それぞれの変数の対応は以下のようになっています。

[code]
test.example.comの場合

%0 test.example.com
%1 test
%2 example
%3 com

%1.1 = t
%1.2 = e
%1.3 = s
%1.4 = t

%1.1+ = test
%1.2+ = est
%1.3+ = st
%1.4+ = t
[/code]

VirtualDocumentRootの欠点

ディレクトリを作成するだけで公開されてしまう為、セキュリティ的には通常運用に比べ弱くなってしまってると言えます。
また、ログファイルは分ける事ができない為、すべてのログが1つに集約されてしまいます。

muninプラグインを作成

muninは数値化できるもの規定の形式で標準出力するだけでなんでもグラフ化できます。

ユーザー数を取得してグラフするプラグインを作成する

サーバー側の情報はほとんどデフォルトのプラグインでカバーされているので今まで不便はなかったのですが勉強の為に運営しているサイトのユーザー数などを取得し、グラフ化してくれるようなプラグインを作成してみました。

ユーザー数を取得するスクリプトを作成

ここは各WEBアプリ部分になるのですがシェル上でたとえば以下のように実行すればDB等を参照してユーザー数が標準出力で返ってくるようなプログラムを作成しておきます。

/home/app/usercount.php (例)
[code]
<?php

$conn = pg_connect(“dbname=hoge“);
$sql = “SELECT id FROM user”;
$result = pg_query($conn, $sql );
echo pg_num_rows($result);
pg_close($conn);
[/code]

シェル上で実行してみます。210人のユーザーがいるみたいですね。

[code]
$ /usr/local/bin/php /home/app/usercount.php
210
[/code]

muninのプラグインを作成

muninのプラグインは与えられた引数ごとにmuninの望む出力をしてあげればOKなので言語は問いませんが、シェルスクリプトでの生成が多いみたいなので今回もシェルスクリプトで作成しました。

とりあえず作成したプラグインの全文を載せます。
作成したプラグインは /usr/share/munin/plugins へ実行権限をつけて保存し、/etc/munin/plugins/ 以下でシンボリックリンクを作成してmunin nodeを再起動します。

vi /usr/share/munin/plugins/usercount
[code]
!/bin/sh

%# family=auto
%# capabilities=autoconf

GETNUM=/usr/local/bin/php /home/app/usercount.php

if [ "$1" = "autoconf" ]; then
if [ -n ${GETNUM} ] ; then
echo yes
exit 0
else
echo no
exit 0
fi
fi

if [ "$1" = "config" ]; then
echo 'graph_title user count'
echo 'graph_args -r –lower-limit 0'
echo 'graph_vlabel users'
echo 'graph_category Apps'
echo 'total.label Total users'
echo 'total.min 0'
echo 'total.draw AREA'
echo 'total.type GAUGE'
exit 0
fi

echo "total.value $GETNUM";
[/code]

実行権限をつける
[code]
chmod 755 /usr/share/munin/plugins/usercount
[/code]

保存が完了したら /etc/munin/plugins/ 以下にシンボリックリンクを作成する
[code]
cd /etc/munin/plugins/
ln -s /usr/share/munin/plugins/usercount usercount
[/code]

最後に再起動
[code]
/etc/init.d/munin-node restert
[/code]

各項目の意味

プラグイン内の各項目の意味です。

munin-node-configureの環境変数設定項目

[code]
%# family=auto
%# capabilities=autoconf
[/code]

Munin付属のプラグイン追加・削除ツールであるmunin-node-configureに通知を行う項目です。
autoconfに対応しているプラグインはmunin-node-configureにより、自動で利用可能か確認し追加することができます。
いろいろな項目が指定できるようですが配布もしないし自分しか使わないようなプラグインであれば何も考えずコピペでOKだと思います。

こちらの項目については以下の公式ドキュメント(英語)が詳しいです。

Magic markers – http://munin-monitoring.org/wiki/ConcisePlugins#Magicmarkers

データ取得

[code]
GETNUM=/usr/local/bin/php /home/app/usercount.php
[/code]

先ほど作成したユーザーカウントを取得するプログラムを実行しています。
$GETNUMにはユーザー数が入ってるはずです。

引数autoconfを与えられた時の実行

[code]
if [ "$1" = "autoconf" ]; then
if [ -n ${GETNUM} ] ; then
echo yes
exit 0
else
echo no
exit 0
fi
fi
[/code]

autoconfに対応したプラグインの場合、こちらでプラグインとして読み込むか否かを設定できます。
今回の場合 変数GETNUMの値が空だった場合は正常にユーザー数を取得できていないのでnoを返す(読み込まれない)よう設定します。
逆に取得ができてるのであればyesを返します。

引数configを与えられた時の実行

[code]
if [ "$1" = "config" ]; then
echo 'graph_title user count'
echo 'graph_args –rigid –lower-limit 0'
echo 'graph_scale no'
echo 'graph_vlabel users'
echo 'graph_category Apps'
echo 'total.label Total users'
echo 'total.min 0'
echo 'total.draw LINE2'
echo 'total.type GAUGE'
exit 0
fi
[/code]

大きくわけてグラフ全体の設定項目と、描写する内容(データソース)の設定にわかれます。
詳しくは以下のリファレンスマニュアルで。(英語)
config reference – http://munin-monitoring.org/wiki/protocol-config

graph_title user count
グラフのタイトルです。グラフの上部に出力されます。

graph_args –rigid –lower-limit 0
グラフパラメータの設定です。
–lower-limit 0 y軸のスタート地点を設定します。
–upper-limit 100 y軸の上限を設定します。
–rigid y軸の上下値のリミットを強制します。
–base 1024 この数字を超える場合に1kと表示される

graph_scale no
上記のbaseを超えた値の1kなどの表示をやめる

graph_vlabel users
y軸の名前。グラフの左に表示されます

graph_category Apps
グラフのカテゴリー

{fieldname}.label Total users
データソースの名前。カウントされた値の項目名としてグラフの下に出力されます。

{fieldname}.max 1000
この値より高い数字を取得した場合に無視します。

{fieldname}.min 0
この値より低い数字を取得した場合に無視します。

{fieldname}.draw LINE2
グラフ描写のタイプを指定
LINE1 1ピクセルのライン
LINE2 2ピクセルのライン (デフォルト)
LINE3 3プクセルのライン
AREA 塗りつぶします
STACK 積み重ねます

{fieldname}.type GAUGE
グラフ描写のタイプ指定
GAUGE 計測をそのまま表示 (デフォルト)
COUNTER 前回記録時との差分を記録し、毎秒あたりの値に変換。
     オーバーフローに対応するが前回の値より下回る事は考慮しない?
DERIVE 前回記録時との差分を記録しつつ毎秒あたりの値に変換。
     オーバーフローには対応していないが前回の値より下回る事を考慮?
ABSOLUTE 基本的にはCOUNTERと同じだけど、増加分の値を表示する? この値はよく意味がわかりませんでした。

引数なしの時の実行

[code]
echo "total.value $GETNUM";
[/code]

最後に値を出力。
これは引数をつけづに実行した際の出力になります。
totalというフィールドセットへの値なので
total.value * みたいな出力となるよう調整します。

確認

[code]

./usercount autoconf

yes

./usercount config

graph_title user count
graph_args –rigid –lower-limit 0
graph_scale no
graph_vlabel users
graph_category Apps
total.label Total users
total.min 0
total.type GAUGE
total.draw LINE2

./usercount

total.value 538
[/code]

完成

現在のユーザー数がグラフ化できました。


(※)途中、COUNTとかを使用するとavgなどが変な値になってしまった・・・

数値化できるものであればなんでもグラフ化できるので便利ですね。

Perlのコンテキストについて

Perlを書いていると次のような部分で引っかかることがあります。

  • スカラー変数($hoge)と配列変数(@hoge)のどちらを使うか
  • 関数の引数の渡し方
  • 関数の返り値の受け取り方

これらは様々なパターンがあるので、Perlを普段から書いていないと迷ってしまいます。

Perlには「コンテキスト」という考えがあります。
コンテキストを理解すれば、すべてのパターンを覚えなくとも、上であげたことが大体わかるようになってきます。

コンテキストとは

コンテキストは、次の二種類あります。

  • スカラーコンテキスト
  • リストコンテキスト

スカラーコンテキストとは以下のようなものが書かれている場所をいいます。

1         # 数値
'hoge'    # 文字列
$hoge     # スカラー変数
\@hoge    # リファレンス

リストコンテキストとは以下のようなものが書かれている場所をいいます。

@array    # 配列
%hash     # ハッシュ
(1, 2)    # リスト

Perlプログラムを書いているときは、どちらのコンテキストになっているのかを意識するようにします。

代入

代入では、左辺と右辺がそれぞれどちらのコンテキストになっているのかを意識します。
ほとんどの場合は、同じコンテキストになるようにします。

左辺も右辺もスカラーコンテキストの場合。

# 左辺がスカラー、右辺もスカラー
my $a = 'test';
my $b = $a;

左辺も右辺もリストコンテキストの場合。

# 左辺がリスト、右辺もリスト
my @a = (1, 2, 3);
my @b = @a;
my ($a, $b) = (1, 2);
my ($a, $b) = @list;
my %a = (a =&gt; 1, b =&gt; 2);
my %b = %a;

配列とハッシュは共にリストコンテキストになるので、相互代入することができます。
実は、Perlのハッシュで使われている「=>」は、「,(コンマ)」と等価です。

my @a = (a =&gt; 1, b =&gt; 2);  # -&gt; ('a', 1, 'b', 2)
my %b = @a                 # -&gt; (a =&gt; 1, b =&gt; 2)
my %a = ('a', 1, 'b', 2);  # -&gt; (a =&gt; 1, b =&gt; 2) ※順番は保持されない

リファレンスはスカラー値なので、スカラー変数で受け取ります。

# 配列リファレンス
my $array_ref = \@a;
# ハッシュリファレンス
my $hash_ref = \%a;
# 無名配列リファレンス
my $array_ref = [1, 2, 3, 4];
# 無名ハッシュリファレンス
my $hash_ref = { a =&gt; 1, b =&gt; 2 };

配列リファレンスをデリファレンスすると、リストコンテキストになるので、配列変数で受け取ります。
ハッシュのリファレンスも同じです。

# デリファレンス後はリストコンテキストなので、配列変数で受け取る
my @a = @{ $array_ref };
my %a = %{ $hash_ref };

for文

for文のカッコの中はリストコンテキストになるので、リストを渡すようにします。

for my $a ( 1, 2, 3 ) {}
for my $a ( @array ) {}
for my $a ( @{ $array_ref } ) {}

関数

関数の引数は、無名配列変数の「@_」で受け取られます。
左辺がリストコンテキストである場合の代入だと考えると良いでしょう。

func($a, $b);
sub func {
my ($a, $b) = @_;
}

配列リファレンスを引数にする場合。

func($a, \@list);
sub func {
my ($a, $list_ref) = @_;
my @list = @{ $list_ref };
}

関数の返り値を受け取る場合も、返り値のコンテキストを意識します。

# 文字列を返す(返り値はスカラーコンテキスト)
sub func1 {
return 'a';
}
my $result = $func1();
# 配列を返す(返り値はリストコンテキスト)
sub func2 {
my @list = (1, 2, 3);
return @list;
}
my @result = func2();
my ($a, $b, $c) = func2();
# 配列リファレンスを返す(返り値はスカラーコンテキスト)
sub func3 {
my @list = (1, 2, 3);
return \@list;
}
my $result = func3();

wantarrayを使うと、関数呼び出し側のコンテキストによって、
返り値のコンテキストを変更することができます。

sub func1 {
my @a = (1, 2, 3, 4);
if (wantarray) {
return @a;
} else {
return \@a;
}
}
# リストコンテキストで受け取る
my @list = func1();
# スカラーコンテキストで受け取る
my $list = func1();

参考ページ

この他にも色々なパターンや例外があるので、後は以下のようなページを参考にして下さい。
コンテキストがわかっていれば、それぞれのパターンについても覚えやすくなるのではないかと思います。

perlbrewで構築するモダンなPerl環境

ダンPerl環境構築方法をご紹介したいと思います。
perlbrewは、ホームディレクトリに複数のperlをインストールして切り替えをしてくれるツールです。

perlbrewのインストール

以下のようなコマンドを実行すると、prebrewをインストールできます。

$ curl -kL http://install.perlbrew.pl | bash

perlbrewのインストール方法はいくつかあります。
他の方法は以下のサイトを参照して下さい。

http://perlbrew.pl/

perlbrewコマンドを使うためにインストールされたコマンドにパスを通す設定が必要です。
bashならbashrcに以下のような設定を書くと良いでしょう。

source ~/perl5/perlbrew/etc/bashrc

perlbrewでPerlをインストール

以下のコマンドで、利用できるPerlのバージョンを確認できます。

perlbrew available

現時点では以下の様なリストが表示されました。

perl-5.17.6
perl-5.16.2
perl-5.14.3
perl-5.12.5
perl-5.10.1
perl-5.8.9
perl-5.6.2
perl5.005_04
perl5.004_05
perl5.003_07

最新版をインストールするとよいでしょう。
注意点としては、
5.xxのxxの部分が奇数のバージョンは開発版であり、
また5.xx.0はバグがある可能性が高いので、
正式に使うのであれば、これらのバージョンを避けたほうがよいでしょう。

今回は、5.16.2をインストールします。

インストールは次のコマンドでできます。

$ perlbrew install 5.16.2

インストールは少し時間がかかります。
テストを省略しても支障がない場合は、次のコマンドでインストールすると、少し早く完了します。

$ perlbrew install --notest 5.16.2

perlbrewでPerlを切り替え

次のコマンドで、インストールしたPerlを確認することができます。

$ perlbrew list
perl-5.16.2
````
Perlを切り替える場合は、次のコマンドで行えます。

$ perlbrew switch 5.16.2

もう一度perlbrew listで確認してみましょう。

$ perlbrew list
* perl-5.16.2

行の最初に米印がついているバージョンが、現在選択しているPerlです。
whichコマンドでperlコマンドのフルパスを確認してみましょう。

$ which perl
/home/uchiyama/perl5/perlbrew/perls/perl-5.16.2/bin/perl

ホームディレクトリにインストールしたPerlが使われるようになっていると思います。
<h2><span style="color: #2196f3">cpanmをインストール</span></h2>
ついでにCPANモジュールをインストールするコマンドであるcpanmもインストールしましょう。
perlbrewのコマンドでインストールできます。

$ perlbrew install-cpanm

これでcpanmコマンドが使えるようになります。
ホームディレクトリにモジュールがインストールされるようになっているので、root権限はいりません。
また、好きなモジュールを入れても全体に影響を及ぼすことはありません。
<h2><span style="color: #2196f3">cronでの使い方</span></h2>
perlbrewで入れたPerlをcronで実行するとき、いくつか設定する必要があります。
まず、次のようなシェルスクリプトを用意します。
env.sh

!/bin/sh

export HOME=/home/uchiyama
source ~/perl5/perlbrew/etc/bashrc
perlbrew use perl-5.16.2

exec “$@”

そして、crontabの設定で次のような感じで記述します。
          • /home/uchiyama/env.sh perl /home/uchiyama/hoge.pl > /tmp/hoge 2>&1
以上のようにに設定することで、perlbrewでインストールされたPerlが使われるようになります。
普通に実行してしまうと、システムに標準で入っているPerlの方で実行されてしまうので注意して下さい。
<strong>参考</strong>
<a href="http://blog.riywo.com/2012/05/26/005232" title="http://blog.riywo.com/2012/05/26/005232" target="_blank">http://blog.riywo.com/2012/05/26/005232</a>
<h2><span style="color: #2196f3">まとめ</span></h2>
perlbrewでperl環境を作る方法をご紹介しました。
共有環境の場合、ホームディレクトリにperlがインストールされていると安心感があります。
Perlを使うときはぜひこの方法で環境構築してみてください。

muninグラフの読み方と意味

サーバー監視ツール「munin」の各グラフの意味と読み方。

muninをインストールすると標準でたくさんのグラフが出力されます。中には聞いた事もない単語のグラフもあり、
「いったいこれは何なのか」と疑問に思っていたので調べてみました。

グラフはすべてplugin (/etc/munin/plugins) の内容から出力されていて、さらにそのほとんどが単なるperlスクリプトシェルスクリプトでした。このpluginが実際はどのようなコマンドを使った結果なのかを確認しながら調べました。プログラムにはあまり明るくない&英語が苦手、なので意味違いしている可能性がありますのでお気づきの点がありましたらコメントを頂けると嬉しいです。
使用したmuninバージョンは1.4.5です。

ほとんどの値は /proc/以下から参照されているようで以下のページがとても役に立ちました。
Man page of PROC – http://linuxjm.sourceforge.jp/html/LDP_man-pages/man5/proc.5.html

disk

Disk IOs per device



プラグイン diskstats
IO使用率(Disk utilization per deviceで得た値)を単位時間内で行ったI/O数で分割し
1秒間で行ったI/O数の平均値を出力します。
グラフを見てると入出力したkbyte数も出力してくれるようです。

実体は /proc/diskstats の出力内容から。
ファイルには各ディスクデバイスのディスク I/O 統計情報が書かれていて、
詳細は以下のカーネルドキュメント。
http://www.kernel.org/doc/Documentation/iostats.txt

各項目の意味はこちらが詳しいです。
http://hagio.org/wiki/index.php/Linux/%E9%9B%91%E8%A8%98#.2Fproc.2Fdiskstats_.E3.81.AE.E5.90.84.E9.A0.85.E7.9B.AE.E3.81.AE.E6.84.8F.E5.91.B3

Disk latency per device



プラグイン diskstats
こちらのグラフもTOPページで全ディスクの読み書きにかかる時間を表示、
クリックで、各ディスクごとの読み書きにかかる時間を表示します。

この時間はsyscallがアプリケーションとデバイスを行き来する平均時間から算出されている?みたいです。
単位はミリセカンド(1000分の1秒)。

実体は /proc/diskstats の出力内容から。

Disk throughput per device



プラグイン diskstats
1秒間に読み書きしたバイト数を表示します。

Disk usage in percent



プラグイン df
ファイルシステムごとのディスク使用率を%で表示。
元コマンドはdfコマンドです。

Disk utilization per device


プラグイン diskstats
こちらのグラフはTOPページで全ディスクのIO使用率を表示、
クリックで、各ディスクごとのIO使用率を表示します。
LinuxではI/Oリクエストの発生中は1ミリセカンドごとにインクリメントするカウンターを使っています。
もしこのカウンターが1秒の間に1000msecぶんインクリメントされるとI/Oリクエストの発生が100%とほぼ近い値になります。
このプラグインは上記の考えからディスクへのIO使用率を%にて提供してくれます。
ただ5分ごとの計測なので当然、瞬間的な表示の取得はできません。

実体は /proc/diskstats の出力内容から。

Inode usage in percent



プラグイン df_inode

ファイルシステムごとのinode使用率を%で表示。
元コマンドはdfコマンドです。

IO Service time



プラグイン iostat_ios
バイスごとの書き込み/読み込みの遅れをグラフ化してくれます。
書き込み/読み込みにかかった時間を成功回数で割った時間です。
左メニューの le-05 などは10の-5乗という意味。
つまりこのグラフが大きければ大きいほどIOがヤバイという事になります。

実体は上記と同じく /proc/diskstats の出力内容から。
・読み込みが成功した回数
・読み込みにかかった時間 (ミリ秒)
・書き込みが成功した回数
・書き込みにかかった時間 (ミリ秒)
の値を取得し前回取得した数の差分を求めて計算する事でグラフ化されています。

IOstat



プラグイン iostat
バイスごとの書き込み能力をグラフ化してくれます。
こちらは表示の仕方が特殊で1秒間に読み書きに成功したセクタ数が出力されています。
グラフの+項目が書き込み、-項目が読み込みの値となっています。

実体は /proc/diskstats の出力内容から。

munin

Munin processing time



プラグイン munin_stats
muninがデータを取得し、グラフ、html出力など、4つの作業ごとにかかった時間を出力します。

network

Connections through firewall



プラグイン fw_conntrack
iptables を使うとき, ip_conntrack というテーブルで tcp のセッションを管理しています。
iptablesを通ったパケットは現在の接続状態などを上記のテーブルへ記録します。
このプラグインではそのテーブルを参照し、現在の状態別の接続数を表示してくれます。

ESTABLISHED
TCPコネクションが確立して通信している状態

FIN_WAIT
終了を宣言した状態

TIME_WAIT
コネクションの終了待ち状態

SYN_SENT
SYNを送信した状態。

UDP_Connections
UDPコネクション数

Assured
ASSUREDフラグの数。
このフラグは

NATed
NATの数?詳細はちょっとわからなかったです。

実態は以下のコマンドで出力される内容です。
cat /proc/net/ip_conntrack

参考
conntrackエントリ – http://www.asahi-net.or.jp/~aa4t-nngk/ipttut/output/theconntrackentries.html

eth0 errors



プラグイン if_err_*
パケットのエラー数を計測します。
基本的にインターフェイスにてエラーパケットが1でも存在すれば設定や接続先のハブに問題がなければ
個人的には信頼できないNICとして交換を視野に入れます。

実態は以下のコマンドで出力される内容です。
cat /proc/net/dev

eth0 traffic



プラグイン if_*
パケットの転送料を計測します。
ethtoolにてNICの接続状態やリンクモード(100Mbpsとか1000Mbpsとか)を取得し、
/proc/net/devの値を取得しています。

inがマイナス、outがプラスとしてグラフ出力されます。

Firewall Throughput


プラグイン fw_packets
1秒間に転送したパケット数を表示します。
/proc/net/snmp のIP:行の値を取得してこちらの値を元に計算されています。

HTTP loadtime of a page



プラグイン http_loadtime
http://localhost/wgetにて取得、その時間をtimeコマンドで取得し表示します。
任意のURLへ変えたい場合は、プラグインの以下の部分
[code]
target – URL to fetch (default: “http://localhost/“)
[/code]
を変更すれば可能だと思います。

ipconntrack



プラグイン fw_forwarded_local
iptables を使うとき, ip_conntrack というテーブルで tcp のセッションを管理しています。
接続数が増えると、それだけip_conntrackテーブルを使用するのですが、
これがいっぱいになると以下のようなログが出て通信がまったく出来なくなる状態となります。
[code]
ip_conntrack: table full, dropping packet
[/code]

このグラフでは現在のip_conntrackの上限値と使用数を表示してくれます。
実態は /proc/sys/net/ipv4/netfilter/ip_conntrack_max に記述されている値(上限値)と
/proc/net/ip_conntrack の値(現在の使用値)です。

中規模〜大規模なサイトだとこの値は意外と問題となってきますので、使用率があがってきたら
メモリと相談しながら上限をあげたり対策を行いましょう。

参考

iptables の ip_conntrack の最大値を変更する方法 – http://www.sssg.org/blogs/naoya/archives/1454

process

Fork rate



プラグイン forks
1秒間にfork(プロセスのコピーを生成する事)された数を表示します。
/proc/stat の値の processes 行(プロセスやスレッドが作成された数)の値を取得しています。

Number of threads



プラグイン threads
スレッドとは一つのプロセスの中で平行に処理を行いたい場合などに使用されます。
このグラフではスレッドがシステム全体でいくつ作られたスレッド数を表示します。

/proc以下にあるプロセスIDのディレクトリからThreadsの値を取得した合計値です。
実際には以下のようなコマンドで表示されていました。
grep -s ‘^Threads’ /proc/[0-9]*/status

Processes



プラグイン processes
プロセスの総数をそれぞれの状態別に表示してくれます。
実態はPSコマンドです

ps –no-header -eo s

Processes priority



プラグイン proc_pri
プロセスの優先度ごとの数を表示します。
元コマンドはpsでstat項目の内容で判断しています。
< 優先度高 (high-priority)
N 優先度低 (low-priority)
L メモリーロックプロセス (locked)
として集計されています。

VMstat



プラグイン vmstat
実行中であるプロセス数、スリープ状態にあるプロセス数をグラフ表示します。
vmstatコマンドのproc行にあるrとbの値をそれぞれ取得しています。

r:実行待ち状態にあるプロセス数
b:割り込み不可能なスリープ状態にあるプロセス数

rは実行可能でCPUの割当をまっている状態のプロセス数なのでグラフではrunningと表示されていますが、実際には実行中ではないような気がします。
この値はロードアベレージとほぼ同じ値を示します。
bはスリープ状態にあるプロセス、、ですがほとんどの原因はI/O待ちで、I/O待ちプロセス数と言い換える事ができます。

System

Available entropy



プラグイン entropy
エントロピー(entropy)は乱雑さを意味します。
主に鍵ファイルの生成などランダムな文字列を作成する時に使用されます。
通常はマウス操作などで自動的に乱数が作成されますが、サーバーの場合は付けっぱなしのまま置く事も多いので
このエントロピーが徐々に枯渇していき、乱数発生の際などに極端に動作が遅くなる事があります。

通常は3000とかくらいまであればいいんですが、仮想サーバーだと思うように溜まらないようです。
今の所問題になるほどの遅延は発生していませんがVMWareやさくらのVPSとかで150程度。
物理サーバーだと3000くらいはすぐに溜まります。

使用可能なエントロピーの値は以下のコマンドで取得でき、グラフ生成も以下のコマンドで行われています。
cat /proc/sys/kernel/random/entropy_avail

CPU usage



プラグイン cpu
CPUの使用率を表示します。
コア数ぶん表示するので、例えばコア2、ハイパースレッディング対応の場合はMAXが400%となります。

system
システムモード

user
ユーザモード

nice
低い優先度のユーザモード

idle
未使用の状態

iowait
I/O の完了を待っていた時間

irq
割り込み処理を行った時間

softirq
ソフト割り込みの処理を行った時間

steal
仮想化環境での動作時に他のオペレーティングシステムにより消費された時間

guest
Linux カーネルの制御下のゲストオペレーティングシステムの仮想 CPU の 実行に消費された時間

基本的には system user iowaitの値を確認します。
システムモードはOSコードやデバイス・ドライバなどで使用された基本的にはOSで使用されるCPU使用
ユーザーモードPHPとかApacheなどのユーザーアプリでのCPU使用
iowatiはHDDへの書き込み/読み込み待ち状態のCPU使用
です。

このプラグインが使用している値は /proc/stat のCPU項目です。

File table usage



プラグイン openfile
オープンされているファイル数とシステム中のオープンファイル管理データの最大数をグラフ化します。

上限値は /etc/security/limits.conf で変更可能
fs.file-max = 90000 など

取得もとは以下の値です。
/proc/sys/fs/file-nr

Individual interrupts



プラグイン irqstats
様々な種類の割り込みの数をグラフ化してくれます。
割り込みの種類はマウスとキーボード(i8042)、イーサネット(eth0)などのハードウェアから生じる割り込みや
カーネルからくる割り込みなど様々な種類があるみたいです。

取得元は /proc/interrupts です

Inode table usage



プラグイン irqstats
システム中のメモリiノードの数および使用中のメモリiノードの数をグラフします。

取得元は /proc/sys/fs/inode-nr の値です。

Interrupts and context switches



プラグイン irqstats
処理された割り込みの回数とコンテクスト・スイッチ(CPUの状態を保存したり復元したりした回数)を表示する。

取得元は /proc/stat の intr および ctxt の値。

Load average



プラグイン load
ロードアベレージの値をグラフしてくれます。

取得元は /proc/loadavg で
cut -f2 -d’ ‘ < /proc/loadavg
として取得されているので5分平均の値です。

Logged in users



プラグイン users
ログインしているユーザー数を表示します。
取得元はwhoコマンド

Memory usage



プラグイン memory
モリーの使用状況をグラフに表示してくれます。

apps
ユーザアプリ使用メモリサイズ
MemTotal – MemFree – Buffers – Cached – Slab – PageTables – SwapCached

page_tables
ページテーブルエントリという「ページ」の管理構造体として利用されているメモリ

swap_cache
物理メモリ上にキャッシュされたスワップページの総容量

vmalloc_used
vmalloc()で確保された物理メモリ領域とMMCONFIGで確保しているメモリ領域の総容量

slab_cache
スラブアロケータで使用されている物理メモリの総容量

cache
ファイルデータのキャッシュなどに使用している物理メモリの総容量。共有メモリは Cached に加算される。SwapCachedは含まない
cache-(SwapCache+buffers)

buffers
ファイルなどのメタデータとして使用している物理メモリの総容量

unused
未使用のメモリ。

swap
スワップとして使用されている量
SwapTotal – SwapFree

committed
全プロセスによって確保された仮想メモリの総容量。

mapped
ページテーブルに登録されている物理メモリの総容量

active
最近アクセスした物理メモリの容量

inactive
最近アクセスしていない解放してよい物理メモリの容量

取得元は /proc/slabinfo と /proc/meminfo

Swap in/out



プラグイン swap
1秒間にswap in/outされたブロック数を表示、

取得元は /proc/vmstat の pswpin / pswpoutの値です

Uptime



プラグイン uptime
パソコンが起動してから現在までの時間。

取得元は /proc/uptime

CGIプログラムをPlack::Testでテストする

以下のようなCGIプログラムのテストを書きたいとします。

 
fizzbuzz.cgi

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI-&gt;new;
my $number = $q-&gt;param('number');
unless (defined $number) {
print $q-&gt;header(
-status =&gt; 200,
-type =&gt; 'text/plain; charset=utf8',
);
print "Please set number";
exit;
}
my $result = '';
if ($number % 3 == 0) {
$result .= 'Fizz';
}
if ($number % 5 == 0) {
$result .= 'Buzz';
}
$result ||= $number;
print $q-&gt;header(
-status =&gt; 200,
-type =&gt; 'text/plain; charset=utf8',
);
print $result;

ロジックが一緒に書かれているので、Test::Moreでテストしづらいですね。
こんなときはCGIプログラムをPSGIアプリに変換し、
Plack::Testでテストすると楽です。

 

PSGIアプリに変換するにはPlack::App::CGIBinを使って、
以下の内容のapp.psgiを作成します。

#!/usr/bin/perl                                                                                                                                            
use strict;
use warnings;
use File::Basename;
use Plack::Builder;
use Plack::App::CGIBin;
my $basedir = dirname(__FILE__);
builder {
mount '/' =&gt;
Plack::App::CGIBin-&gt;new( root =&gt; $basedir, exec_cb =&gt; sub { 1 } )-&gt;to_app;
};

これでめでたくCGIプログラムはPSGIアプリになりました。
普通のPSGIアプリと同様に、plackupコマンドなどで実行できるようになります。

$ plackup app.psgi

ブラウザなどで「http://localhost:5000/fizzbuzz.cgi?number=1」にアクセスすると実行できます。
 

PSGIアプリは、Plack::Testでテストすることができます。
先ほどのCGIプログラムのテストは、次のように書くことができます。

use strict;
use warnings;
use Test::More;
use Plack::Test;use HTTP::Request::Common;
use Plack::Loader;
use Plack::Util ();
my $app = Plack::Util::load_psgi('app.psgi');
test_psgi $app, sub {
my $cb = shift;
my $res;
$res = $cb-&gt;(GET "/fizzbuzz.cgi?number=1");
is $res-&gt;code, '200';
is $res-&gt;content, '1';
$res = $cb-&gt;(GET "/fizzbuzz.cgi?number=2");
is $res-&gt;code, '200';
is $res-&gt;content, '2';
$res = $cb-&gt;(GET "/fizzbuzz.cgi?number=3");
is $res-&gt;code, '200';
is $res-&gt;content, 'Fizz';
$res = $cb-&gt;(GET "/fizzbuzz.cgi?number=4");
is $res-&gt;code, '200';
is $res-&gt;content, '4';
$res = $cb-&gt;(GET "/fizzbuzz.cgi?number=5");
is $res-&gt;code, '200';
is $res-&gt;content, 'Buzz';
$res = $cb-&gt;(GET "/fizzbuzz.cgi?number=15");
is $res-&gt;code, '200';
is $res-&gt;content, 'FizzBuzz';
};
done_testing();

書きやすいですね。

自動テストのためにメールサーバを一時的に起動する

メール本体を組み立てるロジックとメール送信部分が結合しているようなシステムを自動テストする際に、
一時的に立ち上がるメールサーバが欲しくなります。
また、メールサーバにどんなメールが届くのかをチェックできると嬉しいですね。

Test::TCPとNet::Server::Mail::SMTPを使えば、これを実現できます。

Test::TCPは、開いているポートをスキャンして、サーバ側のコードとクライアント側のコードを実行してくれるモジュールです。
Net::Server::Mail::SMTPは、メールサーバを簡単に書けるようにしてくれるモジュールです。各SMTPコマンドに対して処理を書くことができます。

この2つのモジュールを組み合わせれば、以下のようにテストを書くことができます。

 

use strict;
use warnings;
use Test::More;
use Test::TCP;
use Net::SMTP;
use Net::Server::Mail::SMTP;
use Email::MIME;
use Email::Address::Loose;
use Email::MIME::MobileJP::Parser;
my $from = 'test-from@example.com';
my $to   = 'test-to@example.com';
my $body = 'test-body';
my $mime = Email::MIME-&gt;create(
header =&gt; [
From =&gt; $from,
To   =&gt; $to,
Subject =&gt; 'test-subject'
],
attributes =&gt; {
content_type =&gt; 'text/plain',
charset      =&gt; 'ISO-2022-JP',
encoding     =&gt; '7bit',
},
body =&gt; $body
);
test_tcp(
client =&gt; sub {
my $port = shift;
eval {
my $smtp = Net::SMTP-&gt;new(
Host =&gt; 'localhost',
Port =&gt; $port,
Hello =&gt; '[localhost]'
);
$smtp-&gt;mail('test-from@example.com');
$smtp-&gt;to('test-to@example.com');
$smtp-&gt;data();
$smtp-&gt;datasend($mime-&gt;as_string);
$smtp-&gt;quit;
};
if ($@) {
warn $@;
}
},
server =&gt; sub {
my $port = shift;
my $sock = IO::Socket::INET-&gt;new(
LocalAddr =&gt; '127.0.0.1',
LocalPort =&gt; $port,
Proto     =&gt; 'tcp',
Listen    =&gt; 1,
) or die "Cannot open server socket: $!";
# チェック用のリクエストが来るのでパスする                                                                                  
$sock-&gt;accept();
while (my $remote = $sock-&gt;accept()) {
eval {
my $smtp = Net::Server::Mail::SMTP-&gt;new('socket' =&gt; $remote);
$smtp-&gt;set_callback(
'RCPT' =&gt; sub {
my $sess = shift;
my $rcpt = shift;
my ($email) = Email::Address::Loose-&gt;parse($rcpt);
my $domain = $email-&gt;host;
return (0, 513, 'Syntax error.') unless $domain;
return 1;
}
);
$smtp-&gt;set_callback(
'DATA' =&gt; sub {
my $sess = shift;
my $data = shift;
my $mail = Email::MIME::MobileJP::Parser-&gt;new($data);
my $from = $mail-&gt;from();
my $body = $mail-&gt;mail-&gt;body;
is $from-&gt;address, 'test-from@example.com';
like $body, qr/test-body/;
return (1, 250, 'message queued');
}
);
$smtp-&gt;process();
};
if ($@) {
warn $@;
$remote-&gt;close();
}
}
}
);
done_testing;

Test::TCPは、serverに記述されたメールサーバが起動したあとに、clientに記述されたプログラムを実行してくれるようになっています。
便利ですね。

WEBシステム組み込みに適したウィルスソフト

会社のパソコン、家のパソコン、ほとんどのパソコンはウィルスソフトを入れていると思いますが、WEBシステムで利用する組み込み用のウィルスソフトはなにが良いのでしょうか?

利用シーン

利用シーンとしては
ファイルアップロード、ダウンロードなどの仕組みのあるWEBシステム(サーバー側)に組み込む。
ファイルアップロードのタイミングでウィルスが混入しないように。
ファイルダウンロードのタイミングで、そのファイルがウィルスにかかっていないこと無いことをリアルタイムで保証する。
PHP,Perlなど様々な言語からも利用したい。

ストレージ全体のスキャンはそれほど重要視しない。(できればうれしい)

おすすめソフト

ずばり、シーズがお勧めするのは、
F-secure Linux コマンドラインエディション
です。

理由1:シンプルで強力

世の中にあるウィルスソフトには様々な機能が搭載されています。
ウィルスブロック
スパムブロック
ファイヤーウォール
個人情報保護
安全評価
バックアップ
・・・
などなど、
(広告に熱心なメーカーほど高機能な印象がありますが)基本的にWEBシステムの場合はウィルスブロック機能のみしか利用しませんので、シンプルでエンジンが強力なものが一番です。

エンジンの評価は、このあたりのサイトに譲るとします。
http://www.the-hikaku.com/security/08hikaku1.html

理由2:Linux対応

多くのウィルスソフトは、Windows用ですが、WEBシステムはほとんどがLinux上で動作するため、Linux版というのが必須条件です。

理由3:コマンドラインで使える

WEBシステムの場合、ファイルを”今”アップロードした人にメッセージを返す必要があるので、コマンドラインで解析結果を受け取れるのは非常に便利です。

コマンドラインも多様なオプションをつけられるので、強力なエンジンを最大限利用できます。

圧縮ファイルの階層数指定、除外指定など

qmailでbackscatter対策を行う

qmail環境でのbackscatter対策について。

以下のページを参考しました。
Stray Penguin – Linux Memo (qmail) – http://www.asahi-net.or.jp/~aa4t-nngk/qmail.html

qmailではローカル配送時に「存在しないメールアドレス」だった場合に
「正常にメールが配信できなかった」旨を送信元へ連絡します。

[code]
(例)
①メールサーバーAはhoge.comというドメインのメールアドレスを管理している

②abcde1234@hoge.com など、存在しないメールアドレス宛にメールが送信されてきた

③メールサーバーAは「そんなメールアドレスないよ」と送信元へ通知(バウンスメール)
[/code]

メールアドレスを打ち間違えて送信した場合などにバウンスメールが返ってくると間違いに気づく事ができるなどメリットはたくさんあるのですが、スパム配信者など悪意ある送信者の場合、メールアドレスがあるかないかに関わらず手当り次第にメールを送ってくる事があります。

このようなスパム配信者の送信元メールアドレス(リターンパス)は基本的には偽装されており、実際には「存在しないメールアドレス」が設定されている事がほとんどです。メールサーバーAはバウンスメールの送信がタイムアウトになるまで接続を試みる状態となってしまい、結果、キューにメールが溜り続け、メールサーバーはサービスを提供できない状態となってしまいます。

最近ではスパム業者はわざと存在しないメールアドレスへ送信し、送信元やリターンパスを真に送信したいアドレスと設定する事で、バウンスメール送信させる事を目的としている挙動も見られました。このような攻撃をされた場合、最悪の場合はRBL(ブラックリストサーバー)などに登録される恐れもあります。

このように、メールの仕様を逆手にとった攻撃が「backscatter攻撃」で、非常にむかつく攻撃です。
この問題に対応するために行った様々な対策です。

[対策1 バウンスメールを返さない設定にする]

簡単な対策としては、バウンスメールをまったく返さないという設定とする事が考えられます。qmailでは.qmail-defaultというファイルで存在しないメールアドレス宛のメールの挙動を決定しています。存在しないメールアドレス宛のメールを破棄する場合は.qmail-defaultを以下のように編集します。以下の例はvpopmailにて管理してるバーチャルドメインでの.qmail-defaultの例です。

[code]
vi .qmail-default

| /var/vpopmail/bin/vdelivermail ” bounce-no-mailbox

| /var/vpopmail/bin/vdelivermail ” delete
[/code]

このように設定する事でバウンスメールでキューがたまってにっちもさっちもいかなくなる事はなくなりました。ただこの設定を行うと、宛先アドレスを入力ミスしただけの場合でもバウンスメールは帰ってこない為、メールが届いていないのに届いていると誤解されてしまう恐れもあるので、設定の際は注意して下さい。

[対策2 badmailfromで差出人のメールアドレスでsmtpの時点で拒否する]

上記の対策だけでメール破棄が行われてるわけですが、qmail的にはすべて受け取っている事と同じです。そのため

・スパム業者は「なんでもメールおくれるなーこのサーバー。どんどんやったれ」と思われてどんどん数が増えてくる
・配送を行うプロセスを踏むのでSMTP配送の負荷がかかり、ひどいときにはSMTPに遅延が発生しはじめた

という問題が出てきました。
そこでqmailへの配送前の時点で破棄するようなルールを設定しました。特にqmailの再起動をする必要もなく、以下のファイルを作成しメールの受信を拒否する発信元アドレスを記述するだけで設定できます。
[code]
/var/qmail/control/badmailfrom
[/code]
以下のような書き方を行うと特定ホスト(hogehoge.com)からのメールを拒否します
[code]
@hogehoge.com
[/code]

[対策3 差出人/送信先のメールアドレスを正規表現で設定しsmtpの時点で拒否する]

上記の対策2では送信元メールアドレスやホストが一定である必要があり、さまざまなメールアドレスからの送信されてきた場合はお手上げでした。また、[ランダム文字列@yahoo.co.jp]など、有名メールサービスのメールアドレスを詐称された場合、ドメイン単位での拒否を行う事はサービス上できないという問題も発生しました。

そこでqmailに新たな機能を追加する為にqregex というパッチを導入しました。このパッチでは以下の機能がqmailに追加されます。

qregex配布元 The Arda Network – http://www.arda.homeunix.net/index.html

[code]
・badmailfrom の送信元を正規表現で記述できる
・badmailto と呼ばれるbadmailfromの逆の動作(送信先で拒否)を行う機能が追加
・badmailto でも正規表現は記述可能
[/code]

送信元メールアドレスに比べて送信先のメールアドレスは基本的に特定のドメインに集中する為、大きな成果をあげる事ができそうです。

導入方法

以下のサイトからパッチを取得してqmailにパッチをあてます
[code]
cd /path/to/qmail-1.03/
wget http://www.arda.homeunix.net/store/old_software/qregex-20040601.patch
patch < qregex-20040601.patch
[/code]
この際、すでにsmtpd-authなどのパッチを充てていた場合はMakefileqmail-smtpd.cなどで競合が起きてパッチが正常終了しません。正常終了しなかった部分は*.rejというファイルが出力されますのでそちらを見ながら手動でソースを編集する必要があります。

修正が終わったらインストールを実行。すでにqmailが起動している場合は落としておいて下さい。
[code]
make clean
make
make setup
make check
[/code]
インストール後、qmailを起動したら以下のファイルへメールの受信を拒否する送信先アドレスを記述するだけで設定できます。
[code]
/var/qmail/control/badmailto
[/code]
[code]
[a-z]+[0-9]+@hogehoge.com
[/code]
例えば上記のように記述すると英字+数字@hogehoge.com という送信先のメールはすべてsmtp接続時点で拒否する事ができるようになります。またこのパッチを充てたことで[対策2]でお伝えしたbadmailfromも正規表現によるフィルタが可能となります。

最後に

qmailはシンプルでバグも見られずとてもよいMTAなのですがITの進歩と共に追加されてきた機能などを追加するときはどうしてもパッチのつぎはぎになってしまうのが難点ですね。

今回、qregexというパッチを使用しましたが、vpopmail環境の場合はchkuserパッチというものも存在し、こちらはsmtp時点でメールアドレスが存在するか確認できるパッチのようですのでまた検証してみたいと思います。

chkuser 2.0 – http://opensource.interazioni.it/qmail/chkuser.html

Postfixではデフォルトの機能でbackscatter対策が可能なようです

Postfix 後方散乱 Howto
http://www.postfix-jp.info/trans-2.1/jhtml/BACKSCATTER_README.html

#isucon2にていいかんじにスピードアップできなかった話

ずいぶん遅くなってしまいましたが・・・。

なんでもありの「いい感じにスピードアップコンテスト」、isucon2に京都スイーツとして京都から参加しました。
僕は去年のisuconにも参加したのでこれで2回目となります。

メンバーと担当は以下の通り

kirishima氏 (DBまわりや全体の管理)
uchiyama氏 (プログラム周り)
僕 haraguchi (ミドルウェア、インフラ周りの役割。)

前日までにやっていた事

僕は前isucon環境にてnginx + SSI + memcachedをおさらい。
ネットワーク関係のチューニングとか設定ファイルとかを準備。
当日にやる作業とかをまとめていた。
(このメモファイルの抜粋は下の方に追記しました)

メンバーと対策を話した内容

・前回はPOSTが少なかったからかなりPOSTの多い出題になるんじゃないだろうか
・それでもnginx + SSI + memcachedはどこかで使えるんじゃね?
・Redis使おうか・・! → 誰も使った事ないからやめとこ
・前回は全員が一斉に思い思いのチューニングしてfailの原因がわからんくなってしまったから確実に計測していこう
・gitで管理
IRC使う
・余裕があればmysql5.6にしたいね

前回のisuconではMySQLのHandlerSocket使おうぜ!とかvarnishに熱中していた、とか
ジョブキューするworkerをプログラマが自作して持ち込んだり。
僕たちにとっては挑戦と言える準備をしていたのですが
なにがなんだかわからないままFailで終了したという痛い経験がありました。
今回は着実にボトルネックをつぶしていこう、と心に刻んでの参加。

でもこういった熱くなる準備をしなかったのは失敗だったな、、とも思っています。
だって業務ではなかなかできないんだもの・・・醍醐味じゃない。

お題

リバースプロキシサーバ、APPサーバ(*2)、DBサーバー の3台構成。
NHN48とはだいろクローバーZなるアイドルグループのコンサートチケットを販売するサイト。
チケット購入が大量にリクエストされるのでなんとかさばかないと・・・!
というチューニング。

11:00くらい

スタート時点全サーバーで鍵設定して手元のmacから「rev02」とかだけでログインできるように設定。
前回のisuconでは無線の調子が不安定で「鍵設定は必須・・・っ!」って思ってたんだけど
今回のisucon2では無線の状態がビンビンで一度も接続が切れなかった。
んで結局、繋ぎなおす事は1回もなかった。無駄だった。

DBのダンプとか一応プログラムのソースのバックアップを行ったりしている間に
gitいれてリポジトリ作成。(uchiyama)
ボトルネックを調査する為にも各サーバーにいろいろな計測ツールを入れたりする。(kirishima)

そんな感じで初回ベンチを走らせてもらいボトルネックの調査を開始。

~12:00

rev以外のサーバーは全体的に負荷が高かったように思えたけどDBがやっぱりひどい。
でもスローログが出てない。
1秒じゃでないので0.5秒、0.3,0.2と減らしていき、0.1秒くらいで出るようになった。
このあたりのSQL改善に kirishima氏 や uchiyama氏 が当たる。

SQL改善の間に勝手にチューニングしちゃうとまた前回の二の舞になるので我慢して調査に専念する。
全体のインフラ周りの設定確認や、ミドルウェアの設定をサーバー上で作って差し替えれる状態にしたりの作業。
その他スコアに影響与えない範囲でnginxやmemcachedとかを野良ビルドだけしとく。
インフラ周りは
・ローカルポートの使用範囲は多くなってたり (/etc/sysctl.conf)
SElinuxはOFFだったり(これはちょっと問題あったらしいですがw)
・ip_conntrack_maxも5万くらいに設定されていた。
そうそうボトルネックになりそうなところはないように見えた。

この時、気づいたらチケット取得が順番に並んでて笑ったw
ORDER BY RAND()を外してもFailにならなかったそうな。
誰がやったのかなー?

~14:00

SQL周りの修正を行う事でスコアが伸びていく。チケットがたぶん1100枚くらい。でもそこから伸び悩む。
ここで本来なら腰を据えて次のボトルネックを探すべきだったのだが、
「とりあえずmemcacheでキャッシュするか」と、プログラム修正に入る。
思えばここが一番の失敗だったなーと反省・・・。

プログラム修正中にApache、ネットワーク関係、nginxに変えてみるとかの作業とベンチをしてた。

・ネットワーク関係のチューニングを行う
 → ticketが100くらい下がる

Apache
 → workerにしてみたりリミットを増やしたり
 → ticketが100くらい下がる

・nginxに変えてみる
 → ticketが800くらい下がる
 → これは罠に違いない!って事で一瞬で使われる事がなくなった。(orz)

・静的なファイルはrevサーバーで返す
 → ticketが100くらい下がる

どれも惨敗な結果だったのでApache preforkに戻して同時接続数をしぼると
一番パフォーマンスがよかったのでそのように設定。
ここの部分はすごく納得がいかなくて最後までもんもんとしてました。

他の方のブログを見ていると恐らくDBサーバーがデッドロック起こしててrevサーバーががんばっちゃうほど
ベンチ結果が悪くなっていったって事なんだろうか。
でも静的ファイルをそのまま返すとスコアがあがるとの事だったけど僕の環境では下がってた。。。
もしかしたらpostが減っただけでgetの項目は増えてたのかなぁ。そこまで見てなかったなぁ。
まぁまぁ・・これは一人isuconで楽しみな内容の一つ。

こんな感じに僕がもんもんとしてる間
uchiyamaがばりばりとプログラムを書いていて
kirishimaのDB周りのチューニングをばりばりやってました。
詳細は彼らがブログで語ってくれるのではないかと。

そういえばこのあたりで、プログラムの修正を行うと2台あるappを同期してsuperviserdを再起動しないといけないので
rsyncで2台のAppサーバーの同期とsuperviserdを再起動するようなシェルスクリプトを作成したりしてたのを思い出しました。
つまり基本的に小間使い的な立ち位置。

~16:00

左メニューやスゲー数のテーブルが並んだチケット取得状況の部分をmemcachedにつっこむ作業が完了。
でもベンチ結果は変わらず。逆に下がり気味。
ストレージエンジンをmemcacheにしたりIndex張ったり
さらに改修してたところでfailばかりになってしまってプチパニック。
このあたりくらいからIRCがまったく使われなくなってくるw

~18:00

failで終了はイヤダー! って事でベンチが完走した状態までgitで戻す。DB関係もdumpから元に戻す。
そこからベンチが完走する事を確認してmemcachedにつっこめていなかった部分をちょこちこ改修。
DB関係のチューニングも再試行。
とりあえずはちゃんと動く状態にて終了。
結果的にはSQLの改修くらいしかスコアに貢献できてないという散々な結果に。

それでもよかった、楽しかった

結果的にはあまりチューニングできなかったけど
「ちゃんと動くもの」として結果を残せたのはよかったとは思ってます。
うまくいかない事もあり悔しかったけどisuconは本当に楽しいです。
準備して、isuconに挑んで、一人isuconして納得できるチューニング!までがisuconだと思ってます。

今回の大きな反省は、3人で「確実にボトルネックをつぶしていこう!」という話をしていたのに
安易にmemcacheにつっこむ改修をしちゃったところかな、、と。
経験の差もあるとは思うんですが、「確実にボトルネックをつぶす」という作業が
とても基本的なのにいかに難しい事か・・・思い知らされました。

懇親会もみなさんがすごい人ばかりで話をする機会なんかなかなか持てない方々ばかり。
とてもいい刺激になりました。

最後になりましたが運営の方々は本当にお疲れ様でした。
とても楽しい時間を過ごせて感謝でいっぱいです。
また、今回もisucon参加に会社から京都⇔東京の旅費や宿泊費を頂きました。
これだけ支援してくれる会社はなかなかないのでしょうか。とても感謝です。

次があるならまた参加したいです。(心から!)

せっかくなので・・・

僕がisuconの準備で用意してたテキストファイルをのせます。
ほんとにメモ程度のもので信憑性も詳細な説明もありませんが。

鍵作成

[code]
ssh-keygen -t dsa -N “” -f /root/.ssh/rev02
/root/.ssh/authorized_keys にpubを置く
[/code]

telnetいるかも

CentOS6にはtelnetが入っていないかもしれない。
memcacheのステータスを見るのに必要かもしれないからない場合は面食らわないようにー

[code]
yum -y install telnet
[/code]

いらないサービスをストップ

[code]
chkconfig auditd off
chkconfig autofs off
chkconfig avahi-daemon off
chkconfig bluetooth off
chkconfig cups off
chkconfig firstboot off
chkconfig gpm off
chkconfig haldaemon off
chkconfig hidd off
chkconfig isdn off
chkconfig kudzu off
chkconfig lvm2-monitor off
chkconfig mcstrans off
chkconfig mdmonitor off
chkconfig messagebus off
chkconfig netfs off
chkconfig nfslock off
chkconfig pcscd off
chkconfig portmap off
chkconfig rawdevices off
chkconfig restorecond off
chkconfig rpcgssd off
chkconfig rpcidmapd off
chkconfig smartd off
chkconfig xfs off
chkconfig yum-updatesd off
[/code]

コンソールこんなにいらないよ

/etc/inittab

[code]
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6
[/code]

本番ではきれいに1個だけだった。

selinuxを無効

[code]
setenforce 0
[/code]

vi /etc/sysconfig/selinux
[code]
SELINUX=enforcing

SELINUX=disabled
[/code]

ネットワーク関係のチューニング

リバースプロキシサーバーならこれくらいの設定をしちゃえばネットワーク関係の制限でボトルネックとはなりにくいだろう・・・
という設定

vi /etc/sysctl.conf
[code]
net.ipv4.ip_local_port_range = 10240 65000
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 3
net.core.netdev_max_backlog = 30000
net.ipv4.tcp_no_metrics_save=1
net.core.somaxconn = 262144
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_max_tw_buckets = 56384
[/code]

[code]
sysctl -w
sysctl -p
[/code]

iptablesを消す

ip_conntrack_maxがひっかかる場合。

[code]
/etc/init.d/iptables stop
chkconfig iptables off
chkconfig iptables –list
iptables -F
[/code]

memcache関連

yum install libevent-devel
[code]
cd /usr/local/src
wget http://memcached.googlecode.com/files/memcached-1.4.15.tar.gz
tar zxvf memcached-1.4.10.tar.gz
cd memcached-1.4.10
./configure –enable-64bit
make
make install
[/code]

中に入ってるキーを一覧するperlワンライナー
[code]
perl -MCache::Memcached -e ‘$s=”localhost:11211″;$m=new Cache::Memcached({servers=>[$s]});$res=$m->stats(“items”);$i=$res->{hosts}{$s}{items};@a=split(“€n”,$i);while(){if($=~/items:([0-9]+)/){$s{$1}=$}};foreach $key (keys %s){$cm=”cachedump $key 100″;$res=$m->stats($cm);print “— €n”.$cm.”€n”;print $res->{hosts}{$s}{$cm}}’
[/code]

再起動時にキャッシュに突っ込むシェルスクリプト

アプリの構成によるけどこんな感じにしておけばキャッシュをつっこんでおけるかも?

[code]
!/bin/sh

for i in seq 1 1 3000
do
wget http://app01/article/$i -O –
done
[/code]

nginx関連

[code]
wget http://nginx.org/download/nginx-1.3.8.tar.gz
./configure
make
make install
[/code]

nginx.confのひながた

[code]
worker_processes 4;
user root;

events {
worker_connections 10000;
}

http {
#access_log /dev/null;
error_log /dev/null;
include mime.types;
default_type text/html;
open_file_cache max=10000;
sendfile on;
keepalive_timeout 0;

    upstream hoge {
server xxx.xxx.xxx.xxx:5000;
server xxx.xxx.xxx.xxx:5000;
keepalive 1000000;
}
upstream memcached {
server 127.0.0.1:11212;
keepalive 1000000;
}
server {
listen       80;
server_name  127.0.0.1;
#静的ファイルはrevサーバーに移動しておく
location /images {
root /path/to/staticfiles;
}
#特定のURLのみキャッシュをコントロールする場合 (SSI使って切り分けた先のキャッシュ)
location /left_menu {
set $memcached_key "/left_menu";
memcached_pass memcached;
default_type text/html;
error_page         404 502 = @fallback;
}
location / {
if ($request_method = POST) {
proxy_pass http://hoge;
break;
}
ssi on;
ssi_silent_errors on;
set $memcached_key $uri;
memcached_pass     memcached;
default_type       text/html;
error_page 404 =200 @fallback;
error_page 302 =302 @fallback;
error_page 502 =502 @fallback;
}
location @fallback {
proxy_pass         http://hoge;
}
}

}
[/code]

mysql関連

とりあえずダンプとっておく
[code]
mysqldump -pXXXXX -u root -x -A > /home/my.dump
[/code]

スロークエリの設定を動かしながら行う。
mysqlにログインして以下のような感じで
[code]
set global slow_query_log = 1;
set global slow_query_log_file = ‘/path/to/slow.log’;
set global long_query_time = 1;
[/code]

mysql5.6を入れる事になる場合・・・
[code]
wget http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.7-rc-linux2.6-x86_64.tar.gz/from/http://cdn.mysql.com/
wget http://downloads.mysql.com/snapshots/pb/mysql-5.6-labs-innodb-memcached/mysql-5.6.4-labs-innodb-memcached.tar.gz
[/code]

設定用のsqlを流し込む

[code]
mysql < scripts/innodb_memcached_config.sql
[/code]

mysqlにログインしてmemcacheを動かす。
11211で立ち上がるので元のmemcacheはポートかえとくべき。
[code]
install plugin daemon_memcached soname "libmemcached.so";
show plugins;
[/code]

Page 1 of 2

© SEEDS Co.,Ltd.