Top / EBUG勉強会 / 20141115_httpdをスケールさせる

OpenBSDでhttpdをスケールさせる

川俣 吉広
EBUG勉強会 @ 新潟, 2014/11/15

発端

以前この勉強会で紹介したウェブ/メルマガのサーバが、アクセス過多によりトップページが見れなくなっているという報告を受ける。

  • トップページが「500 Internal Server Error」を表示していることを確認。
  • SSHでログインし、状況調査
  • httpd (Apache) の error_logを確認
    • 子プロセスが起動できなくなっている。
      [Mon Nov  3 15:15:51 2014] [error] [client xxx.xxx.xxx.xxx] (35)Resource temporarily 
         unavailable: couldn't spawn child process: /var/www/docs/pub/index.cgi
    • 静的なページは正常にアクセスできる
    • ロードアベレージは、1時間平均で0.8〜1.0, 瞬間最大で7〜8程度
    • CPU usageはピークで85%程度
    • スワップは0MB … 起動から一回も使われていない。
    • ネットワークのスループットは10Mb/sを少し超える程度。
  • ... CPU, メモリなどのリソースは枯渇していないが、プロセスの起動数になんらかの制限がかかっている。
  • ... サーバは2台が並列稼動しているが、両方とも同様な状況。

応急対応

  • /index.cgi を /index.html に変更
    → User Agent によって、ケータイ端末をリダイレクトしていた。
  • 以下のように設定値を変更し、httpdを再起動
    /etc/login.conf
    default:\
            :path=/usr/bin /bin /usr/sbin /sbin /usr/X11R6/bin /usr/local/bin:\
            :umask=022:\
            :datasize-max=512M:\
            :datasize-cur=512M:\
            :maxproc-max=256:\
            :maxproc-cur=128:\
            :openfiles-cur=128:\
            :stacksize-cur=4M:\
            :localcipher=blowfish,6:\
            :ypcipher=old:\
            :tc=auth-defaults:\
            :tc=auth-ftp-defaults:
    
     # www - resource definition for web processes
     #
     www:\
    -       :maxproc=512:\
    +       :maxproc-max=384:\
    +       :maxproc-cur=384:\
            :tc=default:\
    設定をチェックした段階では、defaultログインクラスではmaxproc-max, maxproc-curがそれぞれ 指定されているのに対し、wwwログインクラスではmaxproc指定のみのため、プロセス最大数の設定数が 上書きされているかどうか不明だったため、maxproc-max, maxproc-curを用いる設定に書き直した。

/var/www/conf/httpd.conf

 # It is intended mainly as a brake to keep a runaway server from taking
 # the system with it as it spirals down...
 #
-MaxClients 256
+MaxClients 192

プロセス数の上限で制限がかかっていると仮定し、httpdのプロセス数を減らすことで 子プロセス(*.cgi)の数を増やせるようにした。

→ 30分ほどで不具合は解消

  • アクセスそのものが減少し
  • 上記対策が効を奏した(かどうか、本当は不明)

恒久対策

ProcessModel.jpg

状況をモデリングする

  • httpdに関しては、最大でhttpd.confで定義されたMaxClients個(右図ではNh個)のプロセスが起動する。
  • そのhttpdのうちの幾つかがNp個の子プロセス(*.cgi)を起動する。
  • httpdとその子プロセスの総数(Nh+Np)の上限が login.conf の wwwエントリにある maxproc 設定値で制限される。

→httpd.confのMaxClients、 login.confの maxprocを幾つにすればよいか?


0005mem.gif

設定値の算定

  • プロセス一つ当り、どの程度メモリを消費するか。
  • 動作記録を基に概算する。
    ... 11/8に使用メモリの大幅な変動がある。 これは設定変更の反映のためにhttpdの停止・起動を行ったため。

    ... サーバの実装メモリは4096MB

    ... sysctlによりkern.bufcachepercentが75%に設定されている。つまり、3GBはバッファキャッシュとして使用されている。

    ... 左のグラフをみると、httpdの停止時はhttpdプロセス約200個ほどが停止し、これにより実装メモリの約2割程が解放されている。

    ... よって 4000*0.2 / 200 = 4。大体プロセス一個あたり4MB消費 ... topコマンドの SIZE及びRSS表示値 とも大体一致.

バッファキャッシュ3GBのうち、2GBを取り崩してhttpd用に割り当てるとすると
2048 / 4 = 512

とりあえず、MaxClientを512、ワーストケースとして *.cgiが同数起動されると仮定して、wwwのログインクラスのmaxprocを1024として 動作をみる。

実施と検証

httpd.conf、MaxClientsの設定

MaxClientを512にして実行

# /usr/sbin/httpd -u -DSSL
WARNING: MaxClients of 512 exceeds compile time limit of 256 servers,
 lowering MaxClients to 256.  To increase, please see the
 HARD_SERVER_LIMIT define in src/include/httpd.h.
#

httpd.confの設定値にかかわらず、MaxClientsは256を超えないように ハードコーディングされている。(Apache 2.xでは、ServerLimitディレクティブで 設定可)

ということで

/* Limit on the total --- clients will be locked out if more servers than
 * this are needed.  It is intended solely to keep the server from crashing
 * when things get out of hand.
 *
 * We keep a hard maximum number of servers, for two reasons --- first off,
 * in case something goes seriously wrong, we want to stop the fork bomb
 * short of actually crashing the machine we're running on by filling some
 * kernel table.  Secondly, it keeps the size of the scoreboard file small
 * enough that we can read the whole thing without worrying too much about
 * the overhead.
 */
#ifndef HARD_SERVER_LIMIT
#define HARD_SERVER_LIMIT 256
#endif

この部分変更してコンパイル

# cd /usr/src/usr.sbin/httpd
# make -f Makefile.bsdwrapper obj
# make -f Makefile.bsdwrapper clean
# CFLAGS="-DHARD_SERVER_LIMIT=4096" make -f Makefile.bsdwrapper
     :
     :
# make -f Makefile.bsdwrapper install
# /usr/sbin/httpd -V
Server version: Apache/1.3.29 (Unix)
Server's Module Magic Number: 19990320:15
Server compiled with....
(略)
-D DYNAMIC_MODULE_LIMIT=64
-D HARD_SERVER_LIMIT=4096
-D HTTPD_ROOT="/var/www"

login.confのmaxproc設定

loginclass.jpg
# www - resource definition for web processes
#
www:\
       :maxproc-max=1024:\
       :maxproc-cur=1024:\
       :tc=default:\

この部分の課題 ... setuidした子プロセスについてはlogin classはどうなる(右図)?

(11/21, 後日検証した結果を追記)

運用の監視

SAG(Server Activity Grapher)にプロセス数の項目を組み込んで監視

0005procs.gif
#
#  t0005  -  executing every 5 minutes
#
#  $Id: t0005,v 1.2 2004/05/17 01:21:52 cvs Exp $

(echo =dt $DAYTIME
 echo =mem
 cat /proc/meminfo
 echo =end
 echo =procs `sysctl -n kern.nprocs; pgrep httpd | wc -l`
 echo =sensors `sysctl -n hw.sensors.cpu0.temp0`
 ) >> var/rl0005

この状況で様子を見る。

さらに...

将来的にさらにプロセス数の増加が必要な場合を想定し、どの程度まで増やせるかやってみる。

  • httpd 2048程度に設定。
    ... pgrep httpd | wc -l
  • 実際には1000少しまでしか設定できず。
  • 他アカウントでログイン不可となる。
  • sysctl kern.maxproc が1310となっていた。
  • 増加させてみる。
    # sysctl -w kern.maxproc=8192
    # sysctl kern.maxproc
    kern.maxproc=8192
  • 再試行 ... pgrep httpd | wc -l
  • プロセス数増えたが、設定値どおりにならず ... 2000弱
    httpdでやってるとかったるいので、簡単なスクリプト書いてテスト;
    maxchild-test.sh
    #!/bin/sh
    if [ "$1" = "" ]; then
      childlevel=1
      echo -n "forking "
    else
      childlevel=$((1+$1))
    fi
    
    echo -n " $childlevel"
    if $0 $childlevel; then
      :
    else
      echo "failed at $childlevel"
    fi
    実行結果
    # ./maxchild-test.sh
    forking 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
    36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 
    71 72 73 74 75 76 77 78 79 80 (略) 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869
    1870 1871 1872 1873 1874^C/home/kaw/maxchild-test.sh[17]: cannot fork - try again
    ^C
    #

このとき、syslog (/var/log/messages) に出力されることを発見。

Nov 14 17:56:37 mediakaw /bsd: process: table is full

カーネル内を探す;

# grep -rs ': table is full' /usr/src/sys
Binary file /usr/src/sys/arch/i386/compile/GENERIC/bsd matches
Binary file /usr/src/sys/arch/i386/compile/GENERIC/subr_prf.o matches
/usr/src/sys/kern/subr_prf.c:   log(LOG_ERR, "%s: table is full\n", tab);
#

/usr/src/sys/kern/subr_prf.c内のtablefull関数が実行されている;

# grep -rs 'tablefull.*process' /usr/src/sys
/usr/src/sys/kern/kern_fork.c:                          tablefull("process");

kern_fork.cの該当部分

    if ((flags & FORK_THREAD) == 0) {
            if ((nprocesses >= maxprocess - 5 && uid != 0) ||
                nprocesses >= maxprocess) {
                    static struct timeval lasttfm;

                    if (ratecheck(&lasttfm, &fork_tfmrate))
                            tablefull("process");
                    nthreads--;
                    return (EAGAIN);
            }
            nprocesses++;

maxprocessをどこで初期化しているか?

# grep -rs 'maxprocess.*=' /usr/src/sys
/usr/src/sys/conf/param.c:int   maxprocess = NPROCESS;

NPROCESSは?

# grep -rs '#.*define.*NPROCESS' /usr/src/sys
/usr/src/sys/conf/param.c:#define       NPROCESS (30 + 16 * MAXUSERS)

ということで

  • カーネルレベルでのプロセス最大数はsysctlのkern.maxprocで設定可
  • ただし、kernel configのMAXUSERSで規定される「真の最大値」が存在する。 この値はsysctlで変更不可(変更にはカーネルの再コンパイルが必要)。

11/21追記: login.confでのmaxproc設定関連について

前項「実施と検証」/「login.confのmaxproc設定」について示された疑問を実際に設定を行うことで調査した。

調査方法

  • 3種類のログインクラスエントリ, class0, class1, class2を作成する。
  • class1, class2ともtcによりclass0の設定を引き継ぐようにする。
  • ユーザアカウントuser1, user2を作成し、それぞれのログインクラスをclass1, class2とする。
  • user1, user2アカウントにログインし、ulimit にてmaxprocの表示と設定を行ってみる。

調査1: login.confの記述方法について

以下のケースAでは、ログインクラス class1 でログインした場合、設定値は幾つになるか。
さらに、ケースBではどうか。

ケースA

class0:\
    :maxproc-cur=100:\
    :maxproc-max=200:

class1:\
    :maxproc=150:\
    :tc=class0:

ケースB

class0:\
    :maxproc=300:

class1:\
    :maxproc-cur=150:\
    :maxproc-max=250:\
    :tc=class0:

結果:ケースA、ケースBのどちらでも、class1のログインクラスではclass1自身で書いた値が有効となり、class0で書いたエントリは無効となる。

調査2:

以下の設定で、user1 (ログインクラス:class1)からuser2 (ログインクラス:class2) にsuidした場合、maxprocはどうなるか?

class0:\
    :maxproc=200:

class1:\
    :maxproc=225:

結果:class1の設定値に関係なく、class2の値が新規に設定される。

調査3:

ulimitの挙動について

:maxproc-cur=A:\
:maxproc-max=B:

上記のように記述した場合、ログイン直後の最大プロセス数はAに設定されている。 シェルのビルトイン・コマンドulimit -pで変更する場合、 プロセス数を増加する方向には「Bを超えない範囲、かつ一回だけ」変更できる。
プロセス数を減少させる方向には何回でも可能。
(以上の挙動は使用するシェルに依存する可能性あり)

また、

:maxproc=C:

と記述した場合は、

:maxproc-cur=C:\
:maxproc-max=C:

と書いた場合と等価であり、上記のルールに従う。


Top / EBUG勉強会 / 20141115_httpdをスケールさせる

Attach file: file0005procs.gif 323 download [Information] fileloginclass.jpg 323 download [Information] file0005mem.gif 320 download [Information] fileProcessModel.jpg 322 download [Information]
Reload  New Edit Freeze Diff Attach Copy Rename  Top Index Search Recent Backups  Help  RSS
Last-modified: 2014-11-21 (Fri) 16:50:24 (1247d)