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

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

2014/03

久しぶりの WordPress ネタです。

WordPress をサーバーごと引っ越しする場合、大まかな手順としてはこんな感じになると思います:
(1) 引っ越し先サーバーに PHP, MySQL, HTTPD を導入
(2) 引っ越し先サーバーに MySQL 用のデータベースを作成&アクセスユーザーとパスワードを作成
(3) 引っ越し前サーバーの WordPress ディレクトリをまるごと引っ越し先サーバーにコピー
(4) 引っ越し先サーバーの WordPress の設定ファイルを更新
(5) 引っ越し前サーバーから MySQL 用データベースをまるごとエクスポート
(6) 引っ越し後サーバーに (5) のデータをまるごとインポート
(7) データ調整

(1) は引っ越し先サーバーへのミドルウェアのインストールです。Linux 環境であれば yum で簡単にできちゃうと思っています:
# yum -y install mysql-server php php-mbstring php-mysql httpd

この後の手順で WordPress の環境をディレクトリまるごとコピーして引っ越しすることを想定しているので、理想を言えば PHP や MySQL などのバージョンも合わせておければ確実です。逆を言えば、もしうまく動かなかった場合はミドルウェアのバージョン差異を疑ってみてください。ま、よほど昔の環境からの移行、とかでなければ大抵は大丈夫だと思いますが、、、


(2) は引っ越し先サーバー側の MySQL の設定です。DB 名を wp_db、ユーザー名を wp_user、パスワードを wp_pass にするのであればこんな感じ:
# mysql -u root -p(パスワード)
> create database wp_db default character set utf8;
> grant all privileges on wp_db.* to wp_user@localhost identified by 'wp_pass' with grant option;
> quit
(3) は WordPress そのものをファイルシステムのレベルでコピーしちゃいましょう、ということです。引っ越し元の /var/www/html/wordpress に WordPress が導入されていて、それを引っ越し先の同じディレクトリに移動させるのであればこんな感じでしょうか:
(転送元での作業)
# cd /var/www/html
# zip -r wp.zip wordpress/ 
  (この結果できあがったファイル wp.zip を引っ越し先サーバーの /tmp とかに転送しておく)

(転送先での作業)
# cd /var/www/html
# unzip /tmp/wp.zip

(4) は新しい WordPress の設定ファイルの更新です。上記例であれば wordpress/wp-config.php というファイルが設定ファイルなので、これを新しい環境用に(DB接続情報などを)指定します:

ここまでの作業で WordPress 本体の引っ越しは(ほぼ)完了しています。ただ引っ越し先にはデータの中身が全く入っていない状態なので、それを引っ越し前のサーバーから移動させる作業が残っています。(5), (6) がその作業になります。

(5) は引っ越し前サーバーのデータをまとめて取り出す、という作業です。WordPress のデータが入っているデータベースをテーブル設計からまとめてダンプすることになるので、引っ越し前のデータベース名/ユーザー名/パスワードがそれぞれ wp_db / wp_user / wp_pass だったとして、ダンプの出力先ファイルを wp.sql とすると、こんな感じのコマンドを実行することになります。
# mysqldump -u wp_user -pwp_pass wp_db > wp.sql

(6) は (5) で取り出したデータをまとめて (2) で作った引っ越し先のデータベースに入れる、という作業です。(5) で作成した wp.sql を引っ越し先データベースに転送してからこんな感じのコマンドを実行します:
# mysql -u wp_user -pwp_pass wp_db
> source wp.sql

これでデータベースの中身の引っ越しも終わりました。WordPress ディレクトリごとコピーしているのでプラグイン類もそのまま移行されているし、カスタムフィールド等を使っていたとしてもデータベースまるごと移行しているので、そのカスタムデータも含めて移行されているはずです。事実、ここまでの作業でもう引っ越し先の WordPress にアクセスすればデータが参照できる状態になっているはずです。


でも実はここからが本題です。WordPress も、データベースも、どちらも引っ越し前のものを引っ越し後の環境に移動できました。でもこれだけでは足りないのです。それが (7) の作業になります。

実は WordPress は自分自身の動作環境に関わる設定を上記 (4) の設定ファイル以外にデータベース内にも格納しています。その内容は当然引っ越し前のものであり、それをそのまま引っ越し後の環境で使おうとしても正しい設定ではないため、想定外の動作を引き起こす可能性があります。それを調整する作業が必要です。

具体的には、WordPress が(引っ越し前のサーバーで)最初にセットアップした時に収集した情報が、動作のための情報として wp_options テーブル内に格納されています。その情報には「自分自身のURL」が含まれていて、その内容は当然引っ越し前のサーバーの URL になっています。ということは WordPress が自分自身の URL を調べて戻ろうとすると、引っ越し前のサーバーの URL に戻ってしまう、といった動作を起こしてしまいかねないのでした。そういったことが起こらないよう、データベース内の該当データを調整する必要があります。

そのため、最後の (7) の作業としては上記のような引っ越し前サーバーの情報を新しい引っ越し先サーバーの情報に書き換える、ということになります。

具体的な作業としては、引っ越し前サーバーのホスト名が www1.host.co.jp、引っ越し後サーバーのホスト名が www2.host.com だったとすると、引っ越し後の WordPress の wp_options テーブルには(ホスト名が www2.host.com であるにも関わらず)次のような引っ越し前サーバーの情報が格納されているはずです:
# mysql -u wp_user -pwp_pass wp_db
> select option_name, optoin_value from wp_options where option_name = 'siteurl' or option_name  = 'home';
+-------------+-----------------------------------+
| option_name | option_value |
+-------------+-----------------------------------+
| home | http://www1.host.co.jp/wordpress/ |
| siteurl | http://www1.host.co.jp/wordpress/ |
+-------------+-----------------------------------+

これらを新しい引っ越し後サーバーの情報に書き換える必要があります。SQL だとこんな感じでしょうか:
# mysql -u wp_user -pwp_pass wp_db
> update wp_options set option_value = 'http://www2.host.com/wordpress/' where option_name = 'home' or option_name = 'siteurl';
> select option_name, optoin_value from wp_options where option_name = 'siteurl' or option_name  = 'home';
+-------------+---------------------------------+
| option_name | option_value |
+-------------+---------------------------------+
| home | http://www2.host.com/wordpress/ |
| siteurl | http://www2.host.com/wordpress/ |
+-------------+---------------------------------+

これで WordPress のオプションデータの調整もできました。安心して引っ越し先サーバーで WordPress が使えます。








 

ネットワーク(もしかして Facebook ??)の調子があまりよくない時に気付いた小ネタです。
Facebook を PC のブラウザでページの一番下までスクロールした時の様子。

んー??? こんな感じがしばらく続いて・・・
2014032601

 
で、しばらく待つと中身が入ってこんな感じに(加藤さん、おいしそうな画像使わせていただきました):
2014032602
 

一番下までスクロールして、「次のデータを取りに行こう」とした段階で空のフォームだけは先に描画しちゃってるんですね。で、AJAX とかで中身が取れたら埋める、と。

普段は AJAX で取得するまでが一瞬だと思うので、こんな順序で描画されていることに気付けません。たまたまネットワークの調子が悪かったか Facebook からのリターンが遅れていたことで Facebook 流(?)の方法に気付けました。ラッキー!


 

まずはこの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 を参照することになるので上述のような問題を回避できることになります、たぶん。





 

このページのトップヘ