IDCFクラウド wordpress 障害対応

今までVPSやGMOクラウド時代に、wordpress運用で障害らしい障害は起こしたことなかった。サーバ運用が硬直的だったので大きな変更を行えなかったというのも理由の1つではあるが。IDCFクラウドへの移行後は、vmwareノード上にlxcコンテナを作成して、そのコンテナ上でwordpressを運用している。更にwordpressのphp部分はGlusterFSで、データ部分はMariaDBで2台を同期している。これだけ構成を複雑化させてしまったせいかもしれないが、遂にwebサーバにて予期しない障害が発生した。

1つ目はlxcコンテナを使ったが故の障害。症状だけで言えば、wordpressを動かすための php-fpmプロセスが死んでしまっていた。ログインしても動作が不安定なので、とりあえず再起動する。この時期はまだホストOSの仮想ブリッジ問題を調査する前だったので、サーバ再起動によってコンテナが外部と通信できなくなってしまい、その回復を待つ間も障害が続く形となってしまった。このときは、とにかくいろいろなコマンドを打ってコンテナが仮想ブリッジ経由で外と繋がりだしたところでサービス復旧。何事もなかったかのようにサービスは回復した。

復旧後にログを追ってみると、どうやらサーバのメモリが枯渇し、php-fpmなどのサービスがOOM Killerに殺されてしまったようだ。しかし、何か特別な変更をした訳でもないのに、突然メモリが不足するというのも解せない。ありうるとすれば、運用とともにじわじわと進むようなサーバプロセスのメモリリークか、ファイルシステムキャッシュ辺りの拡大か。障害など皆無だったので、リソースウォッチもしておらず、原因の特定する材料が少ない。OOM Killerが殺したプロセスのサイズを見ても、そこまで膨張はしていないのでメモリリークではなさそう。ユーザープロセスに問題がないとすれば、やはりファイルシステムキャッシュの問題か。

CentOSのファイルシステムキャッシュは物理メモリ上に確保されるが、他のプロセスに要求されれば、その領域を譲ってくれるものと想像しているだろう。しかし、状況によってはキャッシュ領域を解放してくれないことが起きうる。キャッシュ領域の解放はキャッシュしたファイルがクローズされることが条件。いつまでもファイルをオープンしたままだと、そのファイルのキャッシュはメモリ上に乗ったままになってしまう。ファイルシステムキャッシュに乗る代表的なファイルはログファイルで、その多くが定期的なローテーションによってクローズされるため、キャッシュ領域からの解放対象となってくれる訳だ。

これが解放されないのは、逆に考えるとローテーションが行われないようなログファイルが存在するということ。普通に設定するだけでも大抵はローテーションが行われるので、あまりそういう心配をしなくてよいはず。しかし、事実としてlxcコンテナたちのログがローテートされていなかった。確認するとlogrotateの設定はきちんとなされているにもかかわらず。結論から言うと、cronがインストールされいないことが問題でした。logrotateの実行はcron.dailyに設定されるため、crondが動いていなければまったく実行されないことになる。

lxc-createする際の構築処理を自動化するためにtemplateに手は加えているものの、基本的にはlxc-centosテンプレートでコンテナを作成している。このテンプレートの中で初期インストールされるパッケージが設定されているのだが、その中にcronieが含まれていなかった。cronieがインストールされるようにテンプレートを修正、既存ノードにはcronieをインストールしてcrondを起動した状態に修正した。それ以降すでに数ヶ月が経過しているが、今のところOOM Killerのお世話にはなっていない。

もう1つの障害はよくある話でbotのラッシュによるDoSアタック。botもPV増加に繋がりそうなものは歓迎したいので、何だかわからないようなbotでも性能の許す範囲で許容したい。今まではすべてのbotを許可していたのだが、どうしても許せないほど手癖の悪いbotが幾つかいる。その1つがラッシュしてきたせいで、まったくレスポンスが返せない状態に。もともとphpのキャッシュ効率を高めるために、php-fpm数を最小限にしておいたのでラッシュにはそれほど強くない。プロセス数を幾らか増やしてみたが、焼け石に水。そもそもメモリも小さいし、そこまで大胆に数を増やすこともできない。

仕方がないので、このbotを弾く方向で対応を考える。接続元IPは複数にばらけているので、L4的にブロックするのは手間。httpリクエストのユーザーエージェントに含まれる文字列で弾きたい。php内でロジック書けば当然できるが、できればphpに到達する前に弾いてしまいたい。そうすると前段のnginxでのブロックが妥当。nginxはphp-fpm用のものとload balance用のものがあるので、カバー範囲の広い後者で行う。nginxでL7的なブロック方法を知らないので、ぐぐって以下のような設定を入れてみる。

server {
    listen 80;
        :
    if ($http_user_agent ~ "worstbot") {
        return 403;
    }
    location / {
        :
        proxy_pass http://mywordpress;
    }
}

これで最前段のforward proxy用nginxのところでbotを弾くことができるようになった。その後も突然悪質化するbotが現れるので、同じような設定で弾くようにしている。2回以上悪さしたbotは永久追放する方向で。botはIPアドレスが分散するため、L4で弾くのが難しいので、nginxでhttpヘッダベースのL7的ブロックが行えるというのは非常にありがたい。そして、もう1つの問題は障害の発覚が自分の視認だったということ。必要があって自分でHPを訪れるまで、まったく気付くことができなかった。仕事と違って誰にも怒られないし、監視もリソースウォッチもしてこなかったけど、何らか監視系の追加をそろそろ考えないといけないなあ。

コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)