アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Github + Lambda + CodeBuild で自動テスト

morishita です。

いこレポの開発環境でプルリクエストに push したら Lambda と CodeBuild を使って Rspecを実行する仕組みを作ったので、ご紹介します。

どんなの?

Githubでプルリクエストを作ったり、プルリクエストにPushすると、こうなって

Github実行中

テストがすべてパスすると、こうなる仕組みを作りました。

GithubOK

Slackにも通知されます。

slack通知

なぜつくったの?

このユースケース自体は CircleCI等を使えば、わりと簡単にできてしまいます。 ではなぜ、作ったのかというと、次の通りです。

分単位課金で何並行でも使える

CircleCIでフリーだと同時に実行できるテストは1で、同時4テストを実行できるプランだと $150/月かかります。 ところが、CodeBuildだと$0.02/分で8vCPU、15GBのインスタンスが使えます。同時に何並行でも実行でき単純に時間課金のみです。現状、いこレポだと一回のテストに約5分、$0.1しかかかりません。 月に$150分使おうとすると、20営業日/月として1日75回もテストできてしまいます。 実際にはそんなに実行されないので、コスト的にも有利です。

AWSの他サービスと連携しやすい

CodeBuild は CodePipeline や、Lambda、CloudWatchと連携しやすく、今後インテグレーション していきやすそうです。

Serverless Framework を使ってみたかった

これまで、Slack に通知などのちょっとしたLambda関数は作っていて、AWSコンソールで直接実装していました。今回はもう少しコード量が増えそうだったので、コンソールではちょっとつらいなぁと思いServerless Framework を使うことにしました。 Serverless Framework と AWSの各種サービスを使ってどんなことができそうか試してみたかったというのもCIサービスを使わなかった理由です。

利用したサービス・ツール

全体の流れ

全体の流れを表したシーケンスは次のとおりです。 シーケンス

2つの処理から構成されています。ひとつはGithubからSNSへのイベント通知をトリガーにLambdaがCodeBuildを起動して、それをGithub・Slackに通知する処理。そしてCodeBuildの終了をCloudWatch Eventが受けてLambdaがGithub・Slackに通知する処理です。

図が煩雑になるのでシーケンス図上は Lambda は一つしか描いていませんが、 それぞれの処理に対応するLambda関数を一つづつ実装しました。

CodeBuild での Rspec 実行

この仕組のメインは CodeBuild で Rspecを実行しているところです。 RailsのRspec と MySQL を動かすコンテナを docker-compose を使って、実行しています。

Railsコンテナでは test-queue を使ってRspecを分散実行しており、 SimpleCov でテスト結果のコードカバレッジも記録します。

ハマったところ

test-queue 導入に起因したもの

実はテスト実行順のランダム化も設定忘れで実施できていなかったので テスト順が変わってFixtureのロードが漏れてテスト失敗が多発しました。 フィクスチャを用意していたのは都道府県や地方といったマスター系のデータだったのですが、 これは rails_helper に config.global_fixturesを設定することにより解消しました。

また、画像アップロードのテストが時々failすることがありました。 test-queue はフォークして複数プロセスでテストを実行するので、それぞれのプロセスには独立したリソースを用意する必要があります。Paperclipのアップロードされたファイルの保存場所が分離されておらず、たまたまテストタイミングが重なるとエラーとなっていたようでした。test-queueの実行スクリプトの after_fork でプロセスごとの保存場所を設定することで解決しました。

ネームスペースが異なる同名のコントローラでfail

たとえは記事ページのコントローラは AtriclesController、記事管理のコントローラはAdmin::AtriclesController というクラス名で実装しているのですが、何故かこれをテスト中取り違えられてテストがfailするということがありました。 これは不本意ながら、require 'admin/articles_controller' のように、ターゲットクラスをspecファイルで読み込むことにより解決しました1。もっとスッキリした解決法はないものかと思っています。

decker-compose が終了しない

Rspecを実行するためには MysqlとRails2つのコンテナを動かすのですが、Specの実行が終わっても、MySQLが残るので、docker-composeが終わらなくて困りました。 なにか解はないものかとドキュメントを読み返していると--abort-on-container-exit オプションで解決できそうだということがわかりました。これを利用するとコンテナが一つでも終わると、docker-compose自体が終了します。abort と言いながらも、exit 0 で終了するので、CodeBuild的にもFAILにならなず解決できました。

CodeBuildがS3にアップする処理結果は暗号化されている

GithubやSlackに通知した処理結果には、詳細な結果情報を得るためのリンクを付けています。 このリンクのURLはS3の署名付きURLと言うもので、期間限定でS3のファイルをダウンロードできるものです。 SDKを利用すれば、getSignedUrl で簡単にこのURLが生成できると思ったのですが…、 CodeBuildはS3にアップロードするデータは暗号化してしまい、暗号化されたファイルを取得するための署名付きURLは 署名バージョン 4 署名プロセスで署名する必要があったのです。しかしgetSignedUrlはこれに対応していない。 今回は aws-signature-v4を利用してURLを生成して解決しました。 しかし、Javascriptの AWS SDKのs3::getSignedUrlはデフォルトではこれに対応していません。 s3のインスタンスを作る時に次のようにsignatureVersionv4に指定することでs3::getSignedUrlが出力するURLが署名バージョン 4 署名プロセスで署名されたものに変わります。(2017/11/06 修正)

const s3 = new AWS.S3({ signatureVersion: 'v4' });

やってみてどうだったか?

これまで、いこレポではプロダクト自体のコードがそれほど多くなく、自動テストについては サボっていてローカルでRspecを実行していたのですが、pushしたらテストが実行されるのはやはり楽です。

実装に関しては、 Serverless Frameworkの導入はやって良かったです。 Webpackと組み合わせて使えるので、設定すれば babel で ES2015 相当のJavascriptの仕様が使えます。特にasync/awaitが使えるのは大きいです。デプロイもワン・コマンドで済んでしまうのでとても楽できました。ちょっとしたものにも積極的に使っていくべきだと思いました。 また、初期案ではJenkinsを導入しようとしていたので、そのサーバ運用の手間も省けたのも大きいです。 AWS SDKを使ってサービス間をLambdaで繋いでいくことで、サーバレスでの自動化を進めて行けそうな 手応えを得られました。もう少し複雑なことをしたくなったら、Step Functions も使ってみようかなと思っています。

最後に

サーバーレスで、いろいろやってみたいエンジニアを募集していますので、よろしくお願いします。