🦁

jupyter/datascience-notebook で bash をホスト側ユーザと同じIDで起動する方法

2024/03/12に公開

結論

-u root を指定して NB_UIDNB_GID をホスト側と同一にし、起動コマンドを start.sh にするのが鍵。

./Makefile
UID := `id -u`
GID := `id -g`
PWD := `pwd`

.PHONY: shell
shell:
	docker container run -it --rm \
		-p 8888:8888 \
		-u root \
		-e NB_UID=${UID} \
		-e NB_GID=${GID} \
		-v ${PWD}:/home/jovyan/work \
		jupyter/datascience-notebook:python-3.11 \
		start.sh [ここに実行したいコマンドを書く/何も書かなければ bash が起動する]
$ make shell

はじめに

Docker の味を占めている皆様であれば Python の実行環境は Docker のイメージに押し込めているだろうし、データ解析に必要なライブラリが一式入っている jupyter/datascience-notebook やその他の juypter イメージにはお世話になっていることだろう。

https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html

このイメージを Linux ホストの Docker で使うときに問題になるのがユーザの権限である

作業を永続化するにはホスト側のディレクトリをボリュームマウントする必要があるが、実際に以下のコマンドで jupyter/datascience-notebook でカレントディレクトリをマウントして起動してみると、大抵は書き込みができない。

./Makefile
.PHONY: bad_example
bad_example:
	docker container run -it --rm \
		-p 8888:8888 \
		-v .:/home/jovyan/work \
		jupyter/datascience-notebook:python-3.11
$ make bad_example
Entered start.sh with args: jupyter lab
Running hooks in: /usr/local/bin/start-notebook.d as uid: 1000 gid: 100
Done running hooks in: /usr/local/bin/start-notebook.d
Running hooks in: /usr/local/bin/before-notebook.d as uid: 1000 gid: 100
Done running hooks in: /usr/local/bin/before-notebook.d
Executing the command: jupyter lab
[I 2024-03-11 15:29:58.105 ServerApp] Package jupyterlab took 0.0000s to import
[I 2024-03-11 15:29:58.142 ServerApp] Package jupyter_lsp took 0.0365s to import

...

[I 2024-03-11 15:29:59.127 ServerApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 2024-03-11 15:29:59.133 ServerApp]

    To access the server, open this file in a browser:
        file:///home/jovyan/.local/share/jupyter/runtime/jpserver-7-open.html
    Or copy and paste one of these URLs:
        http://xxxxxxxxxxxx:8888/lab?token=f78047b8ec30f26d0f8ec57f0bbfaf37257f93f6764ebd48
        http://127.0.0.1:8888/lab?token=f78047b8ec30f26d0f8ec57f0bbfaf37257f93f6764ebd48
        # この URL の ↑ IP アドレスを自分のサーバーのアドレスに変えて、
        # ブラウザのアドレスバーにコピペするとアクセスできる。
        # 起動時にポートを -p XXXX:8888 で指定している場合はポートも XXXX に変える。

問題の構造としては

  1. Docker は隔離環境なのでコンテナ外のファイルにアクセスできない。
  2. よってコンテナ外に解析用 notebook を配置するときはホスト側ディレクトリをボリュームマウントする必要がある。
  3. ボリュームマウントしたはいいがコンテナ内では勝手に jovyan というユーザになる。
  4. jovyan の ID は Linux ホスト側のディレクトリのユーザとは一般に異なるのでマウントしたディレクトリに書き込みができない。

となる[1]。実際にコンテナの中で id コマンドを実行してみると、jovyan のデフォルト設定が分かる。

$ id
uid=1000(jovyan) gid=100(users) groups=100(users)

Jupyter Lab などを使うときの解決法

通常通り Jupyter LabJupyter Notebook を起動して使う限りは、以下のように root ユーザで起動した上で NB_UIDNB_GID を指定することにより、jovyan ユーザのユーザ ID とグループ ID を書き換えることができるので、これをホスト側のユーザと揃えればこのパーミッション問題は解決する。

https://jupyter-docker-stacks.readthedocs.io/en/latest/using/common.html#user-related-configurations

./Makefile
UID := `id -u`
GID := `id -g`
PWD := `pwd`

.PHONY: run
run:
	docker container run -it --rm \
		-p 8888:8888 \
		-u root \
		-e NB_UID=${UID} \
		-e NB_GID=${GID} \
		-v ${PWD}:/home/jovyan/work \
		jupyter/datascience-notebook:python-3.11
$ make run

# あとの操作は同じ


example.ipynb 作成できたの図

ユーザの ID が変更できてるの図

ホスト側でもちゃんと作成者の ID が揃ってるの図

※ みなさんにとって分かりやすいようにユーザ名とか ID を晒してますがこれは私の遊び用環境でこのためだけにユーザを作り即座に破壊できるからです。攻撃の手がかりになるから良い子のみんなは晒しちゃダメ。

bash を起動したいときの解決法

こっちが本題。起動コマンドを jupyter ではなく bash にして同様にユーザ ID を調整したいことがけっこうある。たとえばコンテナ内でサクッと作成したスクリプトをしばらくお試しでホスト側のコマンドラインから走らせたいとか、テストを Jupyter Lab で起動するのと同等の環境で走らせたいという場合である。

それで以下のように起動コマンドに /bin/bash を指定しようと目論むのだが、この試みはうまくいかない。以下を Makefile に書いて make bad_shell で起動してみれば分かるが、ユーザは root になってしまう。

この起動方法はうまくいきません!!
UID := `id -u`
GID := `id -g`
PWD := `pwd`

.PHONY: bad_shell
bad_shell:
	docker container run -it --rm \
		-p 8888:8888 \
		-u root \
		-e NB_UID=${UID} \
		-e NB_GID=${GID} \
		-v ${PWD}:/home/jovyan/work \
		jupyter/datascience-notebook:python-3.11 \
		/bin/bash

これは NB_UIDNB_GID の設定の反映が、イメージ内の /usr/local/bin にある start.sh によって行われているからである。コマンドを何も指定せずに起動した場合、エントリーポイントは start-notebook.sh になっており、そこから start.shjupyter を起動するコマンドを与えることでユーザ ID の設定が行われてから jupyter のサーバーが起動するという機序になっている。起動コマンドに直接 /bin/bash を指定した場合、この start.sh が実行されないのでユーザ ID の設定が行われないのである。

https://github.com/jupyter/docker-stacks/blob/07dc2d08734b384ab7370956d47909bda9bc928b/images/docker-stacks-foundation/start.sh#L79-L90

https://github.com/jupyter/docker-stacks/blob/07dc2d08734b384ab7370956d47909bda9bc928b/images/base-notebook/start-notebook.py#L17-L31

したがって起動コマンドに start.sh を指定してやれば設定は実行され、意図通りにワークする

./Makefile (こっちがちゃんと動くやつ)
UID := `id -u`
GID := `id -g`
PWD := `pwd`

.PHONY: shell
shell:
	docker container run -it --rm \
		-p 8888:8888 \
		-u root \
		-e NB_UID=${UID} \
		-e NB_GID=${GID} \
		-v ${PWD}:/home/jovyan/work \
		jupyter/datascience-notebook:python-3.11 \
		start.sh [ここに実行したいコマンドを書く/何も書かなければ bash が起動する]
$ make shell

start.sh は引数を与えなければ bash を起動し、引数があればそこに与えられたコマンドを実行する。該当部分のコードは以下。

https://github.com/jupyter/docker-stacks/blob/07dc2d08734b384ab7370956d47909bda9bc928b/images/docker-stacks-foundation/start.sh#L30-L35

https://github.com/jupyter/docker-stacks/blob/07dc2d08734b384ab7370956d47909bda9bc928b/images/docker-stacks-foundation/start.sh#L157-L162

https://github.com/jupyter/docker-stacks/blob/07dc2d08734b384ab7370956d47909bda9bc928b/images/docker-stacks-foundation/start.sh#L254-L255

おしまい

おそらく公式ドキュメントには書いてない?のでソースを読むしかなかった。

みなさんのお役に立てば幸いです。

脚注
  1. これは Linux ホスト特有の問題である。macOS 版の Docker Desktop ではコンテナ内でどんなユーザだろうとコンテナ外にアクセスするときはそのコンテナを起動したホスト側ユーザのように見えるためこの問題は起こらない。Windows は知らん。 ↩︎

Discussion