以前私がやろうとしていたことを公式がスマートにカッコよくやってのけてくれたので、試してみました。かたりぃなです。
その昔、こんなこと実現しようと考えていました。
そしていつの間にやらPFN公式からこんなの出てました。
https://research.preferred.jp/2017/12/chainerui-release/
すごいですね。さすがPFN。早速試してみます。
実験環境
環境はいつものです。今回はdockerを試してみます
- Windows10 Pro
- Docker for Windows
- python3.6
- chainer 0.1.0
- flask
- nginx
どうしてdocker?
今は実験段階なので、もっと単純に仮想化なしでWindows上で直接chainerを動かすほうが便利だったりします。 ただ、今後クラウド上にサービスを乗せることを考えると、やっぱりそういう移行をしやすい状況を作っておきたいと思うわけです。
ansibleとかやってもいいんですが、クラウドサービスを提供する各社ともdockerサポートを進めている状況を見ると、今後はdockerがいいのかなと考えます。 まあ実験の再現をしやすくなるというメリットが大きいと個人的に考えています。
dockerでchainerを動かす場合のGPUについて
色々と調べてみましたが、Docker for Windows上からだと、GPUを利用する方法がわかりませんでした。
NVidiaが出してるnvidia-dockerだと、まだWindowsはサポートしてないようです。 https://github.com/NVIDIA/nvidia-docker/issues/429
というわけで、今回は面倒なのでGPUまでやらなくてもいいやという結論にたどり着きました。本気でやるときはクラウド課金でnvidoa-docker使えばいいです。(まだ試してはいない)
dockerfileを書く
仮想環境構築って、毎回手間がかかりますよね。どんなパッケージを入れるかとか、設定どうするのがいいかとか。
dockerfileはそのあたりの手順をまとめておくためのものです。
内部的にはRUNごとにファイルシステムの状態がコミットされて~とかあるんですが、まあ、そのあたりは実際に触ってみないとイメージしづらいと思うので割愛。
とりあえずchainerとか必要なものを一式インストールしたイメージを生成するdockerfileです。
FROM centos RUN yum -y install initscripts MAKEDEV # SSHサーバ RUN yum -y install openssh-server RUN sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config RUN echo 'root:root' | chpasswd RUN /usr/bin/ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -C '' -N '' RUN /usr/bin/ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -C '' -N '' RUN /usr/bin/ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -C '' -N '' # 開発ツール RUN yum groupinstall -y 'Development tools' RUN yum install -y git wget cmake RUN yum install -y sudo # 文字コード #RUN yum -y reinstall glibc-common RUN localedef -v -c -i ja_JP -f UTF-8 ja_JP.UTF-8; echo ""; env LANG=ja_JP.UTF-8 RUN rm -f /etc/localtime RUN ln -fs /usr/share/zoneinfo/Asia/Tokyo /etc/localtime # python 3 RUN yum install -y https://centos7.iuscommunity.org/ius-release.rpm RUN yum install -y python36u python36u-libs python36u-devel python36u-pip RUN pip3.6 install --upgrade pip RUN pip3.6 install mtgsdk #RUN pip3.6 install flask chaineruiとバージョンかみ合わないので RUN pip3.6 install chainer RUN pip3.6 install chainercv RUN pip3.6 install chainerui==0.1.0 # 共有ディレクトリ RUN mkdir /mnt/data/ # テスト用のファイル ADD example.py /root/ EXPOSE 22 CMD bash -c "/usr/sbin/sshd -D"
flaskのところのコメントが悲しいのですが、バージョンの整合性とるのが面倒になったのでこうしました。 chaineruiの現時点の最新版である0.2.0を入れると、flaskの内部で使っているパッケージの最新版とうまくかみ合いませんでした。
なので、flaskのパッケージとうまくかみ合うchainerui-0.1.0を使うことにしました。
つかいかた
上記dockerfileのあるディレクトリに移動して
PS> docker build -t <任意のタグ名> .
です。
ログが流れてsuccessになっていればOKです。 念のため生成されたイメージを確認したいときは
PS> docker images
です。
あきらめた部分
boostとC++版opencvあたりも入れたかったのですが、python側との整合性とるのが面倒なので辞めました。 作業で使うことはあるので、別のdockerfileにまとめてあります。
dockerコンテナを起動させる
これで一通りのパッケージが入ったイメージが生成できたので、コンテナとして起動させます。 とはいっても、起動パラメータが多くて覚えるのが大変です。
たとえば、
- コンテナ名
- ポートマッピング
- ホストとのファイル共有
- ネットワーク設定。。。
等々。
実際にdockerコマンドから起動する場合は
docker run -it --name chainer_test --rm -v /c/Users:/tmp/data -p 30001:22 -d <buildで指定したタグ名>
みたいになるんですが、毎回コレ打ち込むのは面倒ですよね。 テキストでパラメータをメモしておいてコピペして使うとか、シェルスクリプトとして置いておくなども考えられますが、もう少しスマートに解決したいところです。
docker-composeを使う
docker-composeとは、ざっくりいうとvagrant upみたいになのをdockerで実現してくれるものです。
docker-composeはYAMLでパラメータを記述して、upが叩かれるとそのパラメータに従ってコンテナを起動してくれるというものです。 マニュアル見ながら設定したパラメータをファイルにできれば、毎回調べる手間も減りますし、もしパラメータ忘れても安心です。
本来の利用用途はオーケストレーションと呼ばれるもので、複数台のコンテナを楽に管理するときに使います。 今回は単一コンテナですが、記憶力がない私にとっては十分楽になるものでした。
version: "3" services: web: image: <任意のタグ名> ports: - "30001:22" - "30000:5000" volumes: - "C:/Users/<windowsユーザー名>:/mnt/data/"
このYAMLで定義しているのは
- <任意のタグ名>のイメージをコンテナとして動かす
- ホスト:ゲストのポートマッピングは2つ
- ホスト:ゲストのファイル共有ボリューム
こうしておくと、ポートマッピングと、ボリュームの設定が行われます。 ボリュームはVirtualboxとかでいうホストとの共有ディレクトリみたいなもので、この例では/mnt/dataのディレクトリにホストOSのWindows側のユーザーディレクトリがマウントされます。
sshするときはlocalhost:30001で、flaskが公開するデフォルトポート(5000)へhttpでいくときはlocalhost:30000でいけます。
つかいかた
PS> docker-compose up -d
これでdocker-compose.ymlの指定を読み込んで、コンテナが起動されます。 docker psしてコンテナが起動していることが確認できます。
dockerにssh接続して、chaineruiを起動する
本当はdockerにsshできる環境というのはよろしくないらしいです。 最終的にクラウドとかにデプロイする前提なので、余計な穴は無いほうがいいでしょう。
ただし今の私は実験段階なので、sshできたほうが何かと便利です。既存の仮想マシンと同じように扱えるので。
というわけで、ssh接続してchaineruiを起動します。 Windows10のWSL(旧 bash on ubuntu on Windows)でいきます。
PS1> bash $ ssh root@127.0.0.1:30001 $ cd /mnt/data/<ユーザー名>
コードを編集しやすいようにWindows側からも見える場所で作業することにします。 以降の作業はwindowsのユーザーディレクトリでやります。
とはいっても、今回はchaineruiのquickstartに従ってexampleを動かしてみるだけですが。
chaineruiのquickstartを試す
こんな感じになってるので、順番に実行していけば動きます。 最後の行だけ少しパラメータを追加しました。(--host=0.0.0.0)
# Initialize ChainerUI database. $ chainerui db create $ chainerui db upgrade # Clone examples of train log and create a project. $ git clone https://github.com/chainer/chainerui.git $ cd chainerui # create your first project $ chainerui project create -d examples -n example-project # run ChainerUI server $ chainerui server --host=0.0.0.0
あとはブラウザからlocalhost:30000にアクセスすればOKです。
--hostパラメータについて
これを付けた理由は、コンテナ外部(ホストOS)のブラウザからアクセスするためです。
chaineruiが使っているWebサーバのflaskはデフォルトで127.0.0.1:5000で待ち受けますが、このIPでは外から見えません。 一旦はLAN内で実験する想定なので、IP=0.0.0.0で待ち受けることにします。
もうちょっとスマートにアクセスしたい
WebサーバにアクセスするためにIPとかポート番号を指定する方法では、実験で色々とマシンを増やしたときに管理が大変になってきます。 今立ててあるマシンは生かしておいて、少しパラメータ変更したマシンで試したいとか。
sshは~/.ssh/configに書けばいいんですが、問題はHTTPです。 hostsファイルに書けばホスト名からIPをひけるのですが、これはホストとIPの関係だけであって、ポートまでは書くことができません。
いろいろやりようはありそうですが、単純にHTTPだけの問題ならHTTPレイヤで解決するのがスマートだと思います。
というわけで、HTTPプロキシとしてNginxを立てます。 最終的にサービス作るときはリバースプロキシみたいに振舞わせることになるかもしれませんね。
dockerでnginx
dockerで構築したイメージを公式が公開してくれてたりします。 今回はnginx公式のイメージを使わせてもらいましょう。
PS> docker pull nginx
このイメージに設定ファイルを与えればいいんですが、dockerfileを書くまでもないようです。 設定ファイルと、ポートの設定さえあればいいので。
というわけでdocker-compose.ymlをこんな感じに作りました。
version: "3" services: nginx_proxy: image: nginx ports: - "80:80" volumes: - "./log:/var/log/nginx" - "./:/etc/nginx"
このdocker-compose.ymlと同じディレクトリに、次の2つを準備します。
- logディレクトリ
- 以下のようなnginx.confファイル。
user nginx; worker_processes 1; pid /var/run/nginx.pid; events { worker_connections 1024; } http { sendfile on; keepalive_timeout 60; # local.chainerui.jpでアクセスされたら、localhost:30000に飛ばす。 server { server_name local.chainerui.jp; proxy_set_header Host $http_host; location / { proxy_pass http://localhost:30000; } } }
これで、HTTPのHOSTヘッダがlocal.chainerui.jpのリクエストはlocalhost:30000にパスされます。 local.chainerui.jpは適当につけた名前です。ドメイン持っているわけではないです。
で、localhost:30000はdockerのネットワークを経由して先ほど立てたchaineruiコンテナの5000番にパスされます。 そしてコンテナ側のポート5000はflaskが口を開けて待っているhttpポートなので、無事にchaineruiの画面が見れるというわけです。
ちなみにwindows側のhostsファイルはこんな感じで追記します。
127.0.0.1 local.chainerui.jp
今後の展望
普段とは違うインフラ寄りなことをしたおかげで、いい気分転換になりました。 何かに詰まったり躓いたりしたときは気分転換でこういうことをやるのもいいですね。
chainerui動かして気づいたのは、DBがsqliteなので、コンテナ内にDBとして使うファイルが生成されてしまいます。 どういうことかというと、docker再起動のたびにデータが消えるということを意味します。
これは実際に使い始めたら改善したいですね。たとえばストレージ用ボリュームを作るとか。
それでは今回はこれくらいで。