WordPress + mod_ruid で自動アップデートをハッピーにする。

WordPress の自動アップデートをハッピーにする環境の構築方法をご紹介します。

http://vps.fean.info/archives/server101.html

さらにいい方法があります。

mod_ruidを使う。

mod_ruid とは、CGIでいうSuExecのような仕組みで、Apacheのバーチャルホストを指定したユーザーで実行させるという仕組みです。

たとえば、/home/wp ディレクトリを foo というユーザーで FTP アクセス(SSHでも同じ)して、WordPress を構築するとします。

その場合、そのバーチャルホストが、apache ユーザーではなく、foo という権限で動作してくれれば、パーミッションの設定や FS_METHOD の設定をすることもなく自動アップデートが可能になるわけです。

一方で冒頭に紹介したブログに記載されている方法では、以下のような問題がります。

  • ファイルのアップロード等の作業をrootで行うかapacheユーザーにftpアクセスを許可する必要がある。これはかなり危険。
  • 全てのバーチャルホストが同じ権限で動作するため、複数のユーザーによる運用を考えた場合、セキュリティリスクがある。
    たとえば違うバーチャルホストのファイルを書き換えたりデータベースのパスワードを参照できるなど。

mod_ruid を使用すれば、これらの問題も解決しつつ、パーミッション設定などに煩わされることもありません。

mod_ruid のインストール方法

一応、Apacheモジュールのインストールなのでコマンド入力が必要ですが、それほど大変ではありません。

http://firegoby.theta.ne.jp/archives/9

あと、上記の記事には記載されていませんが、このままではPHPのセッションを使用する際に書き込み権限がなくて不具合が発生します。

以下の作業も必ず行いましょう。

http://firegoby.theta.ne.jp/archives/289

というわけで、今日の記事は過去の記事の焼なおしですいません。

「さくらのVPS」上のWebサーバーのパフォーマンス向上

このサイトは「さくらのVPS」を使用しているのだが、徐々にパフォーマンスが低下するという現象が発生するようになった。
ひどい時には1ページの表示に30秒以上かかる。。。

Apacheを再起動すると状況が改善するため、ApacheとかPHPのチューニングを行うことにした。

以下は次回以降「さくらのVPS」を使用する際のメモということで。

httpd.conf の修正

以下のように設定を変更した。

  • MaxKeepAliveRequestsの値を200から50に変更
  • KeepAliveTimeoutの値を5から2に変更

以上はTCPコネクションの無駄遣いを減らすために行った。
今回のように徐々にパフォーマンスが低下するのは関係ないような気もするけど、そのあたりは詳しくないので、これはなんとなく。

  • PreforkのMaxRequestsPerChildの値を4000から50に変更

このサイトはPHP(WordPress)で動作しており、こまめにプロセスを生成しなおしてメモリーリークなどに備えたいと考えたので。
Apacheが徐々に重くなったのは、この設定で改善されると期待している。

memcacheの導入

以下のコマンドを実行して必要なパッケージをインストールした。
インストールした後は、memcachedの起動とApacheの再起動をしただけで特に設定の変更は行わなかった。

# yum install memcached
# yum install php-pecl-memcache

これはかなり効果があった気がする。
ただ、一時的にパフォーマンスが改善しても、今回のように徐々に重くなった事に対しては、上述のhttpd.confの変更が重要な気がする。

WordPressのキャッシュプラグインについて

いれようかどうしようか迷ったのだが、結局入れなかった。

どのプラグインを入れるべきか悩んだ挙句に面倒になったのと、今回は直感的にボトルネックがWordPressではないような気がしたので。。。

現時点ではキャッシュ系のプラグインをインストールしなくても状況は大幅に改善された。

その他

今回の現象はこれをやっても改善されなかったが、数日前に一応対策済み。

[001387]さくらのVPSで「CentOS」を利用していますが、回線速度が遅くアクセスに時間がかかります。 | FAQ Search – さくらインターネット

Webサーバーの稼働状況を定期的にチェックする

先日データーセンターでネットワークトラブルがあって、そのトラブルそのものはうちの問題ではなかったので、おまかせにしていたら数時間で復旧したのだが、トラブルに弊社が気づくのが遅れてしまい、お客様に余計な心配をかけてしまった。

そんなわけで、外部のサーバーからWebサーバーの稼働状況を監視するPerlスクリプトを作成して、Cronでぶんまわすことにした。

仕組み

仕組みはとても簡単で、ざっと以下のような感じ。

  1. コマンドライン引数に渡されたURLに対してwgetを実行する。
  2. 実行した結果を評価して、エラーがあればメールを送信する。

という感じ。

wgetコマンドを実行する際に実際にファイルをダウンロードしちゃうと面倒なので、以下のようなオプションをつけた。

my $com = "/usr/bin/wget -q -o /dev/null --spider '".$url."'";

ちなみに、pingだとネットワークトラブルしか検出できないが、wgetならネットワークが生きていてapacheが死んでるなんていうシチュエーションも拾ってくれる。

使い方

以下のように、このPerlスクリプトにURLを引数で渡して、Cronに登録する。

*/10 * * * * /path/to/lifecheck.pl http://www.example.com/

ちなみに、Cronに自前スクリプトを登録する際は環境変数によって挙動が変わったりするので、以下のように環境変数をundefしちゃうと都合が良いと個人的に思うのだが。みんなはどうしているんだろう?

undef %ENV;

ソース

以下のソースをコピペして、lifecheck.plというファイル名で任意のパスに保存する。

#!/usr/bin/perl -wT

undef %ENV;

use strict;
use warnings;
use Encode;
use utf8;
use Mail::Sendmail;

my $from 	= 'admin@example.com';
my $to 		= 'you@example.com';

my $reg = q{^https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+$};

my $in = shift @ARGV;

my $url = '';
if ($in =~ /($reg)/) {
    $url = $1;
}

if (!$url) {
    die('url?');
}

$url =~ s/'/'\''/g;

my $com = "/usr/bin/wget -q -o /dev/null --spider '".$url."'";
system($com);

if ($?) {
    &mailto(
        $from,
        $to,
        '['.$url.'] Alert',
        "Can not connect Web Server.\nPlease check below.\n".$url
    );
}

sub mailto
{
    my ($from, $to, $subject, $body) = @_; 

    $subject = encode('MIME-Header-ISO_2022_JP', $subject);
    $body = encode('iso-2022-jp', $body);
    my %mail;
    $mail{'Content-Type'} = 'text/plain; charset="iso-2022-jp"';
    $mail{'From'} = $from;
    $mail{'To'} = $to;
    $mail{'Subject'} = $subject;
    $mail{'message'} = $body."\n";
    sendmail %mail;
}

Amazon S3とmod_proxyでクロスドメインとか

できるのは、わかってたんですけど、一応テストしたのでご紹介を。

mod_proxyでクロスドメイン制約を回避

.htaccessに以下のような内容を記述して、FlashやAjaxのクロスドメイン制約を回避できるかどうかをテストした。

RewriteEngine on
RewriteRule ^uploads/(.*) http://xxxx.s3.amazonaws.com/$1 [P]

結果は良好。

期待通りに外部サーバのswfやXMLを読み込むことができました。。

予想通り、若干重かったのですがCloudFrontとmod_cacheを組み合わせればこの部分は改善できそうです。

CloudFrontを使うには、2行目のURLをxxxxx.croudfront.netのように書き換えればOKです。

S3では認証パラメータをURLに渡すこともできるようなので、もしかしたらそうした方がいいのかもしれない。

Amazonのエラーを見れないようにする

Amazon S3では、HTTPエラーはXMLがかえされるので、これは表示したくない。

ProxyErrorOverride On

httpd.confに上記の記述を入れたら、解決できた。

つづいてmod_cache

以下のような内容をhttpd.confに追加した。

<VirtualHost>
--中略--
    CacheRoot /path/to/cache
    CacheIgnoreCacheControl On
    CacheEnable disk /uploads/
    CacheMinFileSize 0
    CacheMaxFileSize 64000
    CacheDirLevels 5
    CacheDirLength 3
--中略--
</VirtualHost>

/path/to/cache内をのぞいたら意味不明なディレクトリができていたので、成功した模様。
詳細は後日確認する。

ちなみに、テストしたサーバーはmod_ruidを導入しているので、このキャッシュのオーナーもそこで指定したユーザーになっていた。(これ、本当におすすめです。)

初期費用ゼロで大規模分散ストレージをゲット

この方法の最大のメリットは、初期費用をかけることなく、ほぼ無制限に大容量の信頼性の高いストレージが入手できたことです。

ブログなどのサービスを立ち上げる場合に、どうしてもストレージまわりにコストがかかってしまいます。

バックアップの心配も容量の心配もしなくていい時代がこんなに早くくるなんて。。。

mod_ruidとSubversion+Trac

このサイトで公開しているMail_CheckUserCrossOverなどのSubversionリポジトリをWebDAV+Tracで構築した。

構築方法は、Google先生に聞いていただければたくさんあるので、詳細は割愛しますが、このサーバーではmod_ruidを導入しているので、リポジトリのオーナー及びグループを、apacheではなくあえてmod_ruidで設定したユーザー、グループにしてみた。

結果は、全く問題なし。
Tracもさわれてコミットもできました。

phpのsystem()で呼び出されたコマンドの環境変数に注意

phpから外部コマンドを実行する、system()関数やshell_exec()関数で呼び出される外部コマンド内の環境変数には注意しましょう。

たとえば。

$ sudo /etc/init.d/httpd restart

のようにノーマルユーザーでsudoを使ってhttpdを再起動した場合と、

$ su -
# /etc/init.d/httpd restart

のようにsuコマンドでrootに変わった後でhttpdを再起動した場合では、環境変数の値が変わります。

具体的には、sudoで再起動した場合にはsudoを行ったときのユーザーの環境変数が、rootで再起動した場合にはrootユーザーの環境変数が使用されます。

ややっこしいのは、外部コマンド自体はapacheやnobodyなどhttpd.confで指定された権限で動作しますので、実行ユーザーと環境変数が違うユーザーのものになるようです。

実際に私自身SubversionのWEBインターフェースなどを作っていて、いつのまにやら動作しないなんてことになって困ったことがあります。

perlで汚染チェックモードで開発すると分かりますが、意外と突拍子もないところで環境変数が使われたりするので、外部コマンドを使用する場合は、外部コマンド側で環境変数を使用しないようにしましょう。

どうしても、環境変数をあてにしたい場合は、以下のようにphp側で環境変数をexportしてやれば期待通りの環境変数が確実に渡せます。

<?php
$cmd = 'export PATH="/usr/local/bin/"; command ......";
echo "<pre>".shell_exec($cmd)."</pre>";
?>

システムコマンドは、安易に使用しないのがベターなのですが、使用する場合は要注意です。

mod_ruid環境でPHPのセッションを使うには?

いつのまにかphpMyAdminにログインできなくなっていて、apacheのエラーログを見たら、以下のような記述があった。

PHP Warning:  Unknown: Failed to write session data (files).
Please verify that the current setting of session.save_path...(略)

よく調べてみると、mod_ruidを導入した結果、apacheの実行権限がバーチャルホスト毎に変わるようになったため、書き込み権限が無いことが分かった。

CentOS 5.xのPHPでは、デフォルトのsession_save_path()の値が/var/lib/php/sessionになっているので、以下の方法のいずれかを使用してこのエラーを回避する必要がある。

対処方法

いかのいずれかを使用すること。

  1. session_save_path()関数でセッションデータの保存先のディレクトリを変更する。
  2. httpd.confまたは.htaccessでセッションデータの保存先のディレクトリを変更する。
  3. /var/lib/php/sessionのパーミッションを777に変更する。(デフォルトは770)

などなど。

注意事項

  1. 1または2を使用する場合は、DocumentRoot以下にディレクトリを設置しないこと。
  2. DocumentRoot外でもmod_ruidを使用している場合は、パーミッションを緩くしすぎないこと。(通常は755でOK)
  3. 3の方法はなるべく使用しないこと。

これらに気をつけないとセッションハイジャックのリスクがあります。

mod_ruidを導入した。

バーチャルホスト用サーバーの構築にあたり、suExecをやめてmod_ruidを導入した。
OSはCentOS5.x

suExecとは違って、DSO版のPHPに対してもuidが設定されるところが、suExecに対するメリット。

インストール手順

まずはじめに、suExecを無効にする。

# mv /usr/sbin/suexec /usr/sbin/suexec.disabled
# /etc/init.d/httpd restart

libcap-devel及びhttpd-develをインストール。

# yum install libcap-devel
# yum install httpd-devel

mod_ruidをダウンロードしてインストール

# wget http://websupport.sk/~stanojr/projects/mod_ruid/mod_ruid-0.6.tar.gz
# tar xvzf mod_ruid-0.6.tar.gz
# apxs -a -i -l cap -c mod_ruid.c

最後にhttpdを再起動

# /etc/init.d/httpd restart

テスト

httpd.confのVirtualHostディレクティブに以下のような行を挿入して再起動する。

RMode config
RUidGid user group

以下のようなphpスクリプトを作成して、作成されたファイルのパーミッションを確認したら、設定したuser、groupであることを確認できた。

<?php
file_put_contents(dirname(__FILE__).'/test.txt', 'test');
print 'OK';
?>

Perl/CGIでも同様のテスト。

#!/usr/bin/perl

print "Content-type: text/html\n\n";

open(OUT, "> test.txt");
print OUT 'test';
close(OUT);

print 'OK';
exit;

試しにcgiのオーナーをrootに変更してパーミッションを744(rootのみ実行可)に変更したら、期待通りエラーが出た。

さらにオーナーがrootのままパーミッションを755に変更したら、httpd.confで指定したユーザーでファイルが生成された。これも期待通り。

たしか、suExecではCGIスクリプトのオーナーがsuExecで指定されたユーザーと違う場合は動かないので、この挙動はsuEXecとは違うみたい。柔軟性がある?ということで。(笑)

mod_perlを使ってhttpd.confをPerlで動的に生成する

意外と資料が少ないのですが、mod_perlを使用するとhttpd.conf内にPerlスクリプトを記述して、Apacheの起動時に動的にバーチャルホストを定義することができます。

この方法を利用すると、データベースやXMLなどからDocumentRootなどの最小限の情報を取得して、全てのバーチャルホストをテンプレートをベースに構築できますので、効率的で安全なバーチャルホストの設定が行えます。

# 以下をhttpd.conf内等に記述する
<Perl>

use Encode;
use XML::DOM;

my $dom = new XML::DOM::Parser;
my $doc = $dom->parsefile('/home/www/vhost.xml');

my $nodes = $doc->getElementsByTagName ("vhost");
my $httpd_conf = '';

for(my $i=0; $i<$nodes->getLength; $i++){

 my $node = $nodes->item($i);
 my $port = $node->getAttribute('port');
 my $name = $node->getAttribute('name');
 my $path = $node->getAttribute('path');
 my $text = Encode::encode(
        'utf8',
        $node->getFirstChild->getNodeValue()
    );

 if($port && $port != 80){
  $httpd_conf .= "Listen $port\n";
 }else{
  $port = '80';
 }
 $httpd_conf .= "<VirtualHost *:$port>\n";
 $httpd_conf .= "DocumentRoot $path\n";
 $httpd_conf .= "ServerName $name\n";
 $httpd_conf .= "</VirtualHost>\n\n";

}

open(OUT, ">/etc/httpd/conf.d/httpd_conf.cache");
print OUT $httpd_conf;
close(OUT);

$PerlConfig = "Include \"/etc/httpd/conf.d/httpd_conf.cache\"\n";

</Perl>
# httpd.confここまで

上記の例は、弊社内の開発用サーバーで使用しているもので、ポートベース及びネームベースのバーチャルホストをXML(/home/www/vhost.xml)を元に生成しています。

ここで使用されているXMLは、PHPのアプリケーション側でも読み込んで、テストサーバーのリンク集を生成するのにも利用されています。

したがって、「おーい、例の○○向けのシステムってURLなんだっけ?」みたいなやりとりを減らす効果もあります。

XMLのサンプルは以下の通り。

<?xml version="1.0" encoding="UTF-8" ?>
<vhosts>
  <vhost name="10.0.0.5" port="8003" path="/home/www.example.com/html">
    www.example.com向けECサイト
  </vhost>
</vhosts>

ちなみに、Apacheの1.3系と2.x系では記述内容が違います。
今回紹介している例は2.x系でのサンプルですが、2.X系ではセキュリティ上の制限が加わっているらしく設定できる項目に制限があるようでした。(詳細は勉強不足で不明です。)

それを回避するために、httpd_conf.cacheというファイルに一度書き出して、それをincludeするという方法をとっています。

この例は外部に全く公開していないテストサーバーでの事例なので、セキュリティについては多少目をつぶっています。

というわけで、器用貧乏な弊社ではLinuxサーバー構築のお問い合せをお待ちしています。