OSSプロジェクトにLambCIを導入しようとした話
最近はLambStatusというOSSを作っているのですが、今回は、このプロジェクトでCIを回すためにLambCIを導入しようとした話です。結果は失敗に終わったのですが、つまづいた箇所とかが参考になるかもしれないので残しておきます。
LambCIとは
LambCIは、AWS LambdaベースのCIシステムです。〜CIというとCircleCIみたいなクラウドサービスっぽいですが、クラウドサービスではなく、自分で構築して運用していくものです。
以下のMediumの記事が一時期話題になったので、聞いたことがある方もいるかもしれません。
なぜ今更そんな自前運用のCIシステムかというと、LambCIがいわゆるサーバレスアーキテクチャで作られていることが大きいと思います。Jenkins等のCIシステムを自前運用していくのはなかなか大変ですが、サーバレスなら自前運用の辛さをかなり軽減できるかもしれない、また、CI系のクラウドサービスはビルドの並列度を上げると料金が結構高くなりますが、サーバレスなら並列度を上げても全体の料金はほぼ変わらなそう、などのサーバレスを活かした特徴が注目されているようです。
より詳しい紹介やアーキテクチャについては、上記の記事を参照して下さい。
LambCIをOSSプロジェクトで使ってみる
今回LambCIを導入するOSSプロジェクトはJavascriptで作っていて、テストはkarma、mocha、electron等を利用して動かしています。実際のテストや設定はこちらから確認できます。
LambCIのインストールについては公式のREADMEに詳しく書かれているので省略して、設定ファイル.lambci.js
の作成から始めます。レポジトリのトップディレクトリに、以下のような設定ファイル.lambci.js
を作成しました。
module.exports = { cmd: 'cd packages/frontend && npm install && npm run test', build: true }
GitHubにPushしたところ、AWS Lambdaでのビルド中に、以下のようなエラーが出ました。
末尾にError: ENOSPC: no space left on device, write
と書かれています。npm install
中に、ディスクサイズが足りなくなったようです。ローカル環境で確認してみると、babelやelectron等のパッケージにより、依存パッケージだけで700MB程度のディスクを消費していました。AWS Lambdaの一時ディスク容量は512MBなので、これが原因のようです。
まぁディスク容量に限らず、AWS Lambdaには色々制限があります。LambCIは、AWS Lambdaの制限によりビルドがうまく行かないときは、ECS (EC2 Container Service)との組み合わせを勧めています。ディスク制限の回避は難しそうなので、素直にECSを使ってみます。
LambCIとEC2 Container Serviceを組み合わせて使ってみる
LambCIとECSを組み合わせる場合は、以下のレポジトリを利用します。
こちらはまだREADMEがあまり書かれていないので、導入手順を紹介します。
1.CloudFormationスタックの立ち上げ
こちらのテンプレートを使って、AWSコンソールからCloudFormationスタックを立ち上げます。これで、ECSクラスタ等が立ち上がります。今回はt2.small
インスタンスタイプを使いました。
2.CloudFormationテンプレートとスタックの更新
(ECSではなく)LambCI本体の立ち上げに使用したCloudFormationテンプレートを更新します。LambCIのGitHubサイトからテンプレートをダウンロードして、LambdaExecution
という名前のIAM Roleリソースに、以下のポリシーを書き加えます。
{ "PolicyName": "RunECSTask", "PolicyDocument": { "Statement": { "Effect": "Allow", "Action": "ecs:RunTask", "Resource": "arn:aws:ecs:*:*:task-definition/lambci-ecs-BuildTask-XXXXXXXXXXXXX" } }
XXXXXXXXXXXXX
の箇所は、Step.1で作成されたECSのタスク名で置き換えてください。
書き換えたテンプレートを利用して、AWSコンソールからLambCIのCloudFormationスタックを更新します。
3.設定ファイルの更新と追加
.lambci.js
に対して、ビルドに使用するクラスタ名などを以下のように指定します。
module.exports = { ... 既存の設定 ... docker: { cluster: 'lambci-ecs-Cluster-XXXXXXXXXXXXX', task: 'lambci-ecs-BuildTask-XXXXXXXXXXXXX' } }
XXXXXXXXXXXXX
の箇所は、Step.1で作成したECSのクラスタ名とタスク名です。
これに加えて、Dockerfile.test
というファイルをレポジトリのトップディレクトリに作成します。これは、ビルド処理を動かすDockerイメージをビルドするためのファイルです。
今回は以下のようなDockerfile.test
ファイルを作成しました。
FROM node:4.3.2 RUN apt-get update && apt-get install -y xvfb x11-xkb-utils xfonts-100dpi xfonts-75dpi xfonts-scalable \ xfonts-cyrillic x11-apps clang libdbus-1-dev libgtk2.0-dev libnotify-dev libgnome-keyring-dev \ libgconf2-dev libasound2-dev libcap-dev libcups2-dev libxtst-dev libxss1 libnss3-dev \ gcc-multilib g++-multilib libgconf2-4 gtk2-engines-pixbuf ADD packages/frontend/package.json /frontend_build/ RUN cd /frontend_build && npm install ADD . . RUN rm -rf packages/frontend/node_modules && mv /frontend_build/node_modules ./packages/frontend CMD cd packages/frontend && xvfb-run -a --server-args="-screen 0 1024x768x24" npm run test
キャッシュを効かせるためにpakcage.json
をごにょごにょしているため少しややこしいですが、基本的にはnpm install
とnpm run test
をしているだけです。これに加えて、非GUI環境でelectronを動かすため、aptで色々入れています。
実際の設定ファイルは、こちらで確認できます。
これらのファイルをGitHubにPushすると、ECS上でのビルドが実行されます。なお、S3上のビルド結果画面には以下のようなログしか表示されないため、ビルド結果はCloudWatch LogsのECSのログで確認します。
初回は20分ほどかかりましたが、無事にビルドが成功しました。t2.small
インスタンスタイプを利用したので仕方ないと思いますが、同等のビルドをCIサービスのwerckerで動かすと7分ほどで終わったので、ちょっと遅めです。
LambCI導入をやめた理由
ここまでは進めたのですが、しばらくはクラウドサービスを使おうかな、と思っています。理由としては以下です。
ECS上でビルドした結果がS3に反映されない
ECSを利用しない場合、ビルド結果はS3に渡されるため、S3を通してビルド結果を他の開発者と共有できます。同様にビルド結果を示すバッジ画像も、S3を通してREADME等に表示できます。しかしECSを利用すると、ビルド結果がS3に反映されないため、ずっとビルド中の状態になってしまいます。ビルド結果はCloudWatch Logsからわかるのですが、CloudWatch Logsの閲覧権限をパブリックに共有するのは気が進みません。
Lambdaの代替としてECSを使いたくない
今回はLambdaの代わりにECSを使おうとしましたが、Lambdaに期待することをECSで実現するのは無理があると感じました。
例えば、今回は最初
t2.micro
のインスタンスでクラスタを立ち上げたのですが、これだとビルドが何回繰り返しても通りませんでした。インスタンスの性能が足りないのか思い、試しにt2.small
にしてみたところ、通りました。また、クラスタを立ち上げる度にECS Agent
が古いから更新しろと警告してきます(更新は数クリックで済みますし、AMIを適切に作り直せば警告は消えると思いますが)。こういう問題は、始めからECSを利用するつもりなら何でもない話ですが、Lambdaを使うつもりだった立場からすると、ドウシテコウナッタ感があります。また、LambCIは、ビルド並列度を上げても全体の料金がほぼ変わらないところや、ビルドが実行されてないときは料金がほぼかからないところが利点です。しかし、ECSだとその恩恵にあずかるのは難しくなります。オートスケールを組み合わせれば多少ましになるかもしれませんが、それでも、1時間単位課金と100ms単位課金ではだいぶ料金が変わってくる気がします。
1つ目はそれほど大きな理由ではないし、回避策もあると思うのですが、2つ目の理由は結構厳しいと思い、導入をやめました。Lambdaの制限が緩和されて、ECSなくてもいけそう!と思えた段階で、改めて導入しようかと思います。