こんにちは!ともわん(@TomoOne4)です。
僕は、IT企業でマーケティングやセールスをやっています。
Dockerについての第8回目投稿です。
今回は、「ホストのファイルシステムをコンテナで扱えるマウント」を取り上げます。実務ではめっちゃ使う内容のようです。
今回のポイント
- めっちゃ使う!ホストのファイルシステムをコンテナにマウントする
- マウントする際の権限問題(デフォルトではrootになってしまうのだ)
- ポートをつなげてコンテナ上のJupyterをlocalhostで立ち上げて感動する
- メモリリークやOOMを防げ!コンピュータリソースの上限値を設定
ここで書く内容は、Udemy で受講できる、
かめ れおんさんの米国AI開発者がゼロから教えるDocker講座
で学んだ内容を自分の中で定着するために調べたり考えたりした内容を踏まえて進めていきます。
導入は文章より動画のほうがすっと入ってくる人も多いと思いますので、少し記事を読んでいただき
「Docker面白そうだし、簡単そう」
と思った人はぜひUdemyで米国AI開発者がゼロから教えるDocker講座を受けてみてください。
Dockerやコンテナ、仮想化については第1回、第2回、第3回、第4回、第5回、第6回、第7回、第8回がありますのでこちらをみてみてください!
ホストのファイルシステムをコンテナにマウントする
まずは全体の概要を掴みます。こういうことをやっていきます。
普通は、ホストとコンテナのファイルシステムは全く別のもので独立しています。
これは、前回までで、MacのファイルシステムとコンテナにしたUbuntuのファイルシステムが違うこと見てきたのでスッとわかると思います。
しかし、マウントをすることで、あたかもホストのファイルシステムがコンテナのファイルシステムかのように振る舞うことができるのです。なんかすごくないですか?
しかも、めっちゃ使います。
マウントとは?
マウントとは、OSがファイルシステムを介してストレージデバイス上のファイルやディレクトリを利用できるようにすることをいいます。
ではどういうときに使うのかというと、
【使う状況】
ホストのファイルシステムにコードを置いておき、実行するときにコンテナを使う場合(実行環境として、コンテナを使う場合)
→ コンテナにコードなどを置くと、コンテナの容量が大きくなってしまうため。(コンテナは配布や共有という目的があるので大きくしないようにするべき!)
【マウントするコマンド】
docker run -v <host>:<container>
実際にやってみます。
/Users/Username/dockerにmounted_folderというディレクトリを作ってその下で作業をしていきます。
まずは、file_at_hostという名前のファイルを作っておきます。
$ touch file_at_host
build contextは、/Users/Username/docker/testとします。
この中にあるDockerfileを以下のようにしておきます。
FROM ubuntu:latest
RUN mkdir new_dir
この状態でdocker build → docker runして、ホスト側で作成した file_at_host に new_dir をマウントさせます。
docker build . 07:53:25
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM ubuntu:latest
---> adafef2e596e
Step 2/2 : RUN mkdir new_dir
---> Running in 7b8d6ca57951
Removing intermediate container 7b8d6ca57951
---> b5947f5fb8dc
Successfully built b5947f5fb8dc
$ docker run -it -v ~/docker/mounted_folder/:/new_dir b5947f5fb8dc bash
root@17a6f158d72f:/# ll
total 56
drwxr-xr-x 1 root root 4096 Aug 20 22:58 ./
drwxr-xr-x 1 root root 4096 Aug 20 22:58 ../
-rwxr-xr-x 1 root root 0 Aug 20 22:58 .dockerenv*
lrwxrwxrwx 1 root root 7 Jul 3 01:56 bin -> usr/bin/
drwxr-xr-x 2 root root 4096 Apr 15 11:09 boot/
drwxr-xr-x 5 root root 360 Aug 20 22:58 dev/
drwxr-xr-x 1 root root 4096 Aug 20 22:58 etc/
drwxr-xr-x 2 root root 4096 Apr 15 11:09 home/
lrwxrwxrwx 1 root root 7 Jul 3 01:56 lib -> usr/lib/
lrwxrwxrwx 1 root root 9 Jul 3 01:56 lib32 -> usr/lib32/
lrwxrwxrwx 1 root root 9 Jul 3 01:56 lib64 -> usr/lib64/
lrwxrwxrwx 1 root root 10 Jul 3 01:56 libx32 -> usr/libx32/
drwxr-xr-x 2 root root 4096 Jul 3 01:57 media/
drwxr-xr-x 2 root root 4096 Jul 3 01:57 mnt/
drwxr-xr-x 3 root root 96 Aug 20 22:51 new_dir/
drwxr-xr-x 2 root root 4096 Jul 3 01:57 opt/
dr-xr-xr-x 165 root root 0 Aug 20 22:58 proc/
drwx------ 2 root root 4096 Jul 3 02:00 root/
drwxr-xr-x 1 root root 4096 Jul 6 21:56 run/
lrwxrwxrwx 1 root root 8 Jul 3 01:56 sbin -> usr/sbin/
drwxr-xr-x 2 root root 4096 Jul 3 01:57 srv/
dr-xr-xr-x 12 root root 0 Aug 18 04:41 sys/
drwxrwxrwt 2 root root 4096 Jul 3 02:00 tmp/
drwxr-xr-x 1 root root 4096 Jul 3 01:57 usr/
drwxr-xr-x 1 root root 4096 Jul 3 02:00 var/
root@17a6f158d72f:/# cd new_dir/
root@17a6f158d72f:/new_dir# ll
total 4
drwxr-xr-x 3 root root 96 Aug 20 22:51 ./
drwxr-xr-x 1 root root 4096 Aug 20 22:58 ../
-rw-r--r-- 1 root root 0 Aug 20 22:51 file_at_host
file_at_hostを確認できました。
これは、ホスト側のfile_at_hostがマウントされたことによって見えているという状態です。
それでは、ホスト側でこのfile_at_hostを編集してみるとどうなるでしょうか?
ターミナルを別タブで開くなどして、
/Users/Username/docker/mounted_folderの中にあるfile_at_hostを開きます。
ちなみに僕はVisual Studio Codeを使っているのでcodeコマンドで開きますが、Macなのでviでもいいです。
$ code file_at_host
Hello Japan
とテキストを追加して保存します。
そして、ターミナルでコンテナの中で、$ cat file_at_host を実行してみます。
root@17a6f158d72f:/new_dir# cat file_at_host
Hello Japan
コンテナの方からも、先程Host側で更新した内容が確認できていますね!
マウントの際にマウント先のファイルシステム存在しないディレクトリを指定した場合、docker runコマンドの実行で、mkdirもしてくれるので便利です!
例:new_dir_dummyディレクトリがない場合
docker run -it -v ~/docker/mounted_folder:/new_dir_dummy 6cc94f3d638f bash
でも、これだとコンテナ内で、最強root権限でホスト側のファイルシステムを実行できてしまうということになるので、危ない((((;゚Д゚))))ガクガクブルブル
コンテナからホストのファイルシステムにアクセスできる時には権限問題がある
Dockerはデフォルトがroot権限になります。
root権限とは、ほぼ全てのことができてしまう最強権限のことですので、
ホスト側でマウントをしてしまえば、Host側のファイルシステムをroot権限で実行できてしまう(大問題)
そこで、docker runのオプション -u <user id>:<group id> を使って、ユーザーIDとグループIDを指定してコンテナをrunさせます。
user id と group id は、Macのターミナルで確認してみます。
$ id -u
501
$ id -g
20
ここでは、ユーザーIDが501で、ユーザーグループが20でした。
実際にやってみます。
$ docker run -it -u $(id -u):$(id -g) -v ~/docker/mounted_folder:/created_in_run 744de3b06554 bash
ユーザー、グループを指定するには、$(id -u)や$(id -g)とすることで、$()内のコマンド実行結果がここに入ることになるのでこういう書き方をする。
※501など、数字を書かない
$ docker run -it -u $(id -u):$(id -g) -v ~/docker/mounted_folder:/created_in_run <Container Id> bash
みたいな形で覚えておくといいです。
すると、以下のようになります。
I have no name!@3bcd4f5f4681:/$
※ホストとコンテナはユーザーIDとグループIDはシェアするのですが、ユーザーネームはシェアできないので、I have no nameになる。
できたディレクトリを見てみます。
drwxr-xr-x 2 root root 4096 Jul 25 14:54 created_in_Dockerfile
drwxr-xr-x 3 501 dialout 96 Jul 25 10:57 created_in_run
created_in_Dockerfileは、rootユーザーが所有
created_in_runは、501ユーザーが所有
となっているので、501ユーザーでcreated_in_Dockerfileというディレクトリ内にファイルを作ろうとしたりすると、パーミッションエラーになる。
ポートをつないで、コンテナでサービスを動かす
次は、ポートです。
ホストのポートから、コンテナのポートへの接続をしてあげないと外部からはつながらないということになるので、docker runの -p オプションで指定します。
【ポートをつなげる】
$ docker run の -p <host_port>:<container_post> で、ホストのポートをコンテナのポートにつなげることができる。
例えばこんな感じです。
$ docker run -it -p 8888:8888 --rm jupyter/datascience_notebook bash
$ jupyter notebook
これで、ブラウザでlocalhost:8888にアクセスするとコンテナのjupyter notebookが利用できるようになります。
使いすぎを防ぐために、コンテナで使えるコンピューターリソースの上限を設定
コンテナは、ホストのメモリやCPUを使っています。
制限もせずに全てのメモリやCPUを使ってしまうと、OOM(Out Of Memory)やメモリリークで全てのシステムが落ちるということもありうるわけなので、上限をかけていきます。
実際に自分のMacの状況を見てみます。
まずは物理コアを見てみます。
$ sysctl -n hw.physicalcpu_max
4
次に、論理コアを見てみます。
$ sysctl -n hw.logicalcpu_max
8
物理コアと論理コアというのは、Hyper Threadingという技術で、1つの物理コアに対していくつかの論理コアをもたせることができる技術です。
メモリも見てみます。
$ sysctl hw.memsize
hw.memsize: 17179869184
↓
結果の一部
17179869184
単位はbyteなので、Giga Byteに変換してみます。
【byte → Giga Byteへの変換】
17179869184bite /(1024 * 1024 * 1024) =16 GB
(1K byte = 1024 byte ※2^10=1024)
(1M byte = 1024 * 1024 byte)
(1G byte = 1024 * 1024 * 1024 byte)
また、
さて、実際に上限を設定してみます。
【コンテナで使えるコンピュータリソースの上限設定】
–cpus <# of CPUs>:
コンテナがアクセスできる上限のCPU(コア)を設定
–memory <byte>:
コンテナがアクセスできる上限のメモリを設定
$ docker run -it --rm --cpus 4 --memory 2g ubuntu bash
runをしたら、確認です。
$ docker inspect でコンテナのあらゆる情報を見ることができます。
ただ、情報が多すぎるので、大抵はパイプで繋いでgrepで見たい情報を取ってくる感じにします。
CPUを見てみます。
$ docker inspect e503ad12a47f | grep -i cpu
↓
"NanoCpus": 4000000000
“NanoCpus”なので、10の-9乗してあげる必要がある。
4000000000 * 10^-9 = 4
なので、4コアが割当てられている(Allocateされている)
続いて、メモリを見てみます。
$ docker inspect e503ad12a47f | grep -i memory
↓
"Memory": 2147483648
byteになっているので、GBに変換する。
2147483648 / (1024 * 1024 *1024) = 2GB
なので、2GBメモリが割り当てられている。
まとめ:マウント関連の $ducker run オプション振り返り
この長い旅を一度振り返ってまとめとします。
【マウント関連の $ducker run オプション】
・ファイルシステムの共有:
-v <host/path>:<container/path>
・ファイルへのアクセス制限:
-u $(id -u):$(id -g)
・ポートをつなげる:
-p <host_port>:<container_port>
・コンピュータリソースの上限:
–cpus <#ofCPUs> –memory <byte>
・コンテナの詳細を表示:
$docker inspect <container> | grep -i <検索キーワード>