Five tips for CI-friendly Git repos

Sarah Goff-Dupont
Sarah Goff-Dupont
リストに戻る

If you follow Atlassian, you know we're big on continuous integration ("CI") and Git–separately, sure: but even bigger on the power that the two offer in combination. Today I want to share some tips for getting your CI system to interact optimally with your repository, which is where it all begins.

1: Avoid tracking large files in your repo

Git についてよく耳にする話の 1 つですが、バイナリファイルやメディアファイル、アーカイブされたアーティファクトなど、大規模なファイルをリポジトリに置くのは避けるべきだと言われています。いったんファイルを追加すると、そのファイルは常にリポジトリの履歴に含まれ、レポジトリが複製されるたびに、巨大な重いファイルが一緒にクローンされることになります。リポジトリの履歴からファイルを取り出すのは非常に注意が必要な作業で、いわばコードベース上でロボトミー手術をするようなものです。こうした外科手術的なファイル抽出によって、リポジトリの履歴すべてが変更され、どんな変更がいつ行われたか明確に把握することができなくなります。原則として容量の大きいファイルを避けるのはこうした理由からです。

しかし、CI を行っている場合は、容量の大きいファイルを避けることが特に重要になります。

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

Ok, fine. But what if your build depends on binaries from other projects or large artifacts? That’s a very common situation, and probably always will be. So the question is: how can we handle it effectively?

Artifactory(Bamboo 用のアドオンを作成) や Nexus、 Archiva のようなストレージ システムには、自分のチームや周囲のチームが生成するアーティファクトに対する支援機能があります。こうしたシステムでは、ビルド開始時に必要なファイルをビルド ディレクトリにプルできます。Maven や Gradle を通してサード-パーティのライブラリーをプルするようなものです。

Now you may be thinking “Oh, I’ll just sync my big files to the build server each night so I only have to transfer them across disk at build time.”

Even though a disc transfer is much faster than network transfer, I actually recommend against doing this, especially if the artifacts change frequently. 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.

2: Use shallow clones for CI

ビルドを実行するたびに、サーバーはリポジトリのクローンを作業用のカレント ディレクトリに作成します。前にも話しましたが、リポジトリをクローンする際に、Git は既定でリポジトリ全体の履歴をクローンします。そのため、この操作は長い間にますます時間がかかるようになります。ただし、CI システムがシャロー クローンを使用する場合は別です。

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

But let’s say your build requires the full repo history–if, for example, you have a release build that adds a tag or updates the version in your POM, or you’re merging two branches with each build.

Git の以前のバージョンでは、変更をプッシュするために、リポジトリの履歴全体を必要としていました。1.9 では、ファイルの簡単な変更は、履歴全体がなくてもプッシュが可能です。しかし、マージについては、Git が 2 つのブランチをさかのぼって、共通の祖先を見つける必要があるため、依然として履歴全体が必要です。ビルドでシャロー クローンを利用している場合、このことが問題になります。そこで 3 つめのヒントが登場します。

3: Cache the repo on build agents

This also makes the cloning operation much faster, and some CI servers actually do this by default.

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

Shallow clones plus repo caching, divided by persistent vs. elastic agents, equals an interesting web of factors. Here's a little matrix to help you get strategic about it.

Matrix

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

アクティブなブランチのすべてで CI を実行することがよいのは(ほぼ)言うまでもないことです。しかし、あらゆるコミットに対してすべてのブランチでビルドを実行するのはよい考えと言えるでしょうか。おそらくそうではないでしょう。その理由はこうです。

Let's take Atlassian, for example. We have upwards of 500 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.

当社の内部 Bamboo サーバーの1 つには、種類の異なるビルドプランが 935 本収納されています。このサーバーに 141 のビルド エージェントを接続し、アーティファクト パッシングやテストの並行実施といったベストプラクティスを使用して、各ビルドを可能な限り効率的に行いました。しかし、それでも各コミットのビルドは作業を停滞させ続けていました。

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

We found that a good way to balance testing rigor with resource conservation is to make builds on the dev branches push-button. This is where most of the change activity is happening, so it’s the biggest opportunity for savings. Developers find that it fits naturally into their workflow, and they like the extra control and flexibility this gives them.

For critical branches like master and stable release branches, builds are triggered automatically by polling the repo for changes. Since we use dev branches for all our work-in-progress, the only commits coming into master should (in theory) be dev branches getting merged in. Plus, these are the code lines we release from and make our dev branches from. So it’s really important that we get timely test results against every commit.

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

もう一つのオプションは、完全にポーリングをやめて、変更がプッシュされてビルドが必要になった時に、リポジトリが CI サーバーに合図するように設定する方法です。一般的には、これはリポジトリでフックすることによって実行できます。

これはどんなツールを使っても可能ですが、たまたま、当社では最近 Bitbucket Server と Bamboo の統合を追加し、こうした追加の設定が不要になりました。 Bamboo と Bitbucket Server がバックエンドでリンクされた後は、リポジトリ主導のビルドトリガーが細かい設定なしで即時に機能します。フックも特別な設定も必要ありません。

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.)

Rubber, meet road

You can implement every tip I've given here with any CI server on the market. But since we're always looking to make best practices easy to practice, we've baked them all into Bamboo so they're dead-simple to set up. Hop into the tour and check out all the goodness.

Git を学習する準備はできていますか?

この対話式チュートリアルを利用しましょう。

今すぐ始める