Thanks Driven Life

日々是感謝

Docker on CoreOS on Virtualbox(Vagrant) で Selenium Grid Node を大量に作成して並列に囲まれてみたいと願った

ここ最近は Docker が熱いとのことです。最近は

といった記事も目にしました。 私自身 Docker に乗り遅れており、駆動勉強のためにネタを探していたところ、上記の記事からヒントを得て、掲題の仕組みを作ってみました。

結果から言うと下図のような構成になりました。

f:id:gongoZ:20131223215455p:plain

図内にある番号の順番で説明していきます。

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 の構築において

  • Selenium Grid Node (以下、node) は CoreOS 内
  • Selenium Grid Hub (以下、hub) はホストマシン上

で設置しています。

※ 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
  • 5、6行目を行わないと、Firefox インストール時に Upstart にアクセスできないとかなんとか言われたので入れました。
  • 18行目で追加してるディレクトリは 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 の情報が下図のようになっているとおもいます。

f:id:gongoZ:20131223214326p:plain

ここで表示されている「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 のコンソールも賑やかになりました

f:id:gongoZ:20131223214323p:plain

さて、実行する方も並列に行うために、parallel_rspec を使用します

$ cd capybara_example_search_engine
$ bundle exec parallel_rspec spec -n 5

実行後、hub のコンソールを覗いてみましょう

f:id:gongoZ:20131223214321p:plain

ブラウザアイコンが薄くなっているのが使用中を表しています。しっかり5台の node に分散されています

まとめ

職場でも selenium (実際には Capybara + Turnip) を使ったテストの遅さをどうにかしたいと考えていて、 それなら並列だろうって答えの手段の候補として、今回の技術が使えるんじゃないかと考えています。

やっつけ構築なのでまだ実践投入できないですが、いずれやってやりたいですね。

目下問題点としては

  • 複数台 node を docker run すると、hub 側での認識が遅い(全台認識するまで5分ぐらいかかる。1台だとすぐ)。これはコンテナのポートのあれそれが遅いのか、selenium のあれなのかが不明なのでちゃんと調べてないどす
  • あと何かあったけど忘れた

などありますが、ひとまず動いたのでよしとします。