📘

DNS and Jupyter Notebook Using Docker - シリーズ投稿2/7

2022/12/26に公開

Series Top: Dockerで作るおうちLAN遊び場

この投稿はシリーズ第二弾で、Dockerを使ってDNSとJupyter Notebookを動かします。

自分のDNSサーバがあるとシリーズで今後立ち上げていく各サービス用に、自由にDNSレコードを用意できます。

具体例があるとどういうことか分かりやすいので、この投稿ではwebサービスとDNSサーバ、両方ともカバーします。

Accessing service with and without DNS

まずは机上の例として。あるwebサーバが192.168.1.250:80でサービス提供しているとして、「YAHOO!」というサービス応答をするとします。ブラウザでhttp://192.168.1.250/へアクセスすると「YAHOO!」という応答が表示されます。ここでさらにyahoo.example.netというDNSレコードがあり、それが192.168.1.250へと名前解決されるとします。すると、ブラウザなどでhttp://yahoo.example.netへとアクセスすると「YAHOO!」という応答が表示されます。

Jupyter Notebook on Docker

それでは実際に構築を始めていきましょう。まずwebサービス、Jupyter Notebookを走らせます。走らせるマシンは192.168.1.56というIPアドレスで、Jupyter Notebookというサービスは8888ポートでサービス提供させます。すると先の例の通り、ブラウザでhttp://192.168.1.56:8888へアクセスするとJupyter Notebookにアクセスできます。また、このサービスをjupyter.mylan.localと名付けたとして、そのDNSレコードが192.168.1.56で名前解決されるようDNSサーバとDNSレコードも用意すると、今度はブラウザでhttp://jupyter.mylan.local:8888でもサービスにアクセスできます。

なおサラッと触れていますが、シリーズを通してLANで利用するドメインはmylan.localとします。

マシンに接続して手を動かし始めましょう。先の投稿でインストールしたdocker composeが確かにインストールされていることを確認します。

$ docker compose version
Docker Compose version v2.12.0

ディレクトリを用意し、Docker Composeに何をやらせるかの指示書、docker-compose.ymlというファイルを用意します。ディレクトリは$HOME/mylan/jupyterとします。

$ mkdir -p $HOME/mylan/jupyter
$ cd $HOME/mylan/jupyter
# and then create a docker-compose.yml file here

以下がdocker-compose.ymlファイルの中身です。ここではjupyter/base-notebookというDockerイメージを使います。

https://hub.docker.com/r/jupyter/base-notebook

services:
  jupyter:
    container_name: jupyter
    image: jupyter/base-notebook:notebook-6.5.1
    ports:
      - "8888:8888"
    command: "start-notebook.sh --ServerApp.password='' --ServerApp.token='' --ip=0.0.0.0 --no-browser"

ファイルができれば実行準備完了です。docker compose up -dで実行、docker compose psでステータス確認、docker compose downで停止できます。実行するとLAN内の別の端末からhttp://192.168.1.56:8888へブラウザでアクセスすることでJupyter Notebookサービスにアクセスできます。

$ docker compose up -d
[+] Running 13/13
 ⠿ jupyter Pulled                                                                                                         97.2s
   ⠿ cf92e523b49e Pull complete                                                                                           16.1s
   ⠿ d122d224d8ab Pull complete                                                                                           36.9s
   ⠿ da0342913a35 Pull complete                                                                                           37.0s
   ⠿ 4f4fb700ef54 Pull complete                                                                                           37.1s
   ⠿ c2f2efbb391f Pull complete                                                                                           37.2s
   ⠿ 291238b4cf86 Pull complete                                                                                           37.3s
   ⠿ 47fcc7e93076 Pull complete                                                                                           37.4s
   ⠿ 8c8e1d8f7987 Pull complete                                                                                           37.5s
   ⠿ 3c19a726ce99 Pull complete                                                                                           92.8s
   ⠿ 442a90c0da0a Pull complete                                                                                           92.9s
   ⠿ e511a84b0db8 Pull complete                                                                                           93.0s
   ⠿ 44bce5cd969a Pull complete                                                                                           93.1s
[+] Running 2/2
 ⠿ Network jupyter_default  Created                                                                                        0.2s
 ⠿ Container jupyter        Started                                                                                        2.4s
$ docker compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
jupyter             "tini -g -- start-no…"   jupyter             running (healthy)   0.0.0.0:8888->8888/tcp, :::8888->8888/tcp

docker compose downした場合の出力です。

$ docker compose down
[+] Running 2/2
 ⠿ Container jupyter        Removed                                                                                        0.8s
 ⠿ Network jupyter_default  Removed                                                                                        0.4s
$ docker compose ps
NAME                COMMAND             SERVICE             STATUS              PORTS

Unbound DNS on Docker

上と同様に、次はDNS用にディレクトリとdocker-compose.ymlファイルを用意していきます。

$ mkdir -p $HOME/mylan/dns
$ cd $HOME/mylan/dns
# create a docker-compose.yml file here

ファイルの中身は以下で、Dockerイメージとしてはmvance/unboundを使います。

https://hub.docker.com/r/mvance/unbound

services:
  dns:
    image: mvance/unbound:1.16.2
    container_name: dns
    ports:
      - '53:53/udp'
      - '53:53'

早速docker compose up -dで実行しdocker compose psでステータスを確認しましょう。

$ docker compose ps
NAME                COMMAND             SERVICE             STATUS               PORTS
dns                 "/unbound.sh"       dns                 running (starting)   0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp, :::53->53/tcp, :::53->53/udp

先のセクションではサービス実行状態の確認としてブラウザでJupyter Notebookにアクセスしました。同様にDNSサービスの確認としては名前解決をしてみましょう。LAN上のどの端末、どのシステムからでも確認できますが、実行コマンド例は以下です。

host www.google.com. 192.168.1.56
nslookup www.google.com. 192.168.1.56
dig www.google.com. @192.168.1.56

ちなみに利用するDockerイメージバージョンによっては、このDNSサーバは普通にインターネット上のルートネームサーバへ名前解決にいっているかもしれませんし、TLS越しにCloudflare DNSへ名前解決しにいっているかもしれません。ここで私が利用しているバージョンは後者です。

Configure DNS server

Dockerで実行させているDNSサービスが機能していること確認できたと思います。次に、設定を編集してjupyter.mylan.localを名前解決できるようにします。

どのディレクトリに置いてあるどのファイルを変更すればサーバの設定を変更できるのかは、サービスによってまちまちです。利用するDockerイメージのドキュメントないしサービスの公式ドキュメントを確認しにいく必要があるでしょう。

またDockerで実行しているサービスの設定変更をしたいという場合、実行しているコンテナ内で設定ファイルの更新をするのでは、マシンの再起動、そしてDockerのup/downだけでも設定変更は巻き戻ってしまいます。実行コンテナ内でどうこうする代わりに、Dockerを実行しているマシン上に設定ファイルを用意し、Dockerにそのファイルを実行コンテナにも利用してもらうよう持っていってもらいます。

実際にどうすればよいのかやっていましょう。

今回利用しているイメージmvance/unbound:1.16.2の場合、設定ファイルのサンプルがあるのでそれを利用します。設定ファイルを置くためのディレクトリを用意し、実行しているコンテナ内よりサンプルファイルをローカルにコピーしてきます。

# you are at $HOME/mylan/dns
mkdir config
cd config

# copy a-records.conf configuration file from "dns" docker container
docker cp dns:/opt/unbound/etc/unbound/a-records.conf .

コンテナ内よりコピーしてきたa-records.confファイルの中身は以下のとおりです。

$ cat a-records.conf
# A Record
     #local-data: "somecomputer.local. A 192.168.1.1"

# PTR Record
     #local-data-ptr: "192.168.1.1 somecomputer.local."

例に習って行を追加します。先頭の#があると一行丸ごとコメントアウトされるので除いておきましょう。

$ cat a-records.conf
# A Record
     #local-data: "somecomputer.local. A 192.168.1.1"
     local-data: "jupyter.mylan.local. A 192.168.1.56"

# PTR Record
     #local-data-ptr: "192.168.1.1 somecomputer.local."
     local-data-ptr: "192.168.1.56 jupyter.mylan.local."

次に$HOME/mylan/dns配下に作ったdocker-compose.ymlファイルも更新します。カスタムの設定ファイルは./config/a-records.confにあり、それをコンテナ内の/opt/unbound/etc/unbound/a-records.confファイルとしてローカルから持ち込むようDockerに指示します。volumes:以下のセクションがその設定で、ちなみに末尾の:roはread-onlyモードの指示です。

こういったファイルやディレクトリのマッピング、マウント関連オペレーションについて、公式ドキュメントに更に情報があります。

https://docs.docker.com/storage/bind-mounts/

$ cat docker-compose.yml
services:
  dns:
    image: mvance/unbound:1.16.2
    container_name: dns
    ports:
      - '53:53/udp'
      - '53:53'
    volumes:
      - './config/a-records.conf:/opt/unbound/etc/unbound/a-records.conf:ro'

コンテナをリスタートすると追記されたdocker-compose.ymlに従ってカスタム設定ファイルが実行コンテナに載ります。

# run `cd $HOME/mylan/dns` first if you are not there
docker compose restart
# or docker compose down, and then docker compose up -d

スマホ、ラップトップ、LAN上のどの端末でもよいですが、DNS設定を変更して192.168.1.56をDNSサーバとして使うようにしましょう。そうするとようやく、ブラウザでhttp://jupyter.mylan.local:8888とアクセスすることでJupyter Notebookサービスに接続できます。

❯ curl jupyter.mylan.local:8888 -v
*   Trying 192.168.1.56:8888...
* Connected to jupyter.mylan.local (192.168.1.56) port 8888 (#0)

Files created on Jupyter Notebook

DNSサーバの設定のところで触れたとおり、Dockerを実行しているホストマシンを再起動したり、Jupyter Notebookのコンテナを停止して改めて立ち上げると、Jupyter Notebookで作ったファイルなどが毎回失くなっています。Docker Volumeを利用することでこの問題が解決できます。

早速以下に用意しましたが、更新版のdocker-compose.ymlファイルです。今度はjupyter_volumeというボリュームを作成し、今回の例で利用しているJupyter NotebookのDockerイメージのワーキングディレクトリ、/home/jovyanにマウントするよう指示します。

services:
  jupyter:
    container_name: jupyter
    image: jupyter/base-notebook:notebook-6.5.1
    user: root
    ports:
      - "8888:8888"
    command: "start-notebook.sh --ServerApp.password='' --ServerApp.token='' --ip=0.0.0.0 --no-browser"
    environment:
      - "CHOWN_EXTRA=/home/jovyan"
      - "CHOWN_EXTRA_OPTS=-R"
    volumes:
      - type: volume
        source: jupyter_volume
        target: /home/jovyan
        volume:
          nocopy: true

volumes:
  jupyter_volume: {}

ボリューム以外にも増えている行がありますが、Jupyter Notebookのドキュメントで触れられている通りユーザと権限の問題があり追加されている行となります。

https://jupyter-docker-stacks.readthedocs.io/en/latest/using/troubleshooting.html#permission-denied-when-mounting-volumes

Cleaning up Docker

以上でシリーズ投稿第二弾でカバーすべき主なアイテムは全てです。

最後にDocker環境のクリーンアップ方法について触れておきます。

https://docs.docker.com/config/pruning/

jupyterdns用のディレクトリへ移動し、それぞれでdocker compose downを実行して停止させます。そしてdocker system prune --volumesを実行することでイメージ、ボリュームなどあれこれがクリーンアップされます。なお、DNS用に用意したカスタム設定ファイルは影響を受けませんが、Jupyter Notebookで新規追加したファイルなどがあれば、ボリュームがクリーンアップされることですべて消されます。すでにJupyter Notebookであれこれ遊び始めているようであれば実行しないでおきましょう。

$ docker system prune --volumes
WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all volumes not used by at least one container
  - all dangling images
  - all dangling build cache

Are you sure you want to continue? [y/N] y
Deleted Volumes:
5f9710e79b0e3478c1faf63ba85c8f584cddc264cf82ffe912daf4396b164f12
e80bd0c29be5882802c488061f824402965cc7d0bced938ee59ad4d2a9907551
ee0cd42517800cf647cd7ac371e88544ba67c6cdc7448fdecfea7a52e00c1345
...
f443285371dff464c127e1430dc64e41df86675f8b11f7b1ccc80b57a41f7047
994da20fd1461980079a343d20d0326307d421869b955e5f8874c1391ec701d6

Deleted Images:
untagged: redis@sha256:4bed291aa5efb9f0d77b76ff7d4ab71eee410962965d052552db1fb80576431d
deleted: sha256:3900abf4155226f3f62401054b872ce0c85b5c3b47275cae3d16a39c8646e36b
deleted: sha256:8ba2d28fdd3729dec59c7e11c3c99b5df826f7d4fc63c358ae54833e27a16e92
...
deleted: sha256:3779241fda7b1caf03964626c3503e930f2f19a5ffaba6f4b4ad21fd38df3b6b
deleted: sha256:bacd3af13903e13a43fe87b6944acd1ff21024132aad6e74b4452d984fb1a99a

Deleted build cache objects:
l8z524ebp7tmuvmusjxc4jn2b
rp9mtdw4y51kgjuferu1khdzv

Total reclaimed space: 2.925GB

今後様々なイメージ、バージョンを試していったり、ボリュームを作っていったりすると次第にホストマシンのディスク容量を逼迫していくかもしれません。あるいはあちこちに用意されているチュートリアルやクイックスタート手順を試すために環境をクリーンアップしたいという場面が出てくるかもしれません。そういうときにはクリーンアップしましょう。

ちなみにあちこちでdocker compose up -dを実行しすぎて何がどこに置いてあったかわからなくなった場合は、docker psで実行しているコンテナ名を確認し、そのコンテナ名でdocker container inspect コンテナ名 -f '{{ index .Config.Labels "com.docker.compose.project.working_dir"}}'と実行することでそのコンテナのワーキングディレクトリがわかります。

$ docker ps --format '{{.Names}}'
dns
jupyter

$ docker container inspect jupyter -f '{{ index .Config.Labels "com.docker.compose.project.working_dir"}}'
/home/{username}/mylan/jupyter

Closing

この投稿は以上です。次回はリバースプロキシサーバを用意し、Jupyter Notebookなどのサービスへのアクセスの仕方を変えていきます。

next: Nginx Using Docker

Discussion