AWS CodeBuildで分課金のコンテナ実行環境を手に入れる

伏見です。この記事はWanoグループアドベントカレンダー2016の21日目の記事になります。年末感。

書くこと

  • NightmareでのWebサイトE2Eテスト実行環境を、Dockerで構築・CodeBuildで実行してみた
  • 実行時に環境変数も渡せるし割とCodeBuildには使いどころありそう
  • CodeBuild Tokyoリージョン はよ

AWS CodeBuild発表、着想

先日のAWSのイベントre:invent 2016 にて『CodeBuild』 の発表が行われた。
モバイル/Webアプリケーションのビルド/テストのCIのパイプライン上に載せることが想定されているようだ。
現在(2016/12/20) Tokyoリージョンはなく、バージニア、オレゴン、アイルランドでのサービス提供になる。

気になったポイントは2つ。

  • 自分で用意したDockerの実行環境が使える。
  • (ビルド)タスクの実行時間に応じた「分単位での課金体系」である。

自作Dockerイメージが使える

AWS Lambdaのようにコードをサクッと動かしたい時に自作dockerコンテナが使えないかなあ、というのはたまに思っていた。
CodeBuildで実行環境自作のDockerイメージが使えることにより、インスタント環境として AWS Lambda より便利そうな点がある。

  • 使用言語の制限がなくなる。
  • あらかじめカスタマイズ済のイメージを構築しておける。

0.005$~ /分の課金

AWS CodeBuild 料金
CodeBuildはビルド実行時間(分単位 , 1分あたり 0.005$)での課金と転送量での課金になる。
インスタンスのプランをあげると0.02$/分まで上がる。

課金体系がこの通りなら、用途によってはいろいろ捗りそう。

リリース予定の AWS Batch も優秀そうだし、実行環境に信頼度がおけそうな感じがあるが、
あっちは(多分)普通にEC2借りてて1時間単位での課金だし、スポットインスタンス狙っていく作業がちょっと大げさな時もあるかと思う。

何ができそうか考える

  • 普通にAndroidアプリとかのビルド/ DeviceFarmにテスト投げる
  • 最悪止まっても問題なさそうなE2Eテストやスクレイプ作業
  • apiなどの監視タスク

いわゆるCIの用途以外でも考えようとしたけど…。
まあ他にも即時性なくてもいいけどDockerでサクッと動かしたいタスクで何かしら使いどころはあるんじゃないだろうか。

動かしてみるもの

ちょうどWebサイト周りのE2Eテストについて勉強中なので、その実行環境にCodeBuildを是非使ってみたい。
ブラウザE2Eテスト実行環境を整えたDockerイメージを用意し、AWSコンソール上でCodeBuildを実行/テストを走らせてみるとこまでやってみる。

E2Eテストを仕込んだDockerイメージの用意

Nightmare基盤

テストフレームワークNightmareをdockerのコンテナ上で動かす上で,レンダリングのためにxvfbなどの仮想フレームをインストールしたり、その他必要なモジュールをとってこなければならない。
この辺のインストール作業は冗長で、そもそもの興味範囲とは違うところであれがない、これがない、とハマりがち。
その辺をあらかじめ織り込んだDockerfileを公開してくれている人はガンガンいるので、それを参考にイメージを構築することにする。

いざとなったらDockerfileにはlinux上でのモジュールのインストール手順がそのまま記載されているので,別にdocker使わずとも学ぶことが多い。

nightmareのDockerfileを参考に、以下のようなDockerfileを構築する。

FROM node:7.2
MAINTAINER mr.hoge <xxxx.co.jp>

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd --system nightmare && useradd --system --create-home --gid nightmare nightmare
ENV HOME "/home/nightmare"

ENV DEBUG=nightmare
ENV ARGUMENTS=()

RUN apt-get update && apt-get install -y \
  vim  gcc make cmake g++ \
  software-properties-common vim wget curl unzip zip \
  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 && \
    rm -rf /var/lib/apt/lists/* && \
        find /usr/share/doc -depth -type f ! -name copyright | xargs rm || true && \
        find /usr/share/doc -empty | xargs rmdir || true && \
        rm -rf /usr/share/man/* /usr/share/groff/* /usr/share/info/* && \
        rm -rf /usr/share/lintian/* /usr/share/linda/* /var/cache/man/*
RUN apt-get clean

WORKDIR ${HOME}
COPY ./package.json ./
RUN npm install

VOLUME ${HOME}

オリジナルと違うところは、イメージのベースをNodeJS7系にしているところや、とりあえずvimやwgetも入れているところくらい。

E2Eテストに使うモジュールの準備

package.json

{
  "name": "docker-my-nightmare",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "./node_modules/.bin/mocha .test.js --timeout 6000000",
    "e2e_with_debug" : "DEBUG=nightmare:*,electron:* xvfb-run --server-args='-screen 0 1024x768x24' ./node_modules/.bin/mocha --harmony ./test.js  --timeout 600000",
    "e2e_on_docker" : "xvfb-run --server-args='-screen 0 1024x768x24' ./node_modules/.bin/mocha --harmony ./test.js  --timeout 600000"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mocha": "^3.2.0",
    "mocha-co": "^1.17.2",
    "nightmare": "^2.8.1",
    "power-assert": "^1.4.2"
  }
}

ついでにテスト発火用コマンドをpackage.jsonに記載しておく。

テストスクリプトの用意

ブラウザテストそのものや、Nightmare周りについては今回は触れない。
簡単なjavascriptのコードだけ載せる。

test.js

const mocha = require('mocha-co');
const Nightmare = require('nightmare');
const assert = require('power-assert');

const sleep = (ms)=>{
  return new Promise((resolve , err)=>{
    setTimeout(()=>{
      resolve();
    },ms);
  });
};

describe("1st Test", function() {  

  let nightmare;

  before(function*() {
    console.log("Start Testing... #####################");
    nightmare = Nightmare();
  });

  it('googleでhogeを検索', function*() {
    const title = yield nightmare
      .viewport(1024,768)
      .goto('https://www.google.co.jp/?gfe_rd=cr&ei=qTBaWIX5OYqL8QeryomIDg#q=hoge&btnK=Google+%E6%A4%9C%E7%B4%A2')
      .title();

    assert(title === 'Google');
  });
});

ローカルのdocker上でちゃんと動くようであれば、おもむろにコンテナをcommitして、オールインワンなイメージを作っておく。
1.3GBほどになってしまった。

AWS ECRへの dockerリポジトリの登録

DockerHubのAWSバージョンと言えるAmazon EC2 Container Registry にDockerイメージを置くことにする。
ECRは、dockerのプライベートリポジトリを置く上で、割と安いサービス。

CodeBuildをOregon(us-nor)で立てているため、リポジトリもOregonに置く。
Tokyoリージョンに置いたリポジトリがCodeBuild上でうまく指定できなかったためだ。
この辺は後でIAM設定見直すが、できればCodeBuildが早くTokyoリージョンに来てほしい。

先ほどのビルド済イメージをECRにpushしておく。

docker push xxxxxxx.us-west-2.amazonaws.com/yyyyy:latest

ビルドソース準備

CodeBuildはビルドソースとしてS3/GitHub/CodeCommit(AWSのGitホスティング)が使える。
今回はアプリケーションのビルドをするわけじゃないのでソース設定は正直なんでもいいのだが、勉強のためにS3にする。
CodeBuildの設定ファイルであるbuildspec.ymlをS3に置き、そこに書かれているコマンドをdockerで実行する、という構成にしてみる。

buildspec.yml

version: 0.1
phases:
  install:
    commands:
  pre_build:
    commands:
      - echo $MY_VAR
  build:
    commands:
      - cd /home/nightmare && npm run e2e_on_docker
  post_build:
    commands:
      - echo Done!

中でディレクトリ移動しているのは、Dockerファイルに記載されているWORK_DIRがCodeBuildでの実行時に適用されずにnpmの実行エラーを起こしたため。

S3へのビルド対象のアップロード

先ほどのbuildspec.ymlをzipとしてS3にアップロードする。

zip buildspec.yml > buildspec.zip
aws s3 cp buildspec.zip s3://~~~~oregon-bucket/codebuild/

ここで注意点があって、S3のbucketのオリジンもCodeBuildと同じリージョンになければならない。
(IAMの設定割と見直したんだけど、CodeBuildでのビルド実行時にエラーが出てうまくいかなかった。)

CodeBuildのプロジェクトを作る

今回はConsole上で設定する。
regeonはオレゴン(us-west-2)を選択。

実行環境指定

Source: What to build

  • Source provider
    • Oregonに立てたbucketを指定

Environment: How to build

  • Environment image
    • Specify a Docker image
  • Custom image type
    • AmazonECR
  • Amazon ECR repository
    • ECRのリポジトリを指定
  • Amazon ECR image
    • イメージのタグ指定。履歴を残さなければlatestだけでいいはず
  • Build specification
    • ここに直接コマンドも書けるが、buildspec.ymlの設定を使うことにする。

Artifacts: Where to put the artifacts from this build project

  • Artifacts type
    • 本来ならここでビルド成果の置き場所を指定するが、No artifactsにする。
    • 今回のようなテスト用途なら、E2Eテストの結果やサイトのキャプチャをまとめてzipにするといいかもしれない。

Service role

  • ECR,S3に接続するための実行権限をここで付与する。

role周りについての設定は
Amazon ECR Sample for AWS CodeBuild
を参照。

continue -> OK を押してプロジェクト完成!

ビルド設定

“Start Build”ボタンでビルド設定画面に入る。
S3をビルドソースにしている場合、Source versionとしてs3 objectのバージョニング機能が使えるが、これで運用するのもあんまりうまくない気がする。

Environment variablesでコンテナ実行時に環境変数が渡せるため、これを積極的に使っていくことになりそう。(もちろんこの機能はaws-cliのcodebuildからも使える。)
name,valueは実際には

export MY_VAR=hogehoge

されているようなので、普通に$MY_VARとかで受け取れる。
コンテナの条件は変えずに、「動的にスクリプトやコマンドをダウンロードしてきてテストを実行する」基盤を整えるとなかなか便利になりそう。

実行

待つ。待つ。

上が実行ログの例。
テスト実行中のログはコンソール上で見れる他,CloudWatchのログとしても残る。

試算

1.3GBほどのDockerイメージで、実行開始までに1分半ほどかかった。
E2Eのjavascriptコード自体は短いので20秒ほど。
2分 * 0.005$ で 1回あたりの実行料金1円くらいになる。
これ自体はまあ大したことない。
一方のECRの転送料金がいまいち読めないが、こっちは1.3GB転送しているのでこれは金額的に大きそう。
EC2でECRを使うとき、同じリージョンの場合に EC2 <=> ECRの転送自体は無料なので、 CodeBuildでもこのプランが適用されているといいなーというか、してないときついと思う。

まとめ

CodeBuildはいろんな用途に使えそうですが、東京リージョン早くほしいです。
ついでにbitbucket対応もしてほしい。
(最後雑)

Read More