kernel: TCP: time wait bucket table overflow の解消とTIME_WAITを減らすチューニング
整理がてら。
httpdが動いているあるホスト上で、 /var/log/messages に以下のようなメッセージが出ていた。
kernel: TCP: time wait bucket table overflow kernel: printk: 50078 messages suppressed.
"netstat -tna |grep TIME_WAIT"すると、10万以上の数でTIME_WAITが出ている。これを解消するまでの流れ。
そもそもこの値は、カーネルパラメータの
net.ipv4.tcp_max_tw_buckets
で設定されている。デフォルトは16384。
(↑の通り、エラーが出た環境では既にかなり大きな値が設定されていたのだが。)
/proc/sys/net/ipv4/tcp_max_tw_buckets システムが同時に保持する time-wait ソケットの最大数。この数を越えると、time-wait ソケットは直ちに破棄され、警告が表示されます。この制限は単純な DoS 攻撃を防ぐためにあります。わざと制限を小さくしてはいけません。ネットワークの状況によって必要な場合は、 (できればメモリを追加してから) デフォルトより増やすのはかまいません。 http://archive.linux.or.jp/JF/JFdocs/Adv-Routing-HOWTO/lartc.kernel.obscure.html
単純に値を増やして対応しても良いのだが、ホストと動いているサービスの状況によって対応策を使い分けたほうが良いと思う。
- net.ipv4.tcp_max_tw_buckets を増やす
内部NWで閉じて利用しているか、DoS攻撃を防ぐための機能が上位のFirewall等で確実に機能している場合はこれでもよさそう。
- TIME_WAITを減らすようにkernelパラメータをチューニングする
おおまかなパターンとしては以下の二つがあり、今回は後者を採用した。
1. TIME_WAITが自然解消されるまでの時間を減らす
これはかなり難解で、LinuxではTIME_WAITの保持時間を減らすにはカーネルをリビルドするしかない。
http://d.hatena.ne.jp/ono51/20111012/p1
この件を調べている途中、tcp_fin_timeoutを減らしたらOKという記事をたくさん見かけたが、どうも一緒に指定しているtcp_tw_recycleの作用が出ているだけに思える。
tcp_fin_timeout自体は、FIN-WAIT2からTIME_WAITに状態変化する時間のパラメータであり、TIME_WAIT状態が継続する時間とは関係ない。
2. TIME_WAITになったポートを再利用する
サーバ側で net.ipv4.tcp_tw_recycle を有効にする。
これで10万以上あったTIME_WAITが一気に100以下のレベルに。これは、閉じた環境で100ホスト程度がアクセスしてきているという環境だったから。
ただし、サーバ側で net.ipv4.tcp_tw_recycle が有効で、クライアント側でTCPのタイムスタンプオプションが有効(Linuxの場合net.ipv4.tcp_timestamps = 1)だと、NAT/LBを超えたときにSYNを落としてしまい、接続障害になる。
ユーザー向けに使っているとSB携帯などで障害が発生してしまうようなので、使わないほうがいいかも。
net.ipv4.tcp_tw_reuseを有効にしてもほとんど効果がなかった。これはおそらく、クライアント側も同じソケットをTIME_WAITしているため、クライアント側からの接続は新規セッションが使われるからだと思う。(ということは、クライアントとサーバー両方でONにしないとダメなんじゃないか?という疑問が。誰か検証してくれないかなー。)
- アプリケーション側でコネクションの総数を減らす工夫をする
HTTPなので、keepaliveを有効にすることもできる。今回はHTTPを呼び出している側のシステムがコネクションプール的な概念を持たないので断念。
net.ipv4.tcp_tw_recycleを使う時の注意点はこのへんが詳しい。先達に感謝。
どさにっき
2007-05-21 - LowPriority
logrotateで簡単に世代バックアップ(couchdb)
couchDBのデータはreplicationをするか、database fileを適当にバックアップしておいてねという仕様のよう。*1
rsyncやcpのshellを書いてもよいのですが、バックアップの世代管理を手軽に行いたいときはlogrotateを愛用してます。
今回はchefに使われているcouchdbをバックアップしていますが、プロダクトに組み込みの各種DB・ファイル等(pg_dumpだったりその他の普通のtarballだったり)いろいろ使い道はあるかと。
/etc/logrotate.d/couchdb に追記
# 日次バックアップ /var/backups/couchdb_backup_daily.tar.gz { missingok nocreate daily rotate 14 sharedscripts postrotate /bin/tar -C /var/lib/ -czf /var/backups/couchdb_backup_daily.tar.gz couchdb endscript } # 月次バックアップ /var/backups/couchdb_backup_monthly.tar.gz { missingok nocreate monthly rotate 3 sharedscripts postrotate /bin/tar -C /var/lib/ -czf /var/backups/couchdb_backup_monthly.tar.gz couchdb endscript }
頻度(rotate)は適当に変えて下さい。
管理するデータ量が多い場合は、postrotateの圧縮後に、couchDBのデータ容量を削減しておきます。(↓chefの場合)
/usr/bin/curl -H "Content-Type: application/json" -X POST http://localhost:5984/chef/_compact
# 設定のテスト logrotate -d /etc/logrotate.d/couchdb # 手動で実行してみて、rotateされることを確認 logrotate -f /etc/logrotate.d/couchdb
logrotate.dの設定自体にはdailyより細かい指定はできないが、-fオプション付きをcronで回すことで、さらに頻繁にバックアップすることも。
(ログならともかくDBのデータでそこまでするのであれば別の方法がよさそうですが)
shを書いてcronを動作確認してという手間が無いのでlogrotateは便利なんですが、バックアップしてることが他人に伝わりにくいのが難点といえば難点でしょうか…
*1: http://wiki.apache.org/couchdb/How_to_make_filesystem_backups
RPMビルド時のデフォルトファイル名を変更する
自前でrpmをビルドした場合、出来上がるrpmのファイル名はspecファイルによって決まる。
specに指定がない場合、rpmbuildのデフォルトのパラメータが利用され、大体の場合
xxxxx-1.0.1-1.x86_64.rpm
のようなファイルが出来上がる。いくつかのOSが混在している環境でrpmをビルドすると、ファイル名が重複してしまいややこしい。
specを編集したりする方法もあるが、specファイル入のアーカイブからspecだけ取り出して編集するのは面倒であるし、できたファイルをいちいちrenameもしたくないので、rpmbuildの設定ファイルを変更する。
/usr/lib/rpm/macrosを編集し、
%_build_name_fmt %%{ARCH}/%%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
から
%_build_name_fmt %%{ARCH}/%%{NAME}-%%{VERSION}-%%{RELEASE}%{dist}.%%{ARCH}.rpm
にするだけ。
以下仕組み。
rpmbuildの設定は以下のコマンドで参照する。
rpmbuild --showrc |less
rpmbuildの参照する設定ファイルは大体の場合以下のファイル。
/usr/lib/rpm/macros /usr/lib/rpm/redhat/macros /etc/rpm/macros.* ~/.rpmmacros
ファイル名に関連するのは_rpmfilenameで、さらに_build_name_fmtを参照しているようだ。
とりあえずCentOSの6と5を使い分けられればよいので…と思っていたら、いいパラメータがあったので使わせてもらう。
-14: dist .el6
el6.x86_64とかよく見る。
今回はビルドサーバーで作業する全員に反映したいので、~/.rpmmacrosでなく/usr/lib/rpm/macrosに設定した。これで、
xxxxx-1.0.1-1.el6.x86_64.rpm
というファイル名で出来上がってくることになる。
CentOS6.xでは、/etc/rpm/macros.distに上記のdistが定義されていたが、CentOS5.xではshowrcを見た感じ同じようなファイルはなかったので作成した。
cat <<'EOF' >/etc/rpm/macros.dist # dist macros. %rhel 5 %centos 5 %centos_ver 5 %dist .el5 %el5 1 EOF rpmbuild --showrc |grep dist -14: dist .el5
反映されている。あとは、CentOS6と同じように%distを%_build_name_fmtに入れ込めばOK。
iptablesで帯域制限しながらネットワーク負荷をかける
netperfはネットワークの速度を測定するのにいいツールだけど、拠点間なりサービスなりが入っているネットワークで気軽に使って通信が過大になってしまっては怒られると思う。
ので、送信側のiptablesで帯域を制限しながら負荷をかける。*1
今回はiptablesとnetperfを使う場合。
送信側のnetperfのオプション
netperf -H [hostname|ip] -f M -p 12865 -- -P 12866
iptablesのルールに確実に引っ掛けるため、ポートを明示して利用する。
送信側のサーバーに帯域制限のiptablesを設定する
-A OUTPUT -j ACCEPT -p tcp --dport 12866 -m hashlimit --hashlimit 1000/sec --hashlimit-mode dstip --hashlimit-burst 2000 --hashlimit-name netperf-limit -A OUTPUT -j REJECT -p tcp --dport 12866 ... -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
hashlimitはOUTPUTにも指定できる。
あと、IptablesのOUTPUTルールをACCEPTでなくDROPで運用している場合は、今回のルールはESTABLISHEDの上に指定したほうが多分良いと思う。
今回は特定のポートを明示して拒否しているので、REJECTしても大丈夫。
秒間1000パケットの指定だと、1000(p/sec) * 1500(MTU,Bytes) = 1.5MBps ぐらいの速度が出るはず。結果を見てみる。
# netperf -H [hostname|ip] -f M -p 12865 -- -P 12866 Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. MBytes/sec 87380 16384 16384 10.57 1.62
だいたいあってる。
参考までに、受信側のサーバーでの結果。
# dstat -Tclnd --epoch--- ----total-cpu-usage---- ---load-avg--- -net/total- -dsk/total- epoch |usr sys idl wai hiq siq| 1m 5m 15m | recv send| read writ 1331206136| 0 0 100 0 0 0| 0 0 0| 779B 713B| 0 0 1331206137| 0 0 100 0 0 0| 0 0 0|3105k 75k| 0 4096B 1331206138| 0 0 100 0 0 0| 0 0 0|1201k 27k| 0 0 1331206139| 0 0 100 0 0 0| 0 0 0|1563k 35k| 0 0 1331206140| 0 0 100 0 0 0| 0 0 0|1384k 32k| 0 0 1331206141| 0 0 100 0 0 0| 0 0 0|1618k 36k| 0 56k 1331206142| 0 0 100 0 0 0| 0 0 0|1565k 35k| 0 0 1331206143| 0 0 100 0 0 0| 0 0 0|1262k 28k| 0 0 1331206144| 0 0 100 0 0 0| 0 0 0|1557k 35k| 0 0 1331206145| 0 0 100 0 0 0| 0 0 0|1380k 30k| 0 0 1331206146| 0 0 100 0 0 0| 0 0 0|1618k 36k| 0 0 1331206147| 0 0 100 0 0 0| 0 0 0|1606k 36k| 0 76k 1331206148| 0 0 100 0 0 0| 0 0 0| 522k 12k| 0 0 1331206149| 0 0 100 0 0 0| 0 0 0| 368B 326B| 0 0
実はData Connectionのポートが別だということに気づくまで結構悩んでしまった…。
kernel-develが見つからずrpmbuildがエラーになる
IntelのNICドライバの最新版をせっせとRPM化していたら、rpmbuildでエラー。
Makefile:71: *** Kernel header files not in any of the expected locations. Makefile:72: *** Install the appropriate kernel development package, e.g. Makefile:73: *** kernel-devel, for building kernel modules and try again.
うぇい。kernel-develは入ってるぞ。別に入れ忘れてない。
と思ったら、バージョンが違っていた。
後から入れたkernel-develのバージョンがkernelと違っていたらしい。
kernel 2.6.32-220 kernel-devel 2.6.32-220.2.1
kernel-develのインストールでで生成されるフォルダ
/usr/src/kernels/[kernel_ver]
と、
/lib/modules/[kernel_ver]
あたりの関係がおかしくなるようだ。
バージョンの違いを認識する前に
ln -s /usr/src/kernels/2.6.32-220.2.1.el6.x86_64 /usr/src/linux
とかやってみて、これだと成功するなぁ…と悩んでいるログがhistoryにある。
一度removeしてから、バージョン指定でインストールする。(上記の場合)
yum install kernel-devel-2.6.32-220.el6
無事成功。
non-root userでRPMパッケージを作成する (CentOS6.2)
RPMをビルドする必要があったのだが、いろいろなところで
You are strongly advised against package building as root
と推奨されているので、素直に従う。
※こことか HowTos/I need the Kernel Source - CentOS Wiki
まずディレクトリの作成
mkdir ~/rpm mkdir ~/rpm/BUILD mkdir ~/rpm/RPMS mkdir ~/rpm/RPMS/`uname -p` mkdir ~/rpm/RPMS/noarch mkdir ~/rpm/SOURCES mkdir ~/rpm/SPECS mkdir ~/rpm/SRPMS mkdir ~/rpm/tmp
rpmbuild用の設定ファイルを作成
cat <<EOF > ~/.rpmmacros %_topdir $HOME/rpm %_tmppath $HOME/rpm/tmp EOF
あとは、拾ってきたSourceをSOURCESに配置して
rpmbuild -tb ~/rpm/SOURCES/xxxxxx.tar.gz
などでOK。
LDAP環境下の一般ユーザーで作ったけど、全然関係ないサーバーにもrpm持って行ってインストールできたので問題ないと思う。
CentOS5系でもたぶん同じ手順でいけるでしょう。
rpmbuildのオプションは別途。
CentOS 6.2 上のKVMをリモートのvirt-managerから操作する
CentOS系ではyum install qemu-kvm libvirtしたあと、root以外でlibvirtを実行できるような設定になっていない。
・rootユーザーでSSHログインはしない
・virt-managerはサーバー上でなく、手元からリモートで動かしたい
上記を満たすために、libvirtdの設定を変更する。
# vi /etc/libvirt/libvirtd.conf unix_sock_group = "libvirt" unix_sock_rw_perms = "0770"
groupadd libvirt usermod -a -G libvirt [username]
これでlibvirtdを再起動すればOK…のはずだったのだが。
SSHの鍵認証をさせて、virt-managerで接続させても延々と"libvirtError: authentication failed"のような認証エラーが出る。
# vi /usr/share/polkit-1/actions/org.libvirt.unix.policy <allow_any>auth_admin</allow_any> <allow_inactive>auth_admin</allow_inactive>
上記の修正で直ったという情報もあったけれど、どうもしっくりこないので調べた。(そもそもこれで認証が通ったら、unix_sock_rw_perms = "0770"の設定は何なんだ?)
結論としては、PolicyKitの認証が関係していた。
CentOSでは、libvirtd.confがほとんどの場合コメントアウトしてあり、以下の部分も同様。
# /etc/libvirt/libvirtd.conf
# auth_unix_rw = "none"
libvirtd.conf中のauth_unix_rwの説明に、以下のようにある。
# Set an authentication scheme for UNIX read-write sockets
# By default socket permissions only allow root. If PolicyKit
# support was compiled into libvirt, the default will be to
# use 'polkit' auth.
つまりCentOS6では、libvirtが最初からpolicykitを使うようにビルドされているということだ。
libvirtd.confを以下のように編集しなおして、無事操作できた。
# vi /etc/libvirt/libvirtd.conf unix_sock_group = "libvirt" unix_sock_rw_perms = "0770" auth_unix_rw = "none"
ちなみに、Ubuntu系では最初から
auth_unix_ro = "none" auth_unix_rw = "none"
と設定されている。
openSUSEのマニュアルにも、 第7章 接続と権限
推奨設定
SSH を利用したリモートからのトンネル
7.1.1.1項 「パーミッションとグループ所有者を利用した UNIX ソケット向けのアクセス制御」
想定済みというわけらしい…。うーんこのRHEL。
参考にしたサイト