morishitaです。 今回は利用しているAWSのサービスの中でも特にお気に入りの CodeBuildについて書きたいと思います。
CodeBuildとは
CodeBuild とはAWSのサービスの1つで、完全マネージド型のビルドサービスです。
ビルドサービスだからといって、プログラムコードのコンパイルしかできないわけではなく、設定次第で様々な処理を実行できます。
CodeBuildの特徴
スペックの高い処理環境を分単位で利用できる
最もパワフルな設定では8vCPU、15GBメモリの環境を使うことができます。EC2のインスタンスタイプで言えば c5.2xlarge に近いスペックです。
例えば20営業日/月に1時間/日の処理が必要として、c5.2xlargeインスタンスをずっと運用するとなると月に$313.30にかかります。
一方、CodeBuildであれば時間単価こそEC2より高いものの、必要な時間だけの課金なので$0.020/分x60分x20日=$24。たった$24しかかかりません。圧倒的なコストパフォーマンスです。
マネージド型なので手間が少ない
EC2インスタンスだって必要な時間だけ起動すればいいじゃない? と思うかもしれません。もちろんそれもできるでしょう。 そのときに準備するのはEC2インスタンスの起動・停止を行うスクリプトとそれを動かすための別のマシンでしょうか。
しかし、CodeBuildならばAPIで実行を指示するだけで、オンデマンドで実行環境がプロビジョニングされ処理を実行し終了します。終了後の後始末も必要ありません。
実行のトリガーはCloudWatch Eventも利用できるので定期実行する場合も他のマシンでAPIを定期実行する必要もなく設定するだけでできてしまいます。
同時に複数の環境を動かす必要が出ても何台でも並行して動かせます。コンソールから実行するだけです。
Lambdaの様に5分制限もなく、必要なコンピューティングパワーを得ながらサーバ管理のない開放感!
様々な実行環境を選択できる
CodeBuildには次のような様々な言語に対応した実行環境が用意されています。
- Golang
- Java
- Node.js
- PHP
- Python
- Ruby
- .NET Core
- Docker
これらの環境はDockerイメージとして用意されており、ベースはUbuntu 14.04となっています。
実行するとホスト環境が割り当てられ、指定した環境のコンテナが起動します。
利用者側はホスト環境を意識することなくコンテナの中で実行したい処理を定義します。処理に必要なソフトウェアを追加でインストールすることもできます1。
これ以外の環境が必要ならば、自分で作ったDockerイメージを使うこともできます。
また、Windowsの環境も選択可能です。
Githubと連携可能
CodeBuild で処理するコードはGithubから取ってこれるのはもちろん、Githubへのpushをトリガーに処理を開始することもできます。
例えば、pushするとビルドしてデプロイするなんてことも可能です2。
それ以外にも自動でテストを実行したり、DockerイメージをビルドしたりGithubを起点とした処理系にも組み込みやすいです。
AWSの他のサービスとも連携が容易
前述したCloudWatch Eventとの連携はとても便利です。 CloudWatch Eventによりトリガーすることもできますし、CodeBuildの状態変更をCloudWatch Eventで監視して他のサービスをトリガーすることもできます。
他にCodePipeline、CodeDeployと行った他のAWSサービスとも連携しやすいですし、LambdaからCodeBuildをトリガーするのも難しくはありません。
動かしてみよう
ではCodeBuildはどの様に使うのか? RailsアプリケーションのRSpecを実行する例で説明します。
処理の概要は次のとおりです。
- テスト対象のソースコードをGithubから取得する。
- RSpecを実行するための
docker-compose
を実行する。 - テストの結果をファイルに出力して、それらをS3バケットに置く。
CodeBuildプロジェクトの定義
最初にCodeBuildのビルドプロジェクトを定義します。
利用するCodeBuildの実行環境は次のとおりです。
- 環境イメージ:aws/codebuild/docker:17.09.0
- コンピューティングタイプ:15 GB メモリ、8vCPU
- ビルドの対象:Github上の rails-appというリポジトリ
AWSのコンソールを利用します。この例では次の様に定義します。
buildspec.yml
CodeBuildはbuildspec.ymlというファイルで処理内容を定義します。 このファイルは、Githubから取得するコードに含めておきます。 buildspec.yml自体もソースと共にバージョン管理できて便利ですね。
今回の例の場合、その内容は次のようになります。
version: 0.2 phases: pre_build: commands: - echo Starting pre_build phase at `date` - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION) - mkdir -p ./coverage/ build: commands: - echo Build Phase started on `date` - echo Running RSpec test sequence - chmod -R 755 bin - docker-compose -f docker-compose-rspec.yml up --abort-on-container-exit post_build: commands: - echo Post Build Phase started on `date` - cp ./log/test.log ./coverage/ artifacts: files: - '**/*' base-directory: 'coverage'
ポイントを説明します。
phases:
phases
で処理を段階ごとに定義していきます。
- pre_build:
- build:
フェーズのための準備を行います。
- ECRへのログインと、テスト結果を出力するための/coverage/
ディレクトリを作成しています。
- build:
- メインの処理を実行します。
- RSpecを実行するためのdocker-compose
を実行しています。
- post_build:
- build:
フェースの後処理を行います。
- ログファイルを/coverage/
にコピーします。
artifacts:
CodeBuildでは処理の成果物をアーティファクトと呼びますが、このブロックでは そのアーティファクトを指定します。指定されたファイルはS3にプットされます。
この例では、coverage
ディレクトリ以下のすべてのファイルをS3に出力します。
docker-compose-rspec.yml
具体的にRSpecを実行するdocker-compose
の処理内容を示さないとわけがわからないと思いますので、その内容も次に示します。
version: '2' services: db: image: mysql:5.6 environment: MYSQL_ALLOW_EMPTY_PASSWORD: "yes" ulimits: nofile: 65536 rails: image: "xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/rails:base" environment: RAILS_ENV: test RACK_ENV: test SECRET_KEY_BASE: pickasecuretoken ulimits: nofile: 65536 command: [ "prehook", "bundle install --with test", "--", "prehook", "yarn install", "--", "prehook", "bundle exec rake webpacker:compile", "--", "prehook", "bundle exec rake db:create", "--", "bin/rspec-queue", "spec" ] volumes: - .:/rails-app depends_on: - db
ポイントを説明します。
コンテナは2つ
MySQLのdb
コンテナとRSpecを実行するrails
コンテナを起動します。
db
コンテナはDockerHubで公開されているMySQLの公式イメージを利用しています。
一方、rails
コンテナは毎回ゼロからbundle install
やyarn install
を実行するのは非効率です。
予め実行したベースのDockerイメージをECRのプライベートリポジトリに作っておりそれを利用しています。
テストするソースコードはGithubから取得してカレントディレクトリに配置されます3。
rails
コンテナのcommand
rails
コンテナにはEntryKitをインストールしており、
RSpecの実行前に必要なモジュールのインストールやJavacriptのトランスパイル、DBの生成を行っています。
予めbundle install
やyarn install
を実行したベースイメージを使っていますが、イメージを作成した時点から変更されている可能性があるので再度実行して反映します。
利用しているCodeBuildの8vCPUを有効活用するためにtest-queueを使って並列実行しています。この場合、8並行で実行できます。
見ての通り、rails
コンテナはRSPecの実行が終了すれば止まりますが、db
コンテナが止まらないとCodeBuildは処理を終了できません。そのため--abort-on-container-exit
オプション付けてdocekr-compose
を実行しています。これによりいずれかのコンテナが止まるとdocekr-compose
が終了します。
この様に設定して、CodeBuildを実行するとRSpecを実行し、結果がS3バケットに出力されます。 ちなみに、このやり方はいこレポのCI環境でやっていることほぼそのままです。
ハマりどころ
デバッグしづらい
CodeBuildの処理内で標準出力に出力したものはClowdWatch Logsに出力されます。
エラーが起こったときによりどころとなるものはそれしかありません。
地道にecho
コマンドを使ってデバッグプリントを出力して調査することになります。
結構辛いです。
したがって込み入った処理はシェルスクリプト等にして、CodeBuild外でよくテストしておいて、
それをCodeBuildで実行したほうが良いでしょう。
私はシェルスクリプトではなくdocker-compose
でやっています。
シェルスクリプトは実行権限がない
CodeBuildが取得したソースには実行権限がついていないことがあります。
処理をシェルスクリプトにしてCodeBuild内で実行するには、実行権限を付けてやるか、bash
とかruby
といったコマンドに渡してやる必要があります4。
それがわからず最初だいぶハマりました。
終了ステータス 0 以外で止まる
各ステップのコマンドは終了ステータス0
で終了しないといけません。終了ステータス0
以外で終了するとそこで失敗とみなされ終ってしまいます。
終了ステータス0
以外で終了しても続行したい場合には次のようにしてとにかく終了ステータス0
で終わるようにします。
/bin/bash -c '<終了ステータス 0 以外で終了する何らかの処理> || true'
こんな用途にもおすすめ
Dockerイメージのビルドの他にも次のような用途におすすめです。
- Dockerイメージのビルド
- いこレポでは、本番環境にデプロイするDockerイメージのビルドにも活用しています。
- ベースとなるイメージをDockerHubから取得し、ビルドして ECRのプライベートリポジトリにpushするなんてことができます。
- バッチ処理
- いこレポでは開発環境やステージング環境で利用するデータベースを本番データベースから毎日生成しています。
- VPC上のリソースにアクセスできるようになったのでRDSやEC2とも接続でき活用の幅が広がりました。
まとめ
- 手軽に強力なマシン環境が分単位で手に入る
- AWSのサービスとの連携もしやすい
- VPCに接続できるようになりかなり制限が少なくなった
これは使ってみるしかない!
最後に
アクトインディではこのような便利なサービスを活用して、開発したいエンジニアを募集中です。
一緒にランチでもしながらもっと知りたい、話してみたいと思ったら、お気軽に連絡ください!
-
apt-get
コマンドが使えます。繰り返し頻度の高い場合にはインストールを済ませたカスタムイメージを用意し、それを利用したほうが効率的でしょう。↩ -
一つのCodeBuildプロジェクトで行うよりCodePipelineなどと組み合わせてビルド処理とデプロイ処理といったように分離しておいたほうが別の処理を挟んだり組み替えたり、一部だけ実行したりといった小回りがきいていいと思います。↩
-
buildspec.ymlやdocker-compose-rspec.ymlもソースコードの一部としてGithubから取得する↩
-
Githubにコミットする時点で実行権限を付けておりCodeBuildで直接取得するときには大丈夫だと思います。CodePipelineと組み合わせた場合にはS3を経由するのですが、そこで実行権限が外れてしまうようです。↩