- The added line is THIS COLOR.
- The deleted line is THIS COLOR.
#topicpath
*OpenBSDでhttpdをスケールさせる [#v19f2374]
RIGHT:川俣 吉広 ~
EBUG勉強会 @ 新潟, 2014/11/15
**発端 [#wdbc1063]
以前この勉強会で紹介したウェブ/メルマガのサーバが、アクセス過多によりトップページが見れなくなっているという報告を受ける。
- トップページが「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台が並列稼動しているが、両方とも同様な状況。
**応急対応 [#n2f7a8d8]
- index.cgi を index.html に変更 ~
- /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分ほどで不具合は解消
- アクセスそのものが減少し
- 上記対策が効を奏した(かどうか、本当は不明)
**恒久対策 [#o48c211b]
#ref(ProcessModel.jpg,wrap,around,right,70%)
状況をモデリングする
-httpdに関しては、最大でhttpd.confで定義されたMaxClients個(右図ではNh個)のプロセスが起動する。
-そのhttpdのうちの幾つかがNp個の子プロセス(*.cgi)を起動する。
-httpdとその子プロセスの総数(Nh+Np)の上限が login.conf の wwwエントリにある maxproc 設定値で制限される。
→httpd.confのMaxClients、 login.confの maxprocを幾つにすればよいか?
#img(,clear)
----
#ref(0005mem.gif,wrap,around,left,70%)
設定値の算定
-プロセス一つ当り、どの程度メモリを消費するか。
-動作記録を基に概算する。~
... 11/8に使用メモリの大幅な変動がある。
これは設定変更の反映のためにhttpdの停止・起動を行ったため。~
~
... サーバの実装メモリは4096MB~
~
... sysctlによりkern.bufcachepercentが75%に設定されている。つまり、3GBはバッファキャッシュとして使用されている。~
~
... 左のグラフをみると、httpdの停止時はhttpdプロセス約200個ほどが停止し、これにより実装メモリの約2割程が解放されている。~
~
... よって 4000*0.2 / 200 = 4。大体プロセス一個あたり4MB消費 ... topコマンドの SIZE及びRSS表示値 とも大体一致.
#img(,clear)
----
バッファキャッシュ3GBのうち、2GBを取り崩してhttpd用に割り当てるとすると ~
2048 / 4 = 512
とりあえず、MaxClientを512、ワーストケースとして *.cgiが同数起動されると仮定して、wwwのログインクラスのmaxprocを1024として
動作をみる。
**実施と検証 [#n2987708]
***httpd.conf、MaxClientsの設定 [#rf253fa6]
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設定 [#u4f94e5a]
#ref(loginclass.jpg,wrap,around,right,70%)
# www - resource definition for web processes
#
www:\
:maxproc-max=1024:\
:maxproc-cur=1024:\
:tc=default:\
この部分の課題 ... setuidした子プロセスについてはlogin classはどうなる(右図)?
#img(,clear)
***運用の監視 [#ve937cc8]
SAG(Server Activity Grapher)にプロセス数の項目を組み込んで監視
#ref(0005procs.gif,wrap,around,right,70%)
#
# 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(,clear)
**さらに... [#zac46601]
将来的にさらにプロセス数の増加が必要な場合を想定し、どの程度まで増やせるかやってみる。
-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
#
このとき、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)
ということで
-カーネルレベルでのプロセス最大数はsysctlのkern.maxprocで設定可
-ただし、kernel configのMAXUSERSで規定される「真の最大値」が存在する。
この値はsysctlで変更不可(変更にはカーネルの再コンパイルが必要)。