5 tips for CI-friendly Git repositories (and Git-friendly CI)

Set yourself up for success – it all starts with your repository.

Sarah Goff-Dupont Sarah Goff-Dupont

別の記事で述べたように、Git と継続的デリバリーはソフトウェアの世界で時折出会う、「チョコレートとピーナッツバター」のような組み合わせを生み出します。これは、美味しいもの同士を組み合わせると一層美味しくなるということです。そこで、Bamboo 内のビルドと Bitbucket リポジトリをうまく組み合わせるヒントを共有します。ほとんどの相互作用は継続的デリバリーのビルドまたはテストフェーズで発生するため、この記事では「CD (継続的デリバリー)」よりも「CI (継続的インテグレーション)」の見地から説明します。

1: Store large files outside your repo

Git についてよく耳にする話の 1 つですが、バイナリファイルやメディアファイル、アーカイブされたアーティファクトなど、大規模なファイルをリポジトリに置くのは避けるべきだと言われています。いったんファイルを追加すると、そのファイルは常にリポジトリの履歴に含まれ、レポジトリが複製されるたびに、巨大な重いファイルが一緒にクローンされることになります。

Getting a file out of the repo’s history is tricky – it’s the equivalent of performing a frontal lobotomy on your code base. And this surgical file extraction alters the whole history of the repo, so you no longer have a clear picture of what changes were made and when. All good reasons to avoid large files as a general rule. And...

Git リポジトリから大きなファイルを排除することは、CI にとって特に重要です。

ビルドするたびに、CI サーバーはリポジトリのクローンを作業ビルド ディレクトリに作成しなければなりません。リポジトリが山とある巨大なアーティファクトで膨れあがっている場合、その処理が遅くなり、開発者がビルド結果を待っていなければならない時間が長くなります。

それはいいとしましょう。しかし、もしビルドが他のプロジェクトや大規模なアーティファクトのバイナリに依存している場合はどうでしょうか? これは非常によくある状況であり、おそらくこれからも変わることはないと思われます。では質問です。どのようにすればこれを効果的に処理できるでしょうか。

An external storage system like Artifactory (who make an add-on for Bamboo), Nexus, or Archiva can help for artifacts that are generated by your team or the teams around you. The files you need can be pulled into the build directory at the beginning of your build – just like the 3rd-party libraries you pull in via Maven or Gradle.

 Pro tip: If the artifacts change frequently, avoid the temptation to sync your big files to the build server every night so you only have to transfer them across the disc at build time. In between your nightly syncs, you’ll end up building with stale versions of the artifacts. Plus, developers need these files for builds on their local workstations anyway. So overall, the cleanest thing to do is to just make artifact download part of the build.

If you don't already have an external storage system on your network, it's easiest to take advantage of Git large file support (LFS).

Git LFS はリポジトリの大きなファイルにファイルそのものではなくポインターを保存する拡張機能です。ファイル自体はリモートサーバーに保存されます。ご想像のとおり、これでクローン時間が大幅に短縮されます。

Git LSF

Chances are, you already have access to Git LFS – both Bitbucket and Github support it.

2: CI の shallow clone を使用する

ビルドを実行するたびに、サーバーはリポジトリのクローンを作業用のカレントディレクトリに作成します。以前述べたとおり、リポジトリのクローンを作成する際、Git は既定でリポジトリ全体の履歴のクローンを作成します。そのため、この操作は時が経つにつれますます時間がかかるようになります。ただし、Bamboo で shallow clone を有効にする場合は別です。

シャロー クローンを使えば、リポジトリの現在のスナップショットだけがプルされます。そのため、特に大規模な古いリポジトリで作業をする場合、この方法はビルドの時間を削減する非常に有効な手段となります。

Git リポジトリのスクリーンショット

しかし、ビルドが完全なリポジトリの履歴を必要とする場合はどうでしょう。たとえば、ビルド内の工程で POM (または類似の) のバージョン番号を更新する場合、または 2 つのブランチを各ビルドにマージする場合などです。どちらのケースでもリポジトリに変更を反映するには Bamboo が必要になります。

As of Git 1.9, simple changes to files (like updating a version number) can be pushed without the entire history present. But merging still requires the repo's history because Git needs to look back and find the common ancestor of the two branches – that’s going to be a problem if your build uses shallow cloning. Which leads me to tip #3.

3: ビルドエージェントでリポジトリをキャッシュする

This also makes the cloning operation much faster, and Bamboo actually does this by default.

リポジトリキャッシングが役に立つのは、複数のビルドを通じてエージェントを使用している場合のみです。EC2 や他のクラウドプロバイダーでビルドを実行するたびに、ビルドエージェントを作成し削除している場合は、リポジトリキャッシングに意味がなくなります。なぜなら、空のビルドディレクトリで作業していることになり、どのみち毎回リポジトリのフルコピーをプルする必要があるためです。

Shallow clone とリポジトリキャッシングを足して常住エージェントまたはエラスティックエージェントを割ると興味深い事実が分かります。以下の表は戦略を立てる際に役立ちます。

Persistent agents vs elastic agents screenshot

4: トリガーを上手に選ぶ

It goes (almost) without saying that running CI on all your active branches is a good idea. But is it a good idea to run all builds on all branches against all commits? Probably not. Here’s why.

Let’s take Atlassian, for example. We have upwards of 800 developers, each pushing changes to the repo several times a day – mostly pushes to their feature branches. That’s a lot of builds. And unless you scale your build agents instantly and infinitely, it means a lot of waiting in the queue.

One of our internal Bamboo servers houses 935 different build plans. We plugged 141 build agents into this server, and used best practices like artifact passing and test parallelization to make each build as efficient as possible. And still: building after each and every push was clogging up the works.

Bamboo インスタンスごとに 100 以上のエージェントを毎回ひたすら設定するかわりに、一歩下がってこの作業は本当に必要か考えてみました。答えはノーでした。

そこで、ブランチビルドを常に自動的にトリガーするのではなく、開発者にプッシュボタンを作成する選択肢を与えました。これは厳密なテストとリソースの保全のバランスを取るよい方法です。ほとんどの変更アクティビティはブランチで発生していることから、大きく節約できるチャンスがあります。

開発者の多くはプッシュボタンによる追加制御を好み、ワークフローに自然になじむと感じています。いつビルドを実行するか考えずにすむ自動トリガーを好む開発者もいます。どちらのアプローチも有効です。重要な点は、まずブランチをテストし、ビルドに問題がないことを確認してから上流にマージすることです。

Preferences

ただし、master や安定的にリリースされるブランチとなると話は別です。こちらにあるビルドは変更されたリポジトリをポーリングするか、Bitbucket から Bamboo へプッシュ通知を送信することによって自動的にトリガーされます。すべての仕掛り中の作業で開発ブランチを使用しているため、master に入るコミットは (理論的には) マージされる開発ブランチのみのはずです。さらに、これらはリリース元であり開発ブランチの作成元となるコードラインです。したがって、各マージに対してタイムリーなテスト結果を得ることが非常に重要です。

5: ポーリングを止めて、フックを始める

Polling your repo every few minutes looking for changes is a pretty cheap operation for Bamboo. But when you're scaling up to hundreds of builds against thousands of branches involving dozens of repos, it adds up fast. Instead of taxing Bamboo with all that polling, you can have Bitbucket call out when a change has been pushed and needs to be built.

Typically, this is done by adding a hook to your repository, but as it happens, the integration between Bitbucket and Bamboo does all the under-the-hood set-up for you. Once they're linked on the back end, repo-driven build triggers Just Work™ right out of the box. No hooks or special configs required.

Configure Bitbucket screenshot

Regardless of tooling, repo-driven triggers carry the advantage of automatically fading into the sunset when the target branch goes inactive. In other words, you’ll never waste your CI system’s CPU cycles polling hundreds of abandoned branches. Or waste your own time manually turning off branch builds. (Though it’s worth noting that Bamboo can easily be configured to ignore branches after X days of inactivity, if you still prefer polling.)

The key to using Git with CI is...

...simply being thoughtful. All the things that worked great when you were doing CI with a centralized VCS? Some of them will work less great with Git. So check your assumptions – that's the first step. For Atlassian customers, the second step is integrating Bamboo with Bitbucket. Check out our documentation for details, and happy building!