まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

まずはこの2つのスクリーンショットを見比べてください:

(スクリーンショット1)
2014031401

(スクリーンショット2)
2014031402


どちらもアマゾンのウェブページを同じ Chrome ブラウザで見ている時に取得したものです。全般的に似たような内容ですが、パッと見で「同じではない」ことはわかりますよね。

でもどちらも全く同じページ(同じURL)を指しています。どちらのページもアマゾンのトップページから "ThinkPad" を検索した時の結果ページです。PC 用とモバイル用、というわけでもなさそうですよね。 ではこの違いはなんでしょう?



・・・答は User-Agent の違いでした。(1)は普通の Windows 向け Chrome 標準の User-Agent でアマゾン内を検索したものですが、(2)は Google のクローリングボットの User-Agent(Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)) に偽装してから検索したものです。
2014031403

ここからわかることはおそらくアマゾンはGoogle のクローリングボットが来た時用の、なんらかの手を加えて結果を返すようにしている、ということです。そしてそれはおそらく SEO 対策であろう、と推測されます。

普通に人間がウェブページを見る時に見やすい〈求める)情報と、SEO 対策を重視した画面が同じとは限りません。SEO 的に有利なページを作りたい一方で、ユーザーにはパーソナライズやレコメンドを表示するなど使い勝手を損ないたくもない。そのためにはこのような表示出し分けの対策をしている、ということだと思います。

ちなみに (1) の HTML は約80Kバイト、(2) は約110Kバイトでした。SEO 対策済み(?)のサイトの方が3割程度情報量が多いと考えるべきなのか、人間向けのサイトの方が情報をまとめていると考えるべきなのか。


詳しい内容を調べたわけではないのですが、とりあえずこの2つのページの大きな違いとして、(1) は Shift_JIS 、(2) は UTF-8 で記述されていました。まあ根本的に違うエンジンが使われていると考えるべきでしょうね・・・



UNIX 系システムには cron(d) と呼ばれるジョブの実行デーモンが存在しています。簡単にいえば「指定した日時や時刻に特定のコマンドを実行させる仕組み」です。「毎日午前1時にデータベースのバックアップコマンドを実行する」とか、「1時間おきに特定のスクリプトを実行する」とか、そういうものです。バックアップや集計目的のジョブを自動実行させようとすると cron を使うことになると思います。

例えばですが、毎日午前0時になったら /usr/local/bin/xxx.sh を実行したい、という場合は以下の1行を /etc/crontab に加えます:
0 0 * * * /usr/local/bin/xxx.sh

最初の5要素は実行タイミングです。1つ目が分、2つ目は時、3つ目は日、4つ目は月、5つ目が曜日を指定します。上記例では分に0、時に0が指定されているので「毎日午前零時」を指定していることになります。そして6番目の要素が実行させるジョブです。 というわけで上記の1行は毎日午前0時になったら /usr/local/bin/xxx.sh を実行する、という指定をしていることになるわけです。この程度ならシンプルで分かりやすいですよね。


で、今回やりたかったことはこんな内容でした:
(1) 毎日午前零時になったら、
(2) あるディレクトリ(/usr/tmp/logs/)に移動して
(3) php backuplog.php YYYYMMDD 形式のジョブを実行する
(4) 上記の YYYYMMDD は前日を示す日付文字列

要は backuplog.php という、ログのバックアップを行うような PHP スクリプトが /usr/tmp/logs/ に用意されていて、それを自動実行したいのですが、パラメータとして前日の日付文字列を付けたい(前日分のログをバックアップしたいので、その日付をパラメータで渡す)ということです。例えば今日の日付が 20140314 だとしたら、20140313 という文字列を指定して実行したい、ということです。まあ、この要件自体はさほど珍しくはないかな、と。敢えて付けくわえると、ログファイルの場所の関係もあって、直接 /usr/tmp/logs/backuplog.php を実行するのではなく、最初に /usr/tmp/logs にカレントディレクトリを移してから実行したい、という点が要件にあります。まあ、でも、これも珍しい要件とは言えないのかな・・・

ただ実現できるまで結構ハマりました。いくつか隠れた落とし穴があるんです。この要件を実現する crontab の記述内容をすぐ&正確に言える人ってどのくらいいるんだろ・・・



要件のうち、問題になりそうなところを1つずつ解決していきましょう。まず毎日午前零時になったら、は上記で説明しているので問題ないです。

次にカレントディレクトリを移動してからコマンドを実行する、という点。端末上だと cd /usr/tmp/logs と php backuplog.php YYYYMMDD という2つのコマンドを実行することになるのですが、それをどのように1行で記述するか、という問題です。 ただ、これも実は答はシンプルで単に「;(セミコロン)で区切って記述するだけ」です(端末上でもこの書き方で連続実行ができます)。なので crontab の6つ目の要素は cd /usr/tmp/logs;php backuplog.php YYYYMMDD という書き方になります。

さて、ここからがちょっと難しくなります。前日を示す文字列パラメータをどうやって作成すればよいでしょう? これは date コマンドを使うことになります。UNIX の date コマンドはそのまま実行すると日付時刻を表示するのですが、パラメータによって対象日やフォーマットを変更することができます。まず普通に実行するとこんな感じに表示されます:
# date
2014年  3月 14日 金曜日 04:53:43 UTC

このフォーマットを YYYYMMDD 形式に変えるには + オプションで形式を指定します:
# date +%Y%m%d
20140314

いい感じですね、でもこれは今日の日付です。では「前日」をどのようにすればいいでしょうか? こちらは --date オプションで指定することができるようです:
# date --date '1 day ago' +%Y%m%d
20140313

目的の文字列が取得できました! 後はこの結果をコマンドのパラメータとして渡せればいいので、上記コマンドを `(バッククォート)で括ってこんな感じにすると、目的のコマンドが完成したことになります。試しにコマンドラインから実行すると期待通りの結果が得られます:
# php backuplog.php `date --date '1 day ago' +%Y%m%d`


ということは、全てあわせて crontab に追加する内容はこれでいい、ということに・・・
 0 0 * * * cd /usr/tmp/logs;php backuplog.php `date --date '1 day ago' +%Y%m%d`

・・・実はこれ、(惜しいけど)まだ間違ってます。crontab では % という文字にはエスケープが必要になるのでした。最終的な正解はこちらです:
 0 0 * * * cd /usr/tmp/logs;php backuplog.php `date --date '1 day ago' +"\%Y\%m\%d"`

スケジュールエージェントはデバッグが難しい・・・


 

AWS(Amazon Web Services) のストレージサービスである S3。特定条件の中で利用する限りは無料になるし、容量1GBに付き月10円弱で使えるし、これ単体で静的なウェブホスティングにも使えるので、ちょっとしたファイル置き場以上の便利さがあります。

最近、この S3 をバケット単位でマウントできるという s3fs というツールの存在を知りました。その紹介です。

そもそも S3 をマウントできると何がうれしいのか?単なるファイル置き場というだけでなく、自分が最近使う機会の多い WordPress をクラスタリング環境にする際にネットワークマウントは必須になっている、という背景もあり、自分の場合にはそういったネットワークマウントのできるパブリックなクラウドストレージは WordPress と併用する環境としても魅力的なのでした。


ここでちょっとだけ脱線を。WordPress 自体はデータを RDB(一般的には MySQL)内に格納するので、クラスタリング環境を構築する上で特別意識することはないように思えます。ただし、ファイルをアップロードして利用する場合だけは注意が必要です。WordPress のファイルアップロード(メディアアップロード)では、アップロードされたファイルはファイルシステム上に移動されて、WordPress 自身はそのファイルパスの情報だけを格li納します(つまりバイナリの情報はDBには格納しません)。

この挙動は WordPress が1台の HTTP サーバー環境で稼働する場合は大きな問題にはなりにくいと思っていますが、例えば2台の HTTP サーバー(A, B) でクラスタリング環境を構成していた場合で考えると問題が浮き彫りになります。

仮に2台のうちのサーバーA上で、このメディアアップロードが実行されたと仮定します。サーバーA はアップロードされたファイルを自分自身の /var/www/html/wordpress/wp-content/uploads/ 以下に移動します。仮にそのファイル名が /var/www/html/wordpress/wp-content/uploads/aaa.jpg となったとします。

上記のように WordPress ではバイナリの情報は DB に格納せず、そのファイルパス情報だけを DB に格納します。つまりアップロードされたファイルは /var/www/html/wordpress/wp-content/uploads/aaa.jpg に存在する、という情報だけを DB に記憶することになります。サーバーA 内ではこれで問題になることはありません。

ところがクラスタリングのもう一台であるサーバーBも同じDBを参照しているので、サーバーBからも「該当ファイルは /var/www/html/wordpress/wp-content/uploads/aaa.jpg に存在する」と見えてしまします。このファイルはサーバーA のファイルシステムには保存されていますが、サーバーB には存在していません。したがって例えばサーバーB 上の処理でこの画像を表示させようとすると「ファイルが存在しない」ことになってしまいます。クラスタリングの振り分けはユーザーがコントロールできるわけではないので、ユーザーによってファイルが見えたり見えなかったり、ということが起きてしまいます。

これを解決するにはディレクトリを共有するしかありません。上記例であれば /var/www/html/wordpress/wp-content/uploads/ 以下が実際にはネットワークフォルダになっていて、その同じ箇所をサーバーA/B 双方からマウントして参照する、という構成になっている必要があるのでした。そしてそのようなネットワークフォルダをどうやって用意・調達するか、という問題に直面することになるのでした。


少し脱線しましたが、こんな背景の中で「無料でも使える AWS S3 がマウントできる」というのはかなり魅力的な情報なのでした。早速試してみたので以下にその手順を紹介します:

まずは S3 側の準備をします。仮に "awsuser1" という名前のバケットが S3 上に作成済みであると仮定し、このバケットを共有する前提で紹介します:
2014031201


必要な設定は、このバケットに接続するための認証ユーザーの作成です。既に別の AWS サービス用などにユーザーを作成済みであればそのユーザーを流用しても構いませんが、ここでは S3 アクセス用のフル権限を持ったユーザーを新規に作成することにします。

AWS のサービス一覧から IAM を選択します:
2014031202


左ペインで Users を選択し、"Create New Users" ボタンをクリックします:
2014031203


(名前自体はあまり重要ではないので)適当な名前のユーザーを作成します:
2014031204


作成直後のダイアログの "Show User Security Credentials" をクリックして展開します:
2014031205


展開後に表示される "Access Key ID" と "Secret Access Key" 両方の値をメモするかダウンロードして、後から参照できるようにしておきます。記録できたら "Close Window" でこのダイアログを閉じます:
2014031206


次にこのユーザーに S3 へのアクセス権を付与します。左ペインで Users が選択された状態に戻ります。ここで作成したユーザーを選択し、右下のユーザー詳細情報ペインでは "Permissions" タブを選択します。その中の "Attach User Policy" ボタンをクリックします:
2014031207


アクセス権指定のダイアログが表示されます。Select Policy Template と書かれたリストの下の方に "Amazon S3 Full Access" という列があるので、この右の "Select" ボタンをクリックします:
2014031208


確認画面になるので "Apply Policy" をクリックします。これでこのユーザーは S3 へのフルアクセス権を持ったことになります:
2014031209




次はマウント元側の設定です。今作成した(S3 へのフルアクセス権を持った)ユーザーでマウントするための設定を行います。まずインターネットに接続された Linux を用意します。以下 CentOS を使っている前提で紹介しますが、ディストリビューションによる可不可は特にないと思っています(AWS のインスタンスでも、そうでなくてもできます)。

まず事前準備として必要そうなライブラリを導入しておきます:
# yum groupinstall "Development Tools"  (C/C++ コンパイル環境を導入)
# yum install openssl-devel (libcrypt 0.9 を導入) # yum install libcurl-devel (libcurl 7.0 を導入) # yum install libxml2-devel (libxml-2.0.2.6 を導入)

上記3ライブラリに加えて fuse 2.8.5 が必要になります。AWS の EC2 インスタンスからは普通に yum でインストールすることもできます(yum install fuse fuse-devel)が、yum でインストールできないケースも多いのでビルド手順を紹介します。
# cd /usr/local/src
# wget http://sourceforge.net/projects/fuse/files/fuse-2.X/2.8.5/fuse-2.8.5.tar.gz
# tar xzvf fuse-2.8.5.tar.gz
# cd fuse-2.8.5
# ./configure --prefix=/usr
# make
# make install
# ldconfig
# modprobe fuse

fuse までが導入できると s3fs のビルド条件がそろったことになります。以下の手順で s3fs をビルドします:
# cd /usr/local/src
# wget http://s3fs.googlecode.com/files/s3fs-1.61.tar.gz
# tar xzvf s3fs-1.61.tar.gz
# cd s3fs-1.61
# export PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/lib64/pkgconfig/
# ./configure --prefix=/usr
# make
# make install

これで s3fs のインストールが完了しました。最後に S3 へアクセスするためのユーザーの設定を行います。/etc/passwd-s3fs というファイルを、上記ユーザー作成時にメモした Access Key ID と Secret Access Key を使って、以下のような内容で新規に作成します(カッコは不要です):
# echo '(Access Key ID):(Secret Access Key)' > /etc/passwd-s3fs

仮に Access Key ID が ABCD で Secret Access Key が wxyz だった場合、/etc/passwd-s3fs の中身は以下の一行になります:
ABCD:wxyz

このファイルは他者から参照できないようにする必要があるため、ファイルパーミッションを変更します:
# chmod 640 /etc/passwd-s3fs

マウント先のディレクトリを用意します。ここでは /mnt/s3 というディレクトリに S3 をマウントすることにします:
# mkdir /mnt/s3

これで全ての準備が整いました。S3 の awsuser1 というバケットを /mnt/s3/ にマウントするには以下のようなコマンドを実行します。バケット名やマウント先は環境に合わせて適宜変更してください:
# s3fs awsuser1 /mnt/s3/ -o allow_other,default_acl=public_read

結果はこんな感じになりました。S3 って容量としては 256TB も用意されてるんですねw:
2014031210


再起動時にもこのマウントを有効にするには、同じコマンドを手動で再発行するのではなく、 /etc/fstab に以下の1行を追加記述するのがいいと思います。これもバケット名やマウント先は環境に合わせて適宜変更してください:
s3fs#awsuser1 /mnt/s3 fuse allow_other,default_acl=public-read 0 0


こんな感じで S3 をファイルシステムにマウントできることがわかりました。後は WordPress をクラスタ環境で使う場合は、メディアアップロードのディレクトリをこのマウント先にしておけば、同じイメージをクローンしてもクローン先でも同じ S3 を参照することになるので上述のような問題を回避できることになります、たぶん。





 

このページのトップヘ