2021年8月12日

Buildkiteを利用して拡張性の高いセルフホスト型のCI環境を構築する

CIInfrastructureBuildkite

cover

はじめまして、Infrastrucure Division エンジニアの増田です。

突然ですが、Continuous Integration、してますか?

今回はLeapMindのCI環境と、そこで利用しているBuildkiteについて紹介します。

はじめに

LeapMindでは、エッジデバイス上でディープラーニングなどを低消費電力で高速に実行することを可能にするための、EfficieraというFPGAデバイス上もしくはASIC/ASSPデバイス上の回路として動作するCNNの推論演算処理に特化した超低消費電力AIアクセラレータIPを開発しています。また、そのIPに最適化された学習済みモデルや、モデルの学習、コンパイルを行うためのソフトウェアも開発しています。

efficera

この開発には、IP開発のためのシミュレータや実エッジデバイス、モデル開発やSDK開発のためのGPU計算環境、顧客向けProjectのための開発環境など、多種多様な環境やワークフローが求められます。

このような多様な開発の品質を担保するために、LeapMindでは2年ほど前から、社内のCIにBuildkiteというサービスをメインで利用しています。

CI for Efficiera

今回は、Efficieraという製品で実施しているCIを例にして、LeapMindで具体的にどのようなCI環境が必要とされているかについて紹介します。

Efficieraでは大きく下記の2つのテストが必要になります。

  • 合成したIPを配置したデバイス上でのランタイムテスト
    • IP上で計算が正しく行われていることを確認する
    • サポートする全てのデバイスおよびIPの組み合わせでランタイムが期待通り動作することを確認する
  • 合成したIPを配置したデバイス上で、提供する学習済みモデルおよび再学習したモデルを利用した推論速度および精度の評価
    • 提供する全てのモデルおよデバイス、IPの組み合わせで推論速度、精度が期待する基準を満たしていることを確認する

これらのテストを実現するためには、IPの合成、モデルの学習、モデルのコンパイル、デバイスのセットアップ、デバイス上での推論を順次実施していく必要があります。この依存を整理すると下記のようなパイプラインとなります。

efficera-ci-pipeline

パイプライン上の個々のStepはIPのコンフィギュレーションごと、デバイスごと、モデルごとに別のStepとして定義されます。つまり、後半のStepの[精度評価 & 速度評価 on Device]についてはそれぞれの組み合わせ(IPの種類 x デバイスの種類 x モデルの種類)で評価を実施する必要があります。さらには、別リリースバージョンとの互換性担保のためのテストも必要なため、より多くのテストケースが必要になります。

加えて、それぞれのstepの実行環境には下記のような制約があります。

  • 合成用ソフトウェアの制約
    • ライセンスの関係でOn-premises環境でしか利用できないものがある
    • FPGA毎に対応する合成ソフトウェアが異なり、複数の環境要件に対応する必要がある
    • 合成は時間がかかるが、高速化することが難しい
    • 同時実行数がライセンスで制限されているものがあり、並列化に限界がある、CIの並行実行のためのみにライセンスを多数購入するのはコスト的に厳しい
  • 物理デバイスの制約
    • 多数のデバイス実機を利用する必要がある
    • サポートするデバイスの種類がどんどん増加していく
    • デバイスの状態(FPGAのコンフィギュレーションやデバイスツリー、ブートローダー、OSなど)を都度、コンパイル済みのモデルが対応するバージョンに合わせなければいけない
    • デバイス実機での精度評価は時間がかかり、並列化のためには、実機の購入や物理的なセットアップ等が必要なため、拡張するための運用コストが高い
  • 学習環境の制約
    • 高速化にはGPUを必要とするが、コストが高い
    • 並列化には分散学習の導入などが必要で難易度が高い

このように、EfficieraのCIを実現するためには、複数の制約の中で、図に示したような計算コストが非常に大きいCIパイプラインを、できる限り高速かつ低コストで行える環境を構築し、運用していく必要があります。

LeapMindでは、これらを解決するために、下記のような工夫を行っています。

  • 多様な環境条件下でのテストの実現および運用の簡素化
    • Buildkiteによるセルフホスト型CI環境の導入
    • CI環境のコードベース管理
    • デバイスの動的なコンフィギュレーション
  • テスト時間の高速化
    • BazelやBuildkite pluginsを利用したTestのキャッシュ
    • GKEとBuildkiteによるCI実行環境の動的スケーリング
    • Buildkite Dynamic PipelineによるTestの分割、並列化

今回は上記の取り組みの一部を担っているBuildkiteについて紹介します。

(Bazelについてはまた別の機会に紹介すると思うのでお楽しみに。)

Buildkite

Buildkiteはユーザーの環境上でCIパイプラインを実行することに特化したサービスで、一般的なCIで言うセルフホスト型オンリーのCIサービスです。Buildkiteのサービス自体は、Build/Testのための実行環境を提供しません。代わりに、パイプライン管理や、ステータス管理、ログの収集、Github等他サービスとの連携などのCIに必要な共通の機能を提供します。ユーザーは、自身の環境にBuildkiteのAgentを構築することで、自分達に適したCI/CDの実行環境を整備することができます。

さて、ここからはBuildkiteの特徴をLeapMindで利用している具体例を上げながら説明していこうと思います。

様々な環境でCIを実行できる

Buildkiteはユーザーで実行環境を構築することが前提のサービスなので、各種OS(Linux, MacOS, Windows)やDocker, Kubernetes, AWSやGCPなどの各種プラットフォーム向けのInstall方法が整備されており、簡単に環境を構築することができます。他のCIサービスではセルフホストは高度な設定やOption扱いにされていることが多いので、この点はBuildkiteの優れている点と言えます。 (参考: Running Buildkite Agent on Google Cloud Platform)

LeapMindでは、IP合成のためのソフトウェアライセンスの都合や、エッジデバイス上でのテストの必要性などで、Cloudを利用できないワークフローがあり、それらはOn-premises環境上で実行しています。この制約がセルフホスト型のCIを導入している一番の理由です。そういった制約がないものについては、スケーラビリティやポータビリティの観点からGKE(Goocle Kubernetes Engine)上に構築しています(Dockerコマンドが必要なTestなどは一部GCEを利用)。したがって現在は下記のような環境上で多数のAgentが稼働しています。

  • GKE
  • GCE instance
  • On-premises Server
  • On-premises エッジデバイス

これらそれぞれのAgentには用途や環境情報などのTagを複数設定することができ、パイプラインのStep毎に実行したいAgentのTagをに設定することで、実行環境を制御することができます。この、シンプルかつ柔軟なTagを用いた実行環境の制御はBuildkiteの魅力の一つであると言えます。AgentのTagの定義は重要な設計ポイントとなるので、利用する際は実行環境に適した設計をすることをお勧めします。

各Agentにはそれぞれ固有の環境変数を持たせることも可能です。環境変数利用の1例として、LeapMindでは、Agent化できないエッジデバイスに対してテストを行う際に、別のNode上のAgentの環境変数にFPGA_HOST=(hostname of remote device)を設定し、テストスクリプト内で${FPGA_HOST}宛のNW経由でそのデバイスにアクセスすることで、特定のエッジデバイスとAgentを1対1で紐付かせるようにしています。

拡張性が高い

LeapMindではAgentの設定をAnsibleやK8s manifestなどで構成管理しているため、Agent数やAgentのリソースを容易にユーザー側で変更することが可能です。セルフホスト型でないCIサービスと比べた時に、このユーザー側で完結する拡張性の高さは大きな強みとなります。

動的スケーリング

ホスティング型のCIサービスでは、実行可能な並列度は料金プラン等で決定されるため、ユーザーは環境のスケーリングについて考える必要がありません。一方、セルフホスト型のCIでは、要件に応じたAgent環境のスケーリングやコストの最適化を検討しなければなりません。

Buildkiteでは、少しの工夫で動的スケーリング可能な環境を構築できるようになっています。Cloud上でAgentの動的スケーリングが可能になると、短命な複数のAgentを起動して、大量の並列化されたテストを短時間かつ最小限のコストで実行できるようになります。公式サイトにはAWS上での動的スケーリング可能な環境の構築方法が記載されていますので、興味のある方は確認してみてください。(参考: Elastic CI Stack for AWS)

LeapMindでもBuildkite Agent Job Queuesと言う機能を利用して、GKE上で一部のAgentの動的スケーリングを行なっています。仕組みとしては、特定のTag(queue=xxxx)のついたStepがいくつあるかをBuildkiteのAPIを叩いてMonitorし、その数に応じて、特定のAgentをKubernetesのJobとしてCreateするbuildkite-agent-scalerと呼ばれる自前のAppがGKE上にdeployされていて、随時、必要なAgentが立ち上がるようになっています。Agentは1回Buildを実行したら終了するOptionで起動するようにしているため、Buildが終わり次第、自動でスケールインされます。Podが利用するGKEのNode poolをAuto scaling可能にしてあるので、実質的に無限(?)のリソースを利用してCIを行えることになります。動的スケーリングはスケールアウト時の起動時間のオーバーヘッドが比較的大きいのですが、Build時間の大きい特定のワークロードに対してはとても有効に働いています。

Dynamic pipelines

次に、最も特徴的と言える、Dynamic pipelinesと呼ばれる動的なパイプライン設定について紹介します。Buildkiteで複数のテストケースを並列実行する際は、下記の2つの方法があります。

  • parallelismと言うパラメータで1つのStepを複数のAgentで並列実行する
  • Stepを複数定義する

前者の場合はAgent毎の環境変数の差分などによりテストケースを変更して並列テストを可能にしますが、テスト対象が環境変数によって制御されると実行されているテスト内容が暗黙的になってしまうため、LeapMindでは後者の、明示的にStepを分割すると言う方法で並列実行を実現しています。 ここで問題となるのが、例えば複数の学習済みモデルを複数の異なるデバイス上でテストしようとした際に、同じようなStepを大量に記述する必要になってしまうことです。その課題を解決するのがDynamic pipelinesになります。

Dynamic pipelinesは.buildkite/pipeline.sh | buildkite-agent pipeline uploadのようなコマンドをパイプラインの途中のStepに定義することで、生成したパイプライン定義を実行中のパイプラインに動的に追加することができる機能です。つまり、テストケースの数だけ分割した複数のStepを動的に生成し、実行することが可能となります。この機能によって簡単に複数のテストケースの並列化が可能となり、ScalableなAgent環境と組み合わせることで、CIの高速化を実現しています。

柔軟なパイプライン設定

Buildkiteではユーザーがパイプラインの内容をBuildkite上で管理するかあるいはRepository内で管理するかを選択することができます。Repository内で管理する場合、Buildkiteにはbuiildkite-pipeline uploadというパイプラインのStepの中でパイプラインを更新する機能があり、これによって、Repository内の任意のyaml設定ファイル(デフォルトは.buildkite/pipeline.yml)をパイプライン定義として利用することが可能になっています。(参考: Customizing the pipeline upload path)

例えば、Publicなリポジトリにおいて、公開したくないパイプラインがある場合などに、Repository内ではなくBuildkiteのWebUI上(もしくは完全に別のPrivateなリポジトリのYAMLファイル)でパイプラインを定義することで、外部からは変更できないパイプラインを作ることが可能です。このような特殊なユースケースにおいても柔軟に対応できるのが特徴となります。

ちなみに、WebUIでの設定になると設定の管理が問題になりますが、これらBuildkite側に保存されるパイプラインの設定についてはTerraformを利用することでコードベースでの管理が可能です。

LeapMindでは原則としてパイプライン毎に.buildkite/[pipeline_name].yamlを配置し、パイプライン側ではbuildkite-pipeline upload .buildkite/[pipeline_name].yamlすることをルールとしており、パイプラインで実行される内容をRepository管理しています。これにそぐわない特殊な要件がある場合(一時的なテストなど)のみ例外を許可するようにしています。

また、上述のように、1つのRepositoryに対して複数のパイプラインを定義することが可能です。これはmonorepo構成などの、一つのリポジトリに複数のプロダクトやサービスがある場合に、それぞれに対して個別のパイプラインを定義できるため、管理上とても便利です。LeapMindでは10以上のパイプラインがあり、一つのパイプラインで多いもので~100 Step程度あるため、これらをユーザーの好きな単位で別々のyamlで管理することができると言う点も利点の一つであると考えています。

この他にも、パイプラインの中から別のパイプラインをトリガーすることが可能だったり、ユーザーからのInputを待つこともできたりなど、幅広いユースケースに対応することができるようになっています。(参考: Pipelines)

豊富なプラグイン

BuildkiteにはBuidkite Pluginsと呼ばれるhookを利用してcommand stepを拡張する機能があります。PluginsはGit repositoryで管理されOSSとして提供されているものが多数あります。

これらは用途に応じて自分たちで定義することも可能であり、LeapMindでも特定のビルドアーティファクトに対するCache用のプラグインを作成して利用しています。このようなユーザー主体の拡張機能を備えているのもBuildkiteの特徴の一つです。

運用上注意するところ

ここではBuildkiteを運用する上で注意する必要がある点について記載します。

LeapMindではGitHubを利用していますが、例えば、PRのLabelやCommentでBuildをトリガーしたいなどの要求についてはBuildkiteのみでは実現することができません。やはり、GitHub連携においてはGitHub Actionsの方が有利です。LeapMindではGitHubと密な連携が必要な自動化については、独自のGitHub Apps(Bot)を使って実装しています。

パイプライン設定が柔軟なため、動的パイプラインやプラグインなどが増えると、一見してどういったStepが実行されるかが分かりにくくなってしまいます。CI設定が分かりにくくなると、開発者自身によるテスト内容のメンテナンスがされにくくなり、開発のアジリティが低くなってしまいます。こういった拡張機能については、必要な範囲のみに限定し、パイプラインとYAMLの紐付けにルールを設定したり、開発側とCI環境についての共有を密に行うことで、必要以上に複雑にならないように運用しています。

AgentやStepを簡単に追加できるため、AgentのTagの設計を誤ると、予期せぬ環境でBuildが走ったり、無駄なAgent待ちが発生することが起き得ます。例えばTagなしのStepが定義されてしまい、エッジデバイスを利用しないのにエッジデバイス用のAgentで実行されてしまい、無駄なテスト待ちが発生してしまったりします。個々のAgentのTagの設計とStepの定義については、新規Step/Agentの追加や更新時に想定外の動きが起きないようにきちんと管理する必要があります。

費用について、特に既存の別のCIサービスからの移行を考えている場合は、BuildkiteのSaaS費用に加えて、Agentを動かすためのリソースの費用を合わせた比較をする必要があります。この、Agentのための環境コストについては設計次第でかなり抑えることが可能ですが、運用にかかるコストなども含めて、単純なSaaS費用だけでは比較できないため、導入検討の際は注意が必要です。

LeapMindではAgentのコストを抑えるため、一部Preemptibleインスタンス上でAgentを実行しています。この場合Test中にPreemptionが発生しAgentがkillされる可能性があるのですが、Automatic Retry機能を利用することで、Test内容に無関係な環境依存によるFailの場合は自動でRetryするように設定しています。このような工夫を行うことで、CI環境のランニングコストを大幅に削減することが可能になります。

まとめ

今回はLeapMindのCI環境と、そこで利用しているBuildkiteについて紹介しました。

今回紹介した拡張性の高さは諸刃の剣です。動的拡張や並列化などの恩恵を享受しながらも、可能な限りシンプルに設計、運用していくことがBuildkiteを利用する上で最も重要です。Buildkiteについての日本語の記事はほとんど見当たらないので、もしCIサービスを検討している誰かの参考になれば幸いです。

さて、LeapMindでは、CI/CDでいい感じに品質と生産性を向上させることに興味がある方もそうでない方も、様々な職種で現在採用活動を実施しております。特に機械学習、深層学習が得意なエンジニアを強く募集しておりますので、興味がある方はぜひ一度LeapMindの採用ページから応募してみてください。いきなり採用面接は怖い、という人はカジュアル面談から始めることもできるとのことですので、お気軽にお問い合わせください!