morishita です。
いこレポの開発環境でプルリクエストに push したら Lambda と CodeBuild を使って Rspecを実行する仕組みを作ったので、ご紹介します。
どんなの?
Githubでプルリクエストを作ったり、プルリクエストにPushすると、こうなって
テストがすべてパスすると、こうなる仕組みを作りました。
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サービスを使わなかった理由です。
利用したサービス・ツール
- Github
Amazon Web Service
- Docker-Compose
- Slack
全体の流れ
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 署名プロセスで署名する必要があったのです。しかし
しかし、Javascriptの AWS SDKのgetSignedUrl
はこれに対応していない。
今回は aws-signature-v4を利用してURLを生成して解決しました。s3::getSignedUrl
はデフォルトではこれに対応していません。
s3のインスタンスを作る時に次のようにsignatureVersion
をv4
に指定することで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 も使ってみようかなと思っています。
最後に
サーバーレスで、いろいろやってみたいエンジニアを募集していますので、よろしくお願いします。