CakePHP2 アプリケーションを Heroku 上で動かす設定についてです。

以前のエントリにも書きましたが、Heroku で Web アプリケーションを動かす際に重要なのは、Web サーバ自体(Heroku では、Dyno)に、アプリケーションの状態(データ、セッション情報、ログ等)を保持させないということです。
Heroku の Dyno は、デプロイの際や、定常的な再起動により、破棄されるため、記録されたファイルは消えてしまいます。よって、こうしたデータファイルは、アドオンなど外部に記録する必要があります。
Heroku では、アドオンを活用するのがポイントですので、ここでは、主に CakePHP アプリケーションからこうしたアドオンと連携する方法を見ていきます。
Environments Library as a plugin
まず、開発環境と Heroku 環境で設定値を切り替えるために Environments Library as a plugin を使います。
https://github.com/josegonzalez/cakephp-environments
これは、CAKE_ENV という環境変数の値で、環境に応じて、データベースやキャッシュなどの設定を切り替えるものです。昨今のフレームワークではお馴染みの機能ですね。
ここでは、下記の設定について切り替えを行います。
- ログ
- データベース
- キャッシュ / セッション
このプラグインでは、まず Config/bootstrap/environments.php を作成します。このファイルで、環境毎の設定ファイルの読み込みを行います。下記では、Heroku 環境用の heroku.php と、開発環境用の development.php を読み込んでいます。
- Config/bootstrap/environments.php
<?php
CakePlugin::load('Environments');
App::uses('Environment', 'Environments.Lib');
include dirname(__FILE__) . DS . 'environments' . DS . 'heroku.php';
include dirname(__FILE__) . DS . 'environments' . DS . 'development.php';
Environment::start();
次に、Heroku 環境用の Config/bootstrap/environments/heroku.php です。
環境の設定は、Environment::configure() で行います。第一引数に環境名(CAKE_ENVで指定するもの)、第二引数は適用条件(true なら、CAKE_ENV に合致しなければデフォルトとして適用)、第三引数に設定値の連想配列(Configure::write() する)、第四引数にコールバックを指定します。
第三引数で設定する方法もあるのですが、より柔軟な設定を行うために、ここでは、第四引数のコールバックを指定します。実際の設定は、以降で順に指定していきます。
- Config/bootstrap/environments/heroku.php
<?php
Environment::configure('heroku', false, [
], function () {
// Heroku 用設定
});
開発環境用の Config/bootstrap/environments/development です。コールバックで、デフォルトの設定を記述しています。
- Config/bootstrap/environments/development.php
<?php
Environment::configure('development', true, [
], function () {
// Log settings
App::uses('CakeLog', 'Log');
CakeLog::config('debug', array(
'engine' => 'File',
'types' => array('notice', 'info', 'debug'),
'file' => 'debug',
));
CakeLog::config('error', array(
'engine' => 'File',
'types' => array('warning', 'error', 'critical', 'alert', 'emergency'),
'file' => 'error',
));
// Database settings
Configure::write('DATABASE_OPTIONS', [
'datasource' => 'Database/Postgres',
'persistent' => false,
'host' => 'localhost',
'login' => 'shin',
'password' => '',
'database' => 'app',
]);
Configure::write('TEST_DATABASE_OPTIONS', [
'datasource' => 'Database/Postgres',
'persistent' => false,
'host' => 'localhost',
'login' => 'shin',
'password' => '',
'database' => 'app_test',
]);
// Cache settings
Cache::config('default', array('engine' => 'File'));
});
次に、database.php での接続情報を、環境ごとに設定できるように下記のようにします。各環境ファイルで Configure::write()
した値を、DATABASE_CONFIG クラスに設定します。
- Config/database.php
class DATABASE_CONFIG {
public $default = [];
public $test = [];
public function __construct() {
$this->default = Configure::read('DATABASE_OPTIONS');
$this->test = Configure::read('TEST_DATABASE_OPTIONS');
}
}
このプラグインを有効にするために、bootstrap.phpを以下のように変更します。コメント部分は削除しています。
- Config/bootstrap.php
<?php
require_once __DIR__ . '/../vendor/autoload.php';
include __DIR__ . '/bootstrap/environments.php';
Configure::write('Dispatcher.filters', array(
'AssetDispatcher',
'CacheDispatcher'
));
Heroku で環境変数 CAKE_ENV をセット
このアプリケーションが、Heroku にデプロイした際に、heroku.php を利用するように、環境変数CAKE_ENVにherokuをセットします。これで、デプロイしたアプリケーションは、heroku 設定で動作するようになります。
$ heroku config:set CAKE_ENV=heroku
ログ
Heroku 環境でのログの設定です。ログは、ファイルではなく、標準出力もしくは標準エラー出力に出力します。
こうしておくと、heroku logs コマンドでログが確認できます。また、Papertail や FlyData のようにログを各サービスへ転送するアドオンを使うと、出力されたログを閲覧したり、S3 などに転送することができます。
ログは、まずは標準出力もしくは標準エラー出力にして、あとそれをどう活用するかは、アドオンに任せるという考え方です。
下記では、アプリケーションログを標準出力へ、エラー出力を標準エラー出力へ出力しています。
- Config/bootstrap/environments/heroku.php
<?php
Environment::configure('heroku', false, [
], function () {
// Log settings
App::uses('CakeLog', 'Log');
App::uses('ConsoleOutput', 'Console');
CakeLog::config('debug', [
'engine' => 'ConsoleLog',
'stream' => new ConsoleOutput(),
]);
CakeLog::config('error', [
'engine' => 'ConsoleLog',
'stream' => new ConsoleOutput('php://stderr'),
]);
});
あとは、アドオンと追加すれば、出力されたログがアドオンで利用できます。例えば、Papertrailを以下のコマンドで追加して、アプリケーションを動作させると、Papertrail の画面でログが確認できます。
$ heroku addons:add papertrail
データベース
Heroku のデータベースといえば、PostgreSQL なので、Heroku Postgres を使います。(もちろん、MySQL が良ければ、ClearDB なり、RDS なり使えます。)
$ heroku addons:add heroku-postgresql
Heroku 内では接続情報が環境変数で渡されるので、これをパースします。パースされた情報を、heroku.php で、Configure::write() で、セットしていきます。
- Config/bootstrap/environments/heroku.php
<?php
Environment::configure('heroku', false, [
], function () {
// Log settings
(snip)
// Database settings
if (empty(getenv('DATABASE_URL'))) {
throw new CakeException('no DATABASE_URL environment variable');
}
$url = parse_url(getenv('DATABASE_URL'));
Configure::write('DATABASE_OPTIONS', [
'datasource' => 'Database/Postgres',
'persistent' => false,
'host' => $url['host'],
'login' => $url['user'],
'password' => $url['pass'],
'database' => substr($url['path'], 1),
]);
});
キャッシュ / セッション
キャッシュ / セッションのデータストアには、Redis を使います。
ここでは、Redis サービスのアドオンとして、Redis To Go を利用します。
$ heroku addons:add redistogo
データベースと同じく、接続情報が環境変数で渡されるので、それをパース(ホスト、ポート、パスワード)して、セットするだけです。セッションハンドラには、Cache を使いたいので、その指定も行います。
これで、キャッシュもセッションも、Redis To Go に保存されます。
- Config/bootstrap/environments/heroku.php
<?php
Environment::configure('heroku', false, [
], function () {
// Log settings
(snip)
// Database settings
(snip)
// Cache settings
if (empty(getenv('REDISTOGO_URL'))) {
throw new CakeException('no REDISTOGO_URL environment variable');
}
$url = parse_url(getenv('REDISTOGO_URL'));
Cache::config('default', [
'engine' => 'Redis',
'server' => $url['host'],
'port' => $url['port'],
'compress' => false,
'password' => $url['pass'],
'serialize' => 'php'
]);
// Session Settings
Configure::write('Session', [
'defaults' => 'cache',
]);
});
さいごに
ベーシックな部分だけですが、Heroku で CakePHP アプリケーションを動かす設定を見てきました。
ここで紹介した以外では、メールやデータファイル(画像等)など、他にもアプリケーションでは必要なものがありますが、ほとんどのものは、ここで見てきたようにアドオンを活用することで実現できます。
こうした構成にしておけば、Dyno が再起動したり、インスタンス数を増減しても、アプリケーションデータはアドオンに保持されているので、動作に支障はありません。こうしたスケーラブルナ構成にしておくのが、Heroku のような PaaS を活用する肝ですね。
このエントリは、CakePHP Advent Calendar 2014 の 3 日目でした。まだ少し空き枠があるので、CakePHP な何かを書いてみてはどうでしょうか。

