継続的インテグレーション
継続的インテグレーション (continuous integration, CI) システムとは、ソフトウェアのビルドとテストを定期的に自動で行うシステムのことです。CI システムの一番のメリットはビルドとテストの間にある長い時間を無くせることですが、それだけではなく面倒なタスクを単純化および自動化できるというメリットもあります。例えばクロスプラットフォームのテスト、低速なテスト、大量のデータが必要なテスト、構成が難しいテスト、レガシープラットフォームにおける正確なパフォーマンスの検証、稀にしか落ちないテストの検出、最新のリリースの定期的な生成などがそうです。さらに継続的インテグレーションを実装する上でビルドとテストの自動化が不可欠なことから、CI は継続的デプロイフレームワークに向けた最初の一歩となります。ここで継続的デプロイ (continuous deployment) とは、テストが済んだソフトウェアのアップデートを素早く本番システムにデプロイする仕組みのことです。
アジャイルというソフトウェア開発手法が継続的インテグレーションを使っていることもあり、CI はタイムリーな話題です。最近になってオープンソースの CI ツールは爆発的に増えており、様々な言語で書かれた、様々な言語向けの、膨大な機能を様々なアーキテクチャモデルの下で実装した CI ツールが存在します。この章では継続的インテグレーションシステムにおいてよく実装されている機能を説明し、利用可能なアーキテクチャの選択肢を概観し、各アーキテクチャにおいて実装が簡単になる、あるいは難しくなる機能について議論します。
これから、CI システムの設計において可能なアーキテクチャの例を分かりやすく示すシステムをいくつか簡単に説明します。最初の Buildbot はマスター/スレーブモデル、二つ目の CDash はレポートサーバーモデル、三つ目の Jenkins はハイブリッドモデル、四つ目の Pony-Build は Python ベースの非中央集権型レポートサーバーモデルです。本章後半では最後の Pony-Build を軸としてさらに議論します。
概観
継続的インテグレーションシステムのアーキテクチャは両極端にある二つのアーキテクチャによって支配されているように思えます。つまり中央サーバーがリモートビルドの指示と制御を行うマスター/スレーブアーキテクチャと、中央サーバーがクライアントからのビルドレポートの集約を行うレポートアーキテクチャです。私たちが見たことのある継続的インテグレーションシステムはどれもこの二つのアーキテクチャが組み合わさったものを選択しています。
中央集権型アーキテクチャの例は BuildBot であり、このシステムは二つの部分からなっています。一つはビルドのスケジュールと調整を行う中央サーバー、またの名を buildmaster であり、もう一つはビルドを実行するクライアント、またの名を buildslave です。buildmaster はクライアントの接続場所を提供すると共に、クライアントが実行するコマンドとその順番についての設定情報を保持します。buildslave は buildmaster に接続し、詳細な指示を受けます。buildslave の設定で行える処理にはソフトウェアのインストール、マスターサーバーの識別、マスターサーバーに接続するための接続証明書の取得などがあります。buildslave の出力は buildmaster に送られ、ウェブやその他のレポートと通知行うシステムを通じた閲覧のために保存されます。
CI システムのアーキテクチャのもう一端にあるのが CDash が採用したアーキテクチャです。CDash は Kitware, Inc. による Visualization Toolkit (VRK) プロジェクトおよび Insight Toolkit (ITK) プロジェクトで使われています。CDash は本質的にはレポートサーバーであり、CMake と CTest を実行するクライアントから受け取った情報を保存し、それを見やすく提示するように設計されています。クライアントはビルドを行い、テストスイートを実行し、ビルドとテストの結果を記録し、CDash サーバーに接続してその情報を送信します。
最後に、三つ目のシステム Jenkins (2011 年に名前が変更されるまでは Hudson として知られていました) は両方の動作モードをサポートします。Jenkins ではビルドをノードで独立に実行させてからその結果をマスターサーバー遅らせることもできますし、マスターサーバーがビルドの実行のスケジュールおよび調整を行うこともできます。
中央集権モデルと非中央集権モデルの両方に存在する機能もあり、Jenkins のように両方のモデルが単一の実装に共存することも可能です。しかし、Buildbot と CDash は正反対のシステムです。ソフトウェアをビルドしてそれを報告するという同じことを行っているにもかかわらず、基本的なアーキテクチャの全てが異なっているのです。どうしてでしょうか?
疑問は尽きません。異なるアーキテクチャを選択したことで、様々な機能の実装がどの程度簡単に、あるいは難しくなるのでしょうか?中央集権モデルだからこそ自然に生まれた機能というはあるのでしょうか?既存の実装はどの程度拡張可能なのでしょうか?新しいレポートメカニズムを提供したり、多くのパッケージに対してスケールしたり、クラウド環境においてビルドとテストを実行するようシステムを簡単に変更するできるでしょうか?
CI システムは何をするのか
継続的インテグレーションシステムのコアとなる機能は単純です。ソフトウェアをビルドし、テストを実行し、その結果をレポートするだけです。ビルド、テスト、レポートはスクリプトで実行でき、そのスクリプトはスケジュールされたタスクあるいは cron
のジョブとして実行できます。つまり VCS からソースコードを新しくチェックアウトし、ビルドを行い、テストを実行するという単純なスクリプトです。ビルドの出力をファイルに記録したり、あらかじめ決めておいた場所に保存したり、ビルドが失敗した場合には E メールで知らせるようにもできます。以上の処理の実装は簡単であり、例えば UNIX では Python パッケージに対してこの処理を行う CI システムを 7 行のスクリプトで実装できます:
cd /tmp && \
svn checkout http://some.project.url && \
cd project_directory && \
python setup.py build && \
python setup.py test || \
echo build failed | sendmail notification@project.domain
cd /tmp && rm -fr project_directory
図 9.1 において、白い四角が独立したサブシステムおよびその機能を表し、矢印はコンポーネント間の情報の流れを示します。雲がビルドプロセスの中でリモートで実行できる部分を表し、灰色の四角がサブシステムの間で切り離せない可能性のある部分を示します。例えばビルドのモニタリングはビルドプロセス自体の状態やシステムの状態 (CPU 使用率、I/O 使用率、メモリ使用量) の監視を含む可能性があります。
しかし一見した単純さは当てになりません。現実世界の CI システムは通常もっとたくさんのことを行っています。リモートのビルドプロセスを初期化してその結果を受け取る以外に、継続的インテグレーションシステムがサポートできる機能は次のようなものがあります:
-
チェックアウトと更新: 大規模なプロジェクトではソースコードの新しいコピーを毎回チェックアウトしていると帯域と時間を使いすぎるので、多くの CI システムは既存の作業コピーをその場で更新できます。これにより前回の更新からの差分だけを取得するだけで済むというわけです。帯域と時間が節約できる代わりに、システムは作業コピーの状態を管理、更新する方法を知っていなければなりません。そのため VCS との最低限の統合が必要となります。
-
抽象的なビルド指示書: 考えているソフトウェアを構成、ビルド、テストするための指示書を書く必要がありますが、そのためのコマンドはオペレーティングシステムの間で、つまり Mac OS X と Windows と UNIX の間で異なります。オペレーティングシステムごとに指示書を書くこともできますが、バグが入り込む可能性があり、関心が実際のビルド環境から切り離されてしまいます。そのため何らかのレベルの抽象化を使った指示書が CI の設定システムによって提供されることが望ましいです。
-
チェックアウト/ビルド/テスト状態の保存 チェックアウト、ビルド、テストについてその詳細を保存できた方が望ましいでしょう。つまりチェックアウトについては更新されたファイルやコードのバージョン、ビルドについては警告やエラー、テストについてはコードのカバレッジやパフォーマンスおよびメモリ使用量といった情報です。これらの情報を保存しておけば後から解析を行うことができるほか、ビルドアーキテクチャに関する質問 (「最後のチェックインはアーキテクチャのどこかに重大な影響を及ぼしたか?」など) や履歴に関する質問 (「先月コードカバレッジは大きく変動したか?」など) に答えられるようになります。ビルド指示書と同様、この種のイントロスぺクションの仕組みやそこで使われるデータタイプはプラットフォームおよびビルドシステムごとに固有のものになります。
-
パッケージのリリース: ビルドによって生成されるバイナリパッケージなどのプロダクトを、外部からも利用可能にできる設定が必要です。例えばビルドマシンへの直接のアクセスを持たない開発者が特定のアーキテクチャにおける最新ビルドをテストする必要が生じるかもしれません。このような状況に対応するには、ビルド生成物を中央レポジトリに送る機能が CI システムに必要です。
-
複数のアーキテクチャでのビルド: 継続的インテグレーションの目標の一つは複数のアーキテクチャでクロスプラットフォームの機能をテストすることなので、CI ソフトウェアはビルドマシンのアーキテクチャを認識し、ビルドとその出力をクライアントと正しく結び付ける必要があります。
-
リソース管理: あるビルドステップがマシンのリソースの多くを使ってしまうようなら、CI システムはそのステップを条件的に実行できることが望ましいでしょう。例えばそのビルドの実行を他のビルドが無くなるまで保留したり、CPU やメモリの使用量が一定値まで下がってから始めるような処理が必要です。
-
外部リソースの調整: 統合テストがステージングデータベースなどのローカルにないリソースやリモートのウェブサービスを必要とすることがあります。そのため CI システムは複数のマシンの間のビルドを調整し、リソースへのアクセスを統制する必要があります。
-
進捗レポート: 長いビルドシステムではビルドの定期的なレポートが重要となります。5 時間のビルドとテストにおいてユーザーが興味があるのが最初の 30 分の結果だけであるような場合には、結果を見るために実行の終了を待たなくてはいけないのは時間の無駄です。
CI システムの高レベルな外観を 図 9.1 に示します。CI ソフトウェアはここに挙げた要素の一部を実装しています。
外部との対話
継続的インテグレーションシステムと他のシステムとの間で対話が必要になることもあります。考えられる対話内容をいくつか挙げます:
-
ビルドの通知: ビルドの結果を興味あるクライアントに知らせる必要があります。通知にはプル型 (Web, RSS, RPC など) とプッシュ型 (E メール, PubSubHubbub など) の両方が使えるでしょう。内容の例としては、ビルド完了の通知、ビルド失敗の通知、一定の期間実行されていないビルドの通知などがあります。
-
ビルドの情報: ビルドの詳細とその生成物は RPC や一括ダウンロードシステムを使って取得できる必要があります。例えば外部の解析システムを使ってより詳細で的を絞った解析を行ったり、コードカバレッジやパフォーマンスについてのレポートを作成するときにビルドの情報が必要です。加えて外部にテスト結果レポジトリを用意すれば、失敗および成功したテストを CI システムと切り離して管理できます。
-
ビルドリクエスト: ユーザーやコードリポジトリからのビルドリクエストを処理できると便利です。多くの VCS ではコミット後のフックを設定でき、これを利用すれば例えばビルドを開始する RPC コールを行うことができます。あるいはユーザーがウェブインターフェースなどを通した RPC を通して手動でビルドリクエストを送ることも可能なはずです。
-
CI システムのリモートコントロール: より一般的にはランタイム全体が、ある程度きちんと定まった RPC インターフェースを通して変更可能なことが望ましいです。特定のプラットフォームのビルドを特別なパッチが当たったソースブランチで行ったり、条件付きのビルドを行ったりするには、アドホックな拡張またはよりフォーマルなインターフェースが必要になるでしょう。こういった仕組みは CI テストを全て通過した後でのみコミットを受け付けるといったより一般的なワークフローシステムを作ったり、最終的な統合の前に様々な種類のパッチをテストしたりするためにも使うことができます。バグトラッカー、パッチシステム、およびその他の外部システムの種類は多岐にわたるので、CI システム自身にこのロジックを組み込むのは現実的でない場合もあります。
アーキテクチャ
Buildbot と CDash は正反対のアーキテクチャを選択しており、実装されている機能には重なる部分もありますが全く異なる部分もあります。以下では二つのシステムが持つ機能を説明し、それぞれの機能の実装が選択されたアーキテクチャによってどれくらい簡単になっているか、あるいはどれくらい難しくなっているのかを議論します。
実装モデル: Buildbot
Buildbot は単一の中央サーバーと複数のビルドスレーブからなるマスター/スレーブモデルのアーキテクチャを使っており、全てのリモートにおけるビルドの実行はマスターサーバーによってリアルタイムに制御されます。つまりマスターの設定が各リモートシステムで実行するコマンドを指定し、リモートはそれを順番通りに実行します。またマスターはスケジュールとビルドリクエストを調整するだけではなく、その指示も全て行います。ビルトインの指示書のための抽象化はほぼ存在せず、唯一あるのはバージョン管理システムの基本的な機能 (“コードはこのリポジトリにある”を指定する程度)、そしてビルドディレクトリに対して実行されるコマンドとディレクトリ内部で実行されるコマンドの区別だけです。OS 固有のコマンドは設定の中で直接指定されることが多いです。
buildslave で実行するジョブの管理および連携を行うために、Buildbot は全ての buildslave との間の接続を常に保持します。リモートマシンとの間で常時接続を管理するせいで実装が非常に複雑になっており、長い間バグの温床となってきました。長期間に渡ってネットワーク接続を途切れることなく保持するのは単純なことではなく、ローカルの GUI とやり取りを行うアプリケーションをネットワーク越しにテストするのは難易度が高いためです。中でも OS のアラートウィンドウが特に厄介です。しかし常時接続のおかげでマスターはジョブを実行するスレーブを完全に支配下に置けるので、リソースの調整とスケジュールは単純明快になります。
このように Buildbot モデルではスレーブを厳格に制御できるので、ビルド時に行う中央集権型のリソース調整がとても簡単です。Buildbot は buildmaster から利用可能なマスターとスレーブそれぞれに対するロックを実装しており、システム全体およびマシンごとに共有されるリソースを調整しながらビルドを行うことができます。この機能があるので、Buildbot が向いているのは統合テスト (データベースなどのアクセスに時間のかかるリソースを使うテスト) を行う大規模なソフトウェアであると言えます。
しかし中央集権型の設定は分散型のモデルで使うと問題が生じます。例えば全ての buildslave はマスターの設定を受け入れる設定を明示的にしなればならないので、新しい buildslave を動的に中央サーバーに追加してビルドを行ったり結果を送信するのは不可能です。さらに buildslave は完全に buildmaster によって制御されなければならず、クライアントは悪意ある設定や誤って生じた危険な設定に対して脆弱になります。マスターは OS のセキュリティ制限が許す限りあらゆることをクライアントに指示できます。
Buildbot の機能的な制限として、ビルドの生成物を中央サーバーに送り返させる単純な方法が無いというのがあります。例えばコードカバレッジの統計やバイナリビルドはリモートの buildslave に保存されるだけであり、それらを中央の buildmaster に集めて集計、保存するための API は存在しません。なぜこの機能が無いのかはよく分かりませんが、おそらく Buildbot が使っているコマンドがリモートでコマンドの実行させることだけに集中しているためだと思われます。あるいは buildmaster と buildslave の間の接続を RPC メカニズムではなく制御システムとして使うという決断の結果こうなったのかもしれません。
マスター/スレーブモデルおよびこの制限された通信チャンネルの結果として生じた欠点がもう一つあります。それは buildslave がシステムの利用率を報告しないために、リモートが高負荷になっていたとしても buildmaster が気づくことができない点です。
ビルド結果を外部へ CPU から通知するときには buildmaster が全ての処理を行い、通知サービスは buildmaster 自身の中で実装されています。同様に、新しいビルドリクエストを行うには buildmaster と直接通信する必要があります。
実装モデル: CDash
Buildbot とは対照的に、CDash はサーバーモデルを実装します。このモデルでは CDash が中央レポジトリとなり、リモートで実行されるビルドの情報を収集します。この情報に含まれるのは失敗したビルドやテスト、コードカバレッジの解析、メモリ使用量といった情報です。リモートクライアントはビルドを自身のスケジュールに沿って実行し、その結果を XML フォーマットで提出します。ビルドを提出できるのは“正式な”ビルドクライアントだけではなく、コア開発者でない開発者や公開されたビルドプロセスを自身のマシンで実行したユーザーも提出が可能です。
この単純なモデルが可能なのは、Kitware が開発する他のビルドインフラとの密な連携があるためです。つまりビルド構成システム CMake、テストランナー CTest、そしてパッケージシステム CPack です。これらのソフトウェアを使うと、ビルド、テスト、パッケージの各段階を OS に依存しない形で高レベルに実装できます。
CDash は処理をクライアントドリブンに行うことで、クライアント側の CI プロセスの様々な部分を単純化しています。例えばビルドを実行するという判断はクライアントによって行われるので、クライアント側の条件 (日時、負荷など) を見てからビルドを開始することが可能です。またクライアントは好きに仕事を始めたり止めたりできるので、ボランティアによるビルドや“クラウド上での”ビルドが簡単に行えます。さらにビルドの生成物を中央サーバーに送る処理は通常のアップロードメカニズムを使って簡単に行えます。
しかし、このレポートを軸としたモデルと引き換えに、CDash は Buildbot が持つ便利を機能がいくつも失っています。まずリソースを中央から調整するのは不可能であり、信用できない不安定なクライアントが参加する分散環境では実装も困難です。また進捗レポートも実装されておらず、これを行うには中央サーバーがビルド状態のインクリメンタルな更新をサポートする必要があります。さらに、これは当然なのですが、ビルドをクライアント全体にリクエストする方法はありませんし、匿名のクライアントがチェックインのたびにビルドを実行するという確証もありません。クライアントを信頼することはできないのです。
CDash は最近になって“@Home”というクラウドビルドシステムを有効化する機能を追加しました。これを使うとクライアントが CDash サーバーに対してビルドサービスを行うことができます。つまりクライアントがサーバーにビルドリクエストを poll し、リクエストがあればそれを実行し、その結果をサーバーに送り返すのです。現在 (2010 年 10 月) の実装ではビルドはサーバー側から手動でリクエストしなければならず、その時点で接続しているクライアントしかサービスを行うことができません。しかしこの機能を自然に拡張すれば、ビルドのリクエストがサーバーから利用可能なクライアントに自動的に送られるという、さらに一般的なスケジュールビルドモデルを構築することが可能なはずです。この“@Home”システムは後述する Pony-Build システムのコンセプトと非常に似ています。
実装モデル: Jenkins
Jenkins は広く使われている継続的インテグレーションシステムであり、Java で書かれています。2011 年初頭までは Hudson という名前で知られていました。ローカルのシステムで実行を行うスタンドアローンの CI システムとなることもできますし、リモートビルドを管理する CI システムにもなれます。さらにリモートで実行されたビルド情報の受け取り役となることも可能です。様々なテストツールからのレポートを統合するにあたって、Jenkins はユニットテストとコードカバレッジに関する JUnit の XML 規格を利用しています。Jenkins は Sun が起源ですが、利用者は多岐にわたり、活発なオープンソースコミュニティもあります。
Jenkins はハイブリッドモードで動作します。つまりデフォルトではマスターサーバーがビルドを行いますが、リモートビルドを行うための方法もいくつかあります。例えばサーバーの指示によるビルドとクライアントが自発的に始めるビルドは両方サポートされます。ただし本来の設計は Buildbot と同じく中央サーバーを使って制御を行うというものであり、仮想マシンの管理などの様々な分散ジョブの開始メカニズムは後から追加されたものです。
Jenkins ではマスターが複数のリモートマシンを管理することが可能であり、そのときには master からの SSH 接続またはクライアントからの JNLP (Java Web Start) が使われます。接続は両方向であり、オブジェクトやデータをシリアルトランスポートで送り合うことができます。
この接続の詳細は Jenkins の堅牢なプラグインアーキテクチャによって隠蔽されます。これによってバイナリビルドやより重要な結果データを送り返すためのサードパーティのプラグインが開発可能になっています。
また Jenkins には中央サーバーが制御するジョブが並列に実行されないようにする“locks”というプラグインが あります (2011 年 1 月 現在開発が続いています)。
実装モデル: Pony-Build
Pony-Build は proof-of-concept な非中央集権型 CI システムであり、Python で書かれています。Pony-Build を構成する三つの要素を 図 9.4 に示します。リザルトサーバーは中央データベースであり、クライアントから送られてくるビルド結果をまとめます。クライアントは独立して構成情報とビルドコンテキストを持ち、VCS レポジトリへのアクセスやサーバーとの通信のための軽量なクライアントライブラリを持ちます。レポートサーバーはビルド結果の報告および新しいビルドのリクエストのためのシンプルなウェブインターフェースを持ち、省略可能です。レポートサーバーとリザルトサーバーは単一のマルチスレッドプロセスとして実行されるように実装されていますが、API レベルでの結びつきは弱いので独立に実行するのは簡単です。
以上の基本的なモデルに加えて、通知やビルドイントロスぺクションを行うための webhook や RPC の仕組みがあります。例えばコードレポジトリからの VCS 通知はビルドシステムに直接結びついているのではなく、リモートのビルドリクエストはまずレポートシステムに伝えられ、その後レポートシステムがリザルトサーバーとやり取りをするようになっています。同様に E メールやインスタントメッセージなどのサービスを使うビルドのプッシュ通知はレポートサーバーと直接結びついてはおらず、そういった通知は PubSubHubbub (PuSH) というアクティブ通知プロトコルを使って制御されます。これによって様々なアプリケーションが興味あるイベントについての通知を PuSH の webhook を通して受け取ることが可能になります (ただし現在新しいビルドの完了と失敗したビルドの通知しか実装されていません)。
要素同士を切り離すこのモデルには、様々な利点があります:
-
通信が簡単: 基礎となるアーキテクチャ要素と webhook プロトコルは実装が非常に容易であり、ウェブプログラミングの基本的な知識さえあれば実装できます。
-
変更が簡単: 新しい通知メソッド、あるいは新たなレポートサーバーインターフェースの実装は非常に単純です。
-
多言語対応: 要素が互いを呼び出すときに使う webhook はたいていのプログラミング言語でサポートされているので、各要素を異なる言語で実装することが可能です。
-
テストが簡単: 各要素を完全に切り離してモックすることが可能であり、システムを簡単にテストできます。
-
設定が簡単: クライアントで必要となるものは最小限であり、Python 以外に必要となるライブラリは一つだけです。
-
サーバー負荷が最小限: 中央サーバーはクライアントに対して実質何もしないので、クライアントはサーバーとのやり取りを一切せずに独立して並列に動作できます。したがってレポート以外でサーバーに負荷がかかることはありません。
-
VCS の統合: ビルドの設定は全てクライアントにあるので、設定を VCS に含めることも可能です。
-
結果へのアクセスが簡単: ビルドの結果を利用するアプリケーションは XML-RPC リクエストをサポートする任意の言語を使うことができます。Pony-Build システムのユーザーにビルド結果とレポートサーバーへのアクセスをネットワークレベルで与えることもできますし、レポートサーバーにおけるカスタマイズされたインターフェースを使って情報を与えることも可能です。またビルドクライアントに必要なのはリクエストサーバーへ結果を送るアクセスだけです。
残念ながら、Pony-Build のモデルには深刻な欠点もいくつかあります。どれも CDash のモデルと同様のものです:
-
ビルドのリクエストが困難: ビルドクライアントがリザルトサーバーから完全に切り離されているために、ビルドリクエストの処理が困難となります。クライアントにビルドリクエストがあるかどうかをリザルトサーバーに poll させることは可能ですが、そうすると負荷とレイテンシが高まります。そうでなければコマンドと制御のための接続を作成してサーバーからクライアントに直接ビルド通知を通知することになりますが、こうするとシステムが複雑になり、ビルドクライアントを独立させたことによる利点が失われてしまいます。
-
リソースロックのサポートが弱い: リソースロックの取得と解放を行う RPC メカニズムを提供するのは簡単ですが、それよりもはるかに難しいのがクライアントのポリシーを強制することです。CDash のような CI システムはクライアントの誠実さを仮定していますが、クライアントは意図せずに厄介な形で (つまり、ロックを解放せずに) 実行を停止することがあります。堅牢な分散ロックシステムを実装するのは難易度の高い作業であり、望ましくない複雑性を多く追加してしまいます。例えば信頼できないクライアントがある状況では、マスターのロックコントローラーはロックを取得したクライアントがクラッシュしたかデッドロックになったかの理由でロックをいつまでも解放しないという状況に対するポリシーを持たなければなりません。
-
リアルタイムモニタリングのサポートが弱い: ビルドのリアルタイムのモニタリングと制御は常時接続の無いシステムでは実装が困難です。Buildbot がクライアント中心のモデルに対して持つ大きな利点の一つが、ビルドの結果がマスターのインターフェースにインクリメンタルに集まってくるために、長いビルド処理の途中でイントロスぺクションを簡単に行える点です。さらに Buildbot は接続を保持するので、設定ミスや誤ったチェックインによってビルドが途中で失敗した場合にはビルドを停止、終了することが可能です。Pony-Build ではリザルトサーバーがクライアントと対話ができるという保証がないので、このような機能を追加するには、クライアントからの定期的な poll あるいはクライアントへの常時接続を追加しなければなりません。
Pony-Build によって提起された CI の側面がもう二つあります。一つはビルド指示書の実装方法、もう一つは信用の管理方法です。指示書はクライアントで任意のコードを実行できることから、この二つの問題は絡み合っています。
ビルド指示書
ビルド指示書はビルドコマンドを使いやすいように抽象化します。これはクロスプラットフォームの言語を使って複数のプラットフォームに対してビルドを行うシステムにおいて特に便利な機能です。例えば CDash は厳格な指示書を利用しています。つまり CDash を使うほとんど全てのソフトウェアは CMake、CTest、CPack を使ってビルドされ、これらのツールがマルチプラットフォームに関する問題に対処します。全ての問題を他のビルドツールチェインに任せることができるので、継続的インテグレーションシステムから見ればこれは理想的な状況と言えます。
しかし、他の言語と他のビルド環境においては状況が異なります。例えば Python のエコシステムにおいては distutils と distutils2 によるソフトウェアのビルドとパッケージの標準化が進んでいますが、テストを見つけて実行し、さらにその結果を集めるための標準はまだありません。さらに Python の複雑なパッケージは distutils の拡張メカニズムを使って特別なビルドロジックを追加するのですが、これを使うと任意のコードが実行できてしまいます。そしてこれこそが多くのビルドツールチェインで見られる問題です。つまり、実行するコマンドの標準のようなものがあるにはあるのですが、例外とか拡張などと呼ばれるものが必ず付いているのです。
そのためビルド、テスト、パッケージのための指示書は厄介な問題となります。指示書には解決すべき問題が二つあるからです。一つはプラットフォームに依存しない形でビルドコマンドを指定し、単一の指示書を使って複数のシステムでソフトウェアをビルドできるようにすること、そしてもう一つはビルドしているソフトウェアに応じてビルドをカスタマイズすることです。
信用
しかしこれによって三番目の問題が現れます。CI システムの指示書を様々な用途で使うとなると、そのシステムが使うセカンドパーティのソフトウェアを信用しなければならなりません。それもそのソフトウェアに信用が必要なだけではなく、指示書にも信用が必要になります。両方とも CI クライアントで任意のコードを実行できなければならないからです。
こういった信用の問題は統率の取れた制御が可能な環境 (例えばビルドクライアントと CI システムが内部プロセスの一部となっているような企業) では簡単に対処できます。しかしその他の開発環境においても、サードパーティが (例えばオープンソースプロジェクトに) ビルドサービスを提供する場合があります。理想的な解決法は標準的なビルド指示書をソフトウェアに含めることをコミュニティレベルで決めてしまうことであり、Python コミュニティは distutils2 でこの方法を選択しています。もう一つの解決法はデジタル署名された指示書を利用し、信用された人物だけが署名済み指示書の設定と配布を行い、CI クライアントは実行する指示書が信用できるかどうかを確認するというものです。
モデルの選択
私たちの経験では、RPC もしくは webhook を使ったコールバックを基本とする疎結合なモデルを使った継続的インテグレーションシステムは非常に実装しやすいものでした (ただし複雑なやり取りが必要となる厳密なビルド同士の連携は要件から外しています)。リモートでのチェックアウトとビルドの基本的な実行は、ビルドを制御するのがローカルであれリモートであれ同じような設計の制約を受け、ビルドに関する情報 (成功/失敗など) を収集するのはクライアントの仕事であり、アーキテクチャ全体やビルド結果に関する追跡情報を集めるときにもクライアントからの情報が必要になります。そのためレポートモデルを使えば、基本的な CI システムをとても簡単に実装可能です。
疎結合のモデルが非常にフレキシブルで拡張性に優れることも判明しました。要素同士がきちんと分離していて独立性が高いために、結果のレポート、通知メカニズム、ビルド指示書といった新しい機能を追加するのは簡単でした。要素が切り離されることで他の要素に行わせるタスクが明確になるので、要素のテストと変更がしやすくなります。
CDash のような疎結合のモデルを使ったリモートビルドにおいて唯一困難となるのが、ビルド同士の連携です。つまりビルドの開始と停止、現在行われているビルドに関するレポート、異なるクライアント間のリソースロックの調整は他の実装に比べて技術的に難しくなります。
以上を持って疎結合のモデルがどんな場合でも“良い”のだと結論するのは簡単ですが、この主張が成り立つのはビルド同士の連携が必要ないときだけであることを忘れてはいけません。決断を下すときには、プロジェクトが CI システムを使うときに必要となるものをよく考えるべきです。
未来
Pony-Build について考える間に、未来の継続的インテグレーションシステムに期待される機能がいくつか見えてきました。
-
言語に依存しないビルド指示書: 現在の継続的インテグレーションシステムはどれも設定のための言語を独自に実装しています。これは車輪の再発明をしている状況であり、明らかに馬鹿げています。広く利用されているビルドシステムは一ダースもなく、テストランナーも数十程度のはずです。にもかかわらず、どの CI システムも異なる独自の方法でビルドとテストのコマンドを指定しているのです。というよりも、だからこそ同じような CI システムがいくつも存在するのかもしれません。つまりそれぞれの言語とコミュニティが、自分たちのビルドとテストシステムに合うように構成システムを独自に実装し、そのシステムの上に同じ機能を作っているのです。そのため、広く使われている数十個のビルドおよびテストツールチェインが持つ機能を表現できるドメイン固有言語 (DSL) を作ることができれば、CI の世界は大きく単純化されるでしょう。
-
ビルドとテストのレポートのための共通フォーマット: ビルトとテストを行うシステムが提供すべき情報、あるいはそのフォーマットについて決まっていることはほとんどありません。もし共通フォーマットや標準を制定できれば、継続的インテグレーションシステムは詳細で良くまとまったビルドの概要を今までよりもずっと簡単に提供できるはずです。良さそうな選択肢が二つあり、それは Perl コミュニティで使われている Test Anywhere Protocol (TAP) と Java コミュニティで使われている JUnix XML テスト出力フォーマットです。どちらのフォーマットも、テストの実行、成功、失敗の回数やファイルごとのコードカバレッジの詳細をエンコードできます。
-
レポートの粒度とイントロスぺクションの向上: あるいは、構成、コンパイル、テストシステムシステムに対するフックが詳細なドキュメントと共に異なるビルドプラットフォームそれぞれから提供されれば便利です。CI システムがビルドに関する詳細な情報を取り出すためのこの仕組みは、(共通フォーマットではなくて) API の形をとるでしょう。
最後に
これまでに紹介してきた継続的インテグレーションシステムは、アーキテクチャに合う機能を実装しています。またハイブリッドな Jenkins システムは最初マスター/スレーブモデルでしたが、疎結合のレポートアーキテクチャを後から追加しました。
以上の議論からアーキテクチャが機能を縛ると結論したくなりますが、もちろんそれは間違いです。アーキテクチャの選択が特定の種類の機能へと開発を導くようだ、と言うのが正しいでしょう。Pony-Build を通して私たちは、開発の最初に CDash スタイルのレポートアーキテクチャを選択したことが、後の設計と実装の判断に大きな影響を及ぼしたことに驚きました。いくつかの機能、例えば中央で管理される設定やスケジュールシステムが無いことは、Pony-Build の想定された使用例によって決定しました。私たちはリモートを動的に追加するという Buildbot で難しい機能を実装したかったのです。その他の機能、例えば進捗レポートや中央集権型のリソースロックは Pony-Build にあった方が良いのですが、実装されていません。これは機能が複雑なためにどうしても必要でない限り追加する気になれないためです。
同じような考えは Buildbot、CDash、Jenkins にも通用します。どのシステムにも、便利にもかかわらずおそらくはアーキテクチャが適していないという理由で実装されていない機能が存在します。ただし Buildbot と CDash のコミュニティメンバーとの会話および Jenkins のウェブサイトを通じて明らかになったのは、最初に実装するべき機能が選ばれ、その機能が実装しやすいアーキテクチャを使ってシステムが作られたということです。例えば CDash のコミュニティは比較的少数のコア開発者からなり、CDash を中央集権的なモデルを使って開発しました。彼らが一番に考えていたのはコアとなるマシンでソフトウェアを動作させ続けることであり、その次がソフトウェアに精通したユーザーからバグレポートを受け取ることでした。また Buildbot は多数のクライアントからの共有リソースへのアクセスが調整されなければならない複雑なビルド環境での使用例が増えています。Buildbot の持つ柔軟な設定ファイルフォーマットを使えばスケジュール、変更の通知、リソースのロックを細かく指定できるので、複雑な設定が必要な場合には他のシステムよりも Buildbot が好まれています。それから Jenkins は使いやすくて単純な継続的インテグレーションシステムを目指しているようで、設定のための完全な GUI やローカルサーバーで実行するためのオプションなどがあります。
オープンソース開発の社会学も、アーキテクチャと機能の間のもう一つの交絡因子です。開発者がオープンソースプロジェクトを選ぶときには、そのプロジェクトのアーキテクチャが自身の使い道に合うかどうかで選ぶはずです。だとすれば、彼らが行うコントリビューションはこれまでのアーキテクチャで上手く行っているプロジェクトでの使い心地を改善するものになります。コントリビューターは自ら志願してなるものであり、機能が十分でないアーキテクチャを持つプロジェクトは避けられる可能性があるので、プロジェクトは特定の機能を集中的に使うようになるでしょう。これは私たちが Buildbot にコントリビュートすることなく Pony-Build という新しいシステムを実装した理由でもあります。Buildbot のアーキテクチャは数百あるいは数千のパッケージのビルドには使えないのです。
現在の継続的インテグレーションシステムは一般的に言って二つの異なるアーキテクチャのどちらかを使って作られており、望ましい機能の一部分しか実装されていないことが普通です。CI システムが成熟してユーザー数が大きくなれば機能が追加されるのだろうと思うかもしれませんが、実装されていない機能はアーキテクチャによる制限によって実装できないという場合もあります。今後の発展が興味深い分野と言えるでしょう。
謝辞
一般的な CI システムおよび Pony-Build について興味深い議論をしてくれた、Greg Wilson、Brett Cannon、Eric Holscher、Jesse Noller、Victoria Laidler に感謝します。また Jack Carlson、Fatima Cherkaoui、Max Laite、Khushboo Shakya を含む何人かの学生は、Pony-Build の開発に参加してくれました。