#topicpath

*OpenBSDでhttpdをスケールさせる [#v19f2374]
RIGHT:川俣 吉広 ~
EBUG勉強会 @ 新潟, 2014/11/15
**発端 [#wdbc1063]
以前この勉強会で紹介したウェブ/メルマガのサーバが、アクセス過多によりトップページが見れなくなっているという報告を受ける。
以前この勉強会で紹介した[[ウェブ/メルマガのサーバ>EBUG勉強会/20110903_ロードバランサなしで負荷分散してみた]]が、アクセス過多によりトップページが見れなくなっているという報告を受ける。

- トップページが「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 に変更 ~
→ 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はどうなる(右図)? ~
~
(11/21, 後日検証した結果を追記)
#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
 #

このとき、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設定関連について [#scb7eb91]
前項「実施と検証」/「login.confのmaxproc設定」について示された疑問を実際に設定を行うことで調査した。

***調査方法 [#k0b124c7]

-3種類のログインクラスエントリ, class0, class1, class2を作成する。

-class1, class2ともtcによりclass0の設定を引き継ぐようにする。

-ユーザアカウントuser1, user2を作成し、それぞれのログインクラスをclass1, class2とする。

-user1, user2アカウントにログインし、ulimit にてmaxprocの表示と設定を行ってみる。

***調査1: login.confの記述方法について [#lcad16ec]
以下のケース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: [#p6e8e91b]
以下の設定で、user1 (ログインクラス:class1)からuser2 (ログインクラス:class2) にsuidした場合、maxprocはどうなるか?
 class0:\
     :maxproc=200:
 
 class1:\
     :maxproc=225:

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

***調査3: [#s1869268]
ulimitの挙動について
 :maxproc-cur=A:\
 :maxproc-max=B:

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

また、
 :maxproc=C:

と記述した場合は、
 :maxproc-cur=C:\
 :maxproc-max=C:

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

Front page   Edit Diff History Attach Copy Rename Reload   New Page list Search Recent changes   Help   RSS of recent changes