R
docker

R Docker tutorialの訳

まえおき

第1章 Dockerとは何で、何のために使うのか?

この章の目的

  • Dockerの基本的な考え方を理解する。
  • Dockerが便利な理由を確認する。

なぜDockerか

Rを使用して分析を行い、出来上がったコードを知人に送りたい、という場合を考えてみよう。あなたの知人が全く同じデータセットを使って全く同じコードを実行したとしても、結果が僅かに異なるということがあるだろう。これには、OSが違うだとか、Rのパッケージのバージョンが違うだとか、いろいろな理由が考えられる。Dockerはこのような問題を解決するための手段になりうる。

Dockerコンテナはコンピュータの中のコンピュータの様に見える。そしてこのコンピュータは知人にそのまま送ることができ、このコンピュータを起動してコードを実行すれば、全く同じ結果が得られるのだ。

あなたがDockerを使うべき理由を要約すればこうだ。

  • 依存関係に関する問題を、OSレベルではなくRやLaTeXパッケージのバージョンにまで絞り込むことができる。
  • 分析の再現可能性を確実なものとする。

Dockerは他にも以下の点で役立つ。

  • 移植性: Dockerコンテナは他のマシンに簡単に送ることができるので、自分のマシン上でセットアップしてから他のより強力なマシンでそれを実行するということができる。
  • 共有性: Dockerコンテナは誰にでも送ることができる(Dockerの使い方を知ってさえいれば)。

基本用語

イメージ(image)とコンテナ(container)という単語がこの後も頻繁に出てくる。イメージのインスタンスがコンテナである。イメージは仮想マシンの設定である。イメージを起動すると、そのインスタンスが作成されるが、それをコンテナと呼ぶ。同じイメージから複数のコンテナを同時に起動することもできる。

第2章 DockerでRStudioを起動する

この章の目的

  • RStudioをDockerコンテナの中で起動する。
  • ローカルマシンのボリュームとDockerコンテナをリンクする。
  • コンテナにデータを読み込み、プロットを行う。

インストール

まずはDockerをインストールしよう。インストールガイド(Install Docker | Docker Documentation)には、多数の入門資料へのリンクがある。今回のレッスンのためにこれらの資料を全て読む必要は無いが、これらはDockerの使い方に関する素晴らしい入門資料となっている。

DockerでRStudioを起動する

Dockerを起動するためにはまずUnixシェルを起動する必要がある。MacまたはWindowsを使用している場合は、Docker Quickstart Terminalをインストールしておく。Docker Quickstart Terminalを起動すると、シェルが起動しているように見えると思うが、これは実際にはDockerを使用した仮想マシンである。特に注記がない限り、このチュートリアルの残りの部分はこの中で行う。Linuxを使用している場合は通常のターミナルを使用すれば良い。

Macを使用している場合は、ターミナルでDockerの使用方法を選択したり設定を行うこともできる。チュートリアルの途中で Cannot connect to the Docker daemon. Is the docker daemon runnning on this host?というエラーに遭遇した場合は、以下のコマンドを実行することで問題が解決する場合がある。

eval "$(docker-machine env default)"

次に、Dockerに対して既存のイメージを起動するように命令する。今回使用するのはRockerのverseイメージだ。これはRStudioをコンテナ内で利用できるのに加えて、便利なRパッケージが最初からインストールされている。

docker run --rm -p 8787:8787 rocker/verse

-p--rmはコンテナの起動方法に関するオプションである。-pはDockerにポートの利用を許可する。これによりRStudioをブラウザからりようできるようになる(オプションに続く8787:8787で場所を指定している)。--rmはコンテナ終了時にコンテナを確実に削除することを指定する。もしこれを行わないと、コンテナを起動する度にこれがローカルマシンに保存されてしまう。これにより、コンテナを手動で削除しない限り大量のディスク容量が消費される可能性がある。コンテナを保存する方法は後で示す。

ローカルにインストールされていないコンテナを起動しようとした場合は、Dockerは自動的にDocker Hub(Docker imageのオンラインリポジトリ)からコンテナを検索し、存在すればそれをダウンロードする。

上記のコマンドによってRStudioが背後で起動する。これに接続するには、ブラウザを起動し、http://につづいて自分のIPアドレスと:8787を入力する。MacかWindowsの場合は、IPアドレスはDocker Quickstart Terminalを起動した際に最初の行に表示される。例を以下に示す。

                        ##         .
                  ## ## ##        ==
               ## ## ## ## ##    ===
           /"""""""""""""""""\___/ ===
      ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ /  ===- ~~~
           \______ o           __/
             \    \         __/
              \____\_______/


docker is configured to use the default machine with IP 192.168.99.100
For help getting started, check out the docs at https://docs.docker.com

このように表示されていたとすれば、http://192.168.99.100:8787をブラウザのurl欄に入力する。

LinuxマシンでDockerを起動している場合は、IPアドレスの代わりにlocalhostを使用することもできる。つまり、http://localhost:8787でアクセスできる。

アクセスに成功すると、RStudioの起動画面が表示されるはずだ。ユーザー名とパスワードはいずれもrstudioを入力する。

これでブラウザ上でRStudioが使えるようになっているはずだ。これはデスクトップアプリとしてRStudioを起動した時と同じように使用できる。

RStudio serverの実行画面を以下に示す。あなたのマシンでも同じような画面が見えているはずだ(訳注:スクリーンショットを撮影した際はこの記事を書いていたので、ファイルとか色々違うと思う)。

rstudio_server_example.png

仮想マシン(Dockerコンテナ)内のファイルを見てみよう。「file -> open file」で確認してみると、何もファイルは無いはずだ。このイメージにはファイルが無い事が原因だ。次に、Rのスクリプトを作成してみよう。例えば「file -> New file -> R Script」とやって、以下のコードを記入して実行、保存してみよう。

x <- 1:5
y <- 6:10
plot(x, y)

この状態で再度ファイルを確認してみると、保存したファイルが見つかるはずだ。

いま、我々はコンテナを--rmオプション付きで起動したので、作成したものは全てなくなるはずだ。これを確認してみよう。まずはRStudioを開いているブラウザタブを閉じよう。そして、Dockerを起動しているターミナルウィンドウに戻り、Control+Cをタイプする。これにより、Dockerコンテナが終了する。

そうしたら再度、先ほどと同じ方法でコンテナを起動し、RStudioを起動し、先程作成したプロットとファイルがまだ存在するか確認してみよう(無くなっているはずだ)。

volumeをコンテナにリンクして、データアクセスとファイル保存を可能にする

コンテナを終了させるとファイルが消えてしまうのであれば、作業内容はどうやって保存したら良いのだろうか?

一つの解決策は、volume(例えばローカルのハードドライブ)にコンテナをリンクさせることだ。これにより、データにアクセスすることもファイルを保存することもできるようになる。

今回は、コンテナ起動時に-vオプションを使用し、プロジェクトのルートディレクトリを指定する。起動コマンドは以下で示すが、パスについてはあなたが何処にデータを保存しているかに依存する。:の左側にはあなたのコンピュータ上のパスを入れる。これを知りたければ、ターミナル上でプロジェクトのルートディレクトリへ移動し、pwdを入力すれば良い。:の右側には、コンテナ内のパスを入力する。これは大抵の場合/home/rstudio/から始まる。

docker run --rm -p 8787:8787 -v /Users/rito/Documents/R/r-docker-tutor:/home/rstudio/r-docker-tutor rocker/verse

再度RStudioを開いてみよう。ファイルとディレクトリが見えるようになっているはずだ。ここで例えばワーキングディレクトリをr-docker-tutorに変更し、データの読み書きを行ってみよう。

x <- 1:5
y <- 1:5
df <- data.frame(x, y)
readr::write_csv(df, "data/test.csv")

df2 <- readr::read_csv("data/test.csv")

library(ggplot2)
qplot(df2$x, df2$y)
ggsave("data/test.pdf")

上記のスクリプトをtest.Rという名前でワーキングディレクトリに保存し、実行したらコンテナを終了してみよう。その後、ローカルのr-docker-tutorディレクトリを確認してみると、先程作成したファイルが存在しているはずだ。

この章のまとめ

この章ではDockerコンテナの起動方法とRStudioをブラウザで実行する方法を学んだ。コンテナ起動時に--rmフラグを使用すると、コンテナは一時的なものになる。すなわち、コンテナ終了時に削除される。これは、コンピュータの容量を過剰に消費しないように行う。また、我々のマシンの中のボリュームとDockerコンテナをリンクする方法も学んだ。これにより必要に応じてファイルの読み書きが可能になる。

今回使用したコンテナにはRとRStudioと有用なパッケージ群が既にインストールされている。この後のレッスンでは、コンテナを編集して新しいパッケージをインストールする方法と、我々の仕事に役立つ他のコンテナを探す方法を学んでいく。

練習問題

ローカルマシン上でRかRStudioを起動し、installed.packages()関数でインストール済みのRパッケージを確認してみよう。もし隣の人が居たら比べてみよう。そして同じ作業をコンテナから起動したブラウザ上のRStudioでもやってみよう。

第3章 Rのパッケージをインストールする

RStudio内でRのパッケージをインストールする

install.packages()関数を使って、デスクトップ版のRStudioと同様にブラウザ上のRStudioでもパッケージをインストールできる。先の章で学んだようにverseコンテナを立ち上げてRStudio serverを起動し、gapminderパッケージをインストールしてデータを確認してみよう。

install.packages('gapminder')
library(gapminder)
head(gapminder)
country continent year lifeExp pop gdpPercap
Afghanistan Asia 1952 28.801 8425333 779.4453
Afghanistan Asia 1957 30.332 9240934 820.8530
Afghanistan Asia 1962 31.997 10267083 853.1007
Afghanistan Asia 1967 34.020 11537966 836.1971
Afghanistan Asia 1972 36.088 13079460 739.9811
Afghanistan Asia 1977 38.438 14880372 786.1134

素晴らしい!gapminderパッケージをインストールしてデータセットを使った作業ができるようになった。だがちょっと待った。コンテナを終了させたら何が起こるだろうか?今の状態のコンテナは保存していないので、別のインスタンスのコンテナを作成したら、gapminderパッケージを再度インストールしなければならないだろう。

これを防ぐためには、Docker commitを実行してイメージを保存すればよい。これにより、次回コンテナを起動する際にはgapminderパッケージを含むイメージのインスタンスを実行できる。この作業を実行するためには、Dockerコンテナを終了させる前に別のターミナルウィンドウを開く必要がある。

特定のバージョンのイメージを保存するためには、そのコンテナ固有のハッシュ値を見つける必要がある。これは、以下のコマンドを新しく起動したターミナルウィンドウで実行することで確認できる。全ての起動中のコンテナが確認できるはずだ。

docker ps

コマンドの出力は、以下のような感じになっているはずだ。そしてこのコンテナのハッシュ値は最初の列のアルファベットと数字の混ざった文字列だ。

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
892a47d5b60f        rocker/verse        "/init"             2 hours ago         Up 2 hours          0.0.0.0:8787->8787/tcp   eloquent_easley

イメージを保存するためには、新しいターミナルウィンドウの中で次のようにコマンドを実行する。

docker commit -m "verse + gapminder" 892a47d5b60f verse_gapminder

また、イメージ保存のためには変更内容を記述したコミットメッセージを入力する必要がある。これは、-mフラグに続けてクォートで括ったメッセージを記述することで行う。続いて、コンテナのハッシュ値を指定する(892a47d5b60f)。最後に、イメージの新しい名前を指定する。今回はverse_gapminderという名前にした。

これで、ローカルマシン上には2つのDockerイメージが保存されたはずだ。これは、docker imagesコマンドで確認できる。

docker images
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
verse_gapminder               latest              6518452feb37        4 seconds ago       2.52GB
rocker/verse                  latest              88dba27cef17        11 hours ago        2.51GB

それぞれのイメージからDockerコンテナを実行することで中身を確認できる。gapminderパッケージはverse_gapminderイメージにだけインストールされていて、rocker/verseイメージには入っていないはずだ。

Rシステム以外の依存関係をインストールする

RにはR以外のGSLGDALJAGSなどに依存するパッケージが多くある。これらを起動中のコンテナに対してインストールするには、新しいターミナルウィンドウを開き、以下のように行う。

docker ps # 起動中のコンテナのIDを確認する
docker excec -it <コンテナID> bash # コンテナ内でbashシェルを起動するためのdockerコマンド
apt-get install libgsl0-dev # パッケージをインストールする。この例はGSLをインストールしている。

もしapt-getでエラーが出たら、先にapt-get updateを実行しておく。

この変更を保存するためには、さたに他のターミナルウィンドウを起動してdocker commitを使用する。

docker commit -m "verse + gapminder + GSL" 892a47d5b60f verse_gapminder_gsl

これが終わったらdocker execを実行したターミナルウィンドウに戻り、exitを入力してコンテナを閉じる。

練習問題

自分自身や他の誰かが、後でDockerイメージに含まれているものを確認したい、ということがあるだろう。verse_gapminder_gslの詳細を、READMEファイルにまとめてみよう。インストールされているパッケージの一覧については、installed.packages()関数が利用できるだろう。

第4章 Docker Hubへのプッシュとプル

この章の目的

  • イメージがどこから来るのかを理解する。
  • DockerイメージをDocker Hubからプルする。
  • DockerイメージをDocker Hubへプッシュする。

Docke Hubからイメージを取得する

Docker HubはオープンなDockeイメージが保存されている場所だ。我々は最初のイメージを次のように起動した。

docker run --rm -p 8787:8787 rocker/verse

このコマンドを実行すると、最初にこのイメージがコンピュータ上で利用可能であるかどうかと、Docker Hub上からダウンロードしたものでないかどうかを確認する。Docker Hubからのイメージ取得は自動的に行われる。イメージの取得はしたいが実行はしたくないという場合はdocker pullを用いる。

docker pull rocker/verse

Docker Hubへイメージを上げる

自分で作成したDockerイメージを世界中の人と共有したいと思うのであれば、https://hub.docker.com/ のアカウントにサインアップしよう。Eメールの確認を済ませたら、初めてのDockerイメージをアップロードしてみよう。

  1. https://hub.docker.com/ にログインする。
  2. Create Repositoryをクリックする。
  3. 名前を設定し(例:: verse_gapminder)、詳細を記述してCreateをクリック。
  4. Docker Hubにコマンドラインからログインする
docker login --username=<あなたのユーザー名> # --email=<あなたのメールアドレス>

(訳注: メールアドレスは不要だった)

パスワードの入力が求められるので入力すると、次のようなメッセージが表示されるはずだ。

Login Succeeded

次にイメージIDを確認する。

docker images

そしてイメージにタグを付ける。

docker tag 6518452feb37 <あなたのユーザー名>/verse_gapminder:firsttry

数字はイメージIDと一致している必要があり、:firsttryがタグである。一般的には、タグはこのコンテナを何と一緒に使うべきなのかや、何を表しているのかを理解するのに役立つようなものであることが望ましい。もしコンテナを論文で分析に使用するのなら、DOIやジャーナルのシリアルナンバーを使うことを検討して欲しい。特定のバージョンのコードやデータの使用を示すのも良い選択である。

最後に、イメージを先程作成したリポジトリにプッシュする。

docker push <あなたのユーザー名>/verse_gapminder

これであなたのイメージは誰でも使用できる状態になった。

イメージの保存と読み込み

Docker Hubへのプッシュは素晴らしいのだが、いくつかの欠点もある。

  1. 帯域: 多くのISPはダウンロード帯域幅よりアップロード帯域幅の方が狭い。
  2. プライベートリポジトリのために課金しなければ、プッシュはすなわち公開を意味する。
  3. 複数のクラスタで作業する場合、Dockerコンテナを使用するジョブを起動する度にコンテナの取得が行われる。ジョブ数が多くなるとこれは非常に遅くなる場合がある。

これらの問題は、コンテナをtarアーカイブとしてローカルに保存して、必要に応じてロードするようにすることで解決できる。

イメージをプルし、コミットやビルドをした後に保存するためには、docker saveコマンドを使用する。例えばverse_gapminderイメージのローカルコピーを作成するには、以下のようにコマンドを実行する。

docker save verse_gapminder > verse_gapminder.tar

セーブしたイメージの読み込みはdocker loadで行う。

docker load --input verse_gapminder.tar

練習問題

  • Docker Hubで興味のあるイメージを探してみよう。何か役に立ちそうなものは見つかっただろうか?
  • 知らない人のDockerイメージを使用することの長所と短所について話し合ってみよう。

第5章 Dockerfiles

これまでに、ベースのイメージから開始してDocker内部でRStudioを起動する方法、そして、docker commitを使用してイメージの中身を変更する方法を学んだ。これは自分が行ったことを後で再現するための素晴らしい方法だ。しかし、中に何が入っているのかについての明確な記録が欲しい場合、そしてコンテナの中身をもっと簡単に変更したい場合は、一体どうすれば良いのだろうか?このような方法があれば、変化したり進化したりする可能性のあるプロジェクトの実行環境をメンテナンスする場合に便利である。そして、これはDockerfileによって容易に可能となる。

Dockerfileはベースイメージに何をどうやって加えるのかを記述した指示書だ。これは、一連のレイヤーによってカスタムイメージを構築する。新しくDockerfileというファイルを作成し、以下の内容を記述してみよう。

FROM rocker/verse:latest

この記述はDockerをrocker/verseイメージから開始することを示している。FROMコマンドは常にDockerfileの最初に記述する必要がある。これはいわばパイの底だ。

次に、gapminderパッケージをプレインストールして利用可能にしておくために、別のレイヤーを加えよう。

RUN R -e "install.packages('gapminder', repos = 'https://cran.ism.ac.jp')"

RUNコマンドはイメージをビルドする際にシェルコマンドを実行する。パイの比喩で言えば具を詰めていくようなものだ。今回の例では、gapminderパッケージをコマンドラインからinstall.packages()関数を呼び出している。これはRStudioからinstall.packages('gapminder')と実行するのと同じ効果がある。Dockerfileを保存し、ターミナルに戻ったら、次のコマンドを実行してイメージをビルドしよう。

docker build -t my-r-image .

-t my-r-imageでイメージの名前を決めている(イメージ名は常に小文字でなければならない点に注意しよう)。.はイメージに必要な全ての材料がカレントディレクトリに存在することを指定している。イメージ一覧を確認してみよう。

docker images

my-r-imageがリストの中に見つかるはずだ。起動してみよう。

docker run --rm -p 8787:8787 my-r-image

RStudioのターミナルでgapminderをテストしてみよう。

library('gapminder')
readr::write_csv(gapminder, 'data/gapminder-FiveYearData.csv')

今作成した新しいイメージでは、gapminderがプレインストールされていて使用可能となっているはずだ。

パイはほとんど完成だ!あとはトッピングをしよう。gapminderのようなパッケージ以外にも、データなどの静的なファイルが必要となる場合がある。これを追加するには、ADDコマンドをDockerfile内に記述する。

ADD data/gapminder-FiveYearData.csv /home/rstudio

Dockerイメージをリビルドして起動してみよう。

docker build -t my-r-image .
docker run --rm -p 8787:8787 my-r-image

Rstudioを起動してファイルを確認してみると、gapminder-FiveYearData.csvが確認できるはずだ。このようにしてDockerイメージの一部としてファイルを取り込むことができる。これにより、イメージの他の部分と一緒に、全く同じ状態のデータを常に使用することができるようになる。

より高度なtips: レイヤーのキャッシュ

Dockerイメージのビルドとリビルドの際、次のような表示があったことだろう。

Step 2/3 : RUN R -e "install.packages('gapminder', repos = 'https://cran.ism.ac.jp')"
 ---> Using cache
 ---> a6dcda43e2f8

ここでキャッシュバージョンのコマンドが使用されている点に注意して欲しい。イメージをリビルドすると、Dockerは以前のバージョンのイメージで同じコマンドが実行されていないかを確認する。それぞれのステップは別々のレイヤーとして保存され、Dockerはこれらに変更がなければ以前と同じ順序でこれを再利用する。したがって、時間がかかるようなセットアッププロセスはDockerfileの上の方に書くようにして、それより上には何も書かないように注意しよう。特に頻繁に変更するような部分は書かないこと。これにより、ビルドプロセスを大きくスピードアップさせることができる。

この章のまとめ

このレッスンでは、イメージを自由に再作成できるようになるために、Dockerfileの作成方法を学んだ。我々が学んだ3つの主なコマンドは、

  • FROMはDockerfileの最初に書き、元にしたいイメージを指定する。
  • RUNはベースイメージ上でシェルコマンドを実行し、ダウンロードやインストールなどの作業を行う。
  • ADDはコンピュータ上からファイルを追加する。

イメージはdocker built -t my-r-imageを実行することでビルドされる。ADDコマンドで追加するファイルはDockerfileと同じディレクトリに配置すること。

練習問題

  • 第3章の最後で作成したようなイメージを作成するDockerfileを作成しよう。つまり、rocker/verseイメージ上に、gapmindergslをインストールする。さらに、内容について説明したreadmeをイメージに追加しよう。

rocker/verseイメージのDockerfileはDocker Hubから見つけることができる。

  • DockerfileをREADMEファイル(第3章で準備したもの)と比較するとどうだろうか?
  • Docker HubはファイルをGithubやBitbucketに置くと、自動的にイメージをビルドできる。イメージを直接コミットすることに比べて何が利点だろうか?
  • rocker/verseのビルドの詳細を探してみよう。何が分かるだろうか?

第6章 分析の全てを共有する

Dockerfileの使い方を学んだので、分析の全てを共同研究者に送ることができるようになった。分析の実行に必要な全ての依存関係と、データ、そして分析そのものを含むイメージを共有してみよう。

共有するイメージはDockerfileから作成する。以前も使用したrocker/verseイメージを今回も使おう。今回は特定バージョンのR(3.3.2)を使用したいと思う。これは、イメージに対してバージョンタグを付けることで可能になる。rocker/verseイメージで使用可能な全てのタグは、rocker/verse - Docker Hubで確認できる。バージョンタグは分析結果を再現可能にしたい場合に特に便利だ。

FROM rocker/verse:3.3.2

分析には、gapminderデータを使用することにしよう。従って、イメージにパッケージをインストールする必要がある。Dockerfileを編集してパッケージをインストールしよう。

RUN R -e "install.packages('gapminder', repos = 'https://cran.ism.ac.jp')"

あとは分析を書いてDockerfileに追加するだけだ。

今回の分析では、1人あたりGDPと平均寿命の関係をプロットする。

以下のスクリプトを書き込んだファイルを新しく作成しよう。

library(ggplot2)
library(gapminder)

life_expentacy_plot <- ggplot(data = gapminder) +
  geom_point(aes(x = lifeExp, y = gdpPercap, colour = continent))

スクリプトはanalysis.Rという名前で保存し、Dockerfileに追加しよう。

ADD analysis.R /home/rstudio

ビルドを行い、共同研究者と研究したいすべてのものが入っていることを確認する。

docker build -t my-analysis .

分析結果はイメージの一覧に加わっているはずだ。

docker images

あたらしいイメージを実行し、目的のものが全て含まれているかどうかを確認しよう。

docker run -dp 8787:8787 my-analysis

gapminderはインストールされており、スクリプトもあるはずだ。素晴らしい!

この分析結果はDocker Hubにプッシュすることもできる。

Docker HubでCreate Repositoryをクリックしたら、名前(例: gapminder_my_analysis)と説明を書き込み、Createをクリックしよう。

Docker Hubにはコマンドラインから次の様にログインできる。

docker login --username=<ユーザー名>

ユーザー名はアカウント登録時に指定したものを使う。プロンプトが表示されたらパスワードを入力する。

イメージIDは次のように確認できる。

docker images

こんな感じの出力があるので…

REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
my-analysis                      latest              dc63d4790eaa        2 minutes ago       3.164 GB

イメージにタグをつける。

docker tag dc63d4790eaa <Docker Hubのユーザー名>/gapminder_my_analysis:firsttry

そして作成したリポジトリにプッシュする。

docker push <Docker Hubのユーザー名>/gapminder_my_analysis

これでイメージは誰でも使用できる状態になった。

共同研究者がイメージをダウンロードするには、コマンドラインから次のコマンドを実行するだけだ。

docker pull <Docker Hubのユーザー名>/gapminder_my_analysis

これで、彼はあなたの研究イメージを入手したはずだ。