
GitHub を使用した複数の環境への CI/CD を考慮したブランチ戦略
こんにちは。Build サービス推進チームで Solution Architect をしている t_maru です。
今回は GitHub を使って 1 プロダクト複数環境を運用していく場合のブランチ戦略の一例と、GitHub で運用していく場合に注意しなければならない点をご紹介します。
有名なブランチ戦略としては Git-flow、 GitHub Flow、最近ではトランクベース開発と呼ばれるものも出てきていますが、今回紹介するブランチ戦略は Git-flow をベースとしています。
ブランチ戦略の概要
Build サービスでは品質にもこだわりを持って開発を行っているので、ブランチ戦略も既存のものをそのまま取り入れるのではなくカスタマイズして使用することがあります。
今回紹介するブランチ戦略のベースは Git-flow なので、まず Git-flow の戦略を紹介した後、どのようにカスタマイズして使っているかを紹介します。
Git-flow は、Git を使ってソフトウェア開発を行っていくためのリポジトリの運用戦略の一つであり、Git-flow の他にも冒頭で紹介したように GitHub Flow、最近ではトランクベース開発と呼ばれるものもあります。
Git-flow には主に 5 つの種類のブランチが存在しており、それぞれの用途は下記の通りです。
main (昔は master と呼ばれていたブランチ)
正式なリリース履歴を保管しておくブランチ
develop
機能開発、リリース作業のベースとなるブランチ
feature
新規の機能開発をするブランチ
develop からブランチを切り、開発が終わったら develop に merge して消滅
release
リリース前の動作確認やバグ修正などを行うブランチ
develop からブランチを切り、作業が終わったら develop と main に merge して消滅
hotfix
本番リリースされているバージョンに発生しているバグで、すぐに修正が必要なものがある場合に使われる
main からブランチを切り、作業が終わったら main と develop に merge して消滅
これらのブランチのライフサイクルを図示したものが下記になります。

参考にした記事: https://nvie.com/posts/a-successful-git-branching-model/
Git-flow は運用実績もありますので、そのまま使っても問題はないのですが過去私が関わった案件ではカスタマイズをして運用していました。またなぜそのような運用をしていたのかも合わせて下記に記載します。
release ブランチを使用しない
機能が develop に merge される毎に動作確認と決められたテスト(基本的に自動)を実施しているのであえて release ブランチを切っての作業は必要ない
main, develop で commit log が同様になるように運用する
どこまでの機能が反映されているのかすぐに把握できるようにするため
本項の冒頭で書いたように、Build サービスでは品質にもこだわって継続開発を行うために、Quality Engineer (QE) と呼ばれるエンジニアがプロジェクト初期からメンバーとして参加しており、テスト戦略の立案、テスト項目の検討、自動テストの作成などを機能開発と並行して実施しています。このため、新しい機能が develop ブランチに merge されたタイミングで Git-flow の release ブランチで行われる作業と同等のことを実施できているので(プロダクトによって求められる品質や作業内容が異なるため、皆様が release ブランチで行っている全ての作業が含まれているとは限りません)、あえて release ブランチを切って作業することはしませんでした。
動作環境、CI/CD との関連
本項では、Git-flow をカスタマイズしたブランチ戦略が、どのような開発環境に対応しているのかを説明します。
動作環境はプロダクトの置かれた状況に応じて様々だと思いますが、今回は下記の 4 種類の環境があることを想定します。各環境の用途も合わせて記載します。
本番環境
商用環境が動作する環境
ステージング環境
商用環境と同一の設定と同一のソースコードで動作確認、テストを実施する環境
テスト環境
追加された機能を逐次テストするための環境
開発環境
機能開発中に利用する個別環境
これらの環境を各ブランチに対応させるとこのような形になりますが、開発は基本的に Local PC 上で行うことを想定しており、必要に応じて Cloud 環境へデプロイして確認という運用のため、下記のリストから開発環境は除外しています。
本番環境
main ブランチ
ステージング環境
main ブランチ
テスト環境
develop ブランチ
上記を見ると、本番環境とステージング環境の違いは何?と思われるかと思いますが、その疑問を解消するため下記に簡略化した CI/CD フローを掲載します。

上記のフローからわかるように、main ブランチへの変更をトリガーとして起動された CI/CD コンテナはまずユニットテストを実施した後、ステージング環境へのデプロイを実施します。デプロイされたステージング環境に対して自動でインテグレーションテストを実施し、これと合わせて必要に応じてデプロイされたステージング環境を使って手動テストを実施します。これらのテストは develop ブランチで既に実施されているテストで、本番環境と同等の環境でも動作することを念のため確認するために実施しているもので、ステージング環境でしか実施しない特別なテストではありません。
ステージング環境での各種確認が終わると `手動承認` のプロセスとなり、然るべき役割の方がリリースの承認をすると本番環境へのデプロイが実施されるといった流れになります。
GitHub 上で運用する際の注意点は?
これまで話してきたように、今回のブランチ戦略のベースは Git-flow ですので、開発中に一番多い作業であろう機能追加の手順は下記のようになります。
GitHub から develop 最新のコードを取得する
develop ブランチから feature ブランチを切る
開発を行い feature ブランチに commit する
開発が終了したら GitHub に feature ブランチを Push する (自動テストもこのタイミングで実施)
GitHub 上で Pull Request を作成しコードレビューを実施する
GitHub の Pull Request merge 機能を使って develop ブランチに merge
feature ブランチを delete する
ここまでの流れに関しては、レビューから merge までの作業を GitHub 上で行っても特に問題は生じませんが、
develop ブランチの内容を main に merge する
hotfix ブランチの内容を main, develop に merge する
このような処理をする際には GitHub の merge の特徴を把握した上で、作業を行う必要が出てきます。
GitHub の merge の挙動については、下記の公式ドキュメントを参照すると詳細が記載されています。
この中で注目すべき点は下記の点です。
The pull request is merged using the --no-ff option.
この一文からわかるように、GitHub の Pull Request を作成して merge 処理をすると、必ず merge commit が作成される仕様のため、例えば develop ブランチの Pull Request を GitHub 上で作成、merge 処理をすると main ブランチでは、develop ブランチに含まれていた追加機能の変更 commit に加えて merge コミットが作成されることになるため、develop ブランチと同じソースコードにはなりますが commit log が異なる状況となります。
本番環境でバグがなく機能追加だけをしている状況であれば、上記のように commit log が異なる状況でも develop -> main の merge に関しては問題が生じませんが、hotfix ブランチを使った場合に問題が生じることになります。
hotfix ブランチは本番環境で発生しているバグを修正する場合に使われるブランチで、main ブランチから派生します。先程説明したように develop -> main の merge の際に merge commit ができていると、hotfix を develop に merge する際に、ソースコード変更ゼロの commit log が develop に記録されることになるため、commit log の可視性が下がることになると思います。
これを回避するための方法の一つとして、develop -> main や hotfix ブランチの merge は local PC 上で実施するという方法があります。local PC にインストールした git のコマンドラインツールなどを使う場合、何もオプションを指定しなければ fast-forward で merge されるため、GitHub の Pull Request を merge することにより問題になっていた merge commit が生成されず、main と develop で同じ commit log を維持できるようになります。
この方法を取る場合にデメリットになりえる点としては、main や develop ブランチへの push を GitHub の設定で許容しておく必要があります。main や develop ブランチへの push は、デフォルト設定で GitHub リポジトリを使用している場合はそもそも制限されるアクションではありませんが、GitHub で設定のできるブランチプロテクションにより、指定ブランチへの push や merge の条件 (指定人数のレビューが必要など) を設定する機能を使うことができない点はデメリットになると思います。
まとめ
今回は私が過去に関わった案件で使っていたブランチ戦略について、Git-flow と CI/CD フローを使って解説し、GitHub 上でこの戦略で運用する場合に気をつけなければならない merge 時のワークアラウンドを説明しました。
前項の最後でも説明したように、今回のブランチ戦略を使う場合は GitHub のブランチプロテクション機能を有効に使えないというデメリットも存在しますので、開発時のブランチ戦略と CI/CD 戦略はチームメンバーの熟練度なども考慮した上で検討していただればと思います。