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
- Install docker
- Install docker-compose
- Install nvidia-driver:最基本的(只要能執行
nvidia-smi
就可以當作完成了) - Install nvidia toolkit:必須安裝這個才有辦法執行 cuda docker
Build image
git clone https://github.com/kehanlu/server-docker
cd server-docker
docker build \
--build-arg UID=$UID \
--build-arg USER=$USER \
-t lab .
Run init.sh
建立一個資料夾,用來當 docker 的家目錄。
mkdir docker-home
docker run -v $(pwd)/docker-home:/home/$USER lab sh /src/init.sh
Usage
Start Jupyter
-d
:背景執行
docker-compose up -d
NOTE: 修改 .env
可以改成其他 JUPYTER_PORT。
Exec shell
在執行的 container 中(剛剛開啟的 jupyter),執行 shell。
$USER_lab
為 Container 的名字。
docker exec -it $USER_lab zsh
Modified root file
重要! 如果你改動了家目錄以外的檔案(e.g. sudo apt install xxx
),你必須執行 commit
,不然在這個 container 被關閉時,你所建立的改動會不見。也可以把改動加到 Dockerfile 然後再 build image。
docker commit $USER_lab lab
實作細節
Base Image
使用 Nvidia 官方提供的 nvidia/cuda,選擇自己需要的 CUDA 和作業系統版本。
Note::必須選擇 devel
版本才有我們正常開發時候的週邊軟體。
Dockerfile
以下的 Dockerfile 製作了
- cuda10.2 且作業系統為 ubuntu18.04
- 安裝了一些常用的軟體,e.g.
git
,zsh
,curl
… - 在 Docker 中新增了使用者,與 Host 的使用者相同,可以比較方便的管理檔案權限。且這個使用者能執行 sudo。
Dockerfile
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
docker build \
--build-arg UID=$UID \
--build-arg USER=$USER \
-t lab .
Initialization script
在家目錄安裝
- zsh
- pyenv & python3.8
- jupyterlab
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。
# 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 環境了!
docker run -v $(pwd)/docker-home:/home/$USER -it lab zsh
docker-compose
用 docker-compose
- 開啟 jupyter 服務。
- 設定 GPU 資源
在 .env
中設定 JUPYTER_PORT=8888
。
JUPYTER_PORT=8888
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)
docker-compose up
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
在 container 執行 shell
上一步我們開啟了一個執行 jupyter 的 container,我們同時可以連結 shell 到正在執行的 container。
docker exec -it $USER_lab zsh