2013年6月14日金曜日

VirtualBox で「なんちゃってステージング環境」のススメ

山本です。

負荷分散のある環境で試したいけれど、開発環境はマシン1台しかない!
でもステージング環境で試すのはまだ怖い…
そんなときは、 VirtualBox を使って手元にステージングもどき環境を作ってしまうといいかも知れません。

ポイント

  • 本番環境と似たサーバネットワークを、社内環境から隔離したかたちで手元に用意するのが目標
    ↑気軽に負荷分散環境での動作をシミュレート
  • 簡単にバーチャルマシンのコピーが作れるから、OSインストールなどの共通作業だけ先に済ませておける

事前設定

サーバネットワークに使うホストオンリーネットワークを作っておく。
Oracle VM VirtualBox マネージャー(システムツール/Oracle VM VirtualBox)から
「ファイル」→「環境設定」→「ネットワーク」タブ
→「+」(追加)アイコン
→「(ドライバーの絵)」(編集)アイコン
→IPアドレスとネットワークマスクを入力して「OK」
→「OK」

共通作業


OSインストール

(省略;ISOイメージを光学メディアとして直接接続できるので活用!)

事前ダウンロード

aptitude install -d パッケージ名… で、インストールする予定のあるパッケージをあらかじめダウンロードだけしておけるので、これも活用すると便利。

準備できたら

一旦仮想マシンをシャットダウンします。

個別マシン作成

元ネタの仮想マシンを右クリック→「clone」
  • 「Reinitialize the MAC address of all network cards」のチェックを忘れずに!
  • 「Full Clone」を選択←元ネタにクローン側で行った変更が伝染しないようにするため
クローン作成にはホストのディスクI/O性能によって相応の時間がかかります。
(山本のマシンだと、8Gのイメージをクローンするのに3分弱かかりました。)

起動する前にやること

ネットワーク設定(ハード?側)

できたクローンを右クリック→「設定」→「ネットワーク」タブ

  • 「アダプタ1」タブを選択 (こいつが仮想マシンの eth0; apt-get 用+ホストからのアクセス用)
    • 「割り当て」→「NAT」←ブリッジだと無線接続時うまく動かない
  • 「アダプタ2」タブを選択 (これは仮想マシンの eth1; 模擬サーバネットワークにつなげるため)
    • ネットワークアダプタを有効化
    • 「割り当て」→「ホストオンリー アダプタ」
    • 「名前」→事前準備で作ったホストオンリーネットワークを選択
      ↑これで模擬サーバネットワークにつなげる
    • 「高度」→「アダプタタイプ」→「準仮想化ネットワーク (virtio-net)」
      ↑パフォーマンス(10倍くらい違うらしいです)

いざ起動!

初回はネットワーク設定がされていないので、DHCPのタイムアウトで待たされる。
辛抱強く待ちましょう。

ネットワーク設定(ゲストOS側; Ubuntuゲストの例)

Ubuntu、というかいまどきのLinuxは、MACアドレスでインタフェース名を固定する機能があるが、クローンを作る場合はそれが障害になる。
初回起動時に待たされる原因のほとんどはこれが原因です。
  • /etc/udev/rules.d/70-persistent-net.rules 書き換え
    NAME="eth0" の行を削除
    NAME="eth1" の行を書き換え→ NAME="eth0" に
    NAME="eth2" の行を書き換え→ NAME="eth1" に
  • /etc/network/interfaces 追記
    auto eth1
    iface eth1 inet static
        address 10.0.0.11
        netmask 255.255.0.0
    ↑IPアドレスとネットワークマスクは適切なものに読み替えて
  • 再起動!

ミドルウェアその他のインストール&設定

個別にインストール and/or 設定が必要なものは各プロジェクトのインフラ情報を参考に。

その他

起動したままの状態で一時停止

仮想マシンのウィンドウを「閉じる」→「仮想マシンの状態を保存」を選択
→ハイバネーションに似た状態になります。

スナップショット

「在りし日の仮想マシン 」として、大規模変更前などに仮想マシンの状態を記録しておくことができます。
「Oracle VM VirtualBox マネージャー 」ウィンドウで、仮想マシンのアイコンを選択した状態で「スナップショット」タブを表示、カメラのようなアイコンを叩くとスナップショットが作成できます。
作ったスナップショットに仮想マシンの状態を巻き戻したり、クローンの仮想マシンを新たに起こしたりできます。

サーバネットワークのネットワーク経路が入らない?!

原因・再現性とも不明ながら、 eth1 のネットワークのための経路が入らない、もしくは何らかの理由で消えてしまうという現象がおこることがあるようです。
「ゲストOSからホストOSへの ping は通るのに、ゲストOS同士や、ホストからゲストOSへの ping が通らない」のような奇妙な現象として観測されることも。
手動でネットワーク経路を追加してやることで、とりあえず修復できます。
  • sudo route add -net 10.0.0.0/16 gw 10.0.0.11

2013年6月5日水曜日

Zend Framework 2とDependency Injection

高瀬です。

PHP歴約1年、まだYii Frameworkでしか開発出来ないので、少しは他の開発手法もやってみようと思う。そこで、ユニークビジョンでは馴染みがあるようなないようなZend Frameworkに注目。

Zend Frameworkについて


米Zend、PHPアプリフレームワーク「Zend Framework 2.0」をリリース (2012年9月7日)

バージョン1.0のリリースは2007年、2012年9月にバージョン2.0がリリースとのこと。2013年6月時点のバージョンは2.2。

数あるPHPフレームワークの中で、Zemd Frameworkの人気はどうだろうか。

PHPの4大フレームワークの人気を比較 (2013年1月21日)

4大フレームワークと呼ばれるものの中では、Zend Frameworkはいまひとつの様子。

さて、バージョンアップでどのように変わったのか。まずはZend Framework開発メンバーの方が作成されたこちらを参照。

Creating Re-usable Modules for Zend Framework 2 (2012年6月?)

これによると、3つのコンセプトがあるという。

Three core concepts
  • Decoupled and DI-driven (ServiceManager, Di)
  • Event-driven (EventManager)
  • HTTP handlers (DispatchableInterface)

英語で書かれているからということを差し引いても、よくわからない。では、少し観点が違うけれども、日本の方が書かれたこちらも参照。


結局よくわからないが、先ほどの資料と共通しているのは依存性の扱いが変わったことと、イベントという機能が追加されたことか。これは本家のFAQにも書かれている。


ここでは依存性の扱いについて見ていくことにする。

チュートリアルの実施


では、Zend Framework 2を使ってみよう。インストールは、スケルトンアプリケーションをダウンロードし、composerで関連モジュールを取り込む、という手順になる。チュートリアルのページを参照。

Getting started: A skeleton application

上記のページに、php composer.phar create-project --repository-url=... のコマンドでインストールするとあるが、LinuxとWindowsのどちらで試してもエラーが発生し、インストールができなかった。

しかたがないので、GithubからZendSkeletonApplication-master.zipをダウンロードし、解凍する。

解凍したら、composerで関連モジュールを取り込む。これにより、Zend Framework 2本体もインストールされる。

php composer.phar self-update
php composer.phar install

nginxの設定は以下のとおり。

server {
        root /usr/share/nginx/www/workspace/zend/public;
        index index.html index.htm index.php;
        server_name localhost;

        location / {
                try_files $uri $uri/ /index.php$is_args$args;
        }

        location ~ \.(php|phtml)?$ {
                fastcgi_param  PATH_INFO        $fastcgi_path_info;
                fastcgi_pass 127.0.0.1:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param APPLICATION_ENV development;
        }
}

設定できたら、http://lcoalhost/ でアクセスする。スケルトンアプリケーションの画面が表示されたら成功。なお、nginxの設定の問題ではないかと思うが、http://localhost/index.php でアクセスすると、「Page not found.」のエラーとなった。

チュートリアル自体は、ソースコードをコピー&ペーストしながら読み進めていくと、データベースへのデータの登録、更新、取得、削除が一通りできる内容になっている。

「Using ServiceManager to configure the table gateway and inject into the AlbumTable」の手順で、データベースへのアクセスを行うTableGatewayやAdapterのインスタンスを用意しているあたりがDependency Injectionの書き方になっている。

チュートリアルで作成したプログラムが動作したら、Dependency Injectionについて調べてみよう。


Dependency Injection


自分がDependency Injection(以下、DI)という言葉を知ったのは最近だが、これは別に新しい技術ではなく、検索すると2005年ころの記事が多く見つかる。

Java開発を変える最新の設計思想「Dependency Injection(DI)」とは (2005年2月18日)

上記のページから一部抜粋。
----------
DIを実現するメカニズムの概要は、オブジェクト相互の依存性をプログラム外部に記述して、実行時に結合することである。これにより、オブジェクト間の独立性が高まる。

----------


さて、何のことだかわからないが、自分の認識では以下のような内容。


「ソースコードの中にクラス名を直接書いていると、そのクラスを拡張した派生クラスに差し替えようと思うとあちこち修正しなければならなかったり、開発環境と本番環境でクラスを切り替えたり、というようなことがやりにくい。そこで、ソースコード中には仮のクラス名を書いておき、実際にインスタンスを作るクラスの名前は設定ファイルに書くことにする。」



Zend Framework 2ではこれを以下のページの説明のように実現している。


簡単な例が最初に示されている。A、Bという2つのクラスがあったとして、Bはコンストラクタの引数にAのインスタンスを受け取る。すなわち、Bのインスタンスを生成する構文は以下のようになる。

$b = new B(new A());

newが2回も登場するので、Aを差し替えるときも、Bを差し替えるときもソースコードの修正が面倒そうだ。そこで、newを使わずにそれぞれのインスタンスを生成させてみよう。

クラスBのコンストラクタの定義部分で、引数に型を書いておく。

class B {
        public function __construct(A $a) { ...

そのうえで、以下のように記述する。

$di = new Zend\Di\Di;
$b = $di->get('B');

こうすると、$bにはBのインスタンスが代入されるが、その際に自動的にAのインスタンスも生成される。Zend\Di\Diクラスが必要なインスタンスの準備を肩代わりしてくれるのだ。少なくともこれで、クラスAの登場場面が減ったので、Aの差し替えが楽になる。

newが消えたとはいえ、Bの方は相変わらず直接名前が指定されている。$bに代入されるインスタンスの型を設定ファイルへ追い出すことができるかどうかはまだ勉強中。


実行環境による型の切り替え


前述の例では設定ファイルが登場しなかったが、生成されるインスタンスの型を設定ファイルに追い出してみよう。

BookStoreというモジュールに、StoreControllerというコントローラがあるとする。本屋さんが仕入先の出版社から本を入荷する、と考えてみる。

<?php
namespace BookStore\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Di\Di;
use Zend\Di\Config;

class StoreController extends AbstractActionController
{
    public function indexAction()
    {
        $di = $this->getDi();
        $purchase = $di->get('BookStore\Model\Purchase');

        return new ViewModel(array(
            'purchase' => $purchase,
        ));
    }

    protected function getDi()
    {
        // http://stackoverflow.com/questions/8957274/access-to-module-config-in-zend-framework-2
        $config = $this->getServiceLocator()->get('Config');

        // http://www.eschrade.com/page/zf2-dependency-injection-managing-configuration/
        $diConfig = new Config($config['di']);

        $di = new Di;
        $di->configure($diConfig);
        return $di;
    }
}

Purchaseクラスは仕入先を表し、以下のように定義する。

<?php
namespace BookStore\Model;

class Purchase
{
    protected $publisher;

    public function setPublisher($publisher)
    {
        $this->publisher = $publisher;
    }

    public function getPublisher()
    {
        return $this->publisher->getName();
    }
}

コンストラクタの引数にPublisherクラスのインスタンスを受け取る。Publisherクラスは以下のとおり。ついでにほぼ同様の内容でPublisherMockクラスも作っておく。

<?php
namespace BookStore\Model;

class Publisher
{
    protected $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}


ここで、このプログラムを本番環境で動作させる時はPublisherクラスを使用し、開発環境の場合はPublisherMockクラスを使用したいとする。nginxから受け取っている環境変数APPLICATION_ENVで実行環境は判定。設定は config/module.config.php と config/module.config.develop.php に書くことにする。

まず環境変数APPLICATION_ENVの値により読み取る設定ファイルを切り替えるようにしてみる。というか、マージさせてみる。

Module.phpに、設定ファイルのパスを記述している部分がある。

class Module
{
    public function getAutoloaderConfig()
    {
        省略
    }

    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

これを、以下のように変更。config/module.config.phpを読み取ったうえで、環境変数の値に対応するファイルがあったらそれのファイルの内容をマージする。

    public function getConfig()
    {
        $basePath = __DIR__ . '/config/';
        $config = include $basePath . '/module.config.php';
      $mode = $_ENV['APPLICATION_ENV'];

        if (!empty($mode)) {
            $filePath = $basePath . "/module.config.{$mode}.php";

            if (file_exists($filePath)) {
                $overwrite = include $filePath;
                $config = array_merge($config, $overwrite);
            }
        }
        return $config;
    }


それから、config/module.config.phpに以下の内容を追加する。

<?php
return array(
    'di' => array(
        'instance' => array(
            'BookStore\Model\Purchase' => array(
                'parameters' => array(
                    'publisher' => 'BookStore\Model\Publisher',
                ),
            ),
            'BookStore\Model\Publisher' => array(
                'parameters' => array(
                    'name' => 'Amazon',
                ),
            ),
        ),
    ),
    :

さらに、config/module.config.development.php には以下の内容を書いておく。

<?php
return array(
   'di' => array(
        'instance' => array(
            'BookStore\Model\Purchase' => array(
                'parameters' => array(
                    'publisher' => 'BookStore\Model\PublisherMock',
                ),
            ),
            'BookStore\Model\PublisherMock' => array(
                'parameters' => array(
                    'name' => 'Amazon',
                ),
            ),
        ),
    ),
);


以上のコードで、Purchaseクラスのインスタンスが作成されるとき、環境変数APPLICATION_ENVの値がdevelopmentでないときはコンストラクタの引数$publisherにPublisherクラスのインスタンスが注入され、developmentの場合はPublisherMockクラスのインスタンスが注入される。

ソースコードを1行も書き換えずに、環境変数の値だけで使用されるクラスを切り替えることができるようになった。

Zend Frameworkのサイトの説明だけでは分かりにくかったが、以下のページが参考になった。



終わり


正直なところ、DIがどのような場面で活躍するのか今ひとつ想像できていない。逆にソースコードの流れが読み取りにくくなりそうで少々心配だが、それは自分が大規模なシステム開発をしたことがないためか。

Zend Frameworkはまだチュートリアルくらいしか触っていないのでなんとも言えないが、DIやREST用コントローラ、コントローラの単体テストなどYiiでは提供されていなかったり、標準機能ではなかったりするものがあって、いろいろ勉強できそう。

またPHPの4大フレームワークと呼ばれるものを一つも知らないし、最近はFuelPHPやLaravelなどの方が有名らしいので、順番に見ていき、比較ができるようになれればよいと思う。