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

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

AWS CodeBuild 入門

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で監視して他のサービスをトリガーすることもできます。

他にCodePipelineCodeDeployと行った他のAWSサービスとも連携しやすいですし、LambdaからCodeBuildをトリガーするのも難しくはありません。

動かしてみよう

ではCodeBuildはどの様に使うのか? RailsアプリケーションのRSpecを実行する例で説明します。

処理の概要は次のとおりです。

  1. テスト対象のソースコードをGithubから取得する。
  2. RSpecを実行するためのdocker-compose を実行する。
  3. テストの結果をファイルに出力して、それらをS3バケットに置く。

CodeBuildプロジェクトの定義

最初にCodeBuildのビルドプロジェクトを定義します。

利用するCodeBuildの実行環境は次のとおりです。

  • 環境イメージ:aws/codebuild/docker:17.09.0
  • コンピューティングタイプ:15 GB メモリ、8vCPU
  • ビルドの対象:Github上の rails-appというリポジトリ

AWSのコンソールを利用します。この例では次の様に定義します。

f:id:HeRo:20180831053352p:plain

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 installyarn installを実行するのは非効率です。 予め実行したベースのDockerイメージをECRのプライベートリポジトリに作っておりそれを利用しています。 テストするソースコードはGithubから取得してカレントディレクトリに配置されます3

railsコンテナのcommand

railsコンテナにはEntryKitをインストールしており、 RSpecの実行前に必要なモジュールのインストールやJavacriptのトランスパイル、DBの生成を行っています。

予めbundle installyarn installを実行したベースイメージを使っていますが、イメージを作成した時点から変更されている可能性があるので再度実行して反映します。 利用しているCodeBuildの8vCPUを有効活用するためにtest-queueを使って並列実行しています。この場合、8並行で実行できます。

見ての通り、railsコンテナはRSPecの実行が終了すれば止まりますが、dbコンテナが止まらないとCodeBuildは処理を終了できません。そのため--abort-on-container-exitオプション付けてdocekr-composeを実行しています。これによりいずれかのコンテナが止まるとdocekr-composeが終了します。

この様に設定して、CodeBuildを実行するとRSpecを実行し、結果がS3バケットに出力されます。 ちなみに、このやり方はいこレポのCI環境でやっていることほぼそのままです。

tech.actindi.net

ハマりどころ

デバッグしづらい

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上のリソースにアクセスできるようになったのでRDSEC2とも接続でき活用の幅が広がりました。

まとめ

  • 手軽に強力なマシン環境が分単位で手に入る
  • AWSのサービスとの連携もしやすい
  • VPCに接続できるようになりかなり制限が少なくなった

これは使ってみるしかない!

最後に

アクトインディではこのような便利なサービスを活用して、開発したいエンジニアを募集中です。

actindi.net

一緒にランチでもしながらもっと知りたい、話してみたいと思ったら、お気軽に連絡ください!


  1. apt-get コマンドが使えます。繰り返し頻度の高い場合にはインストールを済ませたカスタムイメージを用意し、それを利用したほうが効率的でしょう。

  2. 一つのCodeBuildプロジェクトで行うよりCodePipelineなどと組み合わせてビルド処理とデプロイ処理といったように分離しておいたほうが別の処理を挟んだり組み替えたり、一部だけ実行したりといった小回りがきいていいと思います。

  3. buildspec.ymlやdocker-compose-rspec.ymlもソースコードの一部としてGithubから取得する

  4. Githubにコミットする時点で実行権限を付けておりCodeBuildで直接取得するときには大丈夫だと思います。CodePipelineと組み合わせた場合にはS3を経由するのですが、そこで実行権限が外れてしまうようです。