こんにちは、@ttskchです。先日、Symfony Meetup #5のLTでChatOpsについて発表させていただきました。
発表に使用したスライドはこちらにあります。
具体的には、PHP(特にSymfony2)プロジェクトの開発・リリースのプロセスについてのお話しです。
ここでは、その発表の内容をもう少し詳しく解説したいと思います。大きく5つのステップに分けて、一つずつ解説していきます。少し長いですがお付き合いください。
- Step 1. CIの導入
- Step 2. Capistranoの導入
- Step 3. デプロイの自動化
- Step 4. リリースPR作成のQA最適化
- Step 5. リリースPR作成のChatOps化
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形式)のディレクトリが作られ、そこにプロジェクトがビルドされています -
current
はrelease
配下の特定のリビジョン(通常は最新リビジョン)の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の通知を受け取るように設定していれば、以下のようにデプロイの完了を知ることができます。
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>
のデフォルト値をセットしておくこともできます)
あとは各PRの内容をチェックして、GitHub上でマージボタンをポチッと押すだけでリリースできます
まとめ
リリースプロセスを最適化することで、事故の防止になったり、安心して開発に集中できたり、色々と良いことがあります。 今あまり上手くやれていないという人にこの記事が少しでも参考になれば幸いです
ちなみに
現状のリリースプロセスにも、例えば
-
master
にマージされているPRのうち一部だけをリリースしたいケースがある - Capistranoではデプロイ先サーバで
git clone
してビルド(assetic/gulp/grunt)を実行することになるので、デプロイ時にサーバの負荷が高くなってしまう
などの問題がありそうです。このあたり、どなたかご教授いただけると嬉しいです