PHPUnit
EC2
Jenkins
CI
ECS

Jenkinsをver.1.533(AWS EC2)からver. 2.17(AWS ECS)に移行した

More than 1 year has passed since last update.


■これをやった理由


  • 担当プロダクトでJenkinsを使ったCIを行っているが、開発用のビルドと本番のバッチ処理のトリガとして混在して動作している状態であり、これを dev(開発) と prod(本番) でちゃんと分けたい。

  • Jenkinsによるビルドで開発コードの静的解析(phpmd)、重複コード検知(phpcpd)、Unitテスト実行(phpunit)を行っており、それが1度実行するだけで、20分程度かかってしまい、開発効率の悪い状態があった。

  • 負荷テストが実行できる環境が欲しかったが、現在のJenkinsに入れて、本番のバッチ処理になにか影響があると困るために別で作成することにした。


■GOAL


  • Jenkinsが「開発によるビルドや負荷テスト用(dev)」と「本番のバッチ処理のトリガ用(prod)」として分かれていること。

  • dev のJenkinsで、これまでと同様に、ChatOpsでのビルドが実行できること。 $\color{red}{\rm ※十分長いですが、さらに長くなるのでここでは書きません。 }$

  • ビルド、テストが並列実行され、ビルド完了までの時間が短縮されていること。

  • 負荷テストが実行できる環境が、Jmeter?でも良いので実現されていること。


    • CIで実行でき、結果がJenkins上で確認できる状態を目指す




■どういう手順で行ったか。


既存のJenkinsの移行


  1. 最初は既存のJenkinsをThinBackupプラグインを使っての設定状態のコピーで実行したが、うまくいかず断念。

  2. せっかくの機会なので、AWS ECS と Dockerを使ってJenkinsを立ち上げた。




  3. 既存のJenkinsのプラグインを新Jenkinsへもインストール。いくつかのプラグインがない。。。


    • 探してみたが、なさそうだったプラグイン


      • Duplicate Code Scanner Plug-in

      • PMD's Copy Paste Detector (CPD)

      • External Monitor Job Type Plugin

      • Jenkins build timeout plugin

      • Jenkins Mailer Plugin

      • Jenkins SSH Slaves plugin

      • Jenkins Subversion Plug-in

      • LDAP Plugin

      • PAM Authentication plugin

      • SSH Credentials Plugin



    • 不足していたプラグイン


      • DRY Plugin





  4. インストール後、Gitでのソースpullできるか調査、対応 ※ここで少しはまった・・・


    • JenkinsのmasterのDockerfile作って、ビルドして実行してみたら、ソースは取れた。




  5. Slaveでのジョブ実行をトライ。


    • 下記のプラグイン入れてみて、SlaveをDocker HubやECRから引っ張る設定をして、実行までできた。




    • しかし、Slave上でソースを引っ張るには、また、、、、鍵がSlaveのコンテナに必要なことが判明。。。orz

    • 色々と、Slaveへの鍵の配置をトライしたが、なぜか「ビルドできない」、「配置したと思っても、コンテナ起動しなおすと消えてる」ことが起きたので、STOP。

    • 色々とみていくと、下記プラグインにてソースをSlaveにコピーして解決できることが判明!!!!






  6. とりあえずmasterで実行できるジョブから移管開始。


    • awsコマンドの必要なジョブもawsコマンドのインストールさせて、移管できた。




  7. Copy To Slave PluginでSlaveへのソースのコピーもできるようになったので、実際にphpが動くコンテナを探してみた。


    • JenkinsのSlaveとして動くことが必要なので、Javaが入っていて、jenkins-slaveのエージェントが必要なことが判明

    • 下記のコンテナが使えそうだったが、php5-curlが入ってなかったので、独自でDockerfileいじって、コンテナ作成






ビルドの並列実行環境の構築


  1. ECSプラグインにて、Slaveでのジョブ実行可能になったので、並列実行の仕組みを検討


    • 既存のビルドジョブは下記の2つのジョブを実行しており、直列のため、$\color{red}{\rm 23分程度}$かかっていた。



      • build-phpmd-cpd $\color{red}{\rm 8分弱かかる}$


      • build-phpunit $\color{red}{\rm 15分かかる}$



    • さらに直列での実行であるため、開発者Aさんがビルド実行中の場合は、開発者Bさんはそのビルド実行待ちの状態となっていた・・・

    • 下記の方法がありそうだったので、実装をトライ


      • ジョブの同時実行

      • ジョブ自体の複数起動

      • ジョブ内でさらに並列実行





  2. ジョブの同時実行方法


    • Jenkins2.0系から使える「Pipeline」にて、上記ジョブを同時に実行にて実現

    • それぞれのジョブはSlaveのコンテナにて、実行するよう設定

    • 実際のGroovy Scriptは下記。



  3. ジョブ自体の複数起動


    • 各ジョブの並列実行のため「ビルドを並行実行」のチェックを有効化

    • ビルド自体が各Slaveコンテナで動くように「実行するノードを制限」のチェックを有効化


      • $\color{red}{\rm ※注意  インスタンス自体のリソースが足りない場合は待たさるみたいでした}$ :cry:

      • $\color{red}{\rm ECSインスタンスを2つにしてみたら、コンテナが4つ同時に動かせた!!!}$





  4. ジョブ内でさらに並列実行



    • phingbuild.xmlにて、並列実行の書き方があったので、それを利用

    • さらに、phpunitの並列実行のモジュールparatestを導入してみて、さらなる短縮を試しました。






GroovyScript

// flow.groovyファイル 必ず失敗するパターンの確認用

def builds() {
parallel(phing: {
run 'build-phpmd-cpd'
}, phpunit: {
run 'build-phpunit'
}, fail: {
//run 'test-fail'
})
}

def run(name) {
// pcntを指定プロセス数としてパラメタで指定
build job: name, parameters: [string(name: 'repo', value: repo), string(name: 'branch', value: branch), string(name: 'app_name', value: app_name), string(name: 'env', value: 'development'), string(name: 'pcnt', value: pcnt)]
}

stage 'Build and Unit test'
builds()
return this;



build.xml

    <!-- # of threads to execute parallel tasks -->

<property name="threads" value="3"/>
<target name="main" depends= "phpcs,phpmd,phpcpd" ></target>
<target name="parallel_main">
<parallel threadCount="${threads}">
<phingcall target="tasks_to_try" />
</parallel>
</target>
<target name="tasks_to_try">
<phingcall target="phpcs" />
<phingcall target="phpmd" />
<phingcall target="phpcpd" />
</target>


:exclamation: 結果 :exclamation:


  • 同時実行+並列処理導入で、$\color{red}{\rm 15分程度に短縮}$ :exclamation: もう少し行きたかったな・・・

  • ビルド自体の並列実行が可能となり、後からビルドしたい開発者が待たされることがなくなった :clock12:


負荷テスト環境の構築


  1. まずは、JenkinsとJMeter周りについて調査し、導入をトライしたが、下記の理由で中止。


    • Antによる実行がうまくいかない・・・

    • 調査してると、古い技術に感じたのもあり、使い方を習得して共有してもあまり組織的にメリットがないと思った



  2. 最近の負荷テストツールであるGatlingを下記の理由から導入



    • Gatlingのインストール、Jenkinsプラグインの導入まで簡単

    • レポートのUIがいい感じ :thumbsup:

    • 流行りのScala(これから学習しないといけないけど・・・)

    • こっちの方がScalaも学べる、使い方の共有によるメリットもありそう


    • JMeterのようにrecorderによるブラウザの操作にてテスト計画を作成できる




■はまったところ


  • Dockerで起動した新Jenkinsにて、プロジェクトのソースをgit pullするところ

  • ECSプラグイン入れて、Slaveでジョブが動くところまで確認後、EC2を再起動して、Public IPが変わってしまったら、Slaveでジョブが動かなくなった・・・


    • 原因は、Slave起動時の、tcpSlaveAgentListenerへのアクセスが再起動前のIPアドレスへ接続していたためでした。。。

    • キャッシュが残っているのか、システム設定にて、JenkinsのURLを再起動後のURLへ変更してもダメでした。。。

    • 最終的に一度、Slave Templatesの設定を一度削除 ⇒ 保存 ⇒ 再度、設定 でなおりました。すげー無駄な時間。。。

    • ECSプラグインの「高度な設定」のURLの変更をしていなかっただけでしたw



  • grunt動かすために、nodejs,npm,nvm入れたりと試行錯誤・・・意外とうまくいかず大変だった・・・でも、nvmっていいですね :thumbsup:

  • phpmd,phpcs,phpcpdのインストールとそれが起動できるまでが大変。。。phpcpdだけ、PEARじゃなくなってるし・・・orz


    • むかついたので、Composerでインストールしてやった。



  • ECSで登録したインスタンスタイプをEC2のコンソールから勝手に変えると怒られることが判明

  • docker-compose.ymlの記述がversion2になることで意味不明になった。。。


    • ecs-cliから、死んでてもいいデータコンテナのあるcomposeでの起動をすると、タスクが起動しないことが判明

    • aws/amazon-ecs-cli



  • ECSのインスタンスのディスクがfull diskになった。。。


    • そのデータ食ってる/var/lib/dockerを削除することができない。。。

    • EBS拡張して、なんとか対応w




  • fullbokを試してみたが、Cloudformationのテンプレが起動できず、あきらめた。。。orz


■改善点


  • まだ15分程度かかってしまっているので、さらなるビルドの高速化方法の検証



    • paratest時のプロセスを多く与えてみる?(現在は7~8で実行)

    • phpmd,phpcs,phpcpdのビルドのジョブ自体もそれぞれ別でジョブを作って実行?

    • テストコードのボトルネックをチェックして、改修



  • ECSにて、JenkinsのDocker起動すると、ビルド履歴や設定の保存場所が起動しているインスタンスに限られるため、厳密には自由にスケールできる状態にない。。。


    • EFSを利用してのインスタンス間での設定情報の保存場所の共有を検討中




■その他参考サイト


■まとめ


参考サイトだらけですみません。。。 :bow: 皆さまの知見があって初めてできたことです。ありがとうございます :bow_tone1: :bow_tone2: