APPFogにFuelPHPで作ったアプリをデプロイする

PHPFogが今年でサービス提供を終了するのでAPPFogに乗り換えようと思います。

乗り換えるのは今現在PHPFogにデプロイされている「はてなダイアリーキーワードBot

サインアップとアプリケーション作成

まずはAPPFogにアカウントを作ってアプリケーションを作成します。

  • Step 1: Choose an application

 PHPを選択します。Java, Groovy, Python, Ruby, Nodeとか色々あって嬉しくなりますね。
 ちなみにフリープランだと10Apps作成可能みたいです。フリーでRAMが2GBもあるので結構遊べそうです。

  • Step 2: Choose an infrastructure

 多分どれでもいいと思うんだけど、とりあえずAWSで一番距離的に近そうなシンガポールを選択。

  • Step 3: Choose a subdomain:

 アプリのドメイン。xxx.ap01.aws.af.cm。サブドメインがダサい

データベースの作成

アプリケーションコンソールの「Service」から、MySQLとDB名を指定してCreateします。
DB接続はphpMyAdminとかもできるみたいなのですが、PagodaBoxのようにクライアントからSSHトンネリングしてDBに直繋ぎできるみたいなので今回はそっちでやってみます。

APP Fogコマンドラインツールのインストール

afツールはgemでinstall後、コマンドラインからAPPFogにログインします。
gemはMacOS X Lionなら気にせずafをインストールできます。
https://docs.appfog.com/getting-started/af-cli#max-os-x-linux

$ sudo gem install af
$ af login
$ Attempting login to [https://api.appfog.com]
Email: <登録したemail>
Password: <パスワード>
Successfully logged into [https://api.appfog.com] 

SSHトンネリング

$ af tunnel
To use `af tunnel', you must first install Caldecott:

	gem install caldecott

Note that you'll need a C compiler. If you're on OS X, Xcode
will provide one. If you're on Windows, try DevKit.

This manual step will be removed in the future.

Error: Caldecott is not installed.

ありゃ。caldecottってなんだ。
調べてみたらVMCトンネリングのgem プラグインらしい。うん必要だね。ってことでcaldecottをインストールして再チャレンジ。

$ af tunnel
1: hateda-db
2: hateda-db2
Which service to tunnel to?: 1 
Getting tunnel connection info: OK

Service connection info: 
  username : <内緒>
  password : <内緒>
  name     : <内緒>
  infra    : ap-aws

Starting tunnel to hateda-db on port 10000.
1: none
2: mysqldump
3: mysql
Which client would you like to start?: 1 
Open another shell to run command-line clients or
use a UI tool to connect using the displayed information.
Press Ctrl-C to exit...

最初に使用するDBを聞かれますので対象のDBを選択します。
その後どういった形で使用するか聞かれます。それぞれ、

  1. トンネリングだけ
  2. 接続したMysqlのdumpを取得
  3. mysqlコマンドラインで接続

になります。今回はクライアントの「Sequel Pro」から接続しますので1:noneを選択します。
接続されたらlocalhostのポート10000、コンソールに表示されたusernameとかを入力してDB接続します。
接続できたのでPHPFogのphpMyAdminからdumpを取得してAPPFogのDBに流し込んで移行完了。

環境変数の設定

アプリケーションコンソールの「Env. Variables」から設定します。お決まりのFUEL_ENVやTwitterのコンシューマキーなどをファイルに書きたくないものを設定。
ここで注意したいのは、コンソールから設定した環境変数は$_SERVER変数で参照できません。(ここで嵌りました)
環境変数を参照する際はgetenvで参照する必要があります。
fuel/app/bootstrap.phpも$_SERVER変数見てますので、productionモードでデプロイしたい場合は、アプリケーションコンソールでFUEL_ENVを設定し、かつfuel/app/bootstrap.phpの27行目を以下のように修正する必要があります。

<?php
・・・省略
Fuel::$env = getenv('FUEL_ENV') ? getenv('FUEL_ENV') : Fuel::DEVELOPMENT;

fuel/app/config/production/db.phpの変更

変更前

<?php
/**
 * The production database settings.
 */
return array(
    'default' => array(
        'connection'  => array(
            'dsn'        => "mysql:host=".$_SERVER['MYSQL_DB_HOST'].";dbname=".$_SERVER['MYSQL_DB_NAME'],
            'username'   => $_SERVER['MYSQL_USERNAME'],
            'password'   => $_SERVER['MYSQL_PASSWORD'],
        ),
    ),
);

APPFogだとDBの接続情報に関する扱いがちょっと変わっているのでこれに対応する必要があります。
https://docs.appfog.com/services/mysql
変更後

<?php
/**
 * The production database settings.
 */
$services_json = json_decode(getenv("VCAP_SERVICES"),true);
$mysql_config = $services_json["mysql-5.1"][0]["credentials"];
$username = $mysql_config["username"];
$password = $mysql_config["password"];
$hostname = $mysql_config["hostname"];
$port = $mysql_config["port"];
$db = $mysql_config["name"];

return array(
    'default' => array(
        'connection'  => array(
            'dsn'        => "mysql:host=".$hostname.";port=".$port.";dbname=".$db,
            'username'   => $username,
            'password'   => $password,
        ),
    ),
);

デプロイ

これは簡単です。アプリケーションのルートディレクトリで

$ af update <app_name>

すればおk。コマンドを実行する際のカレントディレクトリのソースをアップロードするみたいで別のディレクトリで上記コマンド叩くとデプロイに失敗しますのでご注意を。(分かり易いといえば分かり易いですが)
移行後のURLはこちらになりましたー
http://hateda-bot.ap01.aws.af.cm

その他雑感

PHPFogと比べると格段に使い易さが向上した気がします。
プランの範囲内ならアプリケーションのインスタンスもRAMも自由に調節できたり、
SSHトンネリングでDBアクセスできたり(phpMyAdmin使わなくてもよいのはデカいと思う)
そこそこいい感じのPaaSです。
PagodaBoxも悪くはないですけど、デプロイできるアプリケーションが無料で1インスタンスなのでAPPFogの方が色々使い勝手がよいかもしれません。

PHPFogが今年いっぱいでサービス提供を終了

今朝PHPFogからこんなメールが来ていました。

Dear Fogger,

It is with a heavy heart that I let you know that the PHP Fog service will be discontinued in December in favor of AppFog, which is PHP Fog 2.0. I am incredibly sorry for any inconvenience this may cause you.

Creating PHP Fog has been an amazing experience for us and it could not have happened without you––thank you for your support. With your help, we’ve built an amazing PaaS for PHP developers. And along the way, we’ve applied what we’ve learned to creating our new product, AppFog.

AppFog is the future of our business, and we very strongly believe it is also the future of PaaS. So, in order to focus our team and efforts on continuing to build a better solution for developers, we will be shutting down the PHP Fog platform this coming January and focusing solely on AppFog.

We have considered this change very, very carefully because we understand that this could present challenges for some of our users. But in the end, we are confident that moving to AppFog will give you additional flexibility, additional languages, additional infrastructures, and the ability to deploy your apps to private cloud infrastructure as well as leverage the strengths of the OpenStack and Cloud Foundry ecosystems.

To help in the migration, we will do be doing everything we can to help this be as easy and painless as possible:
2GB of RAM in our Free Plan
We will be releasing a series of blog posts that walk you through the migration to AppFog.
We will also be publishing documentation of the migration path as well as solutions for some of the edge-case differences between the platforms.
To start with, we have created a migration FAQ that should help you begin the migration process.

I am committed to making AppFog a product that will make you look back at PHP Fog and think, “I’m really happy I switched.”

If you have any questions, please let me know.

Yours,
Chad

今年12月を以って、PHPFogのサービスは廃止し、今後はAPPFogへ力を入れていくようです。
いや、この間PHPFogにデプロイしたばっかりなんだけど・・・
FluxFlexに続いて使い始めてすぐ停止が今年2回目なんだけど・・・
PaaS界隈は結構混沌としてますなー。

APPFogは無料範囲のスペックがPHPFogより上(Freeで2GB RAM)だったり、PHP以外のプラットフォームにも対応している。
PHPFogの実績と反省がAppFogに反映されていますとか言ってるけど、専用言語PaaSって運営が難しいのかもね。

ユーザ数増やすなら多言語に対応してた方がユーザ囲みやすいだろうけど、herokuとかと同じ土俵に立つことになるので、それはそれでどうなんだろう。
専用言語PaaSは○○言語ならそこ一択と言われるぐらいじゃないと厳しいと思う。

ちなみにPHPFogを廃止のお詫びにAPPFogへの移行方法をまとめてくれているようなので、年内中にPHPFogからAPPFogに移行して当ブログにフィードバックしますねー

FuelPHP 1.4でoil g modelに失敗

最新のFuelPHPを使ってみようと

$ oil create fuelphp

して、oil コマンドしたらこれが出た。
http://fuelphp.com/forums/discussion/11496/problems-with-oil-in-version-1-4
原因も知ってたし、バグフィックスも確認してたし、さてやろうかと思ったときにこれ。
本体のsubmoduleリファレンスがまだ修正されてないのが原因みたい。
gitはまだ初心者なので本当にそうなのか自信ないけど。

fuel/fuel
https://github.com/fuel/fuel/commit/6d75a7c66f2bc0f9e4a40b6c55114eb7497102c7
fuel/oil
https://github.com/fuel/oil/commits/1.4/master

submoduleを最新にすれば問題なく動きました。

$ git submodule foreach git pull origin 1.4/master
Entering 'docs'
From git://github.com/fuel/docs
 * branch            1.4/master -> FETCH_HEAD
Already up-to-date.
Entering 'fuel/core'
From git://github.com/fuel/core
 * branch            1.4/master -> FETCH_HEAD
Updating 4431f38..992373a
Fast-forward
 classes/uri.php | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)
Entering 'fuel/packages/auth'
From git://github.com/fuel/auth
 * branch            1.4/master -> FETCH_HEAD
Already up-to-date.
Entering 'fuel/packages/email'
From git://github.com/fuel/email
 * branch            1.4/master -> FETCH_HEAD
Already up-to-date.
Entering 'fuel/packages/oil'
From git://github.com/fuel/oil
 * branch            1.4/master -> FETCH_HEAD
Updating 8d24110..f8e4657
Fast-forward
 classes/generate.php          | 2 +-
 classes/generate/scaffold.php | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
Entering 'fuel/packages/orm'
From git://github.com/fuel/orm
 * branch            1.4/master -> FETCH_HEAD
Already up-to-date.
Entering 'fuel/packages/parser'
From git://github.com/fuel/parser
 * branch            1.4/master -> FETCH_HEAD
Already up-to-date.

それにしてもgit初心者とFuelPHP初学者にはきつい仕打ちだ

追記

11/11 ポッキーの日。本体のsubmodule references更新されてましたー
https://github.com/fuel/fuel/commit/9cbef0781caa953a8c2322738b3915fa3b06725c
これで気兼ねなく$ oil createできますね!

Botサービスはじめました

はてなダイアリーキーワードBot
http://hateda-bot.phpfogapp.com/

何をつぶやくの?

はてなダイアリーをキーワード検索して該当したもの自動的につぶやきます。

例)FuelPHPをキーワードにすると
http://k.hatena.ne.jp/keywordblog/FuelPHP
にHitしたエントリーがTwitterにPOSTされます。

誰がつぶやくの?

当サービスが使用者のTwitterアカウントをお借りしてつぶやきます。
使用者ご自身のアカウントでもBot用のアカウントでもかまいません。
TwitterOAuthにより、パスワードは当サービス内には保存しませんが、認証トークンは保存させていただきますので、その旨ご了承ください。

ざっくり説明

Bot有効・・・押すとBotを有効にし、定期的にTweetされるようになります。過去にTweetされたエントリはTweetされません。
Bot無効・・・押すとBotを停止します。
キーワード・・・はてなダイアリーから検索するキーワード
除外キーワード・・・検索にヒットしたエントリのタイトルおよび文章内において、指定したキーワードが存在する場合、そのエントリはTweetされません。
フィルタ確認・・・過去にTweetしたエントリ、該当キーワードを確認できます。

技術者さん向けに

このサービスは以下のプラットフォーム、技術で構築しました。
PHPFog(PaaS)
FuelPHP(PHP Framework)
fuel-twitter(FuelPHP Package)
mywebcron(Cron Service)

GitHubソースコード一式置いてます。
https://github.com/ya-sasaki/hatena-diary-bot

phpfogの前にPagodaBoxでデプロイを試みたのですがfuel-twitterが上手く動かず、断念しました。
無料の範囲ではありますが両PaaSは結構弄んだので近いうちに、phpfog, pagodaboxの関連エントリ書いていきますねー

PagodaBoxのDB接続で嵌ったのでメモ

PagodaBoxのDBは、クラウド(PaaS)の上の仮想DBとローカルPCをSSHトンネリングして直接繋ぐことができる。
(PaaSで直接DB接続とかすげぇーー)

・・・・・・接続で嵌った・・・
原因は簡単なことだったけど、先入観あると嵌るかもしれません。
ということでエラーの経緯含めて書いてみる。

1.アカウントはGithub連携で作った

多分PagodaBoxを使おうとする人は十中八九GitHubのアカウント持ってるでしょ。
ということはアカウント作成もGitHub経由で作り、ログインもGitHub経由ですると思うの。
というか僕がそれ

2.インストールと接続方法

$ gem install pagoda
$ pagoda -a <app-name> tunnel db1

ドキュメントにはこれでいいよと書いてあったがそうじゃなかった。

~/Gemfile

source :rubygems
gem 'sinatra'
gem 'passenger'
gem "pagoda", "~> 0.6.2" <-これを追加

installしたgemが認識されてなかっただけの話

~/.pagodarc

---
:u: <username> or <email>
:username: <username or email>
:p: <password>
:password: <password>
:a: <app-name> 
:app: <app-name> 
commands:
  :_doc: {}
  :clone: {}
  :create: {}
  :deploy: {}
  :destroy: {}
  :info: {}
  :init: {}
  :list: {}
  :log: {}
  :rollback: {}
  :key:gen: {}
  :key:push: {}
  :tunnel: {}

gem install pagodaしたときにユーザホーム以下に作られていたのに気づかなかった。
pagodaを実行するとここに書いてあるものが使われるらしい。
u username p password a appはアカウント情報のエイリアスになっていて、ここに書いておくとpagodaを実行する際にデフォルトのアカウント情報を拾ってくれます。(心当たりがないけど最初見たらTwitterのuser/passだったっていうのは何故なのか不明)
GitHub経由でアカウント作ったのでGitHubと同じアカウント設定すればと思ったんですが、そうじゃありませんでした。

3.OAuth連携で登録した時のパスワード

よくよく考えれば、githubのパスワードをpagodaに送信するって考えられません。
ということでダッシュボードを徘徊してたらありました。
https://dashboard.pagodabox.com/account/edit
(認証済の人しか見られませんが)
GitHubTwitterからOAuth経由でアカウント登録した場合、パスワードは自動で設定されるらしく、pagodaツールを実行するには明示的に希望するパスワードに変えてあげないといけなかったようです。

4.接続完了

というわけでパスワードを変更した結果、以下のように無事接続できました。

$ pagoda tunnel db1

Tunnel Established!  Accepting connections on :
-----------------------------------------------

HOST : 127.0.0.1 (or localhost)
PORT : 3308
USER : (found in pagodabox dashboard)
PASS : (found in pagodabox dashboard)

-----------------------------------------------
(note : ctrl-c To close this tunnel)
^CTunnel Closed.
-----------------------------------------------

あとはlocalhost:3308に接続すれば無事DB接続完了です。
UserとPassはダッシュボードから作成したDBをクリックした画面下にある「Show Credentials」に書いてありまする
ちなみにSequel Proを使用してます。

SilexをBootStrapさせてみた

巷で騒がれているFuelPHPを横目にSilexが気になっている@sa2yasuでございませう。

Sinatraみたいに超絶手軽なPHP Frameworkないかなーと思ってネットサーフィンしているうちに見つけました。

Silexの特徴

Slideshareにいい感じのものがあったのでご紹介します。
http://www.slideshare.net/brtriver/php2012-silex
Silex自体はコントローラ部分の実装とDIコンテナの実装しかないのですが、サービスプロバイダと呼ばれるコンテナを登録することで機能拡張が可能です。公式非公式プロバイダもありますし、自分でプロバイダを作ることも可能です。

BootStrapしてみる

ものは試しです。BootStrapしてみます。
Silexユーザガイドで紹介されているSilex Kitchen Editionfabpotは所謂全部乗せ状態なのですが、ほんとに全部というわけではないし、全部乗せならフルスタックと変わらないじゃんと思ったのでもうちょっとコンパクトなBootStrapを見つけてきました。
https://github.com/qpleple/silex-bootstrap

$ git clone git://github.com/qpleple/silex-bootstrap.git
$ cd silex-bootstrap
$ curl -s curl -s http://getcomposer.org/installer | php
$ composer.phar install

インストールはたったこれだけ。
続いて設定。
BootStrapはDBアクセスをするサンプルがあるのでDBの設定をします。
silex-bootstrap/src/config.iniを編集

[production]
db.driver        = pdo_mysql
db.dbname    = dumb
db.host          = localhost
db.user          = root
db.password  = root

[staging]
db.driver        = pdo_mysql
db.dbname    = dumb
db.host          = localhost
db.user          = root
db.password  = root

[development]
db.driver        = pdo_mysql <- ここを使用するDriverに
db.dbname     = mytestdb <- ここを使用するDBのインスタンス名に
db.host           = localhost <- 以下略
db.user           = root
db.password   = 

環境によってDB接続定義を変えられるのが良いですね。
BootStrapのサンプルはdumbというテーブルのa,bカラムに乱数をinsertし、それを参照するサンプルになっています。
silex-bootstrap/src/app.php

<?php

$app = require __DIR__.'/bootstrap.php';

$app->get('/', function() use ($app){
    $app['db.dumb']->insert(array('a' => rand(), 'b' => rand()));
    
    $data = $app['db.dumb']->findAll();
    
    return $app['twig']->render('index.html.twig', array('data' => $data));
});

return $app;

ですので、事前にdumbというテーブルにa,bカラムを追加しとく必要があります。
また、サンプルのViewにはTwigのテンプレートエンジンを使用しているため、Twigテンプレートのcacheフォルダも必要になります。

$ mkdir cache
$ chmod 777 cache

諸々の設定が完了してアプリケーションにアクセスするとこんな結果になります。

ここまででSilexのBootStrapは完了です。せっかくなので少し解説を。

Twitter Boot Strap

画面を見てわかるとおり、TwitterBootStrapが使われています。
silex-bootsrap/webフォルダがDocumentRootになっていて、Twitter Boot Strapが既に置いてあります。
プログラマの皆さんは大概デザインが苦手というか敬遠しているので最初からTwitterBootStrapの恩恵に肖れるのはよいですね。

composer.json

silex-bootstrap以下にcomposer.jsonというファイルがあります。

{
    "minimum-stability": "dev",
    "require": {
        "silex/silex": "1.*",
        "twig/twig": "1.6.0",
        "doctrine/dbal": "2.2.*",
        "doctrine/common": "2.2.*"
    },
    "autoload": {
        "psr-0": { "Repository": "src/"}
    }
}

composerはアプリケーションが必要とする外部ライブラリを、そのアプリケーション固有の状態で一元的に管理してくれるツール(コピペ)。java,Groovy界隈で言うところのMaven, Gradle的アレです。pearだとこうはいきません。
requireに記述されているものはcomposer.pharでインストールできるのでphpunitや他のプロバイダを追加したいときはこちらに追記してcomposer.phar installすればすぐに使用できるようになります。

bootstrap.phpを読む

<?php
require_once __DIR__.'/../vendor/autoload.php'; <- コア、外部ライブラリの読み込み

use Silex\Provider\TwigServiceProvider;
use Silex\Provider\DoctrineServiceProvider;
use Repository\DumbRepository;

$env = getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production';
$ini_config = parse_ini_file(__DIR__.'/config.ini', TRUE);
$config = $ini_config[$env]; <- 環境変数読み込み

$app = new Silex\Application(); <- ここからSilexの定義

$app['debug'] = true; <- debug設定をtrueに

$app->register(new TwigServiceProvider(), array( <- Twigをコンテナに登録
    'twig.path' => __DIR__.'/templates', <- テンプレートはここにあるのを使います
    'twig.options' => array('cache' => __DIR__.'/../cache'), <- キャッシュファイルはここに置くから
));

$app->register(new DoctrineServiceProvider); <- Doctrine使う

$app['db.options'] = array( <- DB接続定義
    'driver'   => $config['db.driver'],
    'dbname'   => $config['db.dbname'],
    'host'     => $config['db.host'],
    'user'     => $config['db.user'],
    'password' => $config['db.password'],
);

$app->before(function() use ($app) { <- コントローラにディスパッチする前に呼ばれる
    $app['db.dumb'] = $app->share(function($app) { <- db.dumbにDumbRepositoryを共有サービスとして使えるようにする
        return new DumbRepository($app['db']);
    });
});

return $app;

ポイントは
・使用サービスプロバイダをSilexのコンテナに登録する
ですかね。
app.phpにも書けますけど、app.phpはコントローラの実装を行う場所なので事前準備はbootstrap.phpにまとめた方がapp.phpがすっきりします。
web/index.phpとかsrc/app.phpは読めばすぐわかると思うので割愛。

簡単ですねー。

自分の使用想定としては、CMSとの抱き合わせ(ちょっとした動的コンテンツ部分)で使用するのが適切かなと思っています。
ある程度ルーティングが増えてくるとapp.phpが大変なことになり、コントローラをルーティング毎に切り出すこととか考えだすと、だったら最初からSilexでなくていいじゃんみたいなことになりますので。

いじょ

KeepAliveを有効活用したい

自称プログラマ?のくせにいつも環境まわりのブログばっかり書いている@sa2yasuでございませう(笑)

基本動的Webサイトを作っているのでKeepAliveはこういった理由でoffにしています。

最近の流行だとeginxをリバースプロキシ、SSL処理用にして、バックエンドのWebサーバで動的コンテンツを出力するという構成を組みますが、規模の小さなサイトだと何もそこまでしなくても。。。という感じはします。
比較的小さなサイトなら別にKeepAlive offでもいんじゃねという気はしますが、画像の多いページを見ると「こいつらが1個1個Httpコネクション張りにいってんのか」と貧乏性な僕の心を揺さぶるのです。

ということで1台のWebサーバで静的コンテンツはKeepAlive on、動的コンテンツはKeepAlive offで構築してみます。(このブログはそのメモ)

1.バーチャルホストを2つ用意する

ApacheのKeepAliveはバーチャルホスト毎に設定できるので、静的コンテンツをPort:80、
動的コンテンツをPort:8080に振り分けるようにします。
KeepAliveTimeoutは今やブロードバンドの時代なので5秒持続させれば十分かな(適当w)
VirtualHostの書き方はググってくらさい。

# Static Contents Side
KeepAlive on
KeepAliveTimeout 5
# Dynamic Contents Side
KeepAlive off

2.振り分け

mod_rewriteでアクセスされたパスがファイルでなければPort:8080に応答させるだけ
バーチャルホスト下のDirectoryディレクティブに書いてもいいし、.htaccessに書いてもよいです。
(KeepAliveのパフォーマンス気にするくらいの人ならhttpd.confに書くと思うけど)
mod_rewriteの条件がもの凄く簡単なのは、試した環境の動的コンテンツ側がSilexになっているからです(手抜きw)

例えば、
http://localhost/hoge/hugaにアクセスするとhttp://localhost:8080/index.php/hoge/hugaが応答して
/hoge/hugaがSilexのコントローラにディスパッチされます。


    RewriteEngine on
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ http://localhost:8080/index.php/$1 [P,L]

チューニングの効果は気休め程度?w
アクセス数上がって捌ききれなくなったらサーバわけるとかeginxにするとか検討するといいと思います。
WordPressでパフォーマンスにお困りの方はeginxにすると幸せになれるみたいですよ。
さくらVPSとnginxリバースプロクシで最速WordPressブログを作る方法(ベンチマーク付き)