2015-09-28

fc2blog を Cloud Foundry で動かす

「Cloud Foundry 百日行」第67日目は、有名ブログエンジン fc2blog です。
実はオープンソース化されているので、Cloud Foundry上での動作にチャレンジしてみましょう。

残念ながら一部の機能をうまく動かすことはできませんでしたが、ブログエンジンとしての機能は果たしてくれるところまでは検証ができたため、ご紹介したいと思います。

基本情報

今回の手順の概要は以下の通りです。

  • 1) ソースコード取得
  • 2) MySQLServiceの作成
  • 3) 事前準備
  • 4) Cloud Foundry上へのデプロイ
  • 5) 動作検証
  • 6) 問題点

1. ソースコード取得

$ git clone https://github.com/fc2blog/blog
~~~
$ cd blog
blog$ ls
app  LICENSE.txt  public  README.md

fc2blogのソースコードはPHPで書かれています。
REAME.mdによると環境としてはPHP5.2.17以上、MySQL5.1以上が必要とのことです。

なお、このアプリのデプロイの際には設定ファイルの記述やソースコードの修正が必要なため、変更済みの物も こちら に公開してあります。

2. MySQLServiceの作成

データベースとしてMySQLが要求されているのでMySQLServiceを作成してBindします。
手順はいつも通り --no-start起動create-servicebind-service です。

blog$ cf push fc2blog --no-start
blog$ cf create-service p-mysql 100mb fc2blog-mysql
blog$ cf bind-service fc2blog fc2blog-mysql

3. 事前準備

さて、この章ではいくつか設定ファイルの記述やソースコードの修正が入ります。
修正済みのソースコードを用いて検証中の方は基本的にこの章の作業内容は不要となっているので、4章のデプロイから続きを行って下さい。

configファイルの編集

まずはconfigファイルの修正です。
ここは主にデータベースへ接続するための情報などを書き込む箇所ですが、今回はアプリに割り振られた環境変数

blog$ cf env fc2blog
Getting env variables for app fc2blog in org ukaji / space default as ukaji...
OK
 
System-Provided:
{
 "VCAP_SERVICES": {
  "p-mysql": [
   {
    "credentials": {
     "hostname": "10.244.7.6",
     "jdbcUrl": "jdbc:mysql://10.244.7.6:3306/cf_30cd0cb6_f8fe_4153_ac2f_f70e2b5f7aa2?user=bZb39TC9aZkPDGom\u0026password=6LGBHwEup1ob3NJ0",
     "name": "cf_30cd0cb6_f8fe_4153_ac2f_f70e2b5f7aa2",
     "password": "6LGBHwEup1ob3NJ0",
     "port": 3306,
     "uri": "mysql://bZb39TC9aZkPDGom:6LGBHwEup1ob3NJ0@10.244.7.6:3306/cf_30cd0cb6_f8fe_4153_ac2f_f70e2b5f7aa2?reconnect=true",
     "username": "bZb39TC9aZkPDGom"
    },
    "label": "p-mysql",
    "name": "fc2blog-mysql",
    "plan": "100mb",
    "tags": [
     "mysql"
    ]
   }
  ]
 }
}
{
 "VCAP_APPLICATION": {
  "application_name": "fc2blog",
  "application_uris": [
   "fc2blog.10.244.0.34.xip.io"
  ],
  "application_version": "fbd2db74-bb6e-41a4-90d1-73ad3e137901",
  "limits": {
   "disk": 1024,
   "fds": 16384,
   "mem": 256
  },
  "name": "fc2blog",
  "space_id": "03bf316f-df9e-442e-b127-589e673a5652",
  "space_name": "default",
  "uris": [
   "fc2blog.10.244.0.34.xip.io"
  ],
  "users": null,
  "version": "fbd2db74-bb6e-41a4-90d1-73ad3e137901"
 }
}
 
No user-defined env variables have been set
 
No running env variables have been set
 
No staging env variables have been set

からドメイン名やデータベース名などを取得できるような設定を行います。

blog$ cp public/config.php.sample public/config.php
blog$ vi public/config.php
<?php
 
//error_reporting(-1);
error_reporting(0);
 
// 直接呼び出された場合は終了
if (count(get_included_files())==1) {
  exit;
}
 
// 環境変数からMySQL接続情報を取得
$services = json_decode($_ENV['VCAP_SERVICES'], true);
$service = $services['p-mysql'][0];  // pick the first MySQL service
 
// DBの接続情報
define('DB_HOST',     $service['credentials']['hostname'] . ':' . $service['credentials']['port']);          // dbのホスト名
define('DB_USER',     $service['credentials']['username']);     // dbのユーザー名
define('DB_PASSWORD', $service['credentials']['password']);      // dbのパスワード
define('DB_DATABASE', $service['credentials']['name']); // dbのデータベース名
define('DB_CHARSET',  'UTF8MB4');            // MySQL 5.5未満の場合はUTF8を指定してください
 
// サーバーの設定情報
$application = json_decode($_ENV['VCAP_APPLICATION'], true);
$domain = $application['application_uris'][0];
define('DOMAIN',        $domain);           // ドメイン名
define('PASSWORD_SALT', '1234567890qwertyuiop'); // 適当な英数字を入力してください
 
// 設定クラス読み込み
define('WWW_DIR', dirname(__FILE__) . '/');
require(dirname(__FILE__) . '/../app/core/bootstrap.php');
blog$ git diff --no-index -- public/config.php.sample public/config.php
diff --git a/public/config.php.sample b/public/config.php
index 1c533c4..f2b92cc 100644
--- a/public/config.php.sample
+++ b/public/config.php
@@ -8,18 +8,23 @@ if (count(get_included_files())==1) {
   exit;
 }
 
+// 環境変数からMySQL接続情報を取得
+$services = json_decode($_ENV['VCAP_SERVICES'], true);
+$service = $services['p-mysql'][0];  // pick the first MySQL service
+
 // DBの接続情報
-define('DB_HOST',     'localhost');          // dbのホスト名
-define('DB_USER',     'your user name');     // dbのユーザー名
-define('DB_PASSWORD', 'your password');      // dbのパスワード
-define('DB_DATABASE', 'your database name'); // dbのデータベース名
+define('DB_HOST',     $service['credentials']['hostname'] . ':' . $service['credentials']['port']);          // dbのホスト名
+define('DB_USER',     $service['credentials']['username']);     // dbのユーザー名
+define('DB_PASSWORD', $service['credentials']['password']);      // dbのパスワード
+define('DB_DATABASE', $service['credentials']['name']); // dbのデータベース名
 define('DB_CHARSET',  'UTF8MB4');            // MySQL 5.5未満の場合はUTF8を指定してください
 
 // サーバーの設定情報
-define('DOMAIN',        'domain');           // ドメイン名
-define('PASSWORD_SALT', '0123456789abcdef'); // 適当な英数字を入力してください
+$application = json_decode($_ENV['VCAP_APPLICATION'], true);
+$domain = $application['application_uris'][0];
+define('DOMAIN',        $domain);           // ドメイン名
+define('PASSWORD_SALT', '1234567890qwertyuiop'); // 適当な英数字を入力してください
 
 // 設定クラス読み込み
 define('WWW_DIR', dirname(__FILE__) . '/');
 require(dirname(__FILE__) . '/../app/core/bootstrap.php');
-

PASSWORD_SALT は元の文字列とは別の適当な英数字を設定して下さい。

モジュールの追加

必要なモジュールをphp-buildpackの機能 PHP_EXTENSIONS を用いて導入します。

blog$ mkdir .bp-config
blog$ vi .bp-config/options.json
{
        "PHP_EXTENSIONS": ["mysqli", "pdo", "pdo_mysql", "gettext", "mbstring", "gd"]
}

apache設定ファイルの記述

PaaS上でアプリケーションを動作させる際、アプリケーション開発者の想定しているアプリの形とPaaSプロバイダ側が想定しているアプリの形に、どうしてもズレが生じてしまう場合があります。
今回のアプリは

SERVERのアプリ展開場所 (/var/www/html/ 等)
|-- app/
`-- public/ (←アプリのDocument Root)
    `-- admin/
        `-- install.php

のような構成が製作者サイドでは想定されているようですが、これをCloud Foundry上に何も考えずにデプロイすると、

/home/vcap/app/
`-- htdocs/ (←アプリのDocument Root)
    |-- app/
    `-- public/
        `-- admin/
            `-- install.php

このような展開のされ方をします。
アプリのDocument Rootが異なるとファイルを呼び出すパスが正しく動作しないため、これをApacheの設定ファイルで修正してあげましょう。

blog$ mkdir .bp-config/httpd
blog$ vi .bp-config/httpd/httpd.conf
ServerRoot "${HOME}/httpd"
Listen ${PORT}
ServerAdmin "${HTTPD_SERVER_ADMIN}"
ServerName "0.0.0.0"
DocumentRoot "/home/vcap/app/htdocs/public"
Include conf/extra/httpd-modules.conf
Include conf/extra/httpd-directories.conf
Include conf/extra/httpd-mime.conf
Include conf/extra/httpd-logging.conf
Include conf/extra/httpd-mpm.conf
Include conf/extra/httpd-default.conf
Include conf/extra/httpd-remoteip.conf
Include conf/extra/httpd-php.conf

参考にしたのは こちらのページ です。
ここでは Document Root の項目だけを書き換えています。

composer関連の設定

今回のアプリの中身を見てみると、composer(=PHPのライブラリ依存管理ツール)が使われているようです。

blog$ find -name composer.json
./public/js/jquery/jQuery-Timepicker-Addon/composer.json

Cloud FoundryのPHP Buildpackではcomposerがサポートされているのですが、デフォルトの設定ではアプリケーションのrootにある composer.json ファイルしか読み取ってくれないため、上記の位置にあるような composer.json ファイルを読むような設定を与えてみます。

方法としては、 PHP Buildpackのcomposerサポート機能 の1つ、 COMPOSER_INSTALL_OPTIONS を用いて、 composer install の作業ディレクトリを指定するオプションである --working-dir DIRNAME を使えば良さそうです。

※参考

$ composer --help
Usage:
 help [--xml] [--format="..."] [--raw] [command_name]
 
Arguments:
 command               The command to execute
 command_name          The command name (default: "help")
 
Options:
 --xml                 To output help as XML
 --format              To output help in other formats (default: "txt")
 --raw                 To output raw command help
 --help (-h)           Display this help message
 --quiet (-q)          Do not output any message
 --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
 --version (-V)        Display this application version
 --ansi                Force ANSI output
 --no-ansi             Disable ANSI output
 --no-interaction (-n) Do not ask any interactive question
 --profile             Display timing and memory usage information
 --working-dir (-d)    If specified, use the given directory as working directory.
 
Help:
 The help command displays help for a given command:
  
   php /usr/local/bin/composer help list
  
 You can also output the help in other formats by using the --format option:
  
   php /usr/local/bin/composer help --format=xml list
  
 To display the list of available commands, please use the list command.

設定はお馴染みの .bp-config/options.json 内で完結します。

blog$ vi .bp-config/options.json 
{
        "WEBDIR": "htdocs",
        "LIBDIR": "htdocs/public/js/jquery/jQuery-Timepicker-Addon/",
        "PHP_EXTENSIONS": ["mysqli", "pdo", "pdo_mysql", "gettext", "mbstring", "gd"],
        "COMPOSER_INSTALL_OPTIONS": ["--no-interaction", "--no-dev", "--no-progress", "--working-dir ./public/js/jquery/jQuery-Timepicker-Addon/"]
}

ここで、composerを使う際は WEBDIRLIBDIR を指定するのが一般的のようなので(参考)、併せて記載してあります。

.htaccessファイルの設定

デフォルトの状態でアプリをデプロイをすると、 .htaccess ファイルに書かれた php_value がInvalid commandであるというエラーが表示されてしまいます。

※参考

2015-03-30T17:30:25.38+0900 [RTR]     OUT fc2blog.cf.nttlabs.info - [30/03/2015:08:30:25 +0000] "GET /public/admin/install.php HTTP/1.0" 500 528 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:36.0) Gecko/20100101 Firefox/36.0" 10.0.0.2:60164 vcap_request_id:f0728de1-3393-4eea-7193-5b55ef8adbb7 response_time:0.002464553 app_id:e7dcfe39-8c3d-4dbc-b7ae-ddd36d8ff8a7
2015-03-30T17:30:25.39+0900 [App/0]   OUT 08:30:25 httpd   | [Mon Mar 30 08:30:25.382099 2015] [core:alert] [pid 55:tid 140077086775040] [client 153.142.2.99:24081] /home/vcap/app/htdocs/public/.htaccess: Invalid command 'php_value', perhaps misspelled or defined by a module not included in the server configuration
2015-03-30T17:30:25.39+0900 [App/0]   OUT 08:30:25 httpd   | 153.142.2.99 - - [30/Mar/2015:08:30:25 +0000] "GET /public/admin/install.php HTTP/1.1" 500 528 vcap_request_id=f0728de1-3393-4eea-7193-5b55ef8adbb7 peer_addr=10.0.0.46

これはどうやら
mod_php5.c のモジュールがあれば動かすことができるようですが、 PHP Buildpackでサポートされているモジュール一覧 に無かったため、今回はその場凌ぎの解決策ではありますが条件文を咬ませることでこのエラーを回避しました。
修正箇所は2箇所です。

blog$ cp public/.htaccess public/.htaccess.original
blog$ vi public/.htaccess
<IfModule mod_php5.c>
        php_value display_errors on
</IfModule>
 
RewriteEngine On
RewriteBase /
 
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule . - [L]
 
RewriteRule . index.php [L]
blog$ diff public/.htaccess.original public/.htaccess
1c1,3
< php_value display_errors on
---
> <IfModule mod_php5.c>
>         php_value display_errors on
> </IfModule>
blog$ cp public/admin/.htaccess public/admin/.htaccess.original
blog$ vi public/admin/.htaccess
<IfModule mod_php5.c>
        php_value display_errors on
</IfModule>
 
RewriteEngine On
RewriteBase /admin/
 
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule . - [L]
 
RewriteRule . index.php [L]
blog$ diff public/admin/.htaccess.original public/admin/.htaccess
1c1,3
< php_value display_errors on
---
> <IfModule mod_php5.c>
>         php_value display_errors on
> </IfModule>

ちなみに他には、 php.ini というファイルを作りそこに別途設定を書き込む等の回避策もあるようです。

ファイルの書き込み権限の設定

ファイルに書き込み権限を与えておきます。

blog$ chmod 777 public/uploads/
blog$ chmod 777 app/temp/

manifestファイルの設定

manifestファイルも作っておきましょう。
アプリ名 name はMySQLのServiceにバインドした時と同じ名前を用いて下さい。

blog$ vi manifest.yml
---
applications:
- name: fc2blog
  buildpack: php_buildpack

4. Cloud Foundry上へのデプロイ

ようやくデプロイです。

blog$ cf push
(一部略)
-----> Uploading droplet (34M)

1 of 1 instances running

App started


OK

App fc2blog was started using this command `$HOME/.bp/bin/start`

Showing health and status for app fc2blog in org ukaji / space default as ukaji...
OK

requested state: started
instances: 1/1
usage: 256M x 1 instances
urls: fc2blog.10.244.0.34.xip.io
last uploaded: Mon Sep 28 05:31:20 UTC 2015
stack: cflinuxfs2
buildpack: php_buildpack

     state     since                    cpu    memory          disk      details   
#0   running   2015-09-28 02:32:44 PM   0.0%   49.1M of 256M   0 of 1G 

OKです。

5. 動作検証

README.mdの記述に従い、払いだされたURL(今回の例ではfc2blog.10.244.0.34.xip.io)の /admin/install.php にアクセスします。

項目チェッカーが全て緑表示ならOKです。
Install ボタンを押下します。

次は管理者情報の登録画面です。

こちらは適当な値を設定します。後のログイン時に用いるので値は覚えておきましょう。
Register ボタンを押下します。

インストールが完了するので左側のリンクからログイン画面に遷移し、

先程設定した値でログインします。

こちらが管理者画面です。

試しに新しい記事を作ってみましょう。
新規記事作成は左の Home>New article から。

投稿を行うと、

先ほど作った記事が新たに公開されました。

ブログのデザインも色々なテンプレートが用意されています。
もちろんテンプレートの自作するも可能となっています。

と、ひと通りの機能を無事に動かすことができました、で終われれば良かったのですが、実はこのアプリには、最後まで動かすことができなかった機能があります。

右上に明らかに使用言語を切り替えられそうな見た目のボタンがあるので、これを日本語に切り替えてみたのですが、

ボタン以外日本語になってくれません。

6. 問題点

少しソースコードを調べてみると、日本語化対応(多言語化対応)の箇所で、PHPの gettext モジュールを使った部分が正しく動いていないように見えます。
Noburou Taniguchiさん 調べによると、これはCloud Foundry側に問題がある可能性が大きいようです。

試しに gettext モジュールを使った簡単なアプリをCloud Foundry上で動かしてみます。

$ git clone https://github.com/nota-ja/php-gettext-example
$ cd php-gettext-example/
ukaji@vbx091:~/workspace/php-gettext-example$ for d in locale/*/LC_MESSAGES/; do msgfmt $d/messages.po -o $d/messages.mo; done
php-gettext-example$ cf push php-gettext-example
(一部略)
-----> Uploading droplet (17M)

1 of 1 instances running

App started


OK

App php-gettext-example was started using this command `$HOME/.bp/bin/start`

Showing health and status for app php-gettext-example in org ukaji / space default as ukaji...
OK

requested state: started
instances: 1/1
usage: 256M x 1 instances
urls: php-gettext-example.10.244.0.34.xip.io
last uploaded: Mon Sep 28 06:58:32 UTC 2015
stack: cflinuxfs2
buildpack: PHP

     state     since                    cpu    memory          disk      details   
#0   running   2015-09-28 03:58:50 PM   0.0%   22.6M of 256M   0 of 1G 

gettext モジュールの機能が正しく動いていればこのアプリの画面表示は /home/vcap/app/htdocs/locale: Hello, world になるはずなので、今回のアプリを併せて考えるとCloud Foundry上で gettext 機能がうまく動いていない可能性が高いと言えそうです。

今回使用したソフトウェア