ここ最近は Docker が熱いとのことです。最近は
- headless chrome and firefox in selenium with docker
- 第8回Jenkins勉強会で「Jenkins with Docker」というLTをしました #jenkinsstudy - Yahoo! JAPAN Tech Blog
といった記事も目にしました。 私自身 Docker に乗り遅れており、駆動勉強のためにネタを探していたところ、上記の記事からヒントを得て、掲題の仕組みを作ってみました。
結果から言うと下図のような構成になりました。
図内にある番号の順番で説明していきます。
1. CoreOS のセットアップ
今回、Docker の環境は CoreOS を使いました。 Ubuntu でもよかったのですが、CoreOS も触ったことがなかったので。
セットアップ方法は公式にあるので流す程度に
$ git clone https://github.com/coreos/coreos-vagrant/
$ cd coreos-vagrant
$ vagrant up
2. Selenium Grid Node コンテナの作成
今回、Selenium Grid の構築において
で設置しています。
※ hub も coreos 内にコンテナとして作成してもよかったのですが、まあなんとなくです。
まずは coreos にログイン
$ vagrant ssh
Dockerfile
node のコンテナを作成するための Dockerfile はこんな感じになりました。基本的にはこちらのDockerfile を参考にさせていただきました。
FROM ubuntu:12.10 MAINTAINER Wataru MIYAGUNI "gonngo@gmail.com" # https://github.com/dotcloud/docker/issues/1024#issuecomment-20018600 RUN dpkg-divert --local --rename --add /sbin/initctl RUN ln -s /bin/true /sbin/initctl # Install dependency packages RUN apt-get install -y software-properties-common RUN add-apt-repository -y ppa:mozillateam/firefox-next RUN apt-get update RUN apt-get -y upgrade RUN apt-get install -y curl RUN apt-get install -y openjdk-7-jre-headless RUN apt-get install -y firefox xvfb fonts-ipafont-gothic # Install selenium server ADD ./selenium-server /opt/selenium RUN curl "http://selenium.googlecode.com/files/selenium-server-standalone-2.39.0.jar" -o /opt/selenium/selenium-server-standalone.jar # Start selenium server CMD /opt/selenium/start.sh
サービス起動スクリプト
start.sh
はこんな感じです。xvfb と selenium を起動してるだけですね。
#!/bin/sh # Environments export DISPLAY=:10 # Start xvfb /usr/bin/Xvfb :10 -screen 0 1024x768x24 -ac & # Start selenium SELENIUM_HUB_PORT=${SELENIUM_HUB_PORT:-"4444"} SELENIUM_NODE_HOST=${SELENIUM_NODE_HOST:-"127.0.0.1"} SELENIUM_NODE_PORT=${SELENIUM_NODE_PORT:-"5555"} [ -z "${SELENIUM_HUB_HOST}" ] && exit 1 /usr/bin/java -jar /opt/selenium/selenium-server-standalone.jar \ -role webdriver \ -hub http://${SELENIUM_HUB_HOST}:${SELENIUM_HUB_PORT}/grid/register \ -host ${SELENIUM_NODE_HOST} \ -port ${SELENIUM_NODE_PORT}
node および hub の IP アドレスやポート番号は環境変数で指定するようにしました。 これについては後述します。
あとはビルド
Dockerfile と同じディレクトリに入る状態で、以下のコマンドを実行
core@localhost ~ $ docker build -t gongo/selenium-xvfb .
イメージ名は gongo/selenium-xvfb
にしておきました。docker push しなければなんでもいいと思います。
作成できたらイメージできてます
core@localhost ~ $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE gongo/selenium-xvfb latest 7790d9a06e60 About an hour ago 870.6 MB ubuntu 12.10 b750fe79269d 9 months ago 175.3 MB
作ったイメージを run するのはのちほど。
3. [ホストマシン - コンテナ] 間通信のため Vagrant の port forwarding を変更
node と hub がやりとり(hub に node を登録、hub から node に命令を発信、など)するには、node/hub 間で HTTP 通信が可能である必要があります。
node から hub へのアクセスは、そのままホストマシンのローカルIPアドレス(グローバルIPアドレスを持っていれば当然そこへ)で接続を確認できました。問題は逆です。
今回 node は coreos on Virtualbox 内のコンテナ、hub はホストマシン上で起動しているという構成です。そこで、ホストマシンから coreos へのアクセスを可能にするための準備第一段階として、Vagrant の Port Forwarding を使いました。
diff --git a/Vagrantfile b/Vagrantfile index add438e..e404c24 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -5,6 +5,10 @@ Vagrant.configure("2") do |config| config.vm.box = "coreos" config.vm.box_url = "http://storage.core-os.net/coreos/amd64-generic/dev-channel/coreos_production_vagrant.box" + (5555..5565).each do |port| + config.vm.network :forwarded_port, guest: port, host: port + end + # Fix docker not being able to resolve private registry in VirtualBox config.vm.provider :virtualbox do |vb, override| vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
ホストマシンの 5555...5565
から coreos の 5555...5565
へフォワーディングです。
編集後は vagrant reload
とかしてポートフォワーディングを有効化します。
4. [ホストマシン - コンテナ] 間通信のために、コンテナ起動時のポートを指定
ホストマシンから coreos へのアクセスを可能にするための準備第二段階として、コンテナ起動時にポート指定を行います。
core@localhost ~ $ docker run -p 5555:5555 \ # (1) -e SELENIUM_HUB_HOST=192.168.0.3 \ # (2) -e SELENIUM_NODE_HOST=127.0.0.1 \ # (3) -e SELENIUM_NODE_PORT=5555 \ # (3) -d gongo/selenium-xvfb
(1) コンテナの Listen port & coreos のポートとの紐付け設定
「コンテナが公開するポートは 5555」であり「ホスト(ここでは coreos)の5555番と紐付ける」という設定です。
ちなみに -p 5555
としてしまうと、コンテナと結び付く coreos のポートが自動的に決定されてしまうため、vagrant で設定したポートフォワーディングの範囲内に収まるように、明示しています
※ たとえば 0.0.0.0:49153->5555/tcp
みたいになる
(2) hub が起動しているマシン(今回はホストマシン)の IP アドレス
これは前述の通り、今回はコンテナからホストマシンには何も設定せずアクセスできたのでそのまま指定しました。
(3) node の IP アドレスとポート番号を指定
ここで言う「node の IP アドレスとポート番号」とは、前述の通りホストマシンからアクセス可能な値である必要があります。
ポート番号に関しては、すでに「Vagrant と CoreOS のポートフォワーディング」と「CoreOS とコンテナとのポート紐付け」は完了しているので、あとは docker run
で指定した「コンテナが公開するポート」をここで指定すれば OK です。これで、ようやく「ホストマシンからコンテナへの通信路」が確立されました。
IP アドレスに関してですが、これも Vagrant のポートフォワーディングがセッティング済みなので、Vagrant が起動している IP アドレス、つまり 127.0.0.1 で OK です。
5. 実行あるのみ
ここまできたら、あとは実行するのみです。
まずは selenium server を準備します。
忘れてましたが、前述の docker run
前に実行しておいた方がいいかもしれません。
サーバ起動してないのにコンテナ側の node が「hub がねーぞ」って retry しまくってる可能性もあります。
$ wget http://selenium.googlecode.com/files/selenium-server-standalone-2.39.0.jar $ java -jar selenium-server-standalone-2.39.0.jar -role hub
今回は例題スクリプトとして gongo/capybara_example_search_engine · GitHub を使います。
$ git clone https://github.com/gongo/capybara_example_search_engine.git $ cd capybara_example_search_engine $ bundle install --path vendor/bundle $ bundle exec rspec
実行すると、きっと多分、手元では Firefox が起動せずに、コンテナにある node 上で Firefox が動いているはずです。docker logs
で確認してみるのもよいでしょう。
結果、テストはきっと多分成功すると思います。おめでとうございます。
ちなみに http://localhost:4444/grid/console
にアクセスすると hub の情報が下図のようになっているとおもいます。
ここで表示されている「id : http://127.0.0.1:5555
」というのが、先程 docker run
したコンテナ内の node ですね。
ex. node 一杯起動する
node を5台起動してみましょう。
core@localhost ~ $ \ for port in $(seq 5555 5559); do docker run -p $port:$port \ -e SELENIUM_HUB_HOST=192.168.0.3 \ -e SELENIUM_NODE_HOST=127.0.0.1 \ -e SELENIUM_NODE_PORT=$port \ -d gongo/selenium-xvfb done
docker ps
は賑やかになります (CREATED
STATUS
NAME
は省略)
core@localhost ~ $ docker ps CONTAINER ID IMAGE COMMAND PORTS ba042c136990 gongo/selenium-xvfb:latest /bin/sh -c /opt/sele 0.0.0.0:5559->5559/tcp 4334cee2e5c8 gongo/selenium-xvfb:latest /bin/sh -c /opt/sele 0.0.0.0:5558->5558/tcp 5abbfec5ffb7 gongo/selenium-xvfb:latest /bin/sh -c /opt/sele 0.0.0.0:5557->5557/tcp 60cb4d3a4ae5 gongo/selenium-xvfb:latest /bin/sh -c /opt/sele 0.0.0.0:5556->5556/tcp fb87e2f37bb3 gongo/selenium-xvfb:latest /bin/sh -c /opt/sele 0.0.0.0:5555->5555/tcp
hub のコンソールも賑やかになりました
さて、実行する方も並列に行うために、parallel_rspec
を使用します
$ cd capybara_example_search_engine $ bundle exec parallel_rspec spec -n 5
実行後、hub のコンソールを覗いてみましょう
ブラウザアイコンが薄くなっているのが使用中を表しています。しっかり5台の node に分散されています
まとめ
職場でも selenium (実際には Capybara + Turnip) を使ったテストの遅さをどうにかしたいと考えていて、 それなら並列だろうって答えの手段の候補として、今回の技術が使えるんじゃないかと考えています。
やっつけ構築なのでまだ実践投入できないですが、いずれやってやりたいですね。
目下問題点としては
- 複数台 node を docker run すると、hub 側での認識が遅い(全台認識するまで5分ぐらいかかる。1台だとすぐ)。これはコンテナのポートのあれそれが遅いのか、selenium のあれなのかが不明なのでちゃんと調べてないどす
- あと何かあったけど忘れた
などありますが、ひとまず動いたのでよしとします。