Laravel + Codeception2 でブラウザテストを自動化する
この記事はLaravel Advent Calendar 2014 22日目です。今日はCodeceptionを使ったLaravelアプリケーションのブラウザテストの自動化を紹介したいと思います。
はじめに
Webアプリケーションの開発現場でよくある光景として、ブラウザを使って人力でポチポチやって動作確認をしたりしますよね。ユニットテストを書いているといっても、Webサーバやデータベースまで含めた実際に動いているシステム全体の結合テストとして、リリース前にはブラウザでの動作確認はできればやっておきたいもの。ただ、一度や二度ならまだしもリリースの度に何度も何度も人力でテストするのは大変です(主に精神的に)。リリース頻度が日に数度といったレベルの場合は、そもそも人力テスト自体が不可能になるということも。もうこれは自動化するしかないですね。ということでCodeceptionです。
Codeceptionはユニットテスト、機能テスト、受け入れテストをサポートしたフルスタックのPHP用テスティングフレームワークです。いわゆるBDDフレームワークといわれるもので(厳密にいうとどうなのかはわかりませんが……)、自然言語(英語)に近い記法でテスト対象の振る舞いを記述しテストを行うことができます。
Codeceptionがサポートする3種類のテストのなかでブラウザによるテストに相当するのが受け入れテスト(Acceptance Test)です。受け入れテストとは、アプリケーションの利用者であるユーザの視点から、アプリケーションが期待したとおりに動作するか確認するテストのことです。サイトトップにアクセスして、ナビバーからログインページに飛んで、ユーザ名とパスワードを入力して、ログインボタンをクリックして、Welcome!と表示されたらOK……こういったユーザからみたアプリケーションの挙動を記述したシナリオを元に、Guzzle+Symfony BrowserKitによるスクレイパー(CodeceptionではPhpBrowserと呼ばれています)やPhantomJS、Selenium WebDriverで実際に動作中のアプリケーションにアクセスして挙動をテストすることができます。
この記事ではLaravel Tricksをサンプルとして、Codeceptionで受け入れテストを記述する方法を紹介します。受け入れテストの実行にはPhpBrowserを使用します。PhpBrowserはJavaScriptの実行はできませんが、Codeceptionに標準で入っているのでお手軽です。JavaScriptが必要な場合はPhantomJSやSelenium WebDriverを使用する必要がありますが、これらは別途インストールが必要です。詳しくはCodeceptionのマニュアルを参照してください。
Laravel Tricksのセットアップ
受け入れテストを行うにはWebサーバ、データベース含めてアプリケーションが動作している必要があります。下準備としてまずはLaravel Tricksをローカル環境で動くようにしておきます。
Laravel Tricksのセットアップ自体は本題ではないので、ここではわたしの開発マシンにあわせてあらかじめ設定を済ませておいたforkを利用します*1。Laravel Tricksのセットアップについて詳しくはLaravel Tricksのドキュメントを参照ください。
$ git clone https://github.com/atijust/laravel-tricks.git $ composer install $ php artisan migrate $ php artisan db:seed $ php artisan serve
PHPの組み込みサーバが起動し、http://localhost:8000/でLaravel Tricksにアクセスできるようになります。このローカルホストで動いているLaravel Tricksにたいして受け入れテストを実行します。
Codeceptionのインストール
composerでcodeception/codeceptionをインストールします。
$ composer require "codeception/codeception:2.*"
テストに必要な各種ファイルが格納されたtestsディレクトリと、グローバル設定ファイルであるcodeception.ymlを生成します。
$ php vendor/bin/codecept bootstrap Initializing Codeception in /Users/atijust/Develop/laravel-tricks File codeception.yml created <- global configuration tests/unit created <- unit tests tests/unit.suite.yml written <- unit tests suite configuration tests/functional created <- functional tests tests/functional.suite.yml written <- functional tests suite configuration tests/acceptance created <- acceptance tests tests/acceptance.suite.yml written <- acceptance tests suite configuration tests/_output was added to .gitignore tests/_bootstrap.php written <- global bootstrap file Building initial Tester classes Building Actor classes for suites: acceptance, functional, unit \AcceptanceTester includes modules: PhpBrowser, AcceptanceHelper AcceptanceTester.php generated successfully. 48 methods added \FunctionalTester includes modules: Filesystem, FunctionalHelper FunctionalTester.php generated successfully. 13 methods added \UnitTester includes modules: Asserts, UnitHelper UnitTester.php generated successfully. 17 methods added Bootstrap is done. Check out /Users/atijust/Develop/laravel-tricks/tests directory
以上でCodeceptionのインストールは完了です。
シナリオを作成
例としてログインのシナリオを作成してみましょう。
Laravel Tricksのログインの流れは次のようになります。
/login
にアクセス- ユーザ名とパスワードを入力
- LOGINボタンをクリック
- 投稿したトリックの一覧が表示されればログイン成功
これをシナリオに書き下してみます。
まずはシナリオを生成します。
$ php vendor/bin/codecept generate:cept acceptance Signin Test was created in SigninCept.php
tests/acceptance/SigninCept.phpにひな形が生成されているのでシナリオを記述します。
<?php $I = new AcceptanceTester($scenario); $I->wantTo('log in as regular user'); $I->amOnPage('/login'); $I->fillField('#username','msurguy'); $I->fillField('#password','password'); $I->click('#wrap > div.container > div > div > div > form > div:nth-child(5) > button'); $I->see('My tricks');
なにをやっているかはだいたいわかるのではないでしょうか。
テストシナリオは自然言語(英語)に近いかたちで記述することができます。とはいってもDSLというわけではなく普通のPHPコードです。あくまでもそれっぽく記述できるというだけです*2。
まず$I->amOnPage('/login');
でログインページにアクセスします。
次に$I->fillField('#username','msurguy');
でユーザ名、$I->fillField('#password','password');
でパスワードをフォーム入力します。ユーザ名とパスワードはシーダーでデフォルトで用意されているものです。入力するフィールドはCSSパスで指定していますが他にもさまざまな指定方法が利用できます。
そして$I->click('#wrap > div.container > div > div > div > form > div:nth-child(5) > button');
でLOGINボタンをクリック。ここでもCSSパスでLOGINボタンを指定しています。
ログインが成功したら投稿したトリックの一覧が表示されるのですが、その際に「My Tricks」という文字列が画面に出ているはずなので、それがあればログイン成功とみなします。
テストの実行
テストを実行してみましょう。
アプリケーションのURLをtests/acceptance.suite.yml
に設定します。
config: PhpBrowser: url: 'http://localhost:8000/'
テストを実行します。
$ php vendor/bin/codecept run acceptance --steps Codeception PHP Testing Framework v2.0.9 Powered by PHPUnit 4.4.0 by Sebastian Bergmann. Acceptance Tests (1) ----------------------------------------------------------------------------------------------- Trying to log in as regular user (SigninCept) Scenario: * I am on page "/login" * I fill field "#username","msurguy" * I fill field "#password","password" * I click "#wrap > div.container > div > div > div > form > div:nth-child(5) > button" * I see "My tricks" PASSED -------------------------------------------------------------------------------------------------------------------- Time: 715 ms, Memory: 13.25Mb OK (1 test, 1 assertion)
成功したようです。
このようにしてCodeceptionを使えばブラウザを使ったテストを自動化することができます。ここでは非常な簡単な例のみで、使用したCodeceptionの機能も基礎的なものだけでしたが、受け入れテストを記述するための豊富な機能がCodeceptionには用意されています。スクショ撮ったりとかもできます。
以上、Laravel Advent Calendar 2014 22日目でした。
Rocketeerを本格的に使うのに必要なちょっと進んだ機能
PHPのためのCapistrano風デプロイツール「Rocketeer」でLaravelをデプロイするの補足として、Rocketeerを本格的に運用する上で必要となってくるちょっと進んだ機能を紹介します。
複数のリモートホストを一括で操作する
app/config/packages/anahkiasen/rocketeer/config.php
のconnections
には複数のリモートホストの接続情報を設定することができます。
app/config/packages/anahkiasen/rocketeer/config.php
// The various connections you defined // You can leave all of this empty or remove it entirely if you don't want // to track files with credentials : Rocketeer will prompt you for your credentials // and store them locally 'connections' => array( 'sv1' => array( 'host' => '203.0.113.1', 'username' => 'hogehoge', 'password' => '', 'key' => '/Users/hogehoge/.ssh/id_rsa', 'keyphrase' => '', ), 'sv2' => array( 'host' => '203.0.113.2', 'username' => 'hogehoge', 'password' => '', 'key' => '/Users/hogehoge/.ssh/id_rsa', 'keyphrase' => '', ), ),
各コマンドは--on
オプションで操作対象のリモートホストを指定できます。
$ ./artisan deploy:deploy --on sv1 $ ./artisan deploy:rollback --on sv1
カンマ区切りで複数指定も可能です。
$ ./artisan deploy:deploy --on sv1,sv2 $ ./artisan deploy:rollback --on sv1,sv2
これで複数のリモートホストを一括して操作できますね。
ただ、毎回--on
オプションを指定するのも面倒なので、そんなときはデフォルトで使用する接続情報を設定してやりましょう。
--on
オプションが指定されないときは、default
に設定した接続情報が使われます。
app/config/packages/anahkiasen/rocketeer/config.php
// The default remote connection(s) to execute tasks on 'default' => array( 'sv1', 'sv2', ),
こうしておけば--on
オプションが指定されていないときは自動的にsv1
、sv2
の両方が操作対象になります。
$ ./artisan deploy:deploy
複数のリモートホストを扱う上で注意すべき点としては、リモートホストごとにリリースのタイムスタンプが変わるということでしょうか。
あと、Capistranoと違ってタスクは直列で実行されます。リモートホストの台数が多いと少し時間がかかってしまうかもしれません。
リモートホストごとにデプロイの設定を変える
本番とステージングでデプロイするブランチを変えたい、といった時はどうすればよいでしょうか。
例えば、production
とstaging
の2つのリモートホストがあって、production
ではmaster
ブランチを、staging
ではdevelop
ブランチをデプロイしたいとします。
そんなときは、app/config/packages/anahkiasen/rocketeer/config.php
のon.connections
でグローバルな設定値を上書きすればOKです。
app/config/packages/anahkiasen/rocketeer/config.php
// Contextual options // // In this section you can fine-tune the above configuration according // to the stage or connection currently in use. // Per example : // 'stages' => array( // 'staging' => array( // 'scm' => array('branch' => 'staging'), // ), // 'production' => array( // 'scm' => array('branch' => 'master'), // ), // ), //////////////////////////////////////////////////////////////////// 'on' => array( // Connections configuration 'connections' => array( 'production' => array( 'scm' => array('branch' => 'master'), ), 'staging' => array( 'scm' => array('branch' => 'develop'), ), ), ),
こうしておけば、production
を操作するときはscm.branch
がmaster
、staging
を操作するときはscm.branch
がdevelop
に設定されます。scm.branch
だけでなく、他の設定も上書きすることができますので、色々便利に使えそうですね。
1つのリモートホストに複数のリリースを同居させる
リモートホストを1台しか用意できないなんて場合は、本番用のリモートホストに動作確認用のステージング環境も同居させてしまいたい、なんていうことがあるかもしれません。
そんな場合はディレクトリを分けて1つのリモートホストに複数のリリースを同居させてしまいましょう。
app/config/packages/anahkiasen/rocketeer/stages.php
// Stages // // The multiples stages of your application // if you don't know what this does, then you don't need it ////////////////////////////////////////////////////////////////////// // Adding entries to this array will split the remote folder in stages // Like /var/www/yourapp/staging and /var/www/yourapp/production 'stages' => array('production', 'staging'),
このように設定しておけば、
$ ./artisan deploy:deploy --stage production
とすれば/var/www/rocketeer-example/production
に、
$ ./artisan deploy:deploy --stage staging
とすれば/var/www/rocketeer-example/staging
にデプロイされます。
--stage
オプションを省略したときのデフォルトを設定することもできます
app/config/packages/anahkiasen/rocketeer/stages.php
// The default stage to execute tasks on when --stage is not provided 'default' => 'production',
デフォルトが設定されてない状態で--stage
オプションを省略するとproduction
、staging
両方がデプロイされてしまうので注意。
ステージ毎に設定を変更することもできます。
app/config/packages/anahkiasen/rocketeer/config.php
'on' => array( // Stages configurations 'stages' => array( 'production' => array( 'scm' => array('branch' => 'master'), ), 'staging' => array( 'scm' => array('branch' => 'develop'), ), ), ),
これでproduction
にはmaster
ブランチが、staging
にはdevelop
ブランチがデプロイされるようになります。
以上、Rocketeerを本格的に運用する上で必要なちょっと進んだ機能を紹介しました。他にもタスクをフックしてユーザ定義の処理を差し込んだり、オリジナルのタスクを書いたり、プラグインだったりといった楽しそうなトピックがありますが、まだ自分は使ったこと無いし、その辺の情報は公式ドキュメントが充実しているので、そちらを参照ください( 英語だけどコードを多用した説明なのでエンジニアならなんとかなるハズ)。
PHPのためのCapistrano風デプロイツール「Rocketeer」でLaravelをデプロイする
そろそろrsyncでデプロイするのは卒業したいな、ということでRocketeerというデプロイツールを導入してみました。
RocketeerはPHP製のCapistrano風デプロイツールです。PHP製なだけあってはじめからComposerやPHPUnitをサポートしてるし、当然だけど設定ファイルや新しいタスクもPHPで記述できるしでとても使いやすいです。
Rocketeer自体はフレームワークに依存しないデプロイツールではありますが、Laravelのパッケージとしてインストールすると、artisanからデプロイできたり、データベースのマイグレーションやシーディングなんかもできるようになるので、Laravelアプリケーションのデプロイには特に便利に使えます。
ただ、新興のツールであるからか、日本語での具体的な導入手順について解説している情報があまりありません。素晴らしいツールでありながら導入に障壁があるのももったいないので、このエントリではRocketeerでLaravelで作ったアプリケーションをデプロイする方法を紹介したいと思います。
Rocketeerの概要
RocketeerはSSHでデプロイ先のリモートホストに接続し、タスクとして定義されたコマンド群を実行することでデプロイを行います。ソースコードはリモートホスト上でgitもしくはsvnを使って取得し、ライブラリはcomposerでインストールします。今のところローカルからファイルをコピーしてデプロイする機能はサポートされていないようです。
つまりRcoketeerを導入するには
の3つの前提をクリアしている必要があります。
次にデプロイしたアプリケーションのリモートホスト上でのディレクトリ構造について説明します。
Rocketeerの特徴の一つは、単にアプリケーションをデプロイするだけでなく、過去のリリースにロールバックする機能を持っていることでしょう。そのため、リモートホストでのディレクトリ構造は少々独特です。
リモートホストでの典型的なディレクトリ構造は次のようになります。
/var/www/rocketeer-example/ ├── current -> /var/www/rocketeer-example/releases/20140116170238 ├── releases │ ├── 20140115152810 │ ├── 20140115173018 │ ├── 20140115174248 │ └── 20140116170238 │ ├── app │ │ └── storage │ │ ├── logs -> /var/www/rocketeer-example/shared/app/storage/logs │ │ └── sessions -> /var/www/rocketeer-example/shared/app/storage/sessions │ └── public └── shared └── app └── storage ├── logs └── sessions
Rcoketeerはデプロイ先として指定されたパスにアプリケーション名のディレクトリを作成します。この場合は/var/www
がデプロイ先のパスで、アプリケーション名はrocketeer-example
です。
さらに/var/www/rocketeer-example/
には3つのディレクトリが作成されます。
1つ目はcurrent
です。ここにはアプリケーションの最新のリリースが設置されます。Webサーバはcurrent/public
を公開するように設定します。
2つ目はreleases
で、新しいものから4件(デフォルト値)のリリースが保存されます。current
は実際にはreleases
に保存さているリリースへのシンボリックリンクとなっており、current
のリンク先を変更することでデプロイをロールバックすることができるようになっています。
3つ目はshared
です。このディレクトリにはリリース間で共有する必要のあるファイルが置かれるディレクトリです。各リリースのディレクトリにあるログファイルなどはshared
にあるものにリンクされます。
Capistranoを使ったことがある人にはお馴染みでしょうが、シンボリックリンクを使ってリリースを切り替えるというのは目から鱗で面白いですね。
インストール
composerでanahkiasen/rocketeer
パッケージをインストールします。
$ composer require anahkiasen/rocketeer:dev-master ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) - Installing anahkiasen/rocketeer (dev-master 3591b74) Cloning 3591b74208e632ce5ffe090ffcd19330785feb77 anahkiasen/rocketeer suggests installing anahkiasen/rocketeer-campfire (Campfire plugin to create deployments notifications) Writing lock file Generating autoload files Generating optimized class loader
app/config/app.php
のproviders
配列に次の行を追加。
'Rocketeer\RocketeerServiceProvider',
app/config/app.php
のaliases
配列に次の行を追加。
'Rocketeer' => 'Rocketeer\Facades\Rocketeer',
deploy:
で始まるコマンド群が追加されます。
$ ./artisan deploy deploy:check Check if the server is ready to receive the application deploy:cleanup Clean up old releases from the server. deploy:current Display what the current release is deploy:deploy Deploy the website. deploy:flush Flushes Rocketeer's cache of credentials deploy:ignite Creates Rocketeer's configuration deploy:rollback Rollback to the previous release, or to a specific one deploy:setup Set up the remote server for deployment deploy:teardown Remove the remote applications and existing caches deploy:test Run the tests on the server and displays the output deploy:update Update the remote server without doing a new release.
Laravel4.0の場合はremote
パッケージを別途導入する必要があります。4.0へのインストール方法は公式サイトで解説されていますので参照ください。
設定
具体的な設定を行っていく前に設定に必要な情報を確認しておきましょう。
デプロイ先のリモートホストついては次のように想定しています。
- デプロイ先のパスは
/var/www
/var/www
のユーザとグループはwww-data
/var/www
はセットグループIDされている- デプロイタスクは作業用ユーザ(
hogehoge
)で実行する - 作業用ユーザの補助グループを
www-data
にして/var/www
にアクセスできるようにしてある git
とcomposer
は予めインストール済み(デプロイタスクで使われる)
/var/www
はセットグループIDされているので、作業用ユーザによってデプロイ時に作成されたファイルのグループはwww-data
になります。www-data:www-data
で動作しているWebサーバは、グループの権限を通じてファイルにアクセスできるという仕組みです。
アプリの名前はrocketeer-example
で、Githubのパブリックなリポジトリ(https://github.com/hogehoge/rocketeer-example.git
)で公開されているとします。
それでは具体的な設定を行っていきます。
まずはapp/config/remote.php
にリモートホストの接続情報を設定します。
artisan tail
が使えるようにroot
には/var/www/rocketeer-example/current
を指定しておきましょう。
'connections' => array( 'production' => array( 'host' => '203.0.113.1', 'username' => 'hogehoge', 'password' => '', 'key' => '/Users/hogehoge/.ssh/id_rsa', 'keyphrase' => '', 'root' => '/var/www/rocketeer-example/current', ), ),
次にRocketeerの設定ファイルをpublishします。 アプリケーションのリポジトリと名前について聞かれるので入力します。
./artisan deploy:ignite No repository is set for the repository, please provide one :https://github.com/hogehoge/rocketeer-example.git Configuration published for package: anahkiasen/rocketeer What is your application's name ?rocketeer-example The Rocketeer configuration was created at anahkiasen/rocketeer Execution time: 9.3387s
app/config/packages/anahkiasen/rocketeer/
以下に設定ファイルが生成されます。
各設定ファイルには詳細なコメントがあるので、参考にしながら設定していきましょう。
app/config/packages/anahkiasen/rocketeer/ ├── config.php ├── hooks.php ├── paths.php ├── remote.php ├── scm.php └── stages.php
デプロイ先のディレクトリパスを指定します。
app/config/packages/anahkiasen/rocketeer/remote.php
// The root directory where your applications will be deployed 'root_directory' => '/var/www/',
リリース間で共有されるべきディレクトリとファイルを指定します。SQLiteをプロダクションで使っているのでなければデフォルトで大丈夫だと思います。
app/config/packages/anahkiasen/rocketeer/remote.php
// A list of folders/file to be shared between releases // Use this to list folders that need to keep their state, like // user uploaded data, file-based databases, etc. 'shared' => array( '{path.storage}/logs', '{path.storage}/sessions', ),
Webサーバが書き込みするファイルやディレクトのパーミッションを変更するようにします(chmod -R755 %s
だけでもいいですが、特に実害もないのでなんとなくデフォルトのまま他の2つのコマンドも残してあります)。アプリによってはapp/storage
だけ書き込めるようになっていれば十分で、app/database/production.sqlite
とpublic
は外してしまってもいいかもしれません。アプリの実装によって調整しましょう。
app/config/packages/anahkiasen/rocketeer/remote.php
'permissions' => array( // The folders and files to set as web writable // You can pass paths in brackets, so {path.public} will return // the correct path to the public folder 'files' => array( 'app/database/production.sqlite', '{path.storage}', '{path.public}', ), // Here you can configure what actions will be executed to set // permissions on the folder above. The Closure can return // a single command as a string or an array of commands 'callback' => function ($task, $file) { return array( sprintf('chmod -R 775 %s', $file), sprintf('chmod -R g+s %s', $file), sprintf('chown -R www-data:www-data %s', $file), ); },
パブリックなリポジトリを使うのでリポジトリのユーザ名とパスワードは空にしておきます。プライベートなリポジトリで認証が必要な場合はここでユーザ名とパスワードを指定します。
app/config/packages/anahkiasen/rocketeer/scm.php
// The repository credentials : you can leave those empty // if you're using SSH or if your repository is public // In other cases you can leave this empty too, and you will // be prompted for the credentials on deploy 'username' => '', 'password' => '',
設定は以上です。
設定が完了したら./artisan deploy:check
を実行して、リモートホストがデプロイ可能な状態かチェックしておきます。
$ ./artisan deploy:check No username is set for the repository, please provide one : No password is set for the repository, please provide one : Checking presence of git Checking PHP version Checking presence of Composer Checking presence of mcrypt extension Checking presence of mysql extension The mysql extension does not seem to be loaded on the server Execution time: 2.2685s
レポジトリにアクセスするためのユーザ名とパスを聞かれますが、パブリックなリポジトリを使っているので無視してEnterでOKです。
PHPのconfigureオプションの関係で、mysql extension
がないと怒られてますが実害はないので無視しています(app/config/database.php
で設定されているdatabase.default
の値と同名のextensionが存在するかチェックするように実装されているのですが、これは本来はdriverを見て対応するextension名を決めるように実装したほうがいいと思う)。
デプロイ
deploy:deploy
コマンドでデプロイします。省略してdeploy
だけでもデプロイできます。
$ ./artisan deploy:deploy No username is set for the repository, please provide one : No password is set for the repository, please provide one : Server is not ready, running Setup task Checking presence of git Checking PHP version Checking presence of Composer Checking presence of mcrypt extension Checking presence of mysql extension The mysql extension does not seem to be loaded on the server Cloning repository in "/var/www/rocketeer-example/releases/20140114191141" Initializing submodules if any Installing Composer dependencies Setting permissions for /var/www/rocketeer-example/releases/20140114191141/app/database/production.sqlite Setting permissions for /var/www/rocketeer-example/releases/20140114191141/app/storage Setting permissions for /var/www/rocketeer-example/releases/20140114191141/public Sharing file /var/www/rocketeer-example/releases/20140114191141/app/storage/logs Sharing file /var/www/rocketeer-example/releases/20140114191141/app/storage/sessions Successfully deployed release 20140114191141 No releases to prune from the server Execution time: 111.9915s
/var/www/rocketeer-example
ディレクトリが作成され、最新のリリースへのリンクが/var/www/rocketeer-example/current
に作成されます。Webサーバは/var/www/rocketeer-example/current/public
を公開するように設定すればOKです。
デプロイタスクの流れとしては
- 初回デプロイ時など必要なディレクトリが作成されてない場合はセットアップタスクを実行
- リポジトリをclone
composer install
--tests (-t)
オプションが指定されていればphpunitを実行しテストが失敗したらデプロイを中止config.php
の設定に従いパーミッションを変更--migrate (-m)
オプションが指定されていればartisan migrate
を実行(--seed
と併用された場合はartisan migrate --seed
)--seed (-s)
オプションが指定されていればartisan db:seed
を実行- リリース間で共有されるフォルダにリンクを張る
current
のリンク先を更新
となっています。
一般的なLaravelを使ったアプリケーションのデプロイとして必要なことはひと通りよしなにやってくれます。便利ですね。
ロールバック
deploy:rollback
コマンドでcurrent
のリンク先を変更してデプロイをロールバックできます。
一つ前のリリースに戻す。
$ ./artisan deploy:rollback Rolling back to release 20140114195555 Execution time: 2.4191s
ロールバック可能なリリースのリストを表示し、番号でロールバック先を選択。
$ ./artisan deploy:rollback --list Here are the available releases : [0] 2014-01-14 19:57:28 [1] 2014-01-14 19:55:55 [2] 2014-01-14 19:53:59 [3] 2014-01-14 19:11:41 Which one do you want to go back to ? (0) Rolling back to release 20140114195728 Execution time: 95.3531s
バグが見つかったときも、すぐに以前のリリースに戻すことができるので安心です。
そのほかのコマンド
deploy:current
current
からリンクされているリリースを表示します。
./artisan deploy:current The current release is 20140114191141 (b3f46005e0ec4b1e202a113c5fa31875ef772826 deployed at 2014-01-14 19:11:41) Execution time: 1.6177s
deploy:update
current
からリンクされているリリースでgit pull
とcomposer install
を実行します。
$ ./artisan deploy:update Pulling changes Sharing file /var/www/rocketeer-example/releases/20140115174248/app/storage/logs Sharing file /var/www/rocketeer-example/releases/20140115174248/app/storage/sessions Installing Composer dependencies Setting permissions for /var/www/rocketeer-example/releases/20140115174248/app/database/production.sqlite Setting permissions for /var/www/rocketeer-example/releases/20140115174248/app/storage Setting permissions for /var/www/rocketeer-example/releases/20140115174248/public Successfully updated application Execution time: 8.2524s
deploy:cleanup
保存件数(デフォルトは4件)を超えた古いリリースをリモートホストから削除します。
$ ./artisan deploy:clean Removing 1 release from the server Execution time: 1.8961s
--clean-all
オプションを使えば、current
からリンクされている以外のリリースをすべて削除することもできます。
$ ./artisan deploy:clean --clean-all Removing 3 releases from the server Execution time: 2.7766s
古いリリースはdeploy:deploy
が削除してくれるので、明示的にdeploy:clean
を実行する機会はあまりないとは思います。使いどころとしては、保存件数を減らした時とか、deploy:deploy
を中断してゴミが残ったときなどでしょうか。
deploy:teardown
リモートホストからアプリを削除します。/var/www/rocketeer-example
が削除されます。
$ ./artisan deploy:teardown This will remove all folders on the server, not just releases. Do you want to proceed ? The application was successfully removed from the remote servers Execution time: 3.9923s
deploy:flush
認証情報のキャッシュ(app/storage/meta
あたりに保存されている)を削除します。設定の変更が反映されないときは取り敢えずこのコマンドを叩いてみる感じです。
$ ./artisan deploy:flush Rocketeer's cache has been properly flushed
deploy:setup
デプロイ前にリモートホストにディレクトリを作成するのに使用します。明示的にこのコマンドを実行しなくても、deploy:deploy
コマンドが初回デプロイ時によしなにやってくれるので、直接使うことはあまりなさそうです。
deploy:test
リポジトリにSSHで接続する場合
HTTPSじゃなくてSSHでリポジトリにアクセスすることもできます。その場合は.ssh/config
を駆使して予めリモートホストの作業用ユーザでgit clone
出来るように設定しておきましょう。.ssh/known_hosts
にリポジトリをホストしているサーバの公開鍵がないと、デプロイ時にエラーになるので、予めコマンドラインでgit clone
を実行するなりしておくのも忘れずに。自分はこれでハマりました。
リモートホストの接続情報を変更するときの注意点
app/config/remote.php
の設定を変更するだけではダメです。
artisan deploy:ignite
を実行したときに、app/config/remote.php
を元にしてapp/config/packages/anahkiasen/rocketeer/config.php
に接続情報が設定されるのですが、Rocketeerはconfig.php
に接続情報があればremote.php
より優先して使うので、接続情報を変更する場合は、remote.php
だけではなくconfig.php
も変更する必要があります。接続情報はキャッシュされているので、設定を変更したあとはartisan deploy:flush
で接続情報のキャッシュをフラッシュしましょう。
もしくは、同じ内容の設定が2つのファイルにあるのが気持ち悪いのであれば、config.php
から接続情報を削除し、常にremote.php
を使うようにしてし
まってもいいかもしれません。
実行されるコマンドを確認する
各コマンドに-p
オプションを指定すると、リモートホストで実行されるコマンドを確認できます。これらのコマンドは実際に実行されるわけではありません。どのようなコマンドが実行されるのか不安なときはこれで確認を。-v|vv|vvv
オプションでも色々見れます。
以上、RocketterでLaravelアプリケーションをデプロイする方法について簡単に紹介しました。ステージング環境用に設定を切り替えたり、複数台のサーバへの一括デプロイしたりといった本格的に運用する上で必要なことについて補足エントリを書きましたので、よろしければそちらも参照ください。
さくらVPS(CentOS6)にNginx+PHP5.5+MySQL5.6の環境を構築する(設定編)
インストール編の続きです。
動作確認用のLaravelを用意する
/var/www/testに動作確認用にLaravelをインストールします。
/var/www/testは所有ユーザ、グループともに作業用ユーザ(atijust)にし、nginxユーザの補助グループをatijustにすることで、NginxとPHP-FPM(nginxユーザ・グループで動作するように設定済み)からアクセスできるようにします。一般ユーザのumaskはデフォだと0002になってて、app/storageはatijustグループにも書き込み権限があるので、パーミッションを変更する必要はありません。
# usermod -G atijust nginx # mkdir -p /var/www/test # chown atijust:atijust /var/www/test $ cd /var/www/test $ composer create-project laravel/laravel --prefer-dist .
うーん、NginxやPHP-FPMに作業用ユーザのグループの権限を与えるのは微妙に気持ち悪いのでその辺りは要研究ですね。www-dataみたいなユーザとグループを作って、/var/www以下を所有させる。/var/wwwにセットグループIDして、nginxやatijustの補助グループをwww-dataにするとかかなぁ。ベストプラクティスが知りたい。まぁ、時間有るときに調べてみる感じで。
Nginxの設定
細かい設定はおいおいやるとして、取り敢えずLaravelが動くように最低限の設定を行います。
/etc/nginx/conf.d/default.conf
server { listen 80; server_name 203.0.113.1; root /var/www/test/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include /etc/nginx/fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }
nginxに設定ファイルを再読み込みさせます。
# service nginx configtest nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful # service nginx reload
http://203.0.113.1/にアクセスしてみます(203.0.113.0/24はRFC6890の例示用アドレスです)。
成功っぽい。
LaravelからMySQLが使えるかも試してみましたがこちらも問題なし。
以上、さくらVPSにNginx+PHP5.5+MySQL5.6の環境を構築してLaravelを動かしてみました。プロダクトとして使うにはまったくもって設定が適当過ぎますが、取り敢えず開発中のアプリをデプロイして遊ぶ分にはこれで十分でしょう。願わくばもうちょっと簡単にセットアップしたいので、その辺は今後の課題かな。
さくらVPS(CentOS6)にNginx+PHP5.5+MySQL5.6の環境を構築する(インストール編)
さくらVPS(CentOS6)にNginx+PHP5.5+MySQL5.6の環境を構築してLaravelを動かすまでの記録を備忘録として残しておきます。
Nginxのインストール
公式サイトのインストール方法の説明に従って、公式のyumリポジトリからStable版をインストールします。
まずはCentOS6用のyumリポジトリのセットアップ。 レポジトリの設定ファイルとPGPキーがインストールされます。
# rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
次にyumでnginxのパッケージをインストール。
# yum install nginx
バージョンとビルドのオプションを確認します。
# nginx -V nginx version: nginx/1.4.4 built by gcc 4.4.7 20120313 (Red Hat 4.4.7-3) (GCC) TLS SNI support enabled configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-cc-opt='-O2 -g -pipe -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic'
現時点での最新Stable版が入りました。ひと通りのモジュールは有効になっているみたいですね。設定ファイルは/etc/nginx/nginx.conf
のようです。あと、/etc/logrotate.d/nginx
にlogrotateの設定が追加されて、/var/log/nginx/*
以下をdailyでローテートするようにしている模様。
MySQL5.6のインストール
公式サイトの説明に従ってビルド済みのバイナリをインストールします。
MySQL公式のyumリポジトリからインストールする場合は、すでにインストールされているディストリ標準のmysql関連のパッケージ(mysql-libs
とか)を削除する必要があるのですが、それらに依存しているパッケージの扱いをどうすればいいのかわからなかったので、今回は公式バイナリからインストールすることにしました。mysql-community-libs-compat
という5.1互換のライブラリなんかもあるみたいなので、なんとかなりそうではあるのですが、まぁ、無難に行くということで。
まずは、ダウンロードページからLinux - Generic (glibc 2.5) (x86, 64-bit), Compressed TAR Archiveを取ってきます。
# wget http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.15-linux-glibc2.5-x86_64.tar.gz
公式サイトにある通りにコマンドを実行。
# groupadd mysql # useradd -r -g mysql mysql # cd /usr/local # tar zxvf ~/mysql-5.6.15-linux-glibc2.5-x86_64.tar.gz # ln -s mysql-5.6.15-linux-glibc2.5-x86_64 mysql # cd mysql # chown -R mysql . # chgrp -R mysql . # scripts/mysql_install_db --user=mysql # chown -R root . # chown -R mysql data
ディストリの/etc/my.cnfでdatadirとか勝手に設定されててそのままじゃ立ち上がらないので、取り敢えず適当に置き換え。
# mv /etc/my.cnf /etc/my.cnf.bak # cp my.cnf /etc/my.cnf
立ち上げます。
# bin/mysqld_safe --user=mysql &
mysql_secure_installationを実行。
# bin/mysql_secure_installation
起動時に自動的に立ち上がるようにする。
# cp support-files/mysql.server /etc/init.d/mysql # chkconfig --add mysql # chkconfig | grep mysql mysql 0:off 1:off 2:on 3:on 4:on 5:on 6:off
PHP5.5のインストール
ソースからビルドします。
remiから入れたほうが楽かもしれないけど、PHPは自分でビルドしたい気分なのです。
現時点で最新版の5.5.7のソースをダウンロードして展開。
$ wget http://jp1.php.net/get/php-5.5.7.tar.bz2/from/this/mirror $ tar xf php-5.5.7.tar.bz2
必要なパッケージをインストール。
# yum -y install libxml2-devel libmcrypt-devel libcurl-devel libicu-devel pcre-devel openssl-devel
configureを実行します。 オプションは取り敢えずLaravelとSymfonyを動かすのに必要そう&自分が使いそうなのを最小限指定してます。 実際に使ってみて不足するようならそのとき追加すればいいかな、という方針。
$ cd php-5.5.7 $ ./configure --enable-fpm --with-openssl --with-curl --enable-intl --enable-mbstring --with-mcrypt --enable-opcache --with-pdo-mysql
ビルド&インストール。 3コアのインスタンスなのでmakeは3並列で。
$ make -j 3 # make install
php.iniをコピーします。
# cp php.ini-production /usr/local/lib/php.ini
php-fpmの設定ファイルを用意します。
# cp /usr/local/etc/php-fpm.conf.default /usr/local/etc/php-fpm.conf
pidとログのパスを/var以下に変更。ユーザとグループをnginxに。 pmとかはおいおいベンチ取ってチューニングする方向で。
/usr/local/etc/php-fpm.conf
pid = /var/run/php-fpm.pid error_log = /var/log/php-fpm.log user = nginx group = nginx
PHP-FPM用のinit.dスクリプトを/etc/init.d/にコピーしてパーミッションを変更します。
# cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm # chmod 755 /etc/init.d/php-fpm
pidのパスだけ修正。
/etc/init.d/php-fpm
php_fpm_PID=/var/run/php-fpm.pid
chkconfigで登録。
# chkconfig --add php-fpm # chkconfig | grep php-fpm php-fpm 0:off 1:off 2:on 3:on 4:on 5:on 6:off
init.dスクリプトについてはマニュアルのFPMの項目に付いたコメントを参考にしました。
インストールとしてはこんなところです。
仕事では最近はインフラ担当が全部用意してくれるので、自分でサーバーを弄ることもないのですが、久しぶりにやってみるとやっぱ大変ですね。。
設定編(そのうち公開)につづく。
さくらVPSを借りて作業用ユーザでSSHできるようにするまで
個人でお遊びで使うにはEC2はコスパが微妙なので、さくらVPSを借りることにしました。rootでのログインを禁止し作業用ユーザで公開鍵認証を使ってログインできるようにするまでを備忘録としてメモしておきます。OSはCentOS6(64bit)です。
rootでログインする
仮登録が完了するとサーバのIPとrootアカウントのパスワードを記載したメールが届きますので、まずはrootでログインします。
$ ssh 203.0.113.1 -l root [email protected]'s password: Last login: Fri Jan 3 10:54:47 2014 from 203.0.113.2 SAKURA Internet [Virtual Private Server SERVICE] [root@www7777up ~]#
rootのパスワードを変更します。
[root@www7777up ~]# passwd ユーザー root のパスワードを変更。 新しいパスワード: 新しいパスワードを再入力してください: passwd: 全ての認証トークンが正しく更新できました。
作業用ユーザの作成
作業用のユーザを作成します。su、sudoできるように補助グループをwheelにしています。
[root@www7777up ~]# useradd -m -G wheel atijust [root@www7777up ~]# passwd atijust ユーザー atijust のパスワードを変更。 新しいパスワード: 新しいパスワードを再入力してください: passwd: 全ての認証トークンが正しく更新できました。
wheelグループにsu、sudoを許可。
[root@www7777up ~]# visudo %wheel ALL=(ALL) ALL
作業用ユーザに公開鍵認証でログインできるように設定
先ほど作成した作業用ユーザに公開鍵認証でログインできるように公開鍵を登録します。GitHub に登録した SSH 公開鍵は全世界に公開されているとのことなので、公開鍵はGithubから取ってくるようにすれば簡単です。
[atijust@www7777up ~]$ curl https://github.com/atijust.keys --create-dirs -o .ssh/authorized_keys [atijust@www7777up ~]$ chmod 700 .ssh [atijust@www7777up ~]$ chmod 600 .ssh/authorized_keys
一旦ログアウトして作業用ユーザでログインできるか確認します。
$ ssh 203.0.113.1 -l atijust SAKURA Internet [Virtual Private Server SERVICE] [atijust@www7777up ~]$
rootでのログインとパスワード認証を禁止する
rootにSSHでログインできないようにします。またパスワードでのログインもできないようにします。ポートが22番のままだとアタックを受けて気持ち悪いのでこれも適当な番号に変更しておきます。
[root@www7777up ~]# vi /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no Port 10022 [root@www7777up ~]# /sbin/service sshd restart
以後、サーバへログインするときは作業用ユーザで公開鍵認証を使うようにして、rootでの作業が必要な場合はsudoを使うようにします。
取り敢えず、初期設定としてはこんな感じ。
Laravelでテンプレート用のロジックを整理する方法
Laravel Advent Calendar 2013の15日目です。 robclancy/presenterでテンプレート用のロジックを整理する方法を紹介します。
テンプレート用ロジックの置き場所の問題
Laravelに限らずですが、Webアプリを開発していてぶち当たる問題として、テンプレート用のロジックをどこに置くかというのがあります。
例えばモデルの作成日時をテンプレートで表示するというのはよくありますよね。
{{{$article->created_at->format('M d, Y')}}}
何も考えずに書くとこんな感じかな。テンプレート内でフォーマットを指定して表示しています。 うん問題ない。でも、日付の表示がここ1箇所だけならいいのですが、他に何箇所も表示しなければならないとしたら、ちょっと嫌ですよね。DRYじゃない。表示のフォーマットを変えたくなった場合、すべての箇所を1つずつ修正しなければなりません。
そこで、日付をフォーマットしてくれるヘルパーを用意してみます。
<?php function article_date($date) { return $date->format('M d, Y'); }
{{{article_date($article->created_at)}}}
これなら、フォーマットを変えたくなった時もヘルパーを修正するだけで大丈夫。やったね! …でも、ArticleだけならまだしもCommentやらなんやらの日時も表示するとなると、それに対応するヘルパーが際限なく増えていくわけで、なんか嫌かも。グローバル関数がいっぱいとかゾッとしますよね。
であれば、モデルに書いちゃえ!
<?php class Article extends Eloquent { public function formatDate() { return $this->created_at->format('M d, Y'); } }
{{{$article->formatDate()}}}
いやいやいやモデルにテンプレート用のロジックとかあり得ないだろjk
…このように、テンプレート用のロジックの置き場所というのは悩ましいものです。
それrobclancy/presenterで上手くできるよ!
少し前の自分なら、もやもやした気分のままヘルパーをひたすら増やすか、えいやでモデルにメソッドを追加していたでしょうが、最近いいパッケージを知りました。robclancy/presenterです。このパッケージを使えば、Decoratorパターンでモデルにメソッドを追加できます。
<?php class ArticlePresenter extends Robbo\Presenter\Presenter { public function formatDate() { return $this->created_at->format('M d, Y'); } } // … $article = new ArticlePresenter($article);
{{{$article->formatDate()}}}
このようにテンプレート用のロジックを実装したPresenterを用意し、モデルをラップすることで、モデルのクラスを変更することなくメソッドを追加することができます。Presenterは自身にないメソッドやプロパティへのアクセスは、モデルに自動的に移譲してくれるので、元々のモデルのメソッドも問題なく使えます。
<?php class Article extends Eloquent implements Robbo\Presenter\PresentableInterface { public function getPresenter() { return new ArticlePresenter($this); } }
また、モデルにPresentableInterfaceを実装することで、テンプレートにモデルを渡すときに自動的にPresenterでラップしたものに差し替えることもできます。Controllerで手動でちまちまラップする必要がなくて便利です。
これで、テンプレート用のロジックをキレイに実装できるようになって、気持よくプログラミングに励むことができますね!
最後になりましたが導入方法です。
composer.json
にrobclancy/presenter
を追加します。
"robclancy/presenter": "1.1.*"
app/config/app.php
でServiceProviderを登録します。
このとき必ず Illuminate\View\ViewServiceProvider
より後になるようにしましょう。
<?php 'Illuminate\View\ViewServiceProvider', // … 'Robbo\Presenter\PresenterServiceProvider'
robclancy/presenter自体はLaravel以外でも使える汎用的なライブラリですので、Laravel以外のフレームワークと組み合わせてみるのもいいかもしれませんね。
以上、Laravel Advent Calendar 2013の15日目でした。 明日の担当はHiroKwsさんです。よろしくお願いします!