kehanlu/server-docker:一個基於 nvidia/cuda 的 Docker 環境,包含 Ubuntu, CUDA, Python(Jupyter Lab)…,可以快速、方便的建立不同且獨立的實驗環境。

開啟一個 Jupyter Container,同時也能用 shell & Vscode remote container 的方法開發。

  • Jupyter Lab:從外部透過瀏覽器開啟 public_IP:port
  • ssh: 連進去主機後,再 docker exec 在 container 中執行 shell
  • Vscode remote container

主要的問題 Docker 和 VM 本質上的不同, Docker 每次都是基於 Image 去開啟 Container(也就是 container 關閉之後,所作的改動會不見),所以我的作法是在製作 Image 的時候先安裝大部分需要使用到的軟體,然後在 Host 的建立資料夾 Volume 到 Container 的家目錄,所有可能常常會改動到的環境(包含 Python environment)以及自己的程式碼都裝在家目錄底下。

如果要改動到家目錄以外的檔案(e.g. sudo apt install)就必須另外 Commit 或是寫在 Dockerfile 重新 Build Image。

Installation

Prerequisite

  1. Install docker
  2. Install docker-compose
  3. Install nvidia-driver:最基本的(只要能執行 nvidia-smi 就可以當作完成了)
  4. Install nvidia toolkit:必須安裝這個才有辦法執行 cuda docker

Build image

1
2
git clone https://github.com/kehanlu/server-docker
cd server-docker
1
2
3
4
docker build \
--build-arg UID=$UID \
--build-arg USER=$USER \
-t lab .

Run init.sh

建立一個資料夾,用來當 docker 的家目錄。

1
2
mkdir docker-home
docker run -v $(pwd)/docker-home:/home/$USER lab sh /src/init.sh

Usage

Start Jupyter

-d:背景執行

1
docker-compose up -d

NOTE: 修改 .env 可以改成其他 JUPYTER_PORT。

訪問 IP:PORT 就可以看到 jupyter lab 的預設畫面了。可以用 token 來設定密碼。

Exec shell

在執行的 container 中(剛剛開啟的 jupyter),執行 shell。

$USER_lab 為 Container 的名字。

1
docker exec -it $USER_lab zsh

Modified root file

重要 如果你改動了家目錄以外的檔案(e.g. sudo apt install xxx),你必須執行 commit,不然在這個 container 被關閉時,你所建立的改動會不見。也可以把改動加到 Dockerfile 然後再 build image。

1
docker commit $USER_lab lab

實作細節

kehanlu/server-docker

Base Image

使用 Nvidia 官方提供的 nvidia/cuda,選擇自己需要的 CUDA 和作業系統版本。

Note::必須選擇 devel 版本才有我們正常開發時候的週邊軟體。

Dockerfile

以下的 Dockerfile 製作了

  • cuda10.2 且作業系統為 ubuntu18.04
  • 安裝了一些常用的軟體,e.g. git, zsh, curl
  • 在 Docker 中新增了使用者,與 Host 的使用者相同,可以比較方便的管理檔案權限。且這個使用者能執行 sudo。

Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
FROM nvidia/cuda:10.2-devel-ubuntu18.04


# Basic Setup
ENV TZ=Asia/Taipei
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone


# Install some useful tools
RUN apt-get update
RUN apt-get install -y apt-utils
RUN apt-get install -y git vim wget curl zsh tmux zip htop sudo

# Install pyenv dependencies
RUN apt-get install -y --no-install-recommends make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev


# Custom user
RUN echo '%sudo   ALL=(ALL:ALL) NOPASSWD:ALL' >> '/etc/sudoers'

ARG UID
ARG USER
RUN useradd -r -u ${UID} -G sudo -s /bin/zsh --create-home ${USER}
USER ${USER}
WORKDIR /home/${USER}

# Initialization script, for
# 1. generating configuration in the volume
# 2. set up python and jupyter
COPY init.sh /src/init.sh

Build image

建立名稱為 lab 的 image

1
2
3
4
docker build \
--build-arg UID=$UID \
--build-arg USER=$USER \
-t lab .

Initialization script

在家目錄安裝

  • zsh
  • pyenv & python3.8
  • jupyterlab
1
2
mkdir docker-home
docker run -v $(pwd)/docker-home:/root lab sh /src/init.sh

Note: 請注意,一定要自己建立 mkdir docker-home,若直接執行 docker run,雖然docker 會自己幫你建立一個資料夾,但這個資料夾的權限是屬於 root 的。在執行 init.sh 的時候,會發生 permission denied。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Install zsh
printf "Y\n" | sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"


# Install pyenv
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash

echo '\nexport PYENV_ROOT="$HOME/.pyenv"\nexport PATH="$PYENV_ROOT/bin:$PATH"\neval "$(pyenv init --path)"\neval "$(pyenv virtualenv-init -)"\n' >> .zshrc
echo '\nexport PATH="$HOME/.local/bin:$PATH"' >> .zshrc

$HOME/.pyenv/bin/pyenv install 3.8.5
$HOME/.pyenv/bin/pyenv virtualenv 3.8.5 lab
$HOME/.pyenv/bin/pyenv global lab

# Install jupyter
$HOME/.pyenv/versions/3.8.5/envs/lab/bin/python3.8 -m pip install --upgrade pip
$HOME/.pyenv/versions/3.8.5/envs/lab/bin/python3.8 -m pip install jupyterlab

mkdir -p $HOME/.jupyter
echo "c.ServerApp.ip = '0.0.0.0'\n\
c.ServerApp.open_browser = False\n\
c.ServerApp.notebook_dir = '$HOME'" > $HOME/.jupyter/jupyter_lab_config.py

開始使用

完成 1. 建立 Image 2. 執行 init.sh 之後,我們就得到一個可以用的 docker 環境了!

1
docker run -v $(pwd)/docker-home:/home/$USER -it lab zsh

執行 docker 中的 zsh

docker-compose

用 docker-compose

  • 開啟 jupyter 服務。
  • 設定 GPU 資源

.env 中設定 JUPYTER_PORT=8888

1
JUPYTER_PORT=8888
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
version: "3.4"
services:
  jupyter:
    hostname: "${USER}-lab"
    container_name: ${USER}_lab
    image: lab
    volumes:
      - ${PWD}/docker-home:/home/${USER}
      # source_folder:container_folder
    working_dir: /home/${USER}
    ports:
      - ${JUPYTER_PORT}:8888
    command: zsh -c '/home/${USER}/.pyenv/versions/lab/bin/jupyter lab --allow-root'
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]
              driver: nvidia

啟動 container。

所啟動的 container 會被命名為:$USER_lab(hank_lab)

1
docker-compose up
1
docker-compose up -d # 背景執行

docker ps

CONTAINER ID   IMAGE                      COMMAND                  CREATED          STATUS          PORTS                    NAMES
e041c5de7f7f   lab                        "zsh -c '/home/hank/…"   44 minutes ago   Up 43 minutes   0.0.0.0:8888->8888/tcp   hank_lab

訪問 IP:PORT 就可以看到 jupyter lab 的預設畫面了。可以用 token 來設定密碼。

在 container 執行 shell

上一步我們開啟了一個執行 jupyter 的 container,我們同時可以連結 shell 到正在執行的 container。

1
docker exec -it $USER_lab zsh