NICOA

Gitのブランチ戦略を検討する際のポイント集

Gitのブランチ戦略を検討する際のポイント集
どんな記事?

  • Gitのブランチ戦略の考え方のポイントをご紹介
  • 書籍やネットの記事、個人の開発経験をもとに、4つのポイントをまとめています。

自分がブランチ戦略を考える際に、どのように考えているかをまとめたメモです(随時更新)。
それぞれのポイントは独立しています。

アプリケーションコード と IaCコード の両方を自分が管理しているケースを想定しています。
目次
スポンサーリンク

1️⃣ CI/CDと一緒に考える

多くの場合、GitのPush/MergeやPull requestを契機にCICDをトリガーすることになるため、両方を一緒に考えます。

2️⃣ CIとCDをまとめるか、別にするかを考える

まずはじめに、CIとCDを同時に実行するか、分離するかを考えます。

あらためて、それぞれの違いを整理すると、

  • CI(継続的インテグレーション)
    • 目的: コードベースへのバグ/低品質なコードの混入を防ぐ
    • なにをする: 頻繁にコードをテスト&ビルドし、問題を早期に明らかにする
    • 具体例: mainブランチへの取り込み時に、lint/fmtチェックやtestを実行し、NGとなる場合は取り込みを失敗させる
  • CD(継続的デリバリー)
    • 目的: CIによって品質が担保された高品質なコードを、すばやく本番環境で使えるようにする
    • なにをする: 本番環境に継続的に変更をデプロイする
    • 具体例: 特定ブランチの変更時に、変更を即本番環境に適用させる

です。これらを一度に適用するか、別々に適用するかを考えます。

例えば、一度に適用する場合の例は、IaCコードのデプロイ時に、あわせて各IaCコード組み込みのビルド機能を使い、ビルド&デプロイを同時にするパターンが該当します。このパターンは、一度CI/CDを起動するだけでビルド・デプロイが完了するため、スピード感がよいです。一方でIaC組み込みの機能を使う場合は、デプロイのたびに新規のビルド成果物が生成されるため、確実に同じビルド結果を複数の環境(開発/検証/本番)で使いたい場合や過去の特定のバージョンを指定してデプロイしたい場合は、少し工夫をする必要があります。

逆に、分割適用する場合の例は、アプリコードのpushを契機にビルドまで実施しどこかに保管。IaCコード側でデプロイしたいバージョンのビルド成果物を別途指定して、任意のタイミングでデプロイするパターンが該当します。メリデメは↑の一度に適用するパターンの裏返しです。一度ビルド用のCIを動かして完了を待ち、できた成果物のバージョンをIaCコードに指定してCDを動かす2段階が必要になるため、スピード感は落ちます。一方で、完全に独立して生成されるビルド結果のバージョンを自由に設定できるため、各環境になにをデプロイするかのコントロール性能は高いです。

私の場合、かんたんなちょっとした自分向けシステムの場合は、スピーディに開発を行える 一度に適用を採用し、本格的なシステム開発の場合は、分割適用を採用することが多いです。

もちろん、工夫次第でよい点を両立することも可能です。
私の場合は、

  • 開発環境: 一度に適用
  • 検証環境: 分割適用
  • 本番環境: 分割適用

のようにして、開発スピードを上げつつ、本番とそれに近い環境ではバージョンコントロールを行いやすくする手法を好んで使います。

3️⃣ コンフリクトの解消方法を考える

特に複数人で開発を行う場合、個人ごとの作業がぶつかるコンフリクトの解消方法も考慮しておかないと、開発体験が悪くなります。

コンフリクトの避け方の具体的なプラクティスは多々ありますが、大別すると以下の2種類になると考えています。

  1. 開発途中はコンフリクトを気にせず開発できるようにし、最後に一気に解消する
  2. そもそもコンフリクトが起きづらくなるように開発を進める

1. 開発途中はコンフリクトを気にせず開発できるようにし、最後に一気に解消する

個々人が別々に独立して開発ができる環境を用意し、まずはそこで開発。最後にコンフリクトをまとめて解消するフェーズを設け、一気に解消する、という考え方です。代表的なところだと、featureブランチを機能ごとに切って開発を進めるパターンが該当します。

この手法の場合、開発中のある瞬間までは他者影響を考えずに開発を進められる一方で、独立した環境の保持期間が長ければ長いほどコンフリクトの量が増えていき、最後の一気に解消するフェーズがつらくなりがちです。例えば、数十人が共同で開発しているコードベースで、数ヶ月単位で保持されたfeatureブランチとmainブランチを統合するのは極めて困難です。featureブランチを切る手法を取り入れる場合は、ブランチの生存期間が極力短くなるようにするとよいでしょう(個人的には1日以下がよいと考えてます)。

2. そもそもコンフリクトが起きづらくなるように開発を進める

コンフリクトは同じ箇所を複数人で開発するために発生するので、複数人が同じ箇所を修正する状況をなくせばよい、という考え方です。代表的なところだと、モブプロやペアプロを取り入れてWIP数を減らしたり、並列で開発を行うにしても完全に独立した機能ブロックだけにするなどのパターンが該当します。

この手法は根本原因を取り除くため非常に有益ですが、対応しきれないケースが出てくることもあります。例えば、チームとして優先したいタスクが常に独立した機能ブロックに分散するとは限りません。ある機能の実装に総力を尽くしたくなる、というのはよくあるケースではないでしょうか。

よいところを組み合わせる

ブランチ戦略を検討する際は、1,2の手法を組み合わせて、自分たちが受け入れられるベストなパターンを見出すのがよいかと思います。例えば、

  • 普段は全員モブプロで開発をしてWIP数を下げる(=mainブランチのみで運用)
  • モブを分割して並列開発する かつ 同様の機能ブロックを扱う場合は、独立で開発できる環境を作る(=featureブランチを切る)
  • ただし、コンフリクトの発生確率を極力下げるため、featureブランチは極短期(数時間〜長くて1日)でmainにマージする

などです。

開発手法やチームの状況によっては、そもそもfeatureブランチは不要だったりもします。featureブランチを切る目的はなにか、を答えられない場合はなしでの運用を考えてみるのもいいでしょう。

4️⃣ 環境の状態を特定する方法を考える

開発/検証/本番環境に、なにがデプロイされているのかを特定する方法を考えます。

特定する対象は、

  1. ビルドされたアプリコード
  2. インフラ構成

の2種です。

ここでは各環境へのデプロイは、CD基盤でIaCコードを実行して行われている前提とします。IaCでデプロイを行う場合、どの1.ビルドされたアプリコードを、どのような2.インフラ構成でデプロイするかがIaCのコード内に記述されています。例えば以下のようなイメージです。

# 全体が 2.インフラ構成 に相当
resource "some_compute" "hoge" {
    name = "some-compute"
    code = "${ここにビルドされたアプリコードのID}" # 1.ビルドされたアプリコード に相当

    param1 = ...
    param2 = ...
    param3 = ...
}

2.インフラ構成については、IaCコードそのものとなるため、各環境に向けて実行したデプロイ(=CD)がどのコミットIDに紐づくかを記録しておけば一意に特定できます。具体的に紐付けを行う選択肢は後述します。

工夫が必要なのは1.ビルドされたアプリコードで、Gitにコミットされた特定のコードと一意に紐づくIDを自身で採番して割り振る必要があります。おすすめは、アプリコードのコミットIDをそのままIDとして流用することです。コミットIDは確実に一意なので、取り違えが起きるリスクがありません。他の方法としては、人間が理解しやすいように連番のバージョン番号をv1のように振るというのもあります。この場合は、紐づくアプリコードに対して、gitのtagをつける等が必要です。

IaCコードと環境の紐付け方法

大きく

  1. Gitのブランチを使う (環境一致ブランチを作る)
  2. Gitのtagを使う (ブランチは単一で、デプロイのたびにtagをつける)

が考えられます。

1.Gitのブランチを使う (環境一致ブランチを作る)

例えば env/devenv/prod のような、環境に対応するブランチを作成し、そのブランチ上に存在するIaCコードをそのまま該当環境にデプロイする方式です。ブランチ名からデプロイ対象の環境がわかりやすく、直感性が高いのが特徴です。

私がこの手法をとる場合は、env/*のブランチは独自の追加コミットは禁止し、必ずmainブランチからのPull Requestで変更する運用とすることが多いです。このようにすることで、env/*ごとに個別の差異が発生するリスクを避けることができます。また、Pull Request時にIaC実行前のドライラン(terraform plancdk diff)をするようにすると、ドライラン結果を確認してからデプロイを実行する流れを作りやすくなります。

2.Gitのtagを使う (ブランチは単一で、デプロイのたびにtagをつける)

IaCコードは常に mainブランチなど単一のブランチで管理し、各環境に対するデプロイ時にその時点のコミットに dev-YYYYMMDDprod-v2.3.4 といったタグを打って運用する方式です。IaCの開発は1つのブランチで行い、安定したタイミングでtagを打つことで環境ごとの状態を記録します。

この方法のメリットは、IaCコードの開発が必ず単一ブランチで行われるため、環境ごとの個別差異が発生するリスクを低減できることです。

私の場合は、基本的に1の環境一致ブランチを作る戦略を取ることが多いです。

徐々に改善するマインドが大切

チーム規模や文化、使っているツールによってベストプラクティスは異なります。最初から完璧を目指すのではなく、「少しずつ改善できる戦略」を目指すのがベストです!
本記事の内容は随時加筆していきます。