こんにちは、@ttskchです。先日、Symfony Meetup #5のLTでChatOpsについて発表させていただきました。

発表に使用したスライドはこちらにあります

具体的には、PHP(特にSymfony2)プロジェクトの開発・リリースのプロセスについてのお話しです。

ここでは、その発表の内容をもう少し詳しく解説したいと思います。大きく5つのステップに分けて、一つずつ解説していきます。少し長いですがお付き合いください。

Step 1. CIの導入

まず一番初めにやるべきはCIの導入です。弊社ではCIサービスにCircleCIを使っています。

CircleCIとGitHubを連携させて、GitHubにpushしたコミットがCircleCI上で自動でテストされるようにします。これについてはググればすぐに出来ると思いますので、細かいやり方はここでは割愛します。

Step 2. Capistranoの導入

次に、プロジェクトにCapistrano(カピストラーノ)を導入して、ステージング環境やプロダクション環境へのデプロイをコマンド一発で行えるようにします。

インストール

# Capistranoをインストール
$ gem install capistrano
$ gem install capistrano-symfony # Symfonyプロジェクトのデプロイに便利なプラグイン


CapistranoをSymfonyプロジェクト用に拡張したCapifonyというツールがありますが、使っているCapistranoのバージョンが2のままで開発が終了しており、今後はcapistrano/symfonyを使うように、という声明が出ています

# プロジェクトにCapistranoを導入
$ cd /path/to/project
$ cap install

cap installすると以下のようなファイルが自動生成されます。

$ tree
.
├── Capfile
├── config
│   ├── deploy
│   │   ├── production.rb
│   │   └── staging.rb
│   └── deploy.rb
├── lib
│   └── capistrano
│       └── tasks
:

lib配下はここでは特に使わないので、削除しておきます。

$ rm -rf lib

設定例(Symfonyプロジェクトの場合)

設定ファイルの場所を変更

# 設定ファイルの場所を config → app/config に変更
$ mv config/* app/config/
$ rm -rf config

# 最終的にこのような配置になる
$ tree
.
├── Capfile
├── app
│   └── config
│       ├── deploy
│       │   ├── production.rb
│       │   └── staging.rb
│       ├── deploy.rb
│       :
:

Capfile

+ # 設定ファイルの場所の変更を有効化
+ set :deploy_config_path, 'app/config/deploy.rb'
+ set :stage_config_path, 'app/config/deploy'

# Load DSL and set up stages
require 'capistrano/setup'

# Include default deployment tasks
require 'capistrano/deploy'

+ require 'capistrano/symfony'

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

app/config/deploy.rb

lock '3.4.0'

set :repo_url, 'git@github.com:your/project.git'
set :keep_releases, 3

set :linked_files, fetch(:linked_files, []).push('app/config/parameters.yml')

set :ssh_options, {
  # 環境変数CAP_PRIVATE_KEYでssh鍵のパスを受け取る
  keys: [ENV['CAP_PRIVATE_KEY']],
  forward_agent: true
}

# マイグレーションタスクを定義
namespace :deploy do
  task :migrate do
    invoke 'symfony:console', 'doctrine:migrations:migrate', '--no-interaction'
  end
end

after 'deploy:updated', 'composer:install'
after 'composer:install', 'symfony:assetic:dump'
after 'composer:install', 'deploy:migrate'
after 'deploy:finishing', 'deploy:cleanup'

app/config/deploy/production.rb

server 'sample.com', user: 'ssh-user'
set :branch, 'release'
set :deploy_to, '/var/www/html/sample'

app/config/deploy/staging.rb

server 'staging.sample.com', user: 'ssh-user'
set :branch, 'master'
set :deploy_to, '/var/www/html/sample'

設定方法の細かい説明は割愛します。以下を参考にしてみてください。

  • http://capistranorb.com/documentation/getting-started/configuration/
  • https://github.com/capistrano/symfony/

コマンドからデプロイしてみる

$ export CAP_PRIVATE_KEY=~/.ssh/id_rsa # デプロイ先サーバにログインできるssh鍵のパス
$ cap staging deploy

実行が完了したら実際にデプロイ先サーバの中を確認してみましょう。

$ ssh ssh-user@staging.sample.com -i ~/.ssh/id_rsa

% cd /var/www/html/sample
% tree -L 2
.
├── current -> /var/www/html/sample/releases/20150905074312
├── releases
│   └── 20150905074312
├── repo
│   ├── FETCH_HEAD
│   ├── HEAD
│   ├── branches
│   ├── config
│   ├── description
│   ├── hooks
│   ├── info
│   ├── objects
│   ├── packed-refs
│   └── refs
├── revisions.log
└── shared
    ├── app
    └── web

上記のような形でデプロイが完了していることが確認できます。

  • releases配下にリビジョン名(YYYYmmddHHiiss形式)のディレクトリが作られ、そこにプロジェクトがビルドされています
  • currentrelease配下の特定のリビジョン(通常は最新リビジョン)のsymlinkになっています
    • つまり、ドキュメントルートはcurrentにする必要があります(Symfonyプロジェクトの場合はcurrent/web
  • shared配下にはリビジョンをまたいで共有したいファイル(app/config/parameters.ymlなど)が配置されており、release配下の当該ファイル/ディレクトリはshared配下へのsymlinkになっています

Step 3. デプロイの自動化

さて、次はこのcapコマンドをCircleCIに実行させることで、デプロイを完全に自動化してしまいましょう。

前提

ステージングデプロイまでのプロセス

  • CircleCI上で、masterブランチのテスト成功後にcap staging deployを実行
  • 各開発者はmasterブランチ宛てにPRを作成し、そこで開発を行う
  • レビューを経てmasterにマージされた変更はステージングにどんどん適用されていく

プロダクションデプロイまでのプロセス

  • CircleCI上で、releaseブランチのテスト成功後にcap production deployを実行
  • リリースのタイミングでmasterブランチからreleaseブランチ宛てのPRを作成し、それをマージすることでリリースを行う

設定方法

# circle.yml
deployment:
  production:
    branch: release
    commands:
      - bundle install
      - bundle exec cap production deploy

  staging:
    branch: master
    commands:
      - bundle install
      - bundle exec cap staging deploy
# Gemfile
source "https://rubygems.org"

gem "capistrano"
gem "capistrano-symfony"

これで、master/releaseブランチの変更が自動でステージング/プロダクション環境にデプロイされるようになります。

SlackでCircleCIの通知を受け取るように設定していれば、以下のようにデプロイの完了を知ることができます。

image

Step 4. リリースPR作成のQA最適化

masterブランチに関するプロセスはこれで完成ですが、リリース時にmasterからreleaseブランチ宛てのPR(「リリースPR」と呼んでいます)を作成する手順については改良の余地があります。

と言うのも、前回のリリース以降にmasterブランチにマージされたPRがそこそこたくさんある場合、リリースPRの内容をコミット単位で把握してQAチェックを行うのは至難の業になってきます。

そこで便利なツールがgit-pr-releaseです。

git-pr-releaseコマンドを実行すると、このキャプチャ画像のような、QAチェックに最適化されたリリースPRを作成してくれるという代物です。

リリースPRの作成をローカルで実行する分にはこれでよいのですが、今回はさらにSlackからリリースPRの作成が行えるようにしたいと思います。 その場合はgit-pr-releaseは使う必要はありませんので、インストールや設定の方法などはここでは割愛します。

Step 5. リリースPR作成のChatOps化

git-pr-releaseを使えばローカルからPRリリースを作成できましたが、今回はHubotを使ってSlackからリリースPRの作成が行えるようにしたいと思います。

この場合は、git-pr-releaseよりもgithub-pr-releaseを使ったほうが便利です。 GitHub API経由でリリースPRの作成を行うNodeモジュールとして実装されているので、Hubotスクリプトからも簡単に使えます。

というわけで、こちらを使わせていただき、hubot-github-pr-releaseというHubotスクリプトを作りました。

インストール

npmでインストールして

$ cd /path/to/hubot
$ npm install --save hubot-github-pr-release

external-scripts.jsonに追加します。

 [
+  "hubot-github-pr-release",
   "hubot-help",
   "hubot-redis-brain",
   :
   :
 ]

hubot-github-pr-releaseの実行にはGitHubのアクセストークンが必要なので、 https://github.com/settings/tokens/new でアクセストークンを発行して、以下のように環境変数HUBOT_RELEASE_GITHUB_TOKENにセットします。

$ export HUBOT_RELEASE_GITHUB_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

リリースPRを作ってみる

hubot release <owner>/<repo> という書式で命令できます。(環境変数で<owner>のデフォルト値をセットしておくこともできます)

image

image

あとは各PRの内容をチェックして、GitHub上でマージボタンをポチッと押すだけでリリースできます :+1:

まとめ

リリースプロセスを最適化することで、事故の防止になったり、安心して開発に集中できたり、色々と良いことがあります。 今あまり上手くやれていないという人にこの記事が少しでも参考になれば幸いです :smiley:

ちなみに

現状のリリースプロセスにも、例えば

  • masterにマージされているPRのうち一部だけをリリースしたいケースがある
  • Capistranoではデプロイ先サーバでgit cloneしてビルド(assetic/gulp/grunt)を実行することになるので、デプロイ時にサーバの負荷が高くなってしまう

などの問題がありそうです。このあたり、どなたかご教授いただけると嬉しいです :sweat_smile: