atijust's blog

技術的なこととか。

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.phpproviders配列に次の行を追加。

'Rocketeer\RocketeerServiceProvider',

app/config/app.phpaliases配列に次の行を追加。

'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にアクセスできるようにしてある
  • gitcomposerは予めインストール済み(デプロイタスクで使われる)

/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.sqlitepublicは外してしまってもいいかもしれません。アプリの実装によって調整しましょう。

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です。

デプロイタスクの流れとしては

  1. 初回デプロイ時など必要なディレクトリが作成されてない場合はセットアップタスクを実行
  2. リポジトリをclone
  3. composer install
  4. --tests (-t)オプションが指定されていればphpunitを実行しテストが失敗したらデプロイを中止
  5. config.phpの設定に従いパーミッションを変更
  6. --migrate (-m)オプションが指定されていればartisan migrateを実行(--seedと併用された場合はartisan migrate --seed
  7. --seed (-s)オプションが指定されていればartisan db:seedを実行
  8. リリース間で共有されるフォルダにリンクを張る
  9. 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 pullcomposer 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

リモートホストphpunitを実行します。

リポジトリ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アプリケーションをデプロイする方法について簡単に紹介しました。ステージング環境用に設定を切り替えたり、複数台のサーバへの一括デプロイしたりといった本格的に運用する上で必要なことについて補足エントリを書きましたので、よろしければそちらも参照ください。