あなたの安心を高速に守る
Container-based CI
宮國 渡 (@gongoZ)
2015-4-18
Agenda
ˆ 背景 (過去にあった問題解決の話)
ˆ 新たなる問題
ˆ 振り返らない高速化が生んだ弊害
ˆ 高速と安定の join
ˆ まとめ
自己紹介
社会人
ˆ 宮國 渡 (MIYAGUNI Wataru)
ˆ 株式会社OCC
ˆ PHP な Web アプリケーション開発・運用保守
プライベート
ˆ github.com/gongo
ˆ Just Do Eat
自己紹介
社会人
ˆ 宮國 渡 (MIYAGUNI Wataru)
ˆ 株式会社OCC
ˆ PHP な Web アプリケーション開発・運用保守
ˆ ↑ これにまつわる話します
プライベート
ˆ github.com/gongo
ˆ Just Do Eat
プロローグ(長いです)
Long long ago..
PHP 3, 4, 5 の時代を渡り歩いてきた、とある Web アプリ
ケーションが居ました
ˆ PHP がテンプレートエンジンであった頃
ˆ HTML にビジネスロジックを書き始めた頃
ˆ PHP がアクセス修飾子の無いオブジェクト指向を取り
入れた頃
ˆ 各人から生まれる複数のオレオレフレームワーク
Long long ago..
PHP 3, 4, 5 の時代を渡り歩いてきた、とある Web アプリ
ケーションが居ました
ˆ PHP がテンプレートエンジンであった頃
ˆ HTML にビジネスロジックを書き始めた頃
ˆ PHP がアクセス修飾子の無いオブジェクト指向を取り
入れた頃
ˆ 各人から生まれる複数のオレオレフレームワーク
ˆ 全部混ざってる!
Long long ago..
PHP 3, 4, 5 の時代を渡り歩いてきた、とある Web アプリ
ケーションが居ました
ˆ PHP がテンプレートエンジンであった頃
ˆ HTML にビジネスロジックを書き始めた頃
ˆ PHP がアクセス修飾子の無いオブジェクト指向を取り
入れた頃
ˆ 各人から生まれる複数のオレオレフレームワーク
ˆ 全部混ざってる!
ˆ 何よりテストが無い!
現代の PHP (Versions)
version initial release EOL
5.4 2012/03/01 2015/09/14
5.5 2013/06/20 2016/06/20
5.6 2014/08/28 2017/08/28
(new! →) 7.0 2015/11/xx
via PHP: Supported Versions
現代の PHP (周辺)
ˆ PHP-FIG — PHP Framework Interop Group
ˆ PSR-FIG なるコミュニティが策定する規約
ˆ PSR-0 —Autoloading Standard
ˆ PSR-2 —Coding Style Guide
ˆ PSR-3 —Logger Interface など
ˆ Composer
ˆ パッケージ依存管理ツール (Ruby でいう Bundler)
ˆ Packagist
ˆ Composer リポジトリ (Ruby でいう RubyGems)
やりたいことは
ˆ 例: PHP 5.5 に上げたい
ˆ 例: もうメンテされてないライブラリから移行したい
ˆ 例: リファクタしたい
でも今の僕たちの装備じゃ
現代への 快適でスムーズな移動 のクリア難しい
装備 = 自動テスト
「これまで」
ˆ テスト仕様書 (Excel) を見ながら手動テスト
ˆ 新機能開発であれば手動テストでも良い
ˆ 自動テストの主流である回帰テストはあくまで 回帰
ˆ 新規のバグは発見できない。経験と勘が必要
「これから」
ˆ 中身が変わっても外側が変わらないことを保証したい
ˆ 古いライブラリからの引越しのたびに全手動テスト?
ˆ PHP バージョンアップのたびに全手動テスト?
まずは装備を整える
手動人海戦術はプラン B
via http://migo0110.blog.jp/archives/7187299.html
安心に満たされた穏かな日々を過ごすため
テストの自動化を目指してみることに
前提条件
Resource
ˆ 仕様書はない (ERD 含む)
ˆ 手動テストで使っていた Excel ならある
Policy
ˆ 仕様/UI の変更は無い想定とする
Unit Test ?
テスト書き辛さ問題
ˆ クラスベースなコードの方が少ない
ˆ グローバル関数、グローバル変数が主流
ˆ そもそも <html> の中にビジネスロジックが…
古い PHP に縛られる
ˆ 綺麗に書けなくてイライラ 問題
ˆ trait 使いたいし array(’a’) を [’a’] って書きたい
ˆ バージョンアップに影響をうける
ˆ 本体コード、テストコード両方同時に手を…?
End-to-End test ?
ブラウザ操作してその結果 (画面) をチェック系
ˆ Excel から、ある程度機械的にテストケース作成可能
テストコードが サーバーサイドに依存しない
ˆ 将来的にも使い続けていける可能性が高い
PHP に拘らずに 書ける
ˆ バージョンを気にするのは本体のコードだけ
ˆ テスト環境は現代のツールを導入できる
というわけで
現状の仕様を保ちつつ サーバーサイドの
現代化を行うにはEnd-to-end テストの自
動化が最適と判断
Environment of
integration testing
簡潔に書けそうな Ruby 製を選択
ˆ ブラウザの操作
ˆ Selenium - Web Browser Automation
ˆ Selenium Ruby binding
ˆ RubyBindings - selenium
ˆ Selenium Ruby binding のラッパー
ˆ jnicklas/capybara
ˆ Ruby テストフレームワーク
ˆ RSpec: Behaviour Driven Development for Ruby
Example (Capybara)
構成(個人バージョン)
Next stage after test
automation
テスト実行するのに疲れてしまう (人間だもの)
続けなければ意味がない
自動テストも手動で実行し続けるのはつらい
ˆ たかが「ポチッ」されど「ポチッ」
つらくなるとテストしなくなる
ˆ 「これぐらいの修正なら大丈夫だろう」
自動テストも動かなければただのテキスト
ˆ 動かさないテストは無いほうが良い (メンテ含む)
僕たちがやりたかったのは
テストを実行することなの?
僕たちの力は
テストを動かすためではなく
価値を生み出すため に使うべき
価値を生み出す力を
継続して支えるためには
(機械的な)新たな仕組み
を入れるべき
Continuous Integration
継続したいことは
ˆ コードが commit (push) される度にテスト走るとか
ˆ 深夜に、その日 commit されたコードでテストとか
ˆ 他にもいろいろ定型的な作業とか
via Jenkins CI
構成 -Prologue version-
構成 -Prologue version-
構成 -Prologue version-
構成 -Prologue version-
構成 -Prologue version-
構成を組んだあとは
以下のことが実現
ˆ 深夜に 1 回、全 Integration test を実行
ˆ 前日に master branch にマージされたコードに対して
ˆ 朝、出勤してテストが通ったかどうかチェックできる
ˆ テスト失敗した瞬間のスクショをまとめたレポート
隙があまり生じぬ二段構え
ˆ 「定時内の開発 and レビュー and テスト」
ˆ 「深夜に全テスト」
コアな部分の修正にも安心感が増した
プロローグ
完
本編
テストを書いて
コードを書いて
を繰り返していたある日
速さが足りない!
テストに
1時間半も掛かってる!!
What happened?
テストケースの増大
ˆ テストケースが 600 越えてきた
ˆ 1 テストケース 10 秒で終わるものもあれば 1 分かかる
ものもある
ˆ ログインして 3 画面ぐらい遷移してフォーム入力して…
テストが遅いこと自体は問題ない
 
What happened?
テストケースの増大
ˆ テストケースが 600 越えてきた
ˆ 1 テストケース 10 秒で終わるものもあれば 1 分かかる
ものもある
ˆ ログインして 3 画面ぐらい遷移してフォーム入力して…
テストが遅いこと自体は問題ない
テストが実行されていれば
遅いフルテストの
真の弊害
テスト実行するのに疲れてしまう (みんな人間だもの)
CI に任せてるから
大丈夫だよね?
深夜に 1 回走らせてはいるけど
1 時間半も掛かるテストを定時前に走らせるとか無理
僕たちは安心を求めていた
ˆ フルテストによってそれを担保していたはずだった
ˆ フルテストが億劫になると
ˆ 最小限のテストだけ走らせるようになる
ˆ 見ないふりし始める ( まあ大丈夫だろ 問題)
ˆ 深夜のテスト実行まで待てないし手元で動かすのもめ
んどくさい。リリースが大事だろ ( 任務遂行絶対 問題)
ˆ その結果
ˆ エンバグ、デグレード多発
ˆ その他いろいろな事故リスク増大
「これでは駄目だ」
僕たちは安心を取り戻すために高速化を開始した
まず最初に思いついたのは
高速化と言えば並列実行
並列実行 (RSpec)
現状
ˆ Test runner である RSpec を並列実行したい
ˆ 参考: The Ruby Toolbox - Distributed Testing
今回は test-queue を採用
ˆ GitHub 本体のテストにも使われているライブラリ
ˆ 既存 (RSpec) の環境をほぼそのまま使える
ˆ 実行コマンドが rspec-queue に変わるだけ
ˆ 暇なプロセスを作らず、テストをいい感じに分配
実行例
$ bundle exec rspec-queue spec/
※ 数値はてきとうです
並列実行 (Browser)
\ Selenium Grid /
See: Grid2 SeleniumHQ/selenium Wiki
Selenium Grid
Selenium Grid
Selenium Grid
Selenium Grid
構成 -test-queue +
selenium grid-
構成 -test-queue +
selenium grid-
3 プロセスで実行したら
3倍の早さで終わった
10 プロセスで実行したら
8倍ぐらい早く終わった
ぼくたちは
速さを手に入れた
その代わりに
失なったものがある
安定しなくなった
いろいろ原因はあったが
主な原因は、(テストデータの入った)DB の同一テーブルを
閲覧・変更・削除するテストケースが複数あり、それらが
同時に実行されていたため
Application Server と書いてたが実は全部入っていたのさ
主な失敗パターン
主な失敗パターン
主な失敗パターン
回避策思案 -App いっぱい-
「App + DB」で 1 組のサーバを一杯立てれば?
回避策思案 -App いっぱい-
なんとなくいけそう
ˆ test-queue の各プロセス毎に「この URL にアクセス
して」と固定できるので、使えそう
ˆ See: 参考ページ
サーバ管理めんどくさい
ˆ 構築だけでなく起動や終了も
ˆ テストデータが更新されたら全サーバに rsync して
restore して..
「App + DB」を持ってて
テストデータの更新もすぐにできて
遅くてもいい時は3台だったり
早く終わって欲しいときは20台だったり
好きに起動したり停止できる環境を
お手軽にポチッって操作できる
便利なものないかなー
via Docker
Docker
Docker(ドッカー)はソフトウェアコンテナ内の
アプリケーションのデプロイメントを自動化する
オープンソースソフトウェアである。
Linux カーネルにおける LXC と呼ばれる Linux コン
テナ技術と Aufs という特殊なファイルシステムを
利用してコンテナ型の仮想化を行う。
ˆ Docker - Wikipedia
ˆ What Is Docker? An open platform for distributed apps
Docker を採用した理由は
ただひたすら「手軽」に起動できる
ˆ 環境 (コンテナ) の起動や停止が速い
ˆ 「テストしたい時」「特に必要ない時」でカジュアルに
ON/OFF できる (数秒規模)
ˆ やろうと思えば複数プロセス動かせる (※)
ˆ 僕たちが望んだ「App + DB」の 1 環境ができる
※ 「1 コンテナ 1 プロセス」とよく言われるけど
ˆ 起動して数分で消えるテスト環境のために「web」
「app」「db」に分割するのはコストが高すぎる
ˆ 手軽な使い捨て環境 だし全部入りコンテナで良い
構成 -Docker 導入-
構成 -Docker 導入-
(Cont’d 1st)
Dockerfile を作成する
# Dockerfile
FROM centos:centos6
COPY scripts/* /opt/scripts
RUN sh /opt/scripts/install_dependency_libraries.sh 
&& sh /opt/scripts/install_application.sh 
&& sh /opt/scripts/restore_test_data.sh
CMD service postgresql start 
&& service httpd start 
&& tail -f /var/log/httpd/access_log
EXPOSE 80
$ # Docker コンテナイメージをビルド
$ docker build -t app-php55 .
構成 -Docker 導入-
(Cont’d 2nd)
Fig (現 Docker Compose) を使う
# fig.yml
app:
image: app-php55
ports:
- "80"
$ fig scale app=5 # 5 台起動
$ fig scale app=10 # 5 台追加で *計 10 台* 起動
$ fig scale app=3 # 7 台減らして *計 3 台* 起動
アプリコンテナを Docker
で動かしてると
ˆ selenium node も fig scale でぱぱっと起動したい
ˆ この時点では オレオレ.sh で起動してた
探してみた
Docker Hub
ˆ docker build で作成されたイメージが置かれている
ˆ Ubuntu 公式 だったり 個人で作ったもの だったり
ˆ Dockerfile 公開してないとちょっと心配になる
ありました
ˆ registry.hub.docker.com/repos/selenium/
ˆ Selenium 開発コミュニティが出しているイメージ
ˆ Dockerfile も GitHub に公開 されている
最終形態 (準備)
# fig.yml
app:
image: app-php55
ports:
- "80"
node:
image: selenium/node # <= Docker Hub にあるやつ
links:
- hub
hub:
image: selenium/hub
ports:
- "4444:4444"
最終形態 (召喚)
$ fig up -d hub
$ fig scale app=15 node=15
最終形態
最終形態
最終形態
最終形態
最終形態の実行結果
最終形態の実行結果
最終形態の実行結果
エンディング
以下を両立した Container-Based CI の誕生
ˆ テスト 10 分以内に終わる手軽さ
ˆ 「なぜか失敗する」という暗黒の排除
僕たちは
安心と速度の両方を手にした
エピローグ
近況報告
無事 PHP のバージョン上げられました
1 PHP 5.X と PHP 5.Y それぞれの Docker コンテナを用
意して検証
2 両方でテストが通るか。通らないなら何が原因か
ˆ バージョンの違いによるものか、潜在的なバグか
複数バージョンを高速にチェックできてよかった
近況報告 (Cont’d)
開発期間の変化 (高速化する前から取り組んでいましたが)
ˆ 以前までは 3 ヶ月 - 6 ヶ月などの長期間
ˆ 現在は 2、3 週間スプリントで回している
ˆ 開発 (1、2 週間。この間はレビュー & 自動テスト
チェックをメインに)
ˆ 検証 (4 日間。メインの CHANGE に対する手動テスト)
ˆ ふりかえり (1 日。本スプリントでの KPT)
テストの高速化、および安定化の維持は良い効果
現在の開発フロー
1 手元で開発
2 git push
3 GitHub で Pull Request (以下、PR) を作成
4 Jenkins Master が PR(commit) を検知し、そのブランチ
を Jenkins Slave に教える
5 Jenkins Slave が教えられたブランチを元に Docker のア
プリコンテナを起動
ˆ ついでに Selenium Hub/Node も起動 (停止していれば)
6 Integration test に加えて Unit Test も実行
7 約 10 分ぐらいでフルテスト完了
8 もし fail だったら修正を commit 。以後 4 - 7 を繰り
返す
後輩のコメント
ずっと目指していた
via full test also want to end within 50ms // Speaker Deck
まとめ
手持ちのリソースにあった
パターンを
今回は高スペックなワークステーションがあったので
Docker コンテナを大量召喚できた
ˆ 低スペックだがマシンが大量にあるなら、並列化だけ
ではなく 分散化 も考えるべき
勘違いしてはいけないのは
高速化を目指すことでテストが安定しないのであれば
高速化を諦めてテストの安定に集中すべき
ˆ × 目指すはテストの高速化
ˆ ○ 開発者に心の安らぎを
おわり
PEACE OF MIND
安らぎが力になる

あなたの安心を高速に守る Container-based CI