# 遺伝研でもポータブルな環境を利用するためにDockerに入門する
[TOC]
## 目的
+ できるようになること
+ 環境をビルドしてどこでも実行可能な環境を得られる
+ docker Hubを経由して遺伝研で実行できる
+ できるようにならないこと
+ dockerに対する深い理解
+ singularityに対する深い理解
+ その他諸々の知識
## 前準備
+ dockerのアカウントの作成とインストール
+ https://www.docker.com/
+ 自分のDockerfileをUploadするためには必要です
## 前置き
+ 気になる人はDockerを読んでください
+ https://www.oreilly.co.jp/books/9784873117768/
### コンテナとはなにか
+ コンテナは,ホストOSとリソースを共有するのでVM(仮想マシン)より効率的.コンテナの起動や停止は一瞬で行なる.コンテナ内で動作するアプリケーションのオーバーヘッドはホストOSで直接動作するアプリケーションと比較してもごく僅かかほとんどない.
+ コンテナは可搬性が高いので,動作環境の僅かな違いに起因するタイプのバグを撲滅できる可能性が高い
+ 軽量.VMを上げるよりはるかに多くのコンテナを同時起動できる
+ 環境設定に時間を掛ける必要がない.すでにあるベースのイメージを元に作成すれば自分で環境を構築する必要がなく複雑なプログラムが動作する.
### 三行で
+ **どこでも**
+ **かんたん**
+ **速い**
## docker事始め
### 1. dockerの存在確認
```shell
$ docker version
Client: Docker Engine - Community
Version: 18.09.2
API version: 1.39
Go version: go1.10.8
Git commit: 6247962
Built: Sun Feb 10 04:12:39 2019
OS/Arch: darwin/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 18.09.2
API version: 1.39 (minimum version 1.12)
Go version: go1.10.6
Git commit: 6247962
Built: Sun Feb 10 04:13:06 2019
OS/Arch: linux/amd64
Experimental: false
```
### 2. bashの実行
dockerでdebianを動かしてbashを起動してみよう!
```shell
$ docker run -it debian /bin/bash
root@f3bb2f034004:/# echo "hello world!"
hello world!
root@f3bb2f034004:/# exit
exit
```
### 3. 起動しているdockerの確認
```shell
$ docker run -h CONTAINER -it debian /bin/bash
root@CONTAINER:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@CONTAINER:/# mv /bin /basket
root@CONTAINER:/# ls
bash: /bin/ls: No such file or directory
root@CONTAINER:/#
```
#### 3.1. この状態で別窓でTerminalを起動して中身を確認してみる
```shell
$ docker ps
CONTAINER ID IMAGE COMMAND ... NAMES
6ae21eea7dd6 debian "/bin/bash" ... pedantic_kalam
```
#### 3.2. `pedantic_kalam`の中身の変更の確認
```
$ docker diff pedantic_kalam
A /basket
A /basket/zgrep
A /basket/cat
A /basket/chmod
A /basket/dmesg
A /basket/gunzip
A /basket/mount
A /basket/stty
...
```
#### 3.3. 実行ログの確認
```shell
$ docker logs pedantic_kalam
root@CONTAINER:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@CONTAINER:/# mv /bin /basket
root@CONTAINER:/# ls
bash: /bin/ls: No such file or directory
```
### 4. 実行中のshellに戻ってコンテナの削除
```shell
root@CONTAINER:/# exit
exit
$ docker rm pedantic_kalam
pedantic_kalam
```
## 動くCapRのコンテナを作成してみる
### 1. debianのイメージを引っ張って実行してみよう
```shell
$ docker run -it debian
root@ae8a726edf91:/#
```
+ `run`:コンテナの起動
+ `-it`:インタラクティブセッションの開始
### 2. `mkdir`してみる
```
root@ae8a726edf91:/# mkdir sample
root@ae8a726edf91:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sample sbin srv sys tmp usr var
```
### 3. exitで抜けられる
```shell
root@ae8a726edf91:/# exit
exit
```
### 4. 過去に実行したものに接続するときは`docker start -i [name]`
略称でもいい。今回はae8a726edf91ではなくae8でも良い。
```shell
$ docker start -i ae8
root@ae8a726edf91:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sample sbin srv sys tmp usr var
```
### 5. 必要なライブラリをインストールしよう
```
root@ae8a726edf91:/# apt-get update
root@ae8a726edf91:/# apt-get install g++ make git
```
### 6. CapRを引っ張ってこよう
```
root@ae8a726edf91:/# git clone https://github.com/fukunagatsu/CapR.git
```
### 7. makeしよう
```shell
root@ae8a726edf91:/# cd CapR
root@ae8a726edf91:/# make
```
### 8. (コンテナ内で)実行してみよう
```shell
root@ae8a726edf91:/# apt-get install wget
root@ae8a726edf91:/# wget https://molb7621.github.io/workshop/_downloads/sample.fa
root@ae8a726edf91:/# /CapR/CapR sample.fa sample.out 10
root@ae8a726edf91:/# head sample.out
```
### 9. commitする
以後`debian`と書いていたところを`test/capr`と書けば実行できるようになるぞ!
```shell
$ docker commit ae8 test/capr
```
### 10. 実行してみよう
https://molb7621.github.io/workshop/_downloads/sample.fa
```shell
$ wget https://molb7621.github.io/workshop/_downloads/sample.fa
$ docker run -it -v $PWD:/home test/capr /CapR/CapR /home/sample.fa /home/sample.out 10
$ head sample.out
```
+ `-v $PWD:/home`:現在のディレクトリを`/home`にバインドする
### 11. Dockerfileを書いてみよう
+ Dockerfileに書くと複雑な処理がわかりやすくなる
まずはディレクトリを作ってその中にDockerfileを作成する
```sh
$ mkdir capr
$ cd capr
$ touch Dockerfile
```
Dockerfileに以下を記述する
```dockerfile
FROM debian
RUN apt-get update
RUN apt-get install -y g++ make git
RUN git clone https://github.com/fukunagatsu/CapR.git
WORKDIR /CapR
RUN make
ENTRYPOINT ["/CapR/CapR"]
```
### 12. ビルドする
カレントディレクトリのDockerfileを元にビルドする
```shell
$ docker build -t test/capr .
```
これで`docker run -it test/capr`すれば利用できるようになる
### 13. Docker Hubへのアップロード
DockerHubへアップロードすることでどこからでも利用可能になる
```
$ docker build -t natuski/capr .
$ docker login && docker push natuski/capr
```
### 14. 容量の確認
作成したイメージの大きさは`docker images`で確認できる
```shell
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
natuski/capr latest 4709262dcf12 5 seconds ago 375MB
```
このままだと実行に必要ないライブラリまで入っていることになる.
### 15. 軽量OSを使ってみよう
docker用の軽量OS`alpine`を使って書くとこんな感じになる
```dockerfile
FROM alpine AS builder
RUN apk add --no-cache git make g++
RUN git clone https://github.com/fukunagatsu/CapR.git
WORKDIR /CapR
RUN make
ENTRYPOINT ["/CapR/CapR"]
```
これでビルドしてみると,
```
$ docker build -t natuski/capr_alpine .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
natuski/capr latest 4709262dcf12 5 seconds ago 375MB
natuski/capr_alpine latest 4a4140483354 2 minutes ago 177MB
```
### 16. もっと軽量化してみよう
[マルチステージビルド](https://blog.alexellis.io/mutli-stage-docker-builds/)を利用するともっと軽くなる.ビルドするイメージとバイナリを置くイメージを別にできる.Dockerfileをこね直して,ビルドした結果を新しいイメージにコピーする.動作に必要なライブラリも追加しておく.
```dockerfile
FROM alpine AS builder
RUN apk add --no-cache git make g++
RUN git clone https://github.com/fukunagatsu/CapR.git
WORKDIR /CapR
RUN make
FROM alpine
RUN apk add --no-cache libstdc++
COPY --from=builder /CapR/CapR /CapR
ENTRYPOINT ["/CapR"]
```
これでビルドすると驚異の7.3MB!
```shell
$ docker build -t natuski/capr_alpine_bin .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
natuski/capr latest 4709262dcf12 5 seconds ago 375MB
natuski/capr_alpine_bin latest 732436cac06d 2 minutes ago 7.3MB
natuski/capr_alpine latest 4a4140483354 2 minutes ago 177MB
```
これをuploadしておこう.
```shell
$ docker login && docker push natuski/capr_alpine_bin
```
### 17. 遺伝研で使ってみよう
遺伝研で使用する場合には
```shell
# モジュールのロード
$ module load singularity
# イメージのプル
$ singularity pull docker://natuski/capr_alpine_bin
```
runするとエントリポイントが呼ばれる.そのままでも自分のディレクトリはマウントされているので,サンプルのfastaファイルを引っ張ってきて実行すればok
```shell
$ wget https://molb7621.github.io/workshop/_downloads/sample.fa
$ singularity run capr_alpine_bin.simg ./sample.fa ./sample.out 10
$ head ./sample.out
```
## jupyter-notebookを作成してみる
### 1. `python:3.7`のイメージを引っ張ってくる
エントリポイントがpythonになっているので,何も指定しないとpythonが起動する
```shell
$ docker run -it python:3.7
Python 3.7.3 (default, Jun 11 2019, 01:05:09)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
```
### 2. pipのイメージを入れたいので`bash`を上げる
名前は`jupyter`にでもしておこう.実行したイメージに続けて実行したいプログラムを記載する.今回は`bash`
```shell
$ docker run -it --name jupyter --hostname jupyter python:3.7 bash
root@jupyter:/# ipython
bash: ipython: command not found
root@jupyter:/# pip install jupyter
Collecting jupyter
Downloading https://files.pythonhosted.org/packages/83/df/0f5dd132200728a86190397e1ea87cd76244e42d39ec5e88efd25b2abd7e/jupyter-1.0.0-py2.py3-none-any.whl
Collecting nbconvert (from jupyter)
Downloading https://files.pythonhosted.org/packages/35/e7/f46c9d65f149271e47fca6ab084ef5c6e4cb1870f4c5cce6690feac55231/nbconvert-5.5.0-py2.py3-none-any.whl (447kB)
|████████████████████████████████| 450kB 3.2MB/s
...
root@jupyter:/# ipython
Python 3.7.3 (default, Jun 11 2019, 01:05:09)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.5.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
```
### 3. ipythonを終了し,`exit`して`commit`する
```shell
root@jupyter:/# exit
exit
$ docker commit jupyter test/jupyter
sha256:84078021c81c2f764f77f98db9a3bb7f646fd0ef490e65f0860b695fa2488fbc
```
イメージの作成を行っている.帰ってきた値がイメージのユニークなID
### 4. commitしたら実行するのは簡単
```shell
$ docker run -it test/jupyter ipython
Python 3.7.3 (default, Jun 11 2019, 01:05:09)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.5.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
```
### 5. jupyter-notebookを実行する際にはたくさん引数を渡せばok
```shell
$ docker run -it -p 8888:8888 test/jupyter jupyter notebook --no-browser --allow-root --ip=0.0.0.0
[I 13:07:45.648 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
[I 13:07:46.928 NotebookApp] Serving notebooks from local directory: /
[I 13:07:46.928 NotebookApp] The Jupyter Notebook is running at:
[I 13:07:46.928 NotebookApp] http://(278d34ce0b5d or 127.0.0.1):8888/?token=4f9b53ef3ed0f5dbd97f82ef5d1d4d78bd46a6a3e9333190
[I 13:07:46.929 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 13:07:46.939 NotebookApp]
To access the notebook, open this file in a browser:
file:///root/.local/share/jupyter/runtime/nbserver-1-open.html
Or copy and paste one of these URLs:
http://(278d34ce0b5d or 127.0.0.1):8888/?token=4f9b53ef3ed0f5dbd97f82ef5d1d4d78bd46a6a3e9333190
```
この状態で`http://127.0.0.1:8888/?token=...`に接続するとjupyter-notebookが利用できる
+ `-p`:コンテナ内のポートと手元のポートをつなげる
+ `--no-browser`:ブラウザなし
+ `--allow-root`:ルート権限で実行許可
+ `--ip=0.0.0.0`:どのIPからの接続もok
### 6. 毎回これをやるのは面倒なので`Dockerfile`を使おう
Dockerfileはイメージの作成手順を記載したテキストファイル.適当なディレクトリを作成して中にDockerfileを作成する
```shell
$ mkdir jupyter
$ cd jupyter
$ touch Dockerfile
```
### 7. `vi`でも`emacs`でも`code`でも`nano`でもいいので編集する
```dockerfile
FROM python:3.7
RUN pip install jupyter
```
を書く.
### 8. ビルド
書いたらbuildする
```shell
$ docker built -t test/jupyter-dockerfile .
```
現在のディレクトリ(`.`)の中にあるDockerfileに基づいて`test/jupyter-dockerfile`というタグを付けてビルドする.
### 8. 再度実行してみる
```shell
$ docker run -it -p 8888:8888 test/jupyter-dockerfile jupyter notebook --no-browser --allow-root --ip=0.0.0.0
[I 13:19:30.666 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
[I 13:19:31.991 NotebookApp] Serving notebooks from local directory: /
[I 13:19:31.991 NotebookApp] The Jupyter Notebook is running at:
[I 13:19:31.992 NotebookApp] http://(502b62e6c74b or 127.0.0.1):8888/?token=6989be6638b1669eff54039e08156a73727849ce8b84c739
[I 13:19:31.994 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 13:19:32.002 NotebookApp]
To access the notebook, open this file in a browser:
file:///root/.local/share/jupyter/runtime/nbserver-1-open.html
Or copy and paste one of these URLs:
http://(502b62e6c74b or 127.0.0.1):8888/?token=6989be6638b1669eff54039e08156a73727849ce8b84c739
```
### 9. 毎回引数を渡すのは面倒くさい -> Dockerfileに書こう
便利な`ENTRYPOINT`というのが存在する.
```dockerfile
FROM python:3.7
RUN pip install jupyter
ENTRYPOINT [ "jupyter","notebook","--port=8000","--no-browser","--allow-root","--ip=0.0.0.0"]
```
と書いてあげると実行時に引数を省略できる.
### 10. このままだとちょっと大変
+ 毎回起動するときのディレクトリが`/`になっている
+ トークンの入力が大変
+ ホームディレクトリを既定のディレクトリにしたい
これらを解決するためにはDockerfileの記述を変更しよう
```dockerfile
FROM python:3.7
RUN pip install jupyter
RUN jupyter notebook --generate-config
RUN echo "c = get_config();c.NotebookApp.token = 'XXXX'" >> ~/.jupyter/jupyter_notebook_config.py
WORKDIR /home
EXPOSE 8000
ENTRYPOINT [ "jupyter","notebook","--port=8000","--no-browser","--allow-root","--ip=0.0.0.0"]
```
以下の点を変更した
+ configを作成してパスワードを`XXXX`に設定
+ 起動時のディレクトリを(コンテナ内の)`/home`に設定
あとは起動時に`-v ホストのディレクトリ:コンテナのディレクトリ`のオプションを渡して
```shell
$ docker run -p 8000:8000 -it -v $HOME:/home test/jupyter-dockerfile
```
とすればホームディレクトリを`/home`とつなげることができる
### 11. 作ったイメージをアクセス可能にする
**!Docker Hubのアカウントが必要です!**
アカウントを作成したら,自分のユーザーネームのタグを付けてビルドして,
```
$ docker build -t natuski/jupyter-dockerfile .
```
push!
```
$ docker login && docker push natuski/jupyter-dockerfile
```
で自分のリポジトリにアップロードできる.これでどこでも`docker pull natuski/jupyter-dockerfile`すれば利用できる!
# 終わり
## サンプルのDockerfile
### CapRを動かすサンプル
```dockerfile
FROM alpine AS builder
RUN apk add --no-cache git make g++
RUN git clone https://github.com/fukunagatsu/CapR.git
WORKDIR /CapR
RUN make
FROM alpine
RUN apk add --no-cache libstdc++
COPY --from=builder /CapR/CapR /CapR
ENTRYPOINT ["/CapR"]
```
`Dockerfile`に以上を書いた上で`docker build .`する
イメージの大きさを見たりする場合には`docker images`
```shell
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
natuski/capr_alpine latest 245d66b1731c 11 minutes ago 7.3MB
```
遺伝研で使用する場合には
```shell=
# メモリを明示的に要求してqlogin(sifファイル作成にメモリを食うらしい)
$ qlogin -l s_vmem=32G -l mem_req=32G
# モジュールのロード
$ module load singularity
# イメージのプル
$ singularity pull docker://natuski/capr_alpine
# イメージの実行
$ singularity run capr_alpine.simg
```
runするとエントリポイントが呼ばれる