まえおき
- 以下の文章はR Docker tutorial by ropenscilabsを翻訳したものです。
第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の実行画面を以下に示す。あなたのマシンでも同じような画面が見えているはずだ(訳注:スクリーンショットを撮影した際はこの記事を書いていたので、ファイルとか色々違うと思う)。
仮想マシン(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以外のGSLやGDAL、JAGSなどに依存するパッケージが多くある。これらを起動中のコンテナに対してインストールするには、新しいターミナルウィンドウを開き、以下のように行う。
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イメージをアップロードしてみよう。
- https://hub.docker.com/ にログインする。
- Create Repositoryをクリックする。
- 名前を設定し(例:: verse_gapminder)、詳細を記述してCreateをクリック。
- 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へのプッシュは素晴らしいのだが、いくつかの欠点もある。
- 帯域: 多くのISPはダウンロード帯域幅よりアップロード帯域幅の方が狭い。
- プライベートリポジトリのために課金しなければ、プッシュはすなわち公開を意味する。
- 複数のクラスタで作業する場合、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
イメージ上に、gapminderとgslをインストールする。さらに、内容について説明した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
これで、彼はあなたの研究イメージを入手したはずだ。