ロードバランサとしての pound と nginx

自宅で運営してるサイトの負荷が、結構高い。
で、試しに pound を導入したんだけど、アクセス数が上がると、どうにも安定しない。
結局 nginx でのロードバランスに落ち着いたんだけれど、そこまでの経緯を書きとめる。


いろいろチューニングして行く上で、pixiv の中の人の資料が非常に、非常に、参考になった。ありがとうありがとう。
pixivのインフラを支える技術 techsemi20090925_03_pixiv_kamipo.pdf


Pound でアクセスが増えると Too many open files エラー

こいつがめっちゃ頻発します。解決策としては、起動スクリプトなんかに

ulimit -n 50000

って書いてやればいい。という検索結果が山ほど出るんですが、それでもエラーが止まらない。
試しに、File Discriptors のグラフを取ってみました。
予想では、徐々に数値が上がっていって、制限値に到達したところでエラーが出始めると思ってたんです。
でも、全然そんな感じじゃない。
あるタイミングから、ばいーんと跳ねる。もちろん、ulimit -n 50000 してるので、許容量内です。

  1. 実サーバがアクセス過多で死ぬ?
  2. pound 側で何らかの問題が発生
  3. キューが詰まり始める?
  4. File Discriptors ばいーん現象

その結果、Too many open files エラーが発生しているっぽい感じ。
なので、File Discriptors の制限値が原因ではないようで。。
エラーとしては、こんなのが大量に出てます。

error copy chunk cont: Connection timed out
copy_chunks flush error: Connection timed out

ソースを見てみても、原因は分からず。
一度こうなっちゃうと、restart しないと復帰してくれないので、大変面倒でした。

Pound では reset by peer エラーがたくさん記録される

reset by peer 系のエラーは、クライアントがページのロードが完了する前に
別のページへ移動しようとした場合などに起こるみたいです。
なので、記録しないようにしたかったのです。
しかし、LogLevel 0 とかにしても、こいつは記録されるようで。。
ログファイルの容量も大変なことになるし。
仕方ないので、ソースを少し書き換えて対応しましたが、
微妙に応答速度が遅くなってしまいました('A`)

svc.c の logmsg 関数に追記した内容。
    va_end(ap);
    /* ここから追加 */
    if ( strstr( buf, "reset by peer" ) != NULL ) {
        return;
    }
    /* ここまで追加 */
    if(log_facility == -1) {

機能面の不足

pound にはないけど nginx にはある機能として、次のものがあります。

gzip で圧縮して送信

やりとりする情報を、gzip で圧縮してからクライアントに返す機能がついている。
また、gzip に対応していないクライアントにはそのまま返す、といった機能も備わっている。
gzip での送信を ON にした結果、通信量がかなり減った。
LB鯖なので、inbound に対して outbound がかなり減ることになる。

ファイルのキャッシュ

nginx では、拡張子などを指定して、ファイルをキャッシュする機能がある。
この機能を用いると、バックエンドのサーバとの通信量を減らすことができる。
これも実施した結果、通信量がガッツリ減った。
LB鯖は、キャッシュされている分、inbound が減っている。
バックエンド鯖は、通信量が減ることになる。イイネ!

結論

結論としては、やはり、nginx よいねーという感じ。
pound も悪くはないんだけど、使いどころがちょっと違ったのかなぁ。
開発も盛んな nginx を選んだ方が、無難なのかもしれない。

おまけ1 poundのインストール手順

pound のインストール
yum -y install openssl-devel
cd /usr/local/src
wget http://www.apsis.ch/pound/Pound-2.6.tgz
tar zxvf Pound-2.6.tgz
cd Pound-2.6
./configure
make
make install
vi /usr/local/etc/pound.cfg
LogLevel 3 # 必要に応じて変更、0だとログを記録しない。
LogFacility local1

ListenHTTP
  Address *.*.*.* # ロードバランサの待ち受けIP
  Port 80 # ロードバランサの待ち受けポート
  Service
    # server1
    BackEnd  # BackEnd 〜 End 間に、実サーバを記述。
      #HeadRequire "Host: .*www.server1.com.*" # ドメイン名で振り分けるときは、このように記述する。
      #HeadDeny    "Host: .*www.server0.com.*"
      Address  *.*.*.*
      Port     80
      Priority 1  # 優先度を1〜9で指定。
    End
    
    # server2
    BackEnd
      Address  *.*.*.*
      Port     80
      Priority 1
    End

  End
End
vi /etc/rc.d/init.d/pound # 起動スクリプトの作成

適当に拾ってきたもの。

#!/bin/sh
#
# pound
#
# chkconfig: 345 85 15
# description: reverse-proxy and load-balancer
#

# Source function library
. /etc/rc.d/init.d/functions

# Get network config
. /etc/sysconfig/network

# Pound Directory
POUND="/usr/local/sbin/pound"
CFG="/usr/local/etc/pound.cfg"

# See how we were called.
case "$1" in
  start)      # Check if the normal service is already running?
      if [ ! -f /var/lock/subsys/pound ]; then
          echo "Starting pound:"
          $POUND -f $CFG
          RETVAL=$?
          [ $RETVAL -eq 0 ] && touch /var/lock/subsys/pound
          echo ${base}
      else
          #msg_Already_Running pound
          echo "pound already started."
          # exit 1
      fi
      ;;
  stop)
      # Stop daemons.
      if [ -f /var/lock/subsys/pound ]; then
          #msg_stopping pound
          echo "Stopping pound:"
          killall $POUND
          rm -f /var/lock/subsys/pound > /dev/null 2>&1
          echo
      else
          echo "pound is not running."
          exit 1
      fi
      ;;
  restart|reload)
      $0 stop
      $0 start
      ;;
  configtest)
      $POUND -c -v -f $CFG
      ;;
  *)
      echo "usage: pound {start|stop|configtest|restart}"

      exit 1
      ;;
esac

exit $RETVA
chmod +x /etc/rc.d/init.d/pound
ログ出力設定

rsyslog ではなく syslog を使っている場合は、rsyslog を syslog に読み替えて設定する。

vi /etc/rsyslog.conf
# *.info;mail.none;authpriv.none;cron.none; /var/log/messages
*.info;mail.none;authpriv.none;cron.none;local1.none /var/log/messages # 変更

# 以下の行も追加
local1.*                                  /var/log/pound
service rsyslog restart
pound の起動
service httpd stop # Apache が起動しているなら、停止しておく。
service pound start # Pound の起動
chkconfig pound on # Pound の自動起動設定
Web サーバ (分散先サーバ) のアクセスログ設定
vi /etc/httpd/conf/httpd.conf
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined # 変更
service httpd restart

おまけ2 nginx のインストール手順

nginx インストール
yum -y install pcre-devel
cd /usr/local/src
wget http://nginx.org/download/nginx-1.2.4.tar.gz
tar zxvf nginx-1.2.4.tar.gz
cd nginx-1.2.4
./configure
make
make install
nginx 設定
mkdir /var/log/nginx
vi /usr/local/nginx/conf/nginx.conf
worker_processes 4;
pid /var/run/nginx.pid;


events {
    worker_connections 1024;
}

http {
    proxy_cache_path /var/cache/nginx/static_file_cache levels=1:2 keys_zone=cache_static_file:128m inactive=1d(←1日アクセスがなかったら削除) max_size=512m;
    proxy_temp_path /var/cache/nginx/temp;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    include /usr/local/nginx/conf/mime.types;
    default_type application/octet-stream;


    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;


    gzip on;
    gzip_disable "msie6";


    include /etc/nginx/conf.d/*.conf;

    server {
        listen       *.*.*.*<受付IP>:80;
        server_name  www.hoge.com;

        location / {
            proxy_redirect off;

            proxy_set_header        Host            $host;
            proxy_set_header        X-Real-IP       $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;

            set $do_not_cache 0;
            if ($request_method != GET) {
                set $do_not_cache 1;
            }
            if ($uri !~* ".(jpg|png|gif|jpeg|css|js|swf|pdf|html|htm)$") {
                set $do_not_cache 1;
            }

            proxy_no_cache $do_not_cache;
            proxy_cache_bypass $do_not_cache;
            proxy_cache cache_static_file;
            proxy_cache_key $scheme$host$uri$is_args$args;
            proxy_cache_valid 200 1h;
            proxy_cache_valid any 1m;

            proxy_pass http://backend;
        }

    }

    upstream backend {
        # server1
        server *.*.*.* weight=1;

        # server2
        server *.*.*.* weight=2;

    }

}
起動スクリプトの作成
vi /etc/init.d/nginx

適当に拾ってきたもの。

#!/bin/sh
#
# nginx - this script starts and stops the nginx daemon
#
# chkconfig:   - 85 15 
# description:  Nginx is an HTTP(S) server, HTTP(S) reverse \
#               proxy and IMAP/POP3 proxy server
# processname: nginx
# config:      /usr/local/nginx/conf/nginx.conf
# config:      /etc/sysconfig/nginx
# pidfile:     /var/run/nginx.pid

# Source function library.
. /etc/rc.d/init.d/functions

# Source networking configuration.
. /etc/sysconfig/network

# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0

nginx="/usr/local/nginx/sbin/nginx"
prog=$(basename $nginx)

NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"

[ -f /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx

lockfile=/var/lock/subsys/nginx

make_dirs() {
   # make required directories
   user=`$nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -`
   if [ -z "`grep $user /etc/passwd`" ]; then
       useradd -M -s /bin/nologin $user
   fi
   options=`$nginx -V 2>&1 | grep 'configure arguments:'`
   for opt in $options; do
       if [ `echo $opt | grep '.*-temp-path'` ]; then
           value=`echo $opt | cut -d "=" -f 2`
           if [ ! -d "$value" ]; then
               # echo "creating" $value
               mkdir -p $value && chown -R $user $value
           fi
       fi
   done
}

start() {
    [ -x $nginx ] || exit 5
    [ -f $NGINX_CONF_FILE ] || exit 6
    make_dirs
    echo -n $"Starting $prog: "
        ulimit -n 50000 # ← 念のため。
    daemon $nginx -c $NGINX_CONF_FILE
    retval=$?
    echo
    [ $retval -eq 0 ] && touch $lockfile
    return $retval
}

stop() {
    echo -n $"Stopping $prog: "
    killproc $prog -QUIT
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}

restart() {
    configtest || return $?
    stop
    sleep 1
    start
}

reload() {
    configtest || return $?
    echo -n $"Reloading $prog: "
    killproc $nginx -HUP
    RETVAL=$?
    echo
}

force_reload() {
    restart
}

configtest() {
  $nginx -t -c $NGINX_CONF_FILE
}

rh_status() {
    status $prog
}

rh_status_q() {
    rh_status >/dev/null 2>&1
}

case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart|configtest)
        $1
        ;;
    reload)
        rh_status_q || exit 7
        $1
        ;;
    force-reload)
        force_reload
        ;;
    status)
        rh_status
        ;;
    condrestart|try-restart)
        rh_status_q || exit 0
            ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
        exit 2
esac
nginx の起動
service httpd stop # Apache が起動しているなら、停止しておく。
service nginx start # nginx の起動
chkconfig nginx on # nginx の自動起動設定

余談

分散する程度にサーバの台数が増えてくると、全てのサーバに対して作業するのは何かと面倒。
capistrano を入れたら、世界が変わりました。便利すぎる。この辺の記事は、また縁があれば。


あと、全文検索についても見直し。
mysqlhoge LIKE "%keyword%" みたいなクエリをずっと使ってきたんだけど、、、
tritonn で match(hoge) against(keyword) に変えたら、DBサーバが賢者モードになりました。
最初から tritonn いったくでいいような気がするレベルだなぁ。
これもまた機会があれば、記事にするかも。