Docker のインストールから Docker の Anaconda(Jupyter, Numba) を使うまで

[Python]pip で Numba をインストールする時にエラーになるという状況でしたので、結果的に Docker で Anaconda を使うまでを書いてみました。

Anaconda, pyenv の問題を理解する

Anaconda を使う方向で考え始めたのですが、

もともと、homebrewを用いてNode.js等の環境を構築し、hubotと戯れていた環境。 最近、話題のTensorFlowを使ってみるべくPythonの環境をAnacondaで作り、チュートリアルを少々動かし始めていました。

久々にnpmコマンドを打ったところ…

-bash: npm: command not found

Anacondaを入れたらHomebrewの環境が吹っ飛んだ話 - Qiita

Anaconda を公式サイトの Graphical Installer(.pkg file), Command Line Installer(Anaconda3-4.3.1-MacOSX-x86_64.sh) でインストールすると、環境変数の PATH を Anaconda のコマンドが優先されるように設定されるため、Homebrew でインストールしたコマンドが使えない状況になると解釈しました。

だから、

このままだとHomebrewとAnaconda環境がバッティングするのでコメントアウトしておく。

#export PATH="/Users/*ユーザーネーム*/anaconda/bin:$PATH"

そして、~/.bashrcに以下の関数を追加する。

#!/usr/local/bin/bash
function enter_conda()
{
  source /Users/*ユーザーネーム*/anaconda/bin/activate root
}
function quit_conda()
{
  source /Users/*ユーザーネーム*/anaconda/bin/deactivate
}

[Python]Anacondaで環境構築[Mac] - Qiita

環境変数の設定をこねくり回したりしなければいけないのだと解釈しました。

そうしなくても良いように、

  • 多くは2.xなので、3.xと共存したいなと思います。
  • システムの方をうかつにさわるとはまります。
  • 別途apt-get install python3などとして入れても、環境変数を自分で弄る必要があります。
  • anaconda単独だと実は問題があります。
    • curlやsqlite3,opensslなどを一緒にインストールして動作をのっとります。
  • 推奨はpyenvを経由することです。
    • 現在のpyenvはanacondaに対応しています。

環境構築方法

.1. pyenvをインストールします。

$ git clone https://github.com/yyuu/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(pyenv init -)"' >> ~/.bashrc
$ source ~/.bashrc

.2. anacondaをインストールします。 3系でも2系でもどっちでもいいですが、3で殆どの場合不都合が無いです。 pyenvで両方共存させることも可能ですが、condaで2/3の切り替えができるので片方でいいです。

$ pyenv install -l | grep ana
# 最新版を確認。anaconda3-2.5.0 (2系はanaconda-2.5.0)
# minicondaがいい人はminicondaを入れてください。
$ pyenv install anaconda3-2.5.0
$ pyenv rehash
$ pyenv global anaconda3-2.5.0
# anacondaをメインのpythonに設定。
$ echo 'export PATH="$PYENV_ROOT/versions/anaconda3-2.5.0/bin/:$PATH"' >> ~/.bashrc
$ source ~/.bashrc
# activateがpyenvとanacondaでバッティングするので、pathに明示しておく。
$ conda update conda
#念のためconda自体をアップデートしておく。

データサイエンティストを目指す人のpython環境構築 2016 - Qiita

Anaconda をインストールするのに、pyenv を使った方が良さそうに解釈しました。

ただ、pyenv も Anaconda と衝突するところがあるようで、

pyenvとanacondaのactivate衝突問題とは? linuxやMacではpyenvを介してanacondaをインストールできます。 更に、anacondaで複数開発環境の切り替えをしたいケースがあると思います。 anacondaでの環境は、source activate <環境名>として切り替えを行えますが、pyenvの下では下記のようなエラーが出てシェルごと落ちます。

$ source activate
#>>> pyenv: -bash: command not found

理由はよくわかりませんが、pyenvがshimスクリプトを使ってpathをのっとっているので、変な事が起こっているのかもしれません。

anacondaがconda activateを採用してくれていれば… orz

pyenvとanacondaを共存させる時のactivate衝突問題の回避策3種類 - Qiita

pyenv で Anaconda をインストールしても面倒そうです。

次の記事を見て、

pyenv, Anaconda を避けてきたのですが、Numba のインストールでわざわざ LLVM のバージョンを落としてインストールするのが面倒な状況になりました。

だから、Anaconda で一式使える状態にしようと考え始めたので、

ウェブサービスのガチ開発者

pyenvではなくて、今ならDockerとかvagrantとかを使って、コンテナ化/仮想化の方が良いと思います。Pythonのバージョンだけじゃなくて、データベースのバージョンとか、周りのミドルウェアとか、OSのバージョンとか、サービスごとに要件はいろいろあるはずですし。

pyenvが必要かどうかフローチャート - Qiita

Python と必要なパッケージを、設定の面倒さなく、影響を閉じ込めて使いたいので、Docker のコンテナーを使ってみようと思いました。

Docker が初めてなので、Docker Hub の Anaconda(continuumio/anaconda - Docker Hub) で Numba が使えるところまで進めたいと思います。

Docker のインストール

いくつか種類があるようでしたが、

Docker Community Edition for Mac

Docker Community Edition for Mac(Stable) を選びました。

Docker.dmg をダブルクリックして、ウィザードの通りに進めました。

Install it

Double-click Docker.dmg to start the install process.

When the installation completes and Docker starts, the whale in the top status bar shows that Docker is running, and accessible from a terminal.

Docker Community Edition for Mac - Docker Store

特に悩む選択肢はなかったと思います。

次のコマンドでインストールされていることを確認しました。

$ docker --version
Docker version 17.03.1-ce, build c6d412e
$ docker-compose --version
docker-compose version 1.11.2, build dfed245
$ docker-machine --version
docker-machine version 0.10.0, build 76ed2a6

Docker で hello-world

最初なので、hello-world をしてみます。

Run it

Open a command-line terminal, and try out some Docker commands.

Run docker version to check that you have the latest release installed.

Run docker run hello-world to verify that Docker is pulling images and running as expected.

Docker Community Edition for Mac - Docker Store

ターミナルから次のコマンドを実行すると、Docker Hub からイメージをダウンロードして、実行してくれるみたいです。

docker run - Docker Documentation

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
78445dd45222: Pull complete
Digest: sha256:c5515758d4c5e1e838e9cd307f6c6a0d620b5e07e6f927b07d05f6d12a1ac8d7
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://cloud.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

次のコマンドでローカルにあるイメージが確認できるようです。

docker images - Docker Documentation

$ docker images hello-world
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              48b5124b2768        2 months ago        1.84 kB

Docker Hub のこれ(library/hello-world - Docker Hub)がダウンロードされているようです。

Description

Run a command in a new container

docker run - Docker Documentation

docker run は新しいコンテナーの中でイメージを実行するようです。 hello-world を実行した直後はコンテナーは停止しているだけで、プロセスは残っている状態のようです。

次のコマンドでコンテナーが確認できるようです。 -a のオプションで停止しているコンテナーも含めてすべて表示できるようです。

docker ps - Docker Documentation

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
f033a686f252        hello-world         "/hello"            58 seconds ago      Exited (0) 56 seconds ago                       flamboyant_yalow

次のコマンドでコンテナーを削除することができるようです。

docker rm - Docker Documentation

$ docker rm f033a686f252
f033a686f252

コンテナーが削除されているようです。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

コンテナーはイメージとは別なので、イメージから実行するたびに同じ環境のコンテナーが作成できるものだと認識しました。 オブジェクト指向プログラミングで言うとクラスとインスタンスの関係みたいなものでしょうか。

nginx

nginx イメージのダウンロード

次は HTTP サーバーである nginx(library/nginx - Docker Hub) のイメージを実行してみます。

コンテナーの実行(作成)はしないで、イメージだけをローカルにダウンロードする場合は docker pull コマンドを実行するようです。

docker pull - Docker Documentation

$ docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
6d827a3ef358: Pull complete
b556b18c7952: Pull complete
03558b976e24: Pull complete
9abee7e1ef9d: Pull complete
Digest: sha256:52f84ace6ea43f2f58937e5f9fc562e99ad6876e82b99d171916c1ece587c188
Status: Downloaded newer image for nginx:latest

ダウンロードできました。 イメージを確認してみます。

$ docker images nginx
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              5e69fe4b3c31        11 days ago         183 MB

nginx の実行

nginx のイメージからコンテナーを実行(作成)します。

$ docker run --name some-nginx -d -p 8080:80 nginx
bba2da7144dc2649ce97219de5e8d73b68e9bbbc548f332ccde77ee41a06b417

オプションの --name はコンテナーの名前をつけることができるようです。 -d はコンテナーをバックグラウンドで「デタッチド」モード(detached mode)で実行することができるようです。 -p はコンテナーのポートをホストのポートに公開することができるようです。

コンテナーを確認してみます。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                           NAMES
bba2da7144dc        nginx               "nginx -g 'daemon ..."   10 seconds ago      Up 8 seconds        443/tcp, 0.0.0.0:8080->80/tcp   some-nginx

STATUS が UP の状態でコンテナーが動いているようです。 PORTS は 0.0.0.0:8080->80/tcp となっていて、コンテナーの 80 のポートがホストの 8080 で公開されているようです。 NAMES は --name でつけた some-nginx になっています。

http://localhost:8080 にアクセスしてみます。

nginx の標準のページが表示できました。

コンテナーを停止します。 docker stop でコンテナーを停止することができるようです。

docker stop - Docker Documentation

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                           NAMES
bba2da7144dc        nginx               "nginx -g 'daemon ..."   8 minutes ago       Up 8 minutes        443/tcp, 0.0.0.0:8080->80/tcp   some-nginx
$ docker stop bba2da7144dc
bba2da7144dc

コンテナーを削除します。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
bba2da7144dc        nginx               "nginx -g 'daemon ..."   8 minutes ago       Exited (0) 8 seconds ago                       some-nginx
$ docker rm bba2da7144dc
bba2da7144dc

nginx の実行(オリジナルコンテンツを公開)

nginx の標準のコンテンツを公開するのではなく、自分のコンテンツを公開するために、docker の-v オプションを使うようです。

$ docker run --name some-nginx -v /Users/user/content:/usr/share/nginx/html:ro -d -p 8080:80 nginx
71cd51a3403584329421261d2d49a52a03314b6d1b034b23942042377a5cb21e

/Users/user/content はホストのコンテンツのあるディレクトリーです。 /usr/share/nginx/html はコンテナーの nginx の公開用ディレクトリーのようです。 /Users/user/content:/usr/share/nginx/html のように記述することで、ホストのコンテンツのあるディレクトリーがコンテナーの nginx の公開用ディレクトリーにマウントされるようです。 ro はホストのファイルを読み取り専用にできるようです。

公開用のファイルとして、次のファイルを作成して、/Users/user/content/index.html として保存しました。

<!-- index.html -->
<html>
  <head>
    <title>sample nginx</title>
  </head>
  <body>
    <h1>sample nginx</h1>
  </body>
</html>

http://localhost:8080 にブラウザーでアクセスします。

作成したファイルが公開されているようです。

コンテナーを停止して、削除します。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                           NAMES
71cd51a34035        nginx               "nginx -g 'daemon ..."   3 seconds ago       Up 2 seconds        443/tcp, 0.0.0.0:8080->80/tcp   some-nginx
$ docker stop 71cd51a34035
71cd51a34035
$ docker rm 71cd51a34035
71cd51a34035
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

nginx に限らず、コンテナーの中でドキュメントを永続化するのではなくて、ホストのディレクトリーをマウントすることで、データはホスト側で永続化するようです。

Anaconda

Anaconda のイメージのダウンロード

Docker の Anaconda(continuumio/anaconda - Docker Hub) のイメージをダウンロードします。

$ docker pull continuumio/anaconda
Using default tag: latest
latest: Pulling from continuumio/anaconda
8ad8b3f87b37: Pull complete
16deb7e607c1: Pull complete
8f70420d5249: Pull complete
542bf307a831: Pull complete
Digest: sha256:4c9abc36612cfcaebbacb399696184dba27b132ccdae9c2ccbb4fad81f96902d
Status: Downloaded newer image for continuumio/anaconda:latest
$ docker images anaconda
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
continuumio/anaconda   latest              083a044e94ca        3 weeks ago         2.25 GB

2.25GB もありました。

Anaconda の実行

Anaconda のコンテナーを実行(作成)します。

$ docker run -i -t continuumio/anaconda /bin/bash
root@ea0b0fab39e2:/#

オプションの -i-t は、

Assign name and allocate pseudo-TTY (–name, -it)

The -it instructs Docker to allocate a pseudo-TTY connected to the container’s stdin; creating an interactive bash shell in the container

docker run - Docker Documentation

コンテナーの中の bash の標準入出力を使えるもののようです。

/bin/bash はコンテナーの実行直後に /bin/bash コマンドを実行した状態にしておくものと解釈しました。

Python のバージョンを確認してみます。

root@ea0b0fab39e2:/# python --version
Python 2.7.13 :: Anaconda 4.3.1 (64-bit)
root@ea0b0fab39e2:/#

Python のバージョンは 2.7.13, Anaconda のバージョンは 4.3.1 のようです。

exit でコンテナーの中のシェルから抜けることができるようです。

root@ea0b0fab39e2:/# exit
exit

コンテナーを削除します。

$ docker ps -a
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS                       PORTS               NAMES
ea0b0fab39e2        continuumio/anaconda   "/usr/bin/tini -- ..."   13 minutes ago      Exited (130) 7 seconds ago                       eloquent_bose
$ docker rm ea0b0fab39e2
ea0b0fab39e2
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Anaconda の Jupyter

Alternatively, you can start a Jupyter Notebook server and interact with Anaconda via your browser:

docker run -i -t -p 8888:8888 continuumio/anaconda /bin/bash -c "/opt/conda/bin/conda install jupyter -y --quiet && mkdir /opt/notebooks && /opt/conda/bin/jupyter notebook --notebook-dir=/opt/notebooks --ip='*' --port=8888 --no-browser"

You can then view the Jupyter Notebook by opening http://localhost:8888 in your browser, or http://<DOCKER-MACHINE-IP>:8888 if you are using a Docker Machine VM.

continuumio/anaconda - Docker Hub

Docker Hub の Anaconda のページには、ホストのコマンドからコンテナーの Jupyter を実行するところまでしているようです。 ここでは 1 つずつコマンドを確認したいので、対話式にコマンドを入力してみます。

Jupyter をインストールするコマンドも含まれているようですが、Anaconda の中に Jupyter もインストールされているようですので、Jupyter のインストールはしないこととします。

次のようにコマンドを入力しました。

$ docker run -i -t -p 8888:8888 continuumio/anaconda /bin/bash
root@27ab9f9474ac:/# mkdir /opt/notebooks
root@27ab9f9474ac:/# /opt/conda/bin/jupyter notebook --notebook-dir=/opt/notebooks --ip='*' --port=8888 --no-browser
[I 08:42:05.890 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
[W 08:42:05.909 NotebookApp] WARNING: The notebook server is listening on all IP addresses and not using encryption. This is not recommended.
[I 08:42:05.920 NotebookApp] Serving notebooks from local directory: /opt/notebooks
[I 08:42:05.920 NotebookApp] 0 active kernels
[I 08:42:05.921 NotebookApp] The Jupyter Notebook is running at: http://[all ip addresses on your system]:8888/?token=2c0e5f6c42d282d4d4bad1f00502f1366448ef21e8cba8b4
[I 08:42:05.921 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 08:42:05.923 NotebookApp]

    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
        http://localhost:8888/?token=2c0e5f6c42d282d4d4bad1f00502f1366448ef21e8cba8b4

Jupyter の実行に次の引数が指定されていますが、雰囲気で察することにしました。

  • --notebook-dir=/opt/notebooks
  • --ip='*'
  • --port=8888
  • --no-browser

一番下に表示されている token のついた URL にブラウザーからアクセスします。

Anaconda の Jupyter が表示されました。

Anaconda の Numba

Jupyter で Numba — Numba の Example にあるコードを実行してみます。

Numba のコードもエラーにならずに実行することができました。

コンテナーの /opt/notebooks に作成したファイルが保存されるのですが、コンテナーの中のデータは、コンテナーを停止して削除するとなくなってしまいます。 データの永続化のために docker run-v のオプションでホスト側のディレクトリーをコンテナーにマウントして、ホスト側でデータを永続化しようと思いました。

Anaconda3

後から気づいたのですが、Python 3.5 の Anaconda の Docker イメージがこちらにありました。

continuumio/anaconda3 - Docker Hub

$ docker pull continuumio/anaconda3
Using default tag: latest
latest: Pulling from continuumio/anaconda3
8ad8b3f87b37: Already exists
26e5bbd29116: Pull complete
26a23cde1ff7: Pull complete
0947a413f98b: Pull complete
Digest: sha256:d04148529d340097c08677061e7d08f42e13a51dbdbf7488d92af0a65fd82973
Status: Downloaded newer image for continuumio/anaconda3:latest
$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
continuumio/anaconda3   latest              2f12b7e5bd80        3 weeks ago         2.23 GB

2.23 GB ありました。 Python 2.7 より少し小さいみたいです。

終わり

Numba のエラーから遠回りをしたような… Anaconda が使えるようになりましたので、しばらくこのコンテナーで使ってみたいと思います。

参考