[レポート]Lambda並列実行でCI高速化 #reinvent #ZDC205

2021.01.31

re:Invent 2020のセッション「 No more idling: Creating parallel builds using serverless CI 」を聴講しましたのでレポートします。

セッション概要

Ever find yourself or your teams waiting for builds to start and finish? In this session, learn how to use the speed and parallelization of AWS Lambda so your development teams can move faster. This session reviews what the limits are and how you can set up fallbacks using AWS Fargate or AWS CodeBuild. You learn about AWS Lambda layers, Amazon EFS, and the best available AWS tools for super-fast builds.

あなたもしくはあなたのチームはこれまでにビルドの開始や完了で待たされたことはありませんか?このセッションでLambdaのスピードと並列化の使い方を学ぶことで、開発チームがより速く動けるようになります。制限とは何か、AWS FargateやAWS CodeBuildを使用してフォールバックを設定する方法をレビューします。超高速ビルドのためのLambda layers、EFS、その他利用できるものについても学べます。

  • Session ID: ZDC205
  • Session Level: Intermediate - 200
  • Speaker: Michael Hart, VP Research Engineering - Bustle Digital Group

レポート

Whats's the problem?

nomoreidling-1 これは私が好きな、xkcdのRandall Munroeさんの2007年のイラストです。多くの開発者が抱える問題を表していると思います。私は、これの2020年バージョンは以下のような感じになるのではと思っています。 nomoreidling-2 例えば、本番環境の修正(のビルド)で30分待ちたくないですし、ビルドを待ってる間他のことをしようとするのも、コンテキストスイッチが発生して集中するのが難しいので嫌です。

Do less

ですので、CIビルド内でやることを減らすことによって、ビルド時間を短縮することを試みます。

  • インストールのキャッシュ
  • ビルドアーティファクトのキャッシュ
  • 連続した(直列の)工程を避ける
    • 各工程を削除できないか、もしくは並列実行できないか検討する
  • 不要なツールをベースイメージやレイヤーに入れない
    • jqをAWS CLIが吐くJSONのパースのためにインストールしている場合を多々見かけるが、AWS CLIだけでそれは可能だったりする
  • 逆に必要なツールをベースイメージやレイヤーに入れて、インストール工程を省略する
  • より早いツールを使う
    • JavaScriptだと、ParcelやWebpackは遅い。esbuildやswcといったより早いニューカマーが登場している

Do more (in parallel)

スピードアップさせるための別の方法は並列実行を多くすることです。多くのCIシステムは並列ジョブをサポートしています。AWS CodeBuildではそれをbatch buildsと言います。ビルドを分割することができます。論理的にはいくつかの方法でビルドを分割することができますが、テストやリント、フォーマット、タイプチェック、サイズチェックなど、理想的にはこれらの作業をすべて並行して行うことができます。かなりの時間短縮になるでしょう。

テストに関しては、並列テスト実行をサポートしているテストランナーが多く存在しています。 私が好きなことの一つは、テストジョブをサブジョブのようなものに分割することです。 github-action-workflow-example これはGitHub Actionsワークフローの例ですが、CodeBuildもMatrix Buildsをサポートしています。ジョブをいくつかの異なるピースに分割しています。ここでは3つに分割して、各ジョブは3つ目のテストファイルのみをテストしています。

ci-build-time

これは弊社(Bustle社)のCIビルド時間の変遷を表しています。最初はいい感じだったのですが、TypeSciptとPostgreSQLを使い始めた頃からビルドが遅くなり始め、10分以上かかるようになり、高速化を模索し始めました。

  • NPM インストールキャッシュ
  • リンティングの工程を分割 - 型チェックとリンティングを並列実行するように

これで6分程度に削減できました。さらに

  • ビルドアーティファクトのキャッシュ
  • TypeSciptビルドのキャッシュ
  • できる限りジョブを分割

ジョブ分割は非常によかったのですが、ある地点で壁にぶち当たります。ほとんどのCIシステムは並列実行数に関して制限があります。それは通常、数十になります。

同様に、それら(CIシステム)は通常、分単位の課金です。それはつまり、もし6の並列実行ジョブがあったとして、それらはすべて1分以内に実行完了するものだとしても、あなたは6分間分の請求を受けることになるということです。私達のケースの場合、最初(分割前)は2分間分の課金だったのに、(ジョブを分割していくことによって)6分間分の課金になってしまいました。というわけでネクストステップを検討し始めました。AWS Lambdaをビルドに導入することです。

How serverless CI helped us

Lambdaは100ms単位の課金です。(※アップデートで1ms単位になりました!) デフォルトで1000の同時実行が可能です。また、数msで立ち上がります。

私達は10分かかっていたビルドを1分以下に短縮することができました。10倍の改善です。素晴らしい。 all-checks-have-passed 上記は我々のデベロッパーが今プルリクエスト時に目にする画面です。すべてのジョブがすぐに完了し、もし失敗した場合はすぐにフィードバックを得ることができます。

どうやってこれを実現したのでしょうか。私達はLambCIと呼ばれるシステムを使いました。私が4年半前に作ったサーバレスCIシステムです。 lambci 最近GitHub Actions統合を追加しました。LambCIはあなたに変わってリスニングを行ない、ビルドが発生したらEventBridgeのイベントを送信します。EventBridgeはこれらビルドイベントをフィルターし、Lambda、Fargate、CodeBuildなど任意の場所でビルドが実行されます。そしてビルドの結果はGitHubActionsに送り返されます。ビルドのスケールアップやスケールアウトが可能です。

Overcoming limitations

Lambdaにはいくつか制限があります。それを克服する方法をご紹介します。

  • できるだけLambda Layerに(コードを)格納する
    • (理由がよくわかりませんでした。。)
  • EFSを大きなデータ読み取り用に使う
  • 依存関係を減らす
  • AWSは常に新機能追加や制限緩和をしてくれるので、ウォッチしましょう

デモ

CIでテストとリントを行なうJavaScriptのプロジェクトです。

ここのレポートは割愛させていただきます。以下視聴URLから実際に動画を見ていただくのが良いかと思います。  

視聴URL