CI環境の問題点

CI環境でよく起きる問題として以下のような問題がある。

  • SlowTest
    コードベースが肥大化していくと、それに合わせてテストコードも肥大化し、テストの実行にかかる時間がどんどん伸びていく。テストが全て完了するのに20,30分かかるようなことだと気軽にテストやデプロイなど行えない。
  • テストの実行環境
    プロダクトによって、言語や実行環境が異なるとテスト環境もそれぞれ異なり、複数のプロダクトのCIを同じ環境で動作させようとすると、CI環境上に全てのプロダクトを動作させるのに必要なソフトウェアのインストールや管理が必要になり、CI環境のメンテナンスがカオスになる。

こういった問題を解消するのに、Amazon EC2 Container Service(ECS)を利用してみる。
ECSはEC2インスタンスからなるクラスタ上にDockerベースのコンテナをビルド、実行するプラットフォーム。

Dockerコンテナを利用することで、各プロダクト毎に必要なテスト環境をコンテナベースで管理でき(EC2であればプロビジョニングに分単位かかるがコンテナであれば秒単位)、コンテナをスケールアウトさせることでテストの並列実行を行いやすくするといったメリットがある。

セットアップ

JenkinsにはAmazon EC2 Container Service PluginというPluginがあるのでそれを利用する。

とインストールしたけど、どうも現在のバージョンはECSのリージョンを選択できないため、デフォルトのus-east-1しか利用できない。
これだと東京リージョン利用できなくてツラいので、ECSのリージョンを入力できるようフォークした。今回はこれをローカルでビルドして、Jenkinsにアップロードして使用する。

2015.12.25追記
リージョン指定したPullRequestがマージされたため、現在は公式リポジトリから取得してビルドしてアップロードすれば利用可能。

設定

続いて各種設定。

Amazon ECS Cluster

事前にECSインスタンスが関連付けられたECSクラスタを作成しておく。このECSインスタンスは手動でクラスタに関連付けるか、Amazon Auto Scalingによって動的に作られる。
Jenkins Amazon EC2 Container Service PluginはこのECSクラスタを使って、必要なTask Definitionを自動生成する。

以前、WordPressを起動した「EC2 Container ServiceでWordPressを公開」時と同様にECS Container agentがインストール済みのAmazon ECS-Optimized Amazon Linux AMIを使ってECSインスタンスを起動する。(user dataで今回使用するECSクラスタのクラスタ名を記載しておく)

※注意事項
ここで起動したECSクラスタインスタンスそのままではJenkinsからコンテナ上にSlaveを起動しようとした際に

といったエラーがでて起動に失敗し続ける。これは起動されたamazon-ecs-agentのバージョンが古いため。
Amazon ECS Agent手動アップデートを参考に、事前にamazon-ecs-agentを最新にしておく。(v1.6.0であれば動作する)

Jenkinsのシステム設定

Jenkinsの管理 -> システムの設定 -> Jenkinsの位置 のJenkins URLに↑のECSクラスタからアクセス可能なURLを設定する。

cilocation

Amazon EC2 Container Service Cloud

続いて、設定ページの下にクラウドという枠があるので「Amazon EC2 Container Service Cloud」を追加する。

ecs-setting

  • Name
    ECS cloudの名前を登録。
  • Amazon ECS Credentials
    ECSクラスタ上でTask DefinitionsとTaskを作成する権限をもったIAMのアクセスキーとシークレットアクセスキーを登録。
  • Amazon ECS Region Name
    東京リージョンの場合はap-northeast-1は設定する。
    ※この機能は公式のamazon-ecs-pluginには存在せずフォークした実装による設定。
  • ECS Cluster
    ↑のCredeintialsが有効であれば↑のRegionのECSクラスタが選択可能になるので、JenkinsがECSタスクのビルドを送信するECSクラスタを選択する。

ECS slave template

1つ以上のECS slave templateが登録可能。複数登録するのはビルドジョブによって使用するDockerイメージを切り替えられるようにするため。
「追加」ボタンを押して、以下の設定を行う。

  • Label
    ジョブレベルの設定と組み合わせて使われるラベル。
  • Docker Image
    スレーブを作成する際に使われるDocker Image。
  • Filesystem root
    Jenkinsのワーキングディレクトリ。
  • Memory
    コンテナによって確保されるメモリ(単位はMB)。ここで設定したメモリ以上にメモリ消費が発生するとコンテナは殺される。
  • CPU units
    コンテナによって確保されるCPUのユニット数。ECSのコンテナインスタンスはコアあたり1024個のCPUユニットを持っている。
  • Override entrypoint(拡張設定)
    Dockerイメージのエントリーポイントを上書きする。
  • JVM arguments(拡張設定)
    JVM用の追加引数。-XX:MaxPermSizeとかGC系のオプション等。

ネットワークとファイアウォール

JenkinsのマスターノードとECSのコンテナインスタンスは同じVPC内の同じサブネット内で動作するようにしておくのが一番シンプル。

ファイアウォール

JenkinsのマスターノードとECSのコンテナインスタンス間にネットワークのアクセス制限を設定している場合、以下の許可を設定する必要がある。

  • マスターノードとスレーブ間でJNLPを使って通信する際のTCPポートを固定化する。(Jenkinsの管理 -> グルーバルセキュリティの設定 画面の「JNLPスレーブ用TCPポート番号」)
  • ECSのコンテナインスタンスからマスターノードへのTCPトラフィックを許可する(JNLPとHTTP(S))。

ECSスレーブ用のDockerイメージ

Jenkins Amazon EC2 Container Service Cloud Pluginでは、Jenkins JNLPスレーブノードとして設計されたDockerイメージであれば全て利用可能。以下互換性のあるDockerイメージ。

とりあえず公式のjenkinsci/jnlp-slaveをベースにプロジェクト固有の環境を含んだDockerイメージを作れば良さそう。

各ジョブへの設定

各ジョブの実行をECSで行うよう設定する。ジョブの設定の「実行するノードを制限」にチェックを入れ、ラベル式の欄にECS Slave templateのLabelの設定値を設定すると、そのSlaveの設定でECS上のコンテナでジョブが実行されるようになる。

nodelimit

あとは、上記設定したジョブを実行すれば、ECSクラスタインスタンス上に指定したDockerイメージのコンテナが起動してジョブの実行が始まる!

まとめ

  • プロジェクト毎にDocker Imageを用意してジョブが実行できるのでJenkinsのインスタンス内にプロジェクト実行用の各ソフトウェアのインストール、メンテナンスしなくていいのは楽。
  • Jenkinsのマスターノードはt2系の軽量インタンスで立てておいて、実際のジョブの実行はECSクラスタに必要なスペックのインスタンス詰め込むので良さそう。
  • 各コンテナは独立して動作するので、DBがネックなスローテストもテスト範囲を分割して並列実行とかしやすそう。
  • amazon-ecs-pluginを利用することで、ECSのTaskやTaskDefinitionの設定を意識することなく設定できるのは便利。(リージョンは指定したいのでプルリク取り込まれるといいな)