AWSのCloudFrontでWordPressを高速化する

最近仕事でCloudFrontを使う機会がありました。画像やJS,CSSなどが高負荷の原因となっている事案だったので、そのあたりを回避できないかなぁと
構成はNginx+php-fpmです。WordPress以外でも応用できそうメモ程度に残しておきます。
いろいろとネットを調べると、キャッシュしたいデータをS3にアップロードするパータンのものが多かったのですが、今回はOriginサーバからキャッシュする方法を試して見ました。
キャッシュするオリジンサーバ: iiwake.me
CloudFrontに割り当てるDomain Name: cdn.iiwake.me

・画像ファイルへのリクエスト
[iiwake.me Imageへのリクエスト] -> [cdn.iiwake.meへリダイレクト] -> [CloudFront(キャッシュがあれば返す)] -> [iiwake.me ImageFile]

CloudFrontの設定

CloudFrontを開いて「Create Distribution」ボタンを押す。
cf1
Select delivery methodが表示されるので「Web」を選択して、「Continue」を押す

ディストリビューションの作成、WordPress用にカスタマイズする

cf2
Create distributionが表示されたら各項目に入力していく。※ スクリーンショットの画面は編集画面なので、位置が若干変わってるかもしれません。
また、今回WordPress設定に必要な項目しか書いていませんのであしからず。

Original Settings

cf3
Origin Domain Name ・・・ 元のドメイン名、このブログを参考にしているので仮に「iiwake.me」としています。

Default Cache Behavior Settings

cf4
Viewer Protocol Policy ・・・ 「HTTP and HTTPS」を選択
Allowed HTTP Methods ・・・ 「GET, HEAD, PUT, POST, PATCH, DELETE, OPTIONS」を選択
Forward Cookies ・・・ 「All」を選択
Forward Query Strings ・・・ 「Yes」を選択

Distribution Settings

cf5
Alternate Domain Names (CNAMEs) ・・・ CNAMEを入力する。今回は予めに「cdn.iiwake.me」というサブドメインを入力しておきます。

DNSの設定

cf8
CloudFrontのトップ画面に戻ると、上のようにDistributionsに項目が追加されています。Statusが「InProgress」から「Deployed」に変わるのを待ちましょう。大体20〜30分くらいかかるかもしれません。
cf6
StatusがDeployedになったら、項目を選択してDistributionSettingsを開きます。「General」タブの「Domain Name」を見るとドメインが付与されていることがわかりますね。
cf7
この付与されたドメインを、先ほど設定した「cdn.iiwake.me」というサブドメインのCNAMEに設定します。こちらはお名前.comでの設定後のものです。

サーバ側の設定(Nginx)

以下Nginxの設定です。Locationディレクティブに設定して見ました。もうちょっといい方法がありそうな気もしますが、今回はこれで・・・。

    location / {
        ・・・・・
        set $cloudfront "false";
        if ($http_user_agent = "Amazon CloudFront") {
            set $cloudfront "true";
        }
        if ($cloudfront = "false") {
            rewrite ^.+.(jpg|jpeg|gif|css|png|js|ico)$ http://cdn.iiwake.me$request_uri? last;
        }
        if (-f $request_filename) {
            expires 1d;
            break;
        }
    ・・・・・・・

Apacheの場合も、同じようにリダイレクト設定してあげればいけると思います。
サーバが貧弱で、スケールアウトやスケールアップ考えるのであれば、まずCloudFront試してみるのもありなんじゃないかなぁ。実質のコストで考えると安く済むかもしれません。

Nginxでgzip化した状態でcacheをしてみる

Nginxのキャッシュはproxy_cacheによって、比較的簡単に取ることができるのですが、できればgzip化された状態でキャッシュしたいなぁと思ってやってみました。
1サーバでNginx + Apacheを想定して設定してみました。
これはあくまでgzip化した状態でキャッシュできるというだけで、パフォーマンスについては調査してません。
これをやってパフォーマンスが低下しても責任はもてませんのであしからず。

下記の設定をみればそのままですが、やってることは結局
(Nginx[80]でキャッシュ) (Nginx[8080]でgzip化) (Apache[10080]アプリケーション)
というだけです。
実際のキャッシュの中身を見ると

KEY: http://www.example.com/
...
Content-Encoding: gzip

めでたくgzip化された状態でキャッシュできました。
以下、端折ってますが設定例です。

http {
    ...
    ...
    proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=zone1:4m inactive=1d max_size=100m;
    proxy_temp_path /tmp/nginx_temp;
    upstream backend {
        server localhost:10080;
    }
    upstream gzip_backend {
        server localhost:8080;
    }
    server {
        listen 80;
        server_name www.example.com;
        location / {
            ...
            proxy_cache zone1;
            proxy_cache_key "$scheme://$host$request_uri$is_args$args";
            proxy_cache_valid 200 60m;
            proxy_cache_valid any 1m;
            proxy_pass http://gzip_backend;
    }
    server {
        listen 8080;
        location / {
            ...
            gzip on;
            gzip_http_version 1.0;
            gzip_vary on;
            gzip_comp_level 5;
            gzip_types text/xml text/css text/javascript application/xhtml+xml application/xml application/rss+xml application/atom_xml application/x-javascript application/x-httpd-php;
            proxy_pass http://backend;
        }
    }
}

Python Fabricで複数台のNginxのキャッシュクリアをする小ネタ

前回にNginxのキャッシュについて書いたので、今回は複数台のNginxキャッシュクリアをするコードスニペット的なのを書いてみます。
自動実行でも良いのですが、今回は手動でコマンドを叩いて消します。
キャッシュクリアに使うのはPython Fabricです。
Fabricを使えば、簡単に複数サーバでキャッシュクリアを実行出来るので本当に便利ですね。

# -*- coding: utf-8 -*-
from fabric.api import env, run, roles, cd, sudo
from fabric.contrib import console
def nginx():
        env.roledefs.update({'nginx':['192.168.0.1','192.168.0.2','192.168.0.3']})
@roles('nginx')
def cache_clear():
    with cd('/var/cache/nginx'):
       sudo('ls -la')
       if console.confirm('Do you really want to delete the cache?', default=True):
           sudo('rm -rf ./*')

そして以下のコマンドで実行します。

$ fab nginx cache_clear

結局やってるのは「sudo rm -rf ./*」なのですが、間違うとかなり危険なので一応、一覧参照と確認を入れてます。
たったこれだけで複数台のサーバで、横断的にキャッシュクリアできるのは良いですね。

Nginxのキャッシュとバーチャルホスト設定で気をつけたい事 メモ

Nginxで複数ドメインを扱う時に、気をつけたい事があります。それはキャッシュの設定。
公式を見る限り、proxy_cache_keyのデフォルトは

$scheme$proxy_host$request_uri

などとなっていますが、この設定を変更せずに複数ドメインを扱うとハマるというお話です。
具体的には以下のような設定の場合

server {
    listen       80;
    server_name  example.com *.example.com;;
    location / {
        ......
        ......
        proxy_cache zone1;
        proxy_cache_key $scheme$proxy_host$request_uri;
        proxy_cache_valid  200 1d;
        proxy_cache_valid  any 1m;
        proxy_pass http://backend;
        ......
        ......

この場合、「http://example.com」や「http://www.example.com」など複数ドメインで同一のキャッシュを参照する様になってしまいます。
実際のキャッシュの中身を見てみると・・・

KEY: httpbackend/

あなおそろしや・・・Nginxのキャッシュは手動で消す(or モジュール入れる)しか方法が無いため、複数ドメインで同じキャッシュが見えてしまう状態になってしまいます。
では、次にproxy_cache_keyを変更してみます。

proxy_cache_key "$scheme://$host$request_uri$is_args$args";

nginxを再起動し、キャッシュをクリアして再度キャッシュの中身を見てみます。

KEY: http://www.example.com/

今度はしっかりとドメイン名でキャッシュできました。
この辺りはハマりどころなので気をつけたいところですね。
ちなみに(複数サーバの)Nginxキャッシュの削除は、Python Fabricで簡単に行えるようにしております、Fabricマジ便利!

Nginx ロードバランサー設定でやってしまったミス

せっかくブログ立てたのに、最近更新してないのでメモ的に使っていきます。
Nginxをロードバランサーとして使う際に、upstreamブロックを以下の様にしたところ

upstream backend {
server 10.XX.XX.1 weight=2;
server 10.XX.XX.2;
}

10.XX.XX.2のサーバを誤って落としてしまい、気付かずにブラウザから接続したらNginxからの応答が全く返ってこない始末・・・。
調べてみたらweight以外にも設定があるようで、以下追記

upstream backend {
server 10.XX.XX.1 weight=2;
server 10.XX.XX.2 max_fail=3 fail_timeout=30s;
}

max_failは試行回数、fail_timeoutは再接続(?)までの時間時間みたい。
一応10.XX.XX.2をもう一度落として確認したところ、10.XX.XX.1へ接続するようになったっぽい。

Nginxのempty_gifでhealthcheckをする

Nginxのempty_gifが地味に便利だったのでメモ
EC2などでELBなどを利用していると、Ping Targetにヘルスチェックファイルが必要になります。
インスタンスを立ち上げる際に、いちいち空のファイルを毎回デプロイするのも面倒なのですが、
Nginxのempty_gifを利用すると以下の設定だけでヘルスチェックできる感じです。

location = /healthcheck.html {
access_log off;
empty_gif;
}

empty_gifはメモリ上の1×1の空GIFファイルを取り出してくれるらしいので、実ファイル設置するよりかは多少早いかも。
あと、ELBからのヘルスチェックが頻繁に行われるので、ログに残したく無かったので、access_log off;にしてます。
あとはELB HealthCheckのPing Pathを/healthcheck.htmlなどにすればオッケーでした。