blog single gear
Tutorials

100-day Challenge #067: Running fc2blog on Cloud Foundry

Translator’s note: This is the 15th article of the series “Cloud Foundry 100-day Challenge selection”. “#067” in the title means that it is 67th (published on September 28, 2015) in the original Japanese series.

Original Author: Hiroaki UKAJI (GitHub)


The 67th topic of “Cloud Foundry 100-Day Challenge” is the famous blog engine fc2blog (Translator’s note: “fc2.com” is a well-known blogging service in Japan). It actually is open source ready, so let us try to run it on Cloud Foundry. A part of the functions did not operate because of a glitch, but we were able to make it work up to a point where it functions as a blog engine, so here it is.

Basic Information

The summary of steps is as follows:

  • 1) Retrieving source code
  • 2) Creating MySQL service
  • 3) Preparation
  • 4) Deploying onto Cloud Foundry
  • 5) Checking application behavior
  • 6) Issues

1. Retrieving source code

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

The source code of fc2blog is written in PHP.
According to the REAME.md , the required environment is PHP 5.2.17 or later, and MySQL 5.1 or later.

Since the deployment of this application requires revising the configuration files and the source code, the complete version is also made available to the public here .

2. Creating MySQL service

We will create a MySQL service and bind to it, as it is a requisite to use MySQL as the database.
As usual, the procedures are push with --no-start > create-service > bind-service.

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

3. Preparation

In this section, we will create / modify some configuration files and the source code.
If you are trying with my revised version, you may basically skip this section and continue from “4. Deploying onto Cloud Foundry.”

Editing configuration file

First we edit the application’s configuration file.
This file is primarily where we enter information necessary to connect to its database. This time we will enable taking its domain name, database name, etc. from the application’s environment variables…

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:[email protected]: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

.. for the applicaton.

blog$ cp public/config.php.sample public/config.php
blog$ vi public/config.php

(Translator’s note: Because there are Japanese characters in the original source code, they remains in the following code blocks.)

<?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');
-

For PASSWORD_SALT, please designate a character string consisting of random alphabets and numbers that are different from the original.

Adding modules

We add necessary modules using PHP_EXTENSIONS, a function of php-buildpack.

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

Writing Apache configuration file

When running an application on PaaS, it sometimes is difficult to avoid the discrepancy of the intended directory structure between the application developer and the PaaS provider.
With this application…

<The top directory of the application (/var/www/html/ etc.)>
|-- app/
`-- public/ (<- The Document Root of the application)
    `-- admin/
        `-- install.php

.. it seems that the above structure is intended by the developer, but if we mindlessly deploy this on Cloud Foundry ..,

/home/vcap/app/
`-- htdocs/ (<- The Document Root of the application)
    |-- app/
    `-- public/
        `-- admin/
            `-- install.php

.. this is the result.
Since the file path will not function properly when the Document Roots of the application do not match, we need to revise this with the Apache configuration files.

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

We referred to this page .
Here, only the Document Root is revised.

Configuration for composer

When we take a look inside the application, it seems that composer (PHP’s dependency management tool) is used.

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

The PHP Buildpack for Cloud Foundry supports composer, but the default settings only reads the composer.json file in the application root, so we modify the settings to have the application read composer.json files that are located in places like the example above.

Specifically, it seems that we should be fine using COMPOSER_INSTALL_OPTIONS, one of the composer support functions in the PHP Buildpack , and use the --working-dir DIRNAME option which specifies the working directory for composer install.

*Reference

$ 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.

Settings can be completed entirely within the familiar .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/"]
}

As it seems standard to specify WEBDIR and LIBDIR when using composer, (reference), we have also listed them here.

Configuring .htaccess files

If we deploy the application under default settings, we get an error message that the php_value in the .htaccess file is an Invalid command.

*Reference

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

Apparently, it seems that we can make it work if we have the mod_php5.c module, but as we couldn’t find it in the list of modules supported by PHP Buildpack , we avoided the error by using conditional branching though it is a workaround.
There are two spots that require modification.

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>

There seems to be an alternative solution in which you create a php.ini file and include settings there separately.

Configuring access control to files

We add the authorization to write files.

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

Configuring manifest file

We will also create its manifest file.
The application’s name should be identical to the name used when binding to the MySQL service.

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

4. Deploying onto Cloud Foundry

We finally get to deploy.

blog$ cf push
(snip)
-----> 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 

All seems well.

5. Checking application behavior

In accordance with the README.md , we access /admin/install.php of the obtained URL (fc2blog.10.244.0.34.xip.io in this case).

If all the “Environment Check” items are displayed in green, we are all set.
Let’s proceed to click Install.

Then, we get the screen for registering administrator information.

Here, we enter appropriate values. Remember these values as they will become necessary later when logging in.
Click Register.

The installation will complete, so proceed to the login screen from the link to the left ..

.. and login with the values set earlier.

This is the administrator screen.

Let us try creating a new article.
We can do it from Home > New article on the left.

When we make an entry ..

.. the article created was now published.

There are various design templates for the blog.
Of course, we can also create one.


As such, we have been able to successfully run a good set of functions; however, there was a feature we were not able to run.

To the top right, we found a button that looked like it was obviously for changing language settings. We selected Japanese ..

.. but nothing converted to Japanese except for the button itself.

6. Issue

(Translator’s note: The issue discussed in this section has already been solved. Actually the original Japanese article of this was the trigger to solve it. You may see the process at this thread in the Cloud Foundry community mailing list.)

When we look into the source code, it seems that the section for Japanese language compatibility (multi-language compatibility) using PHP’s gettext module is not functioning correctly. According to Noburou Taniguchi , it seems likely that the issue resides on the Cloud Foundry side. We will try to run a simple application that uses the gettext module on 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
(snip)
-----> 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 

The screen we should get if the gettext module is functioning properly is /home/vcap/app/htdocs/locale: Hello, world (Translator’s note: the author says that “Hello” should start with capital “H”, not small “h”). When taking into account the case we saw with fc2blog, it seems that the gettext function is not running properly on Cloud Foundry.

Software Used in this Post