シンクロ・フード エンジニアブログ

飲食店ドットコムを運営する、株式会社シンクロ・フードの技術ブログです

シンクロ・フードのブランチ運用フロー

シンクロ・フードの五十嵐です。
今回は、私たちの開発フローについてお話したいと思います。
特にGitのブランチ運用については皆さん頭を悩ませる部分だと思いますので、弊社の紆余曲折した経緯も踏まえて公開します。
Gitへの移行を検討している方などの参考になれば嬉しいです。

リリース頻度

お話の前提として、弊社のリリース頻度を紹介します。
平日は、ほぼ毎日リリースをしています。一日数回リリースする日もあります。
月間で捌くチケット数(≒featureブランチ数)の平均は約170です。
それなりの規模になってきたため、開発フローの安定化や効率化は全体の生産性に直結すると考えており、現在も改良し続けています。

GitHub Enterprise の利用

バージョン管理はGitで行っており、GitHub Enterpriseを利用し、プルリクベースで開発を行っています。
全てのプルリクは必ずコードレビューを受けてからマージされます。レビュアーは基本はランダムに決めています。
また、毎日の朝会でレビューの状況を可視化しており、レビューが依頼されたまま放置されることを防いだり、レビューの負担が偏った場合に調整して分散できるようにしています。

Enterpriseにした理由としては、弊社はISMSを取得していて、情報セキュリティマネジメントの観点から、機密性を重視しました。
可用性という面でも、今はEnterpriseのほうが安定しているかもしれません。導入後1年以上が経ちますが、発生した障害はゼロです。

ブランチ運用

本題のブランチ運用についてです。
現在はGitHub Flowを参考にしつつも、独自にアレンジした運用をしています。

f:id:synchro-food:20160318183348p:plain

  • masterブランチは常にデプロイ可能、本番環境と同一の状態
  • developブランチはmaster+テスト中のブランチが取り込まれた状態
  • featureブランチはチケットごとにmasterブランチから作成される
  • featureブランチからdevelopにプルリクエストが作成され、コードレビューを受ける
  • コードレビューがOKならdevelopにマージされる

しかし、この状態に落ち着くまでには、紆余曲折がありました。

Git導入以前

f:id:synchro-food:20160318183611p:plain

多くの企業が同様だと思うのですが、弊社はGit導入以前はSubversion(以下SVN)を利用してバージョン管理を行っていました。
SVN時代は、release - trunk のブランチ構成で、開発者はtrunkにガンガンcommitしていました。
ステージング環境および本番環境にデプロイする際には、releaseにチェリーピックで反映し、releaseブランチをそのままデプロイしていました。

これはこれで数年に及ぶ運用実績のあるフローでしたが、下記のような問題が出てきて、2014年の7月頃から、真剣にGitへの移行を検討することになりました。

  1. 開発者が増えてきて開発環境での競合が増えてきたこと(チケットごとにブランチを分けたい)
  2. コードレビューの工数が増えてきたこと(プルリクを活用してレビューを楽にしたい)
  3. 中途採用の面接時などにバージョン管理ソフトを聞かれるようになったこと(採用戦略としてGit使ってますと言いたい)

Git導入に向けての検討

上記の課題解決のためにGit導入を決意しました。
SVN -> Git へのリポジトリ変換も当時すでに複数の事例や手順の紹介があり、大きな問題にはなりませんでした。
デプロイにはJenkinsを利用していましたが、こちらもGit用のプラグインが存在したため、問題ありません。

弊社が頭を悩ませたのはやはりブランチ運用でした。
まず、弊社の開発フローでは、環境が4つに分かれています。
本番環境、本番デプロイ前の検証を行うステージング環境、企画責任者(非エンジニア)が受け入れテストを行うためのテスト環境、そして各開発者の開発環境です。
SVN時代は本番環境とステージング環境がreleaseブランチ、テスト環境と開発環境がtrunkです。
これを、release -> masterブランチ、trunk -> developブランチにそれぞれ変更すれば、特に大きな変更点なく移行ができると考えていました。
masterブランチからチケットごとにfeatureブランチを作成し、developにマージし、受け入れテストがOKになればmasterにマージするというフローです。

しかし致命的なことに、SVN時代のmasterとtrunkの二大ブランチは根が全く異なっていました。つまり、親子関係がなかったのです。
実際にGit移行のリハーサルでマージを試みると大量の競合が発生してしまい、とても解決しきれない程でした。
これでは、featureブランチをmasterから切った場合にdevelopブランチへ自動マージするのが困難…というよりも無理だということが分かりました。

考えられる手段としては、移行コストと割り切ってmaster(旧release)とdevelop(旧trunk)のマージ作業をしてしまうことですが、弊社は当時8サイトの運営をしており、ライブラリなども含めて移行対象となるリポジトリは10を超えていました。
多数のリポジトリで正しく競合を解決してマージした上に、全機能の動作確認を行うとなると、非常に大きな工数がかかることが予想されます。しかも、その動作確認中にも新しい機能開発はどんどん進んでいきます。

そこで、なんとかmasterとdevelopが断絶したまま運用できないかを考えました(今思えば、この発想が間違いだと思われるのですが…)。
まず当初の想定通りにmasterからfeatureブランチを作成した場合、masterへのマージ(≒本番デプロイ)は簡単に行えますが、developへのマージ(≒受け入れテストの開始)が難しくなります。
また、コードレビューは受け入れテスト前、つまりdevelopブランチへのマージ時に行いたいです。当然のことながら、developに向けたプルリクエストの差分は、そのブランチに関係のある変更だけが出てこないと困ります。
以上のことから、featureブランチはdevelopブランチから作成することが妥当と考えられました。

次なる課題は、developブランチから作成されたfeatureブランチから、どのようにmasterブランチに反映するかというものです。
調査を進めると、Gitにもcherry-pickコマンドが存在することが分かりました。
cherry-pickコマンドを利用すれば、履歴の断絶しているmasterへの反映も行えそうです。実質SVN時代とほぼ差のない開発フローが作れそうです。実際に試してみたところ、上手く運用ができそうでした。

以上の経緯から、移行直後の開発フローは以下のように固まりました。

  1. developブランチからfeatureブランチを作成する
  2. featureブランチからdevelopブランチにプルリクエストを作成する
  3. コードレビュー後にプルリクエストをマージする
  4. 受け入れテスト後にmasterにfeatureブランチのコミットをcherry-pickする
  5. ステージング環境でテストする
  6. 本番環境にデプロイする

折衷案のようなブランチ運用ですが、Git移行の目的であった以下3点は満たすことができていました。

  1. チケットごとにブランチを分けたい
  2. プルリクを活用してレビューを楽にしたい
  3. 採用戦略としてGit使ってますと言いたい

cherry-pick運用の廃止

運用が2~3ヵ月過ぎたころ、次第に無視できないトラブルが発生するようになってきました。
それは、masterへのcherry-pick時の競合解決の失敗や、cherry-pickすべきコミットが多い場合に漏れが出てしまう、更にはdevelopには存在するがmasterには存在しないクラスへの依存に気付けない等のトラブルです。
上記のようなトラブルは、当然ながらエラーが発生したり、バグの温床になったりと、サイトに致命的なダメージを与えてしまいます。

こうしたトラブルには都度、解決策を考えて実施してきました。
例えば1番目の問題については競合解決用のブランチをmasterから作成してレビューを行うようにしたり、2番目の問題についてはdevelopへのマージコミットをcherry-pickすることで漏れを無くす、等の対応を行いました。

しかしながら上記対応は対症療法でしかなく、やはり根本的な問題になっているのは、featureブランチからmasterブランチに直接マージできないことです。
本来なら緊急時に使うべきであろうcherry-pickコマンドを日々の運用で利用してしまっているという状態の気持ち悪さもあり、masterブランチとdevelopブランチの履歴を統一する必要があるという判断に至りました。

そこで弊社は、現在のdevelopブランチを破棄し、新しくmasterブランチからnew-developブランチを作成することで履歴を統一することにしました。
Git移行時にこの判断を見送った経緯としては、masterとdevelopのマージ作業は現実的ではないことが理由でしたが、それはこの時も変わりませんでした。
しかしGit移行後の現在、開発中の内容は全て各featureブランチに存在しています。
移行前はmasterには存在しないがdevelopにのみ存在する開発中の内容を救う手段がマージ作業以外に思い浮かびませんでしたが、各featureブランチが存在する現在は、その内容を拾うのは、それこそcherry-pickコマンドで容易になっています。

そこで、新しくmasterブランチから作成されたnew-developブランチに、開発中のfeatureブランチの内容をcherry-pickで反映していくという作業を行って、新しいブランチ運用を開始しました。旧developブランチは直後に削除され、new-developブランチがdevelopブランチへとリネームされています。
その後、masterブランチへのfeatureブランチのマージもプルリクエスト上で行うことにより、マージ漏れや競合の解消ミスなどもレビューで気付けるようになりました。

現在の開発フロー

上記のような紆余曲折を経て、現在の開発フローに落ち着きました。

  1. 開発ブランチで各開発者がローカルで開発を行う
  2. ローカルでの開発が完了したらdevelopブランチへのプルリクエストを出し、レビュアーがレビューを行う
  3. レビューが完了したらdevelopブランチにマージされ、受け入れテストが行われる
  4. テストが完了したら開発ブランチがmasterブランチにマージされ、ステージング環境でテストが行われる
  5. 問題なければmasterブランチを本番環境へデプロイする
  6. masterブランチからdevelopブランチにマージが行われる(Jenkinsで自動化)

GitHub Flowとの差異としては、masterに取り込む前にdevelopブランチを挟んでいます。
すでに軽く触れていますが、企画責任者(非エンジニア)が受け入れテストを行うための環境です。
非エンジニアなので手元に開発ブランチを落としてきて動作確認するということができないため、テスト用の環境を用意しています。
また、開発ブランチ同士の競合などがdevelopブランチで検知できるという利点もあります。
そのほか、今回は詳しく触れませんが、developブランチが反映されるテスト環境では、Seleniumを用いたE2Eテストが定期実行されており、サイトの品質を担保してくれています。

また、最後のmasterブランチからdevelopブランチへのマージは、現在も正解なのかどうか自信がない部分です…。
取り入れた理由としては、developブランチへのプルリクエストで余計な差分が発生するのを防ぐためです。
masterブランチからdevelopブランチへのマージを行わないと、masterブランチには存在する本番反映のためにマージコミットが、developブランチには存在しないという理由で、関係ないファイルが差分に挙がってきてしまうという現象が発生していました。
Git Flowでもreleaseブランチからdevelopブランチにマージする手順があるので、大きくは間違っていないと思うのですが、どうでしょうか。

なお、releaseブランチを作成しない理由としては、弊社のリリース頻度が多いため、リリース作業の負荷を大きくしたくないためです。
この辺りは各種ツールやデプロイ方法の工夫で乗り切れる部分かもしれません。

最後に

以上、弊社のGitブランチ運用の経緯と現在の開発フローをご紹介しました。
色々なトラブルは発生しましたし紆余曲折もありましたが、現在はGit/GitHubの恩恵を受けながら、かなり快適に開発やコードレビューが行えるようになっています。

尚、シンクロ・フードではエンジニアを大募集しています!
こうした開発フロー改善以外にも色々なことをやっていますので、少しでもご興味があれば、気軽にお話しする場を設けますので、以下よりご連絡ください!

キャリア採用 | シンクロ・フード採用サイト