GANCHIKU.com

nginx, fastcgi_cache, php5-fpm, wordpress multisite(subdomain), super cache の設定

構成としては、 nginx が 80 番で受けて、php5-fpm を socket で動かす。
そして、php5-fpm 上で wordpress をサブドメインでサイトを分けるネットワーク利用にし、さらに wp_super_cache を使用した際の設定。
間違っているかもね。まぁ、適宜間違っていたら修正するかな。

Nginxを使ったもう一歩進んだWordPressチューニングを参考にしました。まぁ、そこの設定ファイルは、マルチサイト用じゃないけど。

/etc/nginx/nginx.conf

user  nginx;
worker_processes  2;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
    use epoll;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

     log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                       '$status $body_bytes_sent "$http_referer" '
                       '"$http_user_agent" "$http_x_forwarded_for" $request_time';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;

    keepalive_timeout  10;

    connection_pool_size 256;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 2k;
    request_pool_size 4k;
    if_modified_since before;
    ignore_invalid_headers on;
    server_tokens off;

    gzip  on;
    gzip_min_length 0;
    gzip_buffers 4 8k;
    gzip_types text/plain text/xml application/x-javascript text/css;
    gzip_disable "msie6";
    gzip_vary on;

    gzip_static on;

    output_buffers 1 32k;
    postpone_output 1460;

    fastcgi_cache_path      /var/cache/nginx levels=1:2 keys_zone=wpcache:10m max_size=50M inactive=30m;

    server {
        listen       80;
        server_name  localhost;
        charset utf-8;

        location / {
            return 403;
        }

        location /nginx_status {
            stub_status on;
            access_log off;
            allow 127.0.0.1;
            deny all;
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    include /etc/nginx/sites-enabled/*;
}

ポイントはfastcgi_cache_pathを指定しているところかな。

次なんだけど、上のサイトと wordpress の codex のサイトを参考にするのがいいかな。 Nginx « WordPress Codex Codex の方が整理しているんだけど、私はベタっとはりつけておきますか。

/etc/nginx/sites-enables/example.conf

    server unix:/var/run/php5-fpm.sock;
}

server {
    listen 80;
    server_name example.com;
    server_name *.example.com;  

    #on server block
    ##necessary if using a multi-site plugin
    server_name_in_redirect off;

    ##necessary if running Nginx behind a reverse-proxy
    port_in_redirect off;
    root /home/app/prod/example/wordpress;
    index  index.html index.htm index.php;

    # Global restrictions configuration file.
    # Designed to be included in any server {} block.

    location = /favicon.ico {
      log_not_found off;
      access_log off;
    }

    location = /robots.txt {
      allow all;
      log_not_found off;
      access_log off;
    }

    # Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
    location ~ /\. {
      deny all;
      access_log off;
      log_not_found off;
    }

    # Deny access to any files with a .php extension in the uploads directory
    location ~* ^/wp-content/uploads/.*.php$ {
      deny all;
      access_log off;
      log_not_found off;
    }

    # Deny access to any files with a .php extension in the uploads directory for multisite
    location ~* /files/(.*).php$ {
      deny all;
      access_log off;
      log_not_found off;
    }
   #on server block
    ##necessary if using a multi-site plugin
    server_name_in_redirect off;

    ##necessary if running Nginx behind a reverse-proxy
    port_in_redirect off;

    location / {
      try_files $uri $uri/ /index.php?$args;
    }

    # Add trailing slash to */wp-admin requests.
    rewrite /wp-admin$ $scheme://$host$uri/ permanent;

    access_log /home/shin/site/example/log/access.log;
    error_log /home/shin/site/example/log/error.log;
   # Directives to send expires headers and turn off 404 error logging.
    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
      expires 24h;
      log_not_found off;
    }

    # Pass uploaded files to wp-includes/ms-files.php.
    rewrite /files/$ /index.php last;

    # For multisite:  Use a caching plugin that creates symlinks to the correct subdirectory structure to get som
e performance gains.
    set $cachetest "$document_root/wp-content/ms-filemap/${host}${uri}";
    if ($uri ~ /$) {
      set $cachetest "";
    }

    if (-f $cachetest) {
      # Rewrites the URI and stops rewrite processing so it doesn't start over and attempt to pass it to the next
 rule.
      rewrite ^ /wp-content/ms-filemap/${host}${uri} break;
    }

    if ($uri !~ wp-content/plugins) {
      rewrite /files/(.+)$ /wp-includes/ms-files.php?file=$1 last;
    }

    # Uncomment one of the lines below for the appropriate caching plugin (if used).
    #include global/wordpress-ms-subdir-wp-super-cache.conf;
    #include global/wordpress-ms-subdir-w3-total-cache.conf;

# WP Super Cache rules.
# Designed to be included from a 'wordpress-ms-...' configuration file.

# Enable detection of the .gz extension for statically compressed content.
# Comment out this line if static gzip support is not compiled into nginx.
gzip_static on;

set $nocache "";
set $supercacheuri "";
set $supercachefile "$document_root/wp-content/cache/supercache/${http_host}${uri}index.html";
if (-e $supercachefile) {
  set $supercacheuri "/wp-content/cache/supercache/${http_host}${uri}index.html";
}

# If this is a POST request, pass the request onto WordPress.
if ($request_method = POST) {
  set $supercacheuri "";
  set $nocache "1";
}

# If there is a query string, serve the uncached version.
if ($query_string) {
  set $supercacheuri "";
}

# Logged in users and those who have posted a comment get the non-cached version.
if ($http_cookie ~* comment_author_|wordpress_logged_in|wp-postpass_) {
  set $supercacheuri "";
  set $nocache "1";
}

# Mobile browsers get the non-cached version.
# Wastes CPU cycles if there isn't a mobile browser WP theme for the site.
if ($http_x_wap_profile) {
  set $supercacheuri "";
  set $nocache "1";
}

if ($http_profile) {
  set $supercacheuri "";
  set $nocache "1";
}

if ($http_user_agent ~* (2.0\ MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|Eu
doraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|
Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyE
ricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|i
Pod|Android|BlackBerry9530|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800)) {
  set $supercacheuri "";
  set $nocache "1";
}
if ($http_user_agent ~* (w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|do
co|eric|hipt|htc_|inno|ipaq|ipod|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|
moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|sec-|send|seri|sgh-|shar|sie
-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|w
inw|xda\ |xda-)) {
  set $supercacheuri "";
  set $nocache "1";
}

# Stop processing if the supercache file is valid.
if ($supercacheuri) {
  rewrite ^ $supercacheuri break;
}

    # Rewrite multisite '.../wp-.*' and '.../*.php'.
    if (!-e $request_filename) {
      rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) $1 last;
      rewrite ^/[_0-9a-zA-Z-]+.*(/wp-admin/.*\.php)$ $1 last;
      rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ $1 last;
    }

    # Pass all .php files onto a php-fpm/php-fcgi server.
    location ~ \.php$ {
      # Zero-day exploit defense.
      # http://forum.nginx.org/read.php?2,88845,page=3
      # Won't work properly (404 error) if the file is not stored on this server, which is entirely possible with
 php-fpm/php-fcgi.
      # Comment the 'try_files' line out if you set up php-fpm/php-fcgi on another machine.  And then cross your
fingers that you won't get hacked.
      try_files $uri =404;

      fastcgi_split_path_info ^(.+\.php)(/.+)$;
      #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

      include fastcgi_params;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    # fastcgi_intercept_errors on;
      fastcgi_pass phpfpm;

      fastcgi_cache         wpcache;
      fastcgi_cache_key     "$scheme://$host$request_uri";
      fastcgi_cache_valid   200 10m;
      fastcgi_cache_valid   404 1m;
      fastcgi_cache_bypass  $nocache;
    }

}

ちょっとベタッと張りすぎかな。codex と fastcgi_cache の重ねワザですな。supercache を使わないところは、fastcgi_cache も使わないようにするとかそういったところかな、と。

とは言ってもこの記事を書いているサイトは激重で apache で super cache も使わずに動かしてます。。。自分のことは面倒になっちゃうんだよなー。機会を見て、なんとかしなきゃ。aws のマイクロでいいんじゃないか、と思っているし。というわけで、サーバを変えて、ちゃんと設定しなおしました。

適当に突っ込んでいただければ、適宜修正します。

nginx(reverse proxy cached) + nginx(backend) + PHP-fpm の設定メモ

既にたくさんの記事でこの設定がありましたが、どれを参考にしてもなかなかうまく動かなかったのですが、自分のためのメモということで、ここに書いておきます。前までは、 nginx(reverse proxy cached) + apache-cgi(backend)でやっていたのですが、nginxがときどき暴走してしまっていたので、ちょっと見直してみました。

うまく動かなかったのは、私の設定がいけてないのがもちろん一番の理由なのですが、もう一つあげるとするのであれば、 wordpress のインストールがドメイン直下ではなく、サブディレクトリ以下だったことです。マルチブログでは、まだ動作を確認していないのですが、近々見てみます。あと、書き込みとかは、全部wordpress_masterサーバで行わせるが、読み込みは、どっちでも行わせるという設定にしています。
wordpressは、 /home/app/ganchiku.com/blog 以下にインストールされていることを前提にして書いています。/home/app/ganchiku.com/ も一応見えるのですが、こっちには他のファイル等を入れていることを前提にしています。

環境:php5-fpm(5.3.5) nginx (1.0.11-1) ubuntu(10.04)
nginx は本家のパッケージを、php5-fpm は launchpadのパッケージをsources.list に加えて適当にインストールします。

nginx(reverse proxy cached)は、ポート80で動かし、リバースプロクシの役割をしてもらいます。また、nginx(backend)は、ポート9001で動かし、ウェブサーバとするのですが、さらにPHPの処理は、バックエンドのPHP-fpmに投げて行います。PHP-fpmはデフォルトでは、ポート9000で動くのですが、unixソケットに変更します。

というわけで、以下ベタ貼り。というか、サイト固有のものだけ変えます。よくここでミスるので、実際に貼りつけてもうまくいかない、なんてことになるんだよなぁ。

まずリバースプロクシ側

proxy_cache_path  /var/cache/nginx levels=1:2 keys_zone=czone:180m max_size=512m inactive=120m;
proxy_temp_path   /var/tmp/nginx;
proxy_cache_key   "$scheme://$host$request_uri";
proxy_set_header  Host               $host;
proxy_set_header  X-Real-IP          $remote_addr;
proxy_set_header  X-Forwarded-Host   $host;
proxy_set_header  X-Forwarded-Server $host;
proxy_set_header  X-Forwarded-For    $proxy_add_x_forwarded_for;
proxy_buffers     32 16k;

upstream wordpress_master {
  server 192.168.1.5:9001 fail_timeout=120s;
}

upstream wordpress_slave {
  server 127.0.0.1:9001 fail_timeout=120s;
}

server {
  listen 80;

  server_name dev.ganchiku.com;

  proxy_cache_valid 200 20m;

  expires off;

  set $do_not_cache 0;

  location / {
    root /home/app/ganchiku.com;
    rewrite ^/$ /blog/ permanent;
  }

  location /blog/wp-admin {
    proxy_pass http://wordpress_master;
  }
  location /blog/wp-login.php {
    proxy_pass  http://wordpress_master;
  }
  location ~ .*\.php {
    proxy_pass http://wordpress_master;
  }

  location /blog {

    set $mobile 0;
    if ($http_user_agent ~* '(DoCoMo|J-PHONE|Vodafone|MOT-|UP\.Browser|DDIPOCKET|ASTEL|PDXGW|Palmscape|Xiino|sharp pda browser|Windows CE|L-mode|WILLCOM|SoftBank|Semulator|Vemulator|J-EMULATOR|emobile|mixi-mobi
le-converter)') {
      set $mobile 1;
    }
    if ($http_user_agent ~* '(iPhone|iPod|Opera Mini|Android.*Mobile|NetFront|PSP|BlackBerry)') {
      set $mobile 2;
    }

    # If logged in, don't cache.
    if ($http_cookie ~* "comment_author_[^=]*=([^%]+)%7C|wordpress_logged_in_[^=]*=([^%]+)%7C") {
      set $do_not_cache 1;
    }
    if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
      set $do_not_cache 1;
    }

    proxy_no_cache     $do_not_cache;
    proxy_cache_bypass $do_not_cache;
    proxy_cache        czone;
    proxy_cache_key    "$scheme://$host$request_uri$is_args$args$mobile";
    proxy_cache_valid  200 20m;
    proxy_cache_valid  404 5m;
    proxy_pass         http://wordpress_slave;
  }

  location ~* \.(jpg|png|gif|jpeg|css|js|swf|pdf|ppt|pptx)$ {
    proxy_cache_valid  200 120m;
    expires            864000;
    proxy_cache        czone;
    proxy_pass         http://wordpress_master;
  }

  location ~* wp\-.*\.php|wp\-admin {
                  # Don't static file cache admin-looking things.
                  proxy_pass http://wordpress_slave;
  }

  location  ~* \/[^\/]+\/(feed|\.xml)\/? {
    if ($http_cookie ~* "comment_author_[^=]*=([^%]+)%7C|wordpress_logged_in_[^=]*=([^%]+)%7C") {
      set $do_not_cache 1;
    }
    proxy_no_cache     $do_not_cache;
    proxy_cache_bypass $do_not_cache;
    proxy_cache        czone;
    proxy_cache_valid  200 60m;
    proxy_pass         http://wordpress_master;
  }
  location ~* \.(jpg|png|gif|jpeg|css|js|mp3|wav|swf|mov|doc|pdf|xls|ppt|docx|pptx|xlsx)$ {
    # Cache static-looking files for 120 minutes, setting a 10 day expiry time in the HTTP header,
    # whether logged in or not (may be too heavy-handed).
    proxy_cache_valid 200 120m;
    expires 864000;
    proxy_pass http://wordpress_slave;
    proxy_cache czone;
  }

  location = /50x.html {
    root   /var/www/nginx-default;
  }

  #AWS uses Elastic Load Balancer.
  set_real_ip_from        10.0.0.0/8;
  real_ip_header  X-FORWARDED-FOR;
  add_header X-Forwarded-For $http_x_forwarded_for;

   log_format htest '$http_x_forwarded_for - $remote_addr - $remote_user [$time_local]  '
      '"$request" $status $body_bytes_sent '
      '"$http_referer" "$http_user_agent"';

   #Custom Error Page
   error_page   404          /404.html;
   error_page   502 503 504  /50x.html;

   access_log /home/app/log/ganchiku.proxy.log;
   error_log /home/app/log/ganchiku.proxy.log;

   # Set the real IP.
   proxy_set_header X-Real-IP  $remote_addr;

   # Set the hostname
   proxy_set_header Host $host;

   #Set the forwarded-for header.
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

上記で書きましたが、upstreamに wordpress_masterとwordpress_slaveを2つ用意しています。192.168.1.5を書き込みを行うサーバのIPアドレスと固定します。そして、読み込みは、どのサーバで動かしてもローカルのIPとします。これは実は肝で、この設定でオートスケールしても、そのまま使えるようにしているんですね。あと AWS の Elastic Load Balancerを使用しているので、その辺の設定も適当に付けておきました。

次にバックエンド側

server {
  listen 9001;

  server_name dev.ganchiku.com;
  root /home/app/ganchiku.com;

  access_log /home/app/log/ganchiku.backend.log;
  error_log /home/app/log/ganchiku.backend.log;

  index index.php;
  try_files $uri $uri/ /blog/index.php?q=$uri;

  location ~ \.php$ {
    include       fastcgi_params;
    fastcgi_pass  unix:/tmp/php.socket;
#    fastcgi_pass  127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param SERVER_PORT 80;
    fastcgi_param SERVER_NAME dev.ganchiku.com;
    expires 2h;
  }
}

肝は、try_files と indexの指定かな。indexの指定がないと、index.phpがディレクトリインデックスだと認識してくれないので、ちゃんと書きます。デフォルトでなっているだろう、と思ってここを書かずに、うまくいかずに数時間費やしてしまいました。とほほ。
fastcgi_passには、PHP-fpmを指定します。ポートで動かしているのであれば、コメントアウトしている方を使ってください。UNIX ソケットで使用しているのであれば、UNIXソケットのファイルを指してください。

PHP-fpmをUNIXソケットで動かす

Ubuuntuの場合は、 /etc/php5/fpm/pool.d/www.confを変更すればいいかな。独自にファイルを作ってもいいですが、基本一行のみの変更なので、修正しました。

;listen = 127.0.0.1:9000
listen = /tmp/php.socket

listenしている行を変更します。127.0.0.1:9000をコメントアウトして、 /tmp/php.socket に変更してphp-fpmを再起動すれば、ソケットで通信をすることがでできます。いくつかのブログでソケット通信の方が速いと書いてありましたので、それに倣ってみました。

実際は、この設定をサブドメインのマルチブログに適用していないのですが、しなければいけないので、その内容を見てみます。
まぁ、いつものように間違いがあれば、指摘してくださいな。

Custom Field Template を拡張して、緯度経度も保存する

2011年11月14日

というわけで、2日連続の投稿をしてみます。ツイッターで、「つーか、wordpress の方にいったらデザイナとか華やかな層に受けるのかな。私だってモテたいし。」と書いてみたのですが、実際デザイナは華やかでないとか、突っ込まれて少しモチベーションが落ちている shin です。

さて、今回は、つぶやいたことについて、せっかくなのでブログを書いてみることにしました。Wordpressですよ!Wordpressはやっぱり完成度が高いので、これをそのままCMSとして採用しているところも多いんですよね。管理画面とかも一般的なブログの仕組みの延長なのでわかりやすいし、やっぱりフルスクラッチで書くと時間がかかるところを、短縮できるのが素晴らしいですね。そして、いざCMSとして使おうとなると、もう少し系統だった情報を入れたいときがあると思います。そのためにあるのが、カスタムフィールドですね。このカスタムフィールドのひな形を予め作成しておくことで、CMSとして、使うに足りるほとんどのことができてしまうと思います。
まぁ、細かいバリデーションだったり、インデックスだったり、絞り込みができないので、そういったときはカスタムフィールドが使えないかな、と思うのですが、そこまで細かな指定がしたければ、独自テーブルに入れたり、もしくはフルスクラッチで書いた方がいいでしょう。この辺は、人材や費用のリソースと相談してくださいな。

さて、このカスタムフィールドを1つずつ指定してもいいのですが、それぞれの投稿で、同じものを使うことは多いと思います。そのまとまった同じものを指定できるプラグインが、 Custom Field Template プラグインです。私が試したのは、Wodpress3.1.2 で、現行では最新のものとなります。各投稿が実はイベント管理システムだったりするときなどに、カスタムフィールドで日にちや時間、アクセス方法などを指定できたらいいですよね。そんなときに役に立つプラグインだと思います。実際、私も使ってみて、こりゃあ楽だわ、と思いました。

しかし、場所に関する入力が弱い感じがしました。というのも、イベント管理システムでも、お店紹介システムでも、なんでもいいのですが、投稿のほとんどに地図を連携したくなると思うんですね。投稿に関する地図上の場所をGoogle Mapsなどで表示したいときとか、もう少しハックする必要があります。このプラグインでも住所の項目があるので、これを入れておいて、表示する際に、逆引きして地図上に表示する、ということもできます。ただ、このときは、入れた住所がちゃんと逆引きできることを想定しているので、間違った住所を入れたとき、逆引きで引けない住所をいれたとき、に地図が表示されない、もしくは間違った場所で表示されてしまう、ということが出てきてしまいます。まぁ、その都度プレビューで確認すればいいのですが、その場で確認できたら尚良いと思います。また、一覧で表示するときなど、複数の住所を逆引きするよりも、緯度経度もデータベースに格納していた方が処理が速く済みます。

というわけで、Custom Field Template を拡張してやってみました。ちょっとコーディングスタイルがだいぶ違うのと、あまり柔軟には作っていないので、本家に連絡するのは、どうかなー、と思ってやっていません。いろいろ設定オプションを用意するのって、結構面倒くさいなぁ、と思って、id などを直指定しました。custom-field-template.phpファイルの1616行目辺りのdateの条件の後にこんな感じで、geocodeの条件を加えてみました。本来なら、buttonのvalue値や、地図のサイズ、クラス名などを設定可能にしておくべきなのでしょうが、そこまで考えてもしょうがなさそうなので、しませんでした。また、クイック編集ではidを直接指定しているため、動きません。なので、この際、クイック編集はオフにしてしましましょう。実際、違うカスタムフィールドのidを取得するのが難しいので、強引にclassを指定して、値を取ったり、指定したりしていますので、複数回呼ばれることを想定していません。というわけで、やっつけ仕事としては、だいたい下のような感じになります。

      if (isset($geocode)) {
        $out .= <<
EOL; }

んー。なんかこのシンタックスハイライターでは、ヒアドキュメントのEOLの最初の指定のとこが小文字になってしまうわ。それに、最後にも</eol>っていうゴミがつきよる。。。うまいこと変換されないや。適当に修正してくださいな。
テンプレートは、例えば次のような感じ。

[住所]
type = text
size = 50
geocode = true
class = address-latlng

[緯度]
type = text
size = 25
readOnly = true
class = latitude
[経度]
type = text
size =25
readOnly = true
class = longitude

「住所」の指定にgeocodeと書いておいて、その際に忘れずに「緯度」「経度」も定義しておきます。このgeocodeってところに、「緯度」「経度」のclass名を入れた方が拡張性は高いだろうなぁ。「緯度」「経度」は、入力不可にしておき、「緯度経度を取得」ボタンが押されたときのみ、「住所」の値から指定させることにします。JavaScriptの指定は読んだままです。「住所」の値を逆引きして、「緯度」「経度」を指定したり、その際の地図を表示したり、地図をドラッグして「緯度」「経度」を指定したりできます。

そうすると、カスタムフィールドを入力する際に、以下のような感じになります。投稿の際に、「住所」も「緯度」も「経度」も保存できてうれぴー。

WordPressのプラグインって、あまりgithubで公開されていないのですが、そもそもこのプラグインはGPLなので、そのまま派生させてもありかなーと思ったりしましたが、あまりにも俺得な感じなので、やり方だけ書いておくことにしました。

wordpress のアップロード画像を S3 に置いて、アクセス制御も行う

ブログを書こう書こうと思いつつ、すっかり書かなくってなってしまいましたshinです。最近は、自分のブログを充実させるくらいなら、Symfonyのドキュメントを充実した方が、もっと読まれる内容になるだろうなぁ、と思っています。とは言っても、Symfony と関係のないネタなどは、やはり書く場所もないので、ブログに戻ってきてしまいます。

さて、今回のネタはwordpressです。現在このブログが走っているサーバもAmazon Web Service のEC2 なのですが、画像の置き場所について調べてみました。実際は、お手伝いしているブログの画像の管理についてだったのですが、実験用として自分のブログサーバでやってみました。内容はタイトルにあるように「wordpress のアップロード画像を S3 に置いて、アクセス制御も行う」です。

まず、なぜwordpressでアップロードしたファイルをS3に置く必要があるのでしょうか?私のこのブログのような個人ブログではあまり必要はないのですが、次の三点からその必要があると思います。

  • S3はバックアップ用途として使える。
  • S3の信頼度は、New Amazon S3 Reduced Redundancy Storage (RRS)によると、99.999999999%らしいです。つまりバックアップストレージとして最適です。自分のサーバに入れておいて、クラッシュする可能性も考えておくと、なんかしらのバックアップの仕組みが必要になると思います。その際に、画像をローカルに置いておいてもいいのですが、S3のような信頼性のあるストレージに置けるのであれば、それはそれで素晴らしいバックアップ戦略だと思います。

  • スケールアウトした際に、複数のサーバから同じリソースを見ることができる
  • アクセスの少ないこのブログでは、特に関係はないのですが、スケールアウトした際にアップロードしたファイルを外部の同じ場所に置いておけるのは素晴らしいです。もし、外部に置かなければ、ディスクをマウントして使い回したり、複数のサーバ間でsyncする必要があります。外部に置いておけば、複数のウェブサーバから同じファイルを指すだけで可能になり、Wordpressの運営で素晴らしいスケールアウト戦略だと思います。

  • S3に置いておいて、将来クラウドフロントに乗り換える
  • これは、Wordpressに限った話ではありませんが、さらなるスケールアウトに便利です。S3に置いておけば、将来 Amazon Cloud Front を使用して、リソースをCDNサービスで配信することができます。アップロードファイルは、往々にしてファイルサイズが大きいので、これらのファイルを CDN で配信できれば、良いスケールアウト戦略です。

さて、Wordpressでこの機能を実現するためには、次のプラグインが必要になります。

このプラグインを使用して、適切に指定を行うとアップロードをS3にしてくれます。私の使用している WordPress 3.2.1 で、このプラグインのバージョン0.4.1.1では、アップロードに関しては動作を確認しています。実際は次のような問題がありますが、それほど大きな問題ではありません。まず、削除に関しては動作しませんし、ACLに関しては public-read でS3にアップロードしてしまいます。Wordpress をマルチブログ(ネットワーク)で使用している際には、config.php.sample をconfig.phpにコピーして、使用します。あとは、素直に設定をするだけで基本は動くようになります。普通にアップロードを行うと、S3の指定したバケットにアップロードしてくれます。
S3では、CNAMEを指定することができますので、指定しました。私の場合であったら、media.ganchiku.com というバケットを作成しておき、次のようにDNSで指定してあげます。

media CNAME media.ganchiku.com.s3.amazonaws.com

しかし、アクセスが多いブログはコピーされてしまったり、アップロードした画像を直接リンクをされてしまうことがありますよね。S3は確かに格安のサービスなのですが、大きな画像や映像らが頻繁にダウンロードされるような状況になると、それでもお金がかかってしまいます。そこで、アクセス制御を行います。具体的には、自分のサイト、Google, FacebookがRefererであった際には、画像を表示するが、他のドメインであった際には画像を表示させない、というようにします。また、直接画像に対してリクエストがあった際にも画像を表示させません。これは、S3のbucket policy をすることで可能になります。bucket policy は、AWSのコンソールから、左のバケットを選択肢、右クリックで、プロパティを編集しましょう。その後、「Edit bucket policy」というリンクがあるので、ここで指定してあげます。そうすると次のような画像なります。

ちなみに、この画像自体、S3にアップロードされており、bucket policyを指定してありますので、直接 http://media.ganchiku.com/wp-content/uploads/2011/11/ishot-17-1024×703.jpg のURLをコピーアンドペーストで 見ることはできません。実際は、キャッシュが有効になっているので、このページを見ているだけで、一度目は見えるのですが、再読み込みをすると、XMLのエラーコードが返ってきます。

さて、bucket policyですが、私の場合は次のようになりました。

{
	"Version": "2008-10-17",
	"Statement": [
		{
			"Sid": "GIve asccess if refere is set",
			"Effect": "Allow",
			"Principal": "*",
			"Action": "s3:GetObject",
			"Resource": "arn:aws:s3:::media.ganchiku.com/*",
			"Condition": {
				"StringLike": {
					"aws:Referer": [
						"http://*.google.co.*/*",
						"http://*.facebook.com/*",
						"http://*.google.com/*",
						"http://ganchiku.com/*",
						"http://*.ganchiku.com/*",
					]
				}
			}
		}
	]
}

このままでは、Amazon Cloud Front から使うことはできないのですが、そこまで調べていないのですが、できるかもしれません。 http://blog.cloudberrylab.com/2010/09/how-to-grant-permissions-to-cloudfront.html この辺とか https://forums.aws.amazon.com/thread.jspa?threadID=74717 とか見ると、完全に同じことはできないのですが、オリジナルのサイトからのみのアクセスということならばできそうです。しかし、そうすると、feed readerとかでは画像が表示できなくなってしまいますね。悩ましいなぁ。

wordpressのsuper cacheの話でも

ブログを再開するとか言いつつ、なんか書けなくなってしまったよ。><

とりあえず簡単なネタでやるしかないか。というわけで、wordpressのsuper cacheではまったので、その話でも。

wordpressでキャッシュを使っていますか?毎回データベースにアクセスが飛ぶとやっぱり重くなってしまうわけです。特にブログなんて書いてしまえば、実際編集しなおしなんてあまりしないのでちょっとよろしくないんですね。そこで、キャッシュですよ。特にsuper cache。wordpressの人気プラグインの1つに WP Super Cacheってのがあるんですね。これを使うのが一般的なのですが、なぜかはまってしまったので、その際に私がした解決方法について書こうと思います。

今回使用したwordpressは、3.1.2。5/19/2011の現時点では最新です。また、WP Super Cacheは0.9.9.9です。何がはまったかというと、キャッシュを有効にするとなぜか画面が白くなる症状になってしまうという問題に突き当たったんですね。こうなるとしょうがないので、wp-config.phpのWP_CACHEをfalseにして一度使わないようにしないといけないんです。そして、問題なのはエラーログに何も書かれないことなんです。。。>< エラーログに書かれないと何が問題なのか追うのが大変で、もういっそのことここでキャッシュを使うのをやめて、もっと前にリバースプロクシサーバを立ててキャッシュをしようかと思ったくらいです。

さて、WP Super Cacheを使う際に、どのキャッシュを使うか、というオプションがありますよね。そこでは、

  • キャッシュファイルの提供に mod_rewrite を利用する。 (推奨)
  • キャッシュファイルの提供に PHP を利用する。
  • レガシーなページキャッシング。

まぁ、なんか3つ目は、「レガシーなページキャッシングを利用する。」と日本語を変えた方がいいような気がしないでもないですが、これで私がはまったのは、推奨とされているmod_rewriteを使わなかったからなんですね。素直に使っておけばサクッとできたのかもしれないのに。。。そして、PHPを利用したり、ダメだー、と思ってレガシーなページキャッシングをしたりして、余計にはまっていました。噂(WP Super CacheのFAQ)では、apcをオフにしたらオーケーとか書いてあるんですけど、その選択肢は選びたくなかったんですね。

というわけで、格闘をしていたら、mod_rewriteでさくっとできた、ってわけです。これからはmod_rewriteを素直に使いましょう。

    # BEGIN WPSuperCache
    
        RewriteEngine On
        RewriteBase /        AddDefaultCharset UTF-8        RewriteCond %{REQUEST_URI} !^.*[^/]$        RewriteCond %{REQUEST_URI} !^.*//.*$
        RewriteCond %{REQUEST_METHOD} !POST        RewriteCond %{QUERY_STRING} !.*=.*        RewriteCond %{HTTP:Cookie} !^.*(comment_author_|wordpress_logged_in|wp-postpass_).*$
        RewriteCond %{HTTP:X-Wap-Profile} !^[a-z0-9\"]+ [NC]
        RewriteCond %{HTTP:Profile} !^[a-z0-9\"]+ [NC]
        RewriteCond %{HTTP_USER_AGENT} !^.*(2.0\ MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|I
EMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-
TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry95
30|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800).* [NC]
        RewriteCond %{HTTP_user_agent} !^(w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|ji
gs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|se
c-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-).* [NC]
        RewriteCond %{HTTP:Accept-Encoding} gzip
        RewriteCond %{DOCUMENT_ROOT}/../custom-content/cache/supercache/%{HTTP_HOST}/$1/index.html.gz -f
        RewriteRule ^(.*) "/../custom-content/cache/supercache/%{HTTP_HOST}/$1/index.html.gz" [L]        RewriteCond %{REQUEST_URI} !^.*[^/]$

        RewriteCond %{REQUEST_URI} !^.*[^/]$        RewriteCond %{REQUEST_URI} !^.*//.*$
        RewriteCond %{REQUEST_METHOD} !POST        RewriteCond %{QUERY_STRING} !.*=.*        RewriteCond %{HTTP:Cookie} !^.*(comment_author_|wordpress_logged_in|wp-postpass_).*$
        RewriteCond %{HTTP:X-Wap-Profile} !^[a-z0-9\"]+ [NC]
        RewriteCond %{HTTP:Profile} !^[a-z0-9\"]+ [NC]
        RewriteCond %{HTTP_USER_AGENT} !^.*(2.0\ MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|I
EMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-
TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry95
30|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800).* [NC]
        RewriteCond %{HTTP_user_agent} !^(w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|ji
gs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|se
c-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-).* [NC]
        RewriteCond %{DOCUMENT_ROOT}/../custom-content/cache/supercache/%{HTTP_HOST}/$1/index.html -f
        RewriteRule ^(.*) "/../custom-content/cache/supercache/%{HTTP_HOST}/$1/index.html" [L]
    
    # END WPSuperCache

こんな感じになりました。気付いた人はいるかもしれないですが、custom-contentって何?と思うかもしれないです。それは次回で。一度に書くと、話のネタがなくなるので。
というわけで、軽いポストです。これからはWP Super Cacheを使うときはmod_rewriteを使いましょう。

Shin Ohno 2003-2012