2025/05/04 Updated by

Docker Image を自作する (6)

PyTorch23.07 + sshd + jupyter

つくりながら学ぶ!LLM自作入門」が動作する Docker (GPU) 環境


[Up] Japanese English
このページ内での表記:
「ホストOSの対話環境」は背景色を黄色(lightyellow)で表す。
「Conainer 内の root 権限の対話環境」は背景色を水色(azure)であらわす。
「Conainer 内の一般ユーザ権限の対話環境」は背景色を赤色(#ffeeee)であらわす。
「他のPCの対話環境」は紫色(#eeeeff)で表す。

目的


前提条件


Python, PyTorch, TensorFlow のバージョンについての検討事項


nVidia GPU (CUDA) に対応した Docker Image を探す: PyTorch


(参考) nVidia GPU (CUDA) に対応した Docker Image を探す: TensorFlow


「つくって学ぶ!LLM自作入門」が動作する Docker Image (GPU 版) を作成する

方針

作成手順

  1. Windows11 上の Ubuntu-20.04 (WSL) を使って作業を進める。
  2. Windows において WSL のファイルシステムは \\wsl$\Ubuntu-20.04\home\nitta にあることに注意すること。 Ubuntu-20.04 から、Windows11 のホームディレクトリは /mnt/c/User/nitta/ として見える。
  3. 作業用フォルダを作成する
  4.   $ cd /mnt/c/Users/nitta/Documents/docker 
      $ mkdir -p llm_diy_torch2307  
    
  5. 作業用フォルダの中に Dockerfile を作成する。
  6. Dockerfile
    # ゲストOS: Ubuntu 22.04 LTS
    
    FROM nvcr.io/nvidia/pytorch:23.07-py3
    
    # Change Your Own UNAME, UID, GID, PASS
    
    ENV UNAME=guest
    ENV UID=3000
    ENV GID=3000
    ENV PASS=password
    
    ENV SSHD_PORT=22
    
    # 必要なパッケージのインストール
    
    RUN apt-get update && \
        DEBIAN_FRONTEND=noninteractive apt-get install -y \
        sudo \
        bash \
        openssh-server \
        supervisor \
        && rm -rf /var/lib/apt/lists/*
    
    
    # SSH 設定: パスワード認証を有効化
    
    RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
        sed -i "s/^#Port.*/Port ${SSHD_PORT}/" /etc/ssh/sshd_config && \
        mkdir /var/run/sshd
    
    
    # supervisord の設定ファイルを設置する (Daemon 起動用)
    
    RUN mkdir -p /var/log/supervisor
    COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
    
    
    # "Build a LLM from scratch"
    
    WORKDIR /app
    
    # RUN git clone https://github.com/rasbt/LLMs-from-scratch.git
    COPY LLMs-from-scratch/requirements.txt /app/LLMs-from-scratch/
    
    WORKDIR /app/LLMs-from-scratch
    RUN pip install --no-cache -r requirements.txt
    
    # ポート開放
    
    EXPOSE 22
    
    
    # Copy Shell Script "entrypoint.sh"
    
    COPY entrypoint.sh /entrypoint.sh
    RUN chmod +x /entrypoint.sh
    
    
    ######
    
    ENTRYPOINT ["/entrypoint.sh"]
    
    CMD []
    
  7. 作業用フォルダの中に entrypoint.sh を作成する
  8. entrypoint.sh
    #!/bin/bash
    set -e
    
    if [ ! -f /var/app/.initialized ]; then
        ######## First Time ########
    
        echo "First run. Setting up ..."
        mkdir -p /var/app
        touch /var/app/.initialized
    
        # ユーザーが存在しない場合のみ作成する
        if id "${UNAME}" &>/dev/null; then
            echo "User ${UNAME} already exists. Skipping creation."
        else
            # 同名グループが無ければ作成する
            if ! getent group "${UNAME}" &>/dev/null; then
                echo "Creating group ${UNAME} with GID=${GID}"
                groupadd -g ${GID} ${UNAME}
            else
                echo "Group ${UNAME} already exists. Skipping group creation."
            fi
        
            echo "Creating user ${UNAME} with UID=${UID}, GID=${GID}"
            useradd -m -u ${UID} -g ${GID} -s /bin/bash ${UNAME}
            echo "${UNAME}:${PASS}" | chpasswd
            usermod -aG sudo ${UNAME}
        fi
    
        # ホームディレクトリの Owner が root:root になることがあるので明示的に変更する。
        chown -v ${UNAME}:${UNAME}  /home/${UNAME}
    
        # SSHD のポート番号を変更する
        sed -i "s/^Port.*/Port ${SSHD_PORT}/" /etc/ssh/sshd_config
    
        
    else
        ######## Second Time or Later ########
        
        echo "Starting for the second time or later ..."
    
    fi
    
    # supervisord start (background)
    /usr/bin/supervisord -c /etc/supervisor/supervisord.conf &
    
    # Execute Commands in CMD
    
    if [ "$#" -gt 0 ]; then
        exec "$@"
    else
        echo "No command provided. Starting bash ..."
        exec bash
    fi
    
  9. 作業用フォルダの中に supervisord.conf を作成する
  10. supervisord.conf
    # supervisord の設定ファイル
    
    [supervisord]
    nodaemon=true
    
    
    # sshd を起動する
    
    [program:sshd]
    command=/usr/sbin/sshd -D -e
    autostart=true
    autorestart=true
    stdout_logfile=/var/log/sshd_stdout.log
    stderr_logfile=/var/log/sshd_stderr.log
    
  11. 上記で作成したファイルを以下のように配置する。llm_diy_torch2307.zip
  12.   llm_diy_torch2307/
        |
        +-- Dockerfile
        |
        +-- entrypoint.sh
        |
        +-- supervisord.conf
        |
        +-- LLMs-from-scratch/
              |
              +-- requirements.txt
    
  13. Image を build する。
  14.   $ docker build -t llm_diy_torch2307 .  
      ...
    成功
    
  15. 生成した Image を確認する
  16. $ docker image ls  
    REPOSITORY               TAG         IMAGE ID       CREATED       SIZE
    llm_diy_torch2307        latest      122a76367d82   8 hours ago   29GB
    ...
    
  17. (注意) 上で用意した supervisod.conf は、Container の /etc/supervisor/conf.d/supervisord.conf というパスにコピーしている。 supervisord を起動するときに設定ファイルとして指定しているのは、標準でインストールされる /etc/supervisor/supervisord.conf である。このファイルには /etc/supervisor/conf.d/*.conf をインクルードするように記述されている。
  18. [参考] 標準の /etc/supervisor/supervisord.conf
    ; supervisor config file
    
    [unix_http_server]
    file=/var/run/supervisor.sock   ; (the path to the socket file)
    chmod=0700                       ; sockef file mode (default 0700)
    
    [supervisord]
    logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
    pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
    childlogdir=/var/log/supervisor            ; ('AUTO' child log dir, default $TEMP)
    
    ; the below section must remain in the config file for RPC
    ; (supervisorctl/web interface) to work, additional interfaces may be
    ; added by defining them in separate rpcinterface: sections
    [rpcinterface:supervisor]
    supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
    
    [supervisorctl]
    serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL  for a unix socket
    
    ; The [include] section can just contain the "files" setting.  This
    ; setting can list multiple files (separated by whitespace or
    ; newlines).  It can also contain wildcards.  The filenames are
    ; interpreted as relative to this file.  Included files *cannot*
    ; include files themselves.
    
    [include]
    files = /etc/supervisor/conf.d/*.conf
    

Container 用の永続的なファイルシステムを作成する

コンテナに永続的なファイルシステムを提供するために、1777 のパーミッションでフォルダを作っておく。 skicky bit が on (1777) のフォルダには、 「誰でもファイルを作成できるが、作成した本人だけがファイルを変更したり消したりできる」 という特徴がある。

$ sudo mkdir -p /home/docker            ← ディレクトリを作成する
$ sudo chmod 1777 /home/docker          ← 誰でもファイルを作成できるが、作成した本人にしか消去できないモードに設定する
$ ls -ld /home/docker                   ← ディレクトリのsticky bit が on になっていることを確認する。
drwxrwxrwt 3 root root 4096  4月 26 15:47 /home/docker

Docker Container を生成する

Image llm_diy_torch2307 のデフォルトのユーザ情報とSSHサーバ情報を用いて、 新しい Container llmdiy_01 を生成する。

  1. Image から Container を生成して起動する。ユーザ情報はデフォルト値 (guest) を利用する。 Container のファイルシステム内にホストOSのディレクトリをマウントする。 Container を起動するたびに、sshd サーバが起動される。
  2. $ docker run --name llmdiy_01 \
        --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864 --gpus all \
        --restart always \
        -p 7076:22 -p 8086:8888 \
        -v /home/docker/llmdiy_01:/mnt/hostos \
        -it llm_diy_torch2307  
    
    起動オプション
  3. Container を起動した対話環境が、そのまま Container 内で動作する bash との対話環境になる。root権限でloginした状態である。
  4. First run. Setting up ...       ← 生成された Container 内で entrypoint.sh が実行される
    Creating group guest with GID=3000
    Creating user guest with UID=3000, GID=3000
    ownership of '/home/guest' retained as guest:guest
    No command provided. Starting bash ...
    root@af401d3cdf85:/# 2025-05-11 09:32:40,710 CRIT Supervisor is running as root.  Privileges were not dropped because no user is specified in the config file.  \
    If you intend to run as root, you can set user=root in the config file to avoid this message.
    2025-05-11 09:32:40,711 INFO supervisord started with pid 38
    2025-05-11 09:32:41,715 INFO spawned: 'sshd' with pid 41
    2025-05-11 09:32:42,717 INFO success: sshd entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
    #              ← Container 内の対話環境 (root権限の bash) が動く
    
  5. (重要) (Container 内で) 直ちに新規ユーザ guest のパスワードを変更する。
  6. $ passwd guest   
    New password:                   ← 新しいパスワード を入力する。
    Retype new password:            ← もう一度新しいパスワード を入力する。
    passwd: password updated successfully
    
  7. (Container 内で) ホストOSのマウントポイントを調べる。
  8. # ls -ld /mnt/hostos    
    drwxr-xr-x 2 root root 4096 May 11 09:32 /mnt/hostos
    
  9. (Container 内で) 新規ユーザのホームディレクトリを調べる。
  10. # ls -ld /home/guest    
    drwxr-x--- 2 guest guest 4096 May 11 09:32 /home/guest
    # ls -la /home/guest    
    total 20
    drwxr-x--- 2 guest guest 4096 May 11 09:32 .
    drwxr-xr-x 1 root  root  4096 May 11 09:32 ..
    -rw-r--r-- 1 guest guest  220 Mar 31  2024 .bash_logout
    -rw-r--r-- 1 guest guest 3771 Mar 31  2024 .bashrc
    -rw-r--r-- 1 guest guest  807 Mar 31  2024 .profile
    
  11. (Container 内で) Control-P と Control-Q を順にタイプして、ホストOSの対話環境に戻る。 Container 内のシェルは動作したままとなる。
  12. # ^p ^q                ← Container の対話環境を抜ける
    $              ← ホストOS 内の対話環境に戻る
    
  13. (ホストOS上) Container からマウントされているホストOSのディレクトリを調べる。
  14. $ ls -ld /home/docker/llmdiy_01  
    drwxr-xr-x 2 root root 4096 Nov  3 09:00 /home/docker/llmdiy_01
    
  15. (ホストOS上) docker 上の実行中の container の状態を調べる
  16. $ docker container ls  
    CONTAINER ID   IMAGE               COMMAND            CREATED        STATUS        PORTS                   NAMES
    2ef13be3f778   llm_diy_torch2307   "/entrypoint.sh"   10 hours ago   Up 10 hours   0.0.0.0:7076->22/tcp,   llmdiy_01
                                                                                       [::]:7076->22/tcp, 
                                                                                       0.0.0.0:8086->8888/tcp,
                                                                                       [::]:8086->8888/tcp,
                                                                                       [::]:8086->8888/tcp
    ...
    
  17. (ホストOS上) docker 上のすべての(停止中を含む) container の状態を調べる
  18. $ docker container ls -a  
    CONTAINER ID   IMAGE               COMMAND            CREATED        STATUS        PORTS                   NAMES
    2ef13be3f778   llm_diy_torch2307   "/entrypoint.sh"   10 hours ago   Up 10 hours   0.0.0.0:7076->22/tcp,   llmdiy_01
                                                                                       [::]:7076->22/tcp, 
                                                                                       0.0.0.0:8086->8888/tcp,
                                                                                       [::]:8086->8888/tcp,
                                                                                       [::]:8086->8888/tcp
    ...
    
  19. (ホストOS上) docker 上の Image の一覧を表示する。
  20. $ docker image ls  
    REPOSITORY               TAG         IMAGE ID       CREATED        SIZE
    llm_diy_torch2307        latest      ae6c6c06ccf1   10 hours ago   29GB
    torch2302_sshd           latest      642c2e1fae87   3 weeks ago    20.6GB
    nvcr.io/nvidia/pytorch   23.07-py3   bd784c42fdf0   2 years ago    19.8GB
    nvcr.io/nvidia/pytorch   23.02-py3   7c3375e220ea   2 years ago    20.5GB
    

Docker Host から ssh を用いてContainer 内の対話環境にアクセスする

  1. ホストOSから、Continer の guest ユーザのアカウントに ssh でアクセスする。
  2. $ ssh -p 7076 guest@localhost ← ホストOSの 7076 番ポートに sshアクセスする The authenticity of host '[localhost]:7076 ([127.0.0.1]:7076)' can't be established. ECDSA key fingerprint is SHA256:l7tsXhvQOaz9nn7Aa2JuAyjHN5QTVrxlywRxklHDixA. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '[localhost]:7076' (ECDSA) to the list of known hosts guest@localhost's password: パスワードを入力する。エコーバックされない。
    Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.11.0-25-generic x86_64). ... $ ← Container 内の guest 権限の対話環境が開始する
  3. (Container 内の guest 権限で) ユーザ名を表示する。
  4. $ whoami   
    guest
    
  5. (Container 内の guest 権限で) ホームディレクトリを表示する。
  6. $ pwd   
    /home/guest
    
  7. (Container 内の guest 権限で) ホームディレクトリにあるファイルの一覧を表示する。
  8. $ ls -la   
    total 24
    drwxr-x--- 3 guest guest 4096 Nov  3 01:08 .
    drwxr-xr-x 1 root  root  4096 Nov  3 01:04 ..
    -rw-r--r-- 1 guest guest  220 Jan  6  2022 .bash_logout
    -rw-r--r-- 1 guest guest 3771 Jan  6  2022 .bashrc
    drwx------ 2 guest guest 4096 Nov  3 01:07 .cache
    -rw-r--r-- 1 guest guest  807 Jan  6  2022 .profile
    -rw-r--r-- 1 guest guest    0 Nov  3 01:08 .sudo_as_admin_successful
    
  9. (Container 内の guest 権限で) ssh 経由の対話環境を終了する。
  10. guest@af401d3cdf85:~$ exit logout
    Connection to localhost closed. $

他のPCからネットワーク経由で Container 内の対話環境にアクセスする

  1. (ホストOS上) 外部のPCからネットワーク経由で Container にアクセスするためには、ホストOSの 7076 番ポートを開けておく必要がある。
  2. Docker の公式文書では、「docker のポートフォワーディングは ufw のフィルタリングよりも前に行わるために、ufw の影響を受けない」 と記述されている (2025年春時点) が、これは現時点では間違いのようだ。 Containerに外部からアクセスするためには、ホストOSのポートを開けておく必要がある。

    Windows 11 の場合:

    「設定」→「ネットワークとインターネット」→「ネットワークの詳細設定」→「Windows ファイアウォール」→ 詳細設定
    1. ssh 用のポートを開ける。
      ローカルコンピューターのセキュリティ
      → 受信の規則
      → 新しい規則
      → 規則の種類「ポート」 → 次へ
      → プロトコルおよびポート → TCP → 特定のローカルポート → 7070-7079 → 次へ
      → 操作「接続を許可する」→ 次へ
      → プロファイル: ドメイン、プライベート、パブリックのすべてをチェックする
      → 名前: docker ssh → 完了
    2. WWWサーバ (jupyter) 用のポートを開ける
    3. 上と同様で → ... →
      → プロトコルおよびポート → TCP → 特定のローカルポート → 8080-8089 → 次へ → ...
      → 名前: docker http → 完了

    Ubuntu 24.04LTS の場合:

    ufw を用いる
    1. (ホストOS上) ファイアウォール ufw を有効化する。(既に有効化してあれば必要なし)
    2.   $ sudo apt update                          ← aptのデータベースを更新する
        $ sudo apt install -y ufw                  ← ufw をインストールする。
        $ sudo systemctl enable ufw                  ← ufw を有効化する
        $ sudo systemctl restart ufw                  ← ufw を再起動する
        
    3. (ホストOS上) ホストOSの 7076 番ポートを開放する。
    4.   $ sudo ufw allow 7076                          ← 7076 番ポートを開放する
        ルールを追加しました
        ルールを追加しました (v6)
        
    5. (ホストOS上) ファイアウォールの状態を確認する。
    6.   $ sudo ufw status 7076                          ← 7076 番ポートを開放する
        ...
      状態: アクティブ
      
      To                         Action      From
      --                         ------      ----
      ...
      7076                      ALLOW       Anywhere
      ...
      7076 (v6)                 ALLOW       Anywhere (v6)
        ...
      
  3. (ネットワーク上の他のPC) 他のマシンから、ホストOS上の Container に ssh 接続する。
  4. 以下は、ホストOSの IPアドレスが 192.168.12.10 の場合の、ssh アクセスの様子である。

    (他のPCから) $ ssh -p 7076 guest@192.168.12.10 guest@192.168.12.10's password: ← パスワードを入力する。エコーバックされない。
    Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.11.0-25-generic x86_64) ... guest $ whoami ← Container に guest 権限でアクセスできる guest guest $ exit logout
    Connection to 192.168.12.10 closed. $

Container が再起動すると sshd も自動起動されることを確認する

  1. (ホストOS上で) Container を停止する
  2. $ docker container stop llmdiy_01   
    
  3. (ホストOS上で) Container が停止されたことを確認する
  4. $ docker container ls   
    CONTAINER ID   IMAGE           COMMAND            CREATED         STATUS         PORTS          NAMES
    
  5. (ホストOS上で) 停止中の Container を起動する
  6. $ docker start llmdiy_01   
    
  7. (ホストOS上で) Container に ssh でアクセスして、sshd が自動的に起動していることを確認する。
  8. $ ssh -p 7076 guest@localhost ← ホストOSの 7076 番ポートに sshアクセスする guest@localhost's password: パスワードを入力する。エコーバックされない。
    Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.11.0-25-generic x86_64) ... $ ← Container 内の guest 権限の対話環境が開始する

「つくって学ぶ!LLM自作」入門のソースコードを使って jupyter サーバを動作させる。

  1. guest ユーザとして ssh 接続する。
  2. github からそーそコードをダウンロードする。
  3. $ git clone https://github.com/rasbt/LLMs-from-scratch.git  
    
  4. ./LLMs-from-scratch/ フォルダに移動してから、jupyter lab を起動する。
  5. $ cd LLMs-from-scratch  
    $ jupyter lab --allow-root --ip=0.0.0.0 --no-browser  
    
  6. 動作中の jupyter notebook の token を知るには、同じマシンの別のシェルで以下のコマンドを実行する。
  7. $ jupyter lab list  
    

外部のマシンからブラウザで jupyter サーバに接続する。

  1. ローカルネット内のリモートマシンからアクセスする。
  2. http://192.168.12.10:8086/lab?token=...
    
  3. インターネット上のリモートマシンからアクセスする場合の URL:
  4. http://ynitta.net:18086/lab?token=...
    
    (ルータで 18086 番ポートを 192.168.12.10:8086 にポートフォワーディングしている場合)