以前この勉強会で紹介したウェブ/メルマガのサーバが、アクセス過多によりトップページが見れなくなっているという報告を受ける。
[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
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分ほどで不具合は解消
状況をモデリングする
→httpd.confのMaxClients、 login.confの maxprocを幾つにすればよいか?
#img(): File not found:設定値の算定
バッファキャッシュ3GBのうち、2GBを取り崩してhttpd用に割り当てるとすると
2048 / 4 = 512
とりあえず、MaxClientを512、ワーストケースとして *.cgiが同数起動されると仮定して、wwwのログインクラスのmaxprocを1024として 動作をみる。
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"
# www - resource definition for web processes # www:\ :maxproc-max=1024:\ :maxproc-cur=1024:\ :tc=default:\
この部分の課題 ... setuidした子プロセスについてはlogin classはどうなる(右図)?
(11/21, 後日検証した結果を追記)
SAG(Server Activity Grapher)にプロセス数の項目を組み込んで監視
# # 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
この状況で様子を見る。
#img(): File not found:将来的にさらにプロセス数の増加が必要な場合を想定し、どの程度まで増やせるかやってみる。
# sysctl -w kern.maxproc=8192 # sysctl kern.maxproc kern.maxproc=8192
#!/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 #
このとき、syglog (/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)
ということで
前項「実施と検証」/「login.confのmaxproc設定」について示された疑問を実際に設定を行うことで調査した。
以下のケース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で書いたエントリは無効となる。
以下の設定で、user1 (ログインクラス:class1)からuser2 (ログインクラス:class2) にsuidした場合、maxprocはどうなるか?
class0:\ :maxproc=200: class1:\ :maxproc=225:
結果:class1の設定値に関係なく、class2の値が新規に設定される。
ulimitの挙動について
:maxproc-cur=A:\ :maxproc-max=B:
上記のように記述した場合、ログイン直後の最大プロセス数はAに設定されている。
シェルのビルトイン・コマンドulimit -pで変更する場合、
プロセス数を増加する方向には「Bを超えない範囲、かつ一回だけ」変更できる。
プロセス数を減少させる方向には何回でも可能。
(以上の挙動は使用するシェルに依存する可能性あり)
また、
:maxproc=C:
と記述した場合は、
:maxproc-cur=C:\ :maxproc-max=C:
と書いた場合と等価であり、上記のルールに従う。