如何建立多人AI開發環境?JupyterHub安裝分享

辦公室有一台GPU server供幾位工程師共用,有不便放上雲端的資料就可以直接放進去跑。深度學習演算法的開發常常需要使用不同環境,因此我們以docker方式提供Tensorflow 2.x, 1.x, Pytorch等幾種不同環境,然後每種環境以不同的port為進入點,都以Jupyterlab為介面。

這樣的環境算是堪用,但也有許多不便,首先,沒有帳號管理,不同人的檔案只能以資料夾做區隔,也無法得知是否有人正在跟你共用同一個環境,然後每個port到底裝了什麼環境只能靠大家的記憶或是擲筊。

利用JupyterHub可以解決上述問題,每個使用者能自己選擇現在想使用的環境(這些環境都各是不同的docker image產生),且除了自身就有帳號管理機制外,還能連結像是Google、GitHub之類的帳號,admin介面能夠看到目前的使用者,也能手動或設定自動關掉沒在跑的server。

admin介面

為了維持乾淨整潔,JupyterHub是以docker的方式安裝,另外之前提到的Tensorflow, Pytorch等,也都會建立成一個個不同的docker image。

安裝JupyterHub

我們以docker compose方式安裝JupyterHub,所以其實只需要三個設定檔,首先是docker-compose.yml:

要注意的是,為了避免檔案因為docker container重啟而消失,我們將本機資料夾/home/aifuser/jupyterhub掛到docker裡的/persist上。

 version: '3'
 
 services:
   # Configuration for Hub+Proxy
   jupyterhub:
     build: .                # Build the container from this folder
     container_name: jupyterhub_hub   # The service will use this container name.
     volumes:                 # Give access to Docker socket.
       - /var/run/docker.sock:/var/run/docker.sock
       - jupyterhub_data:/srv/jupyterlab
       - /home/aifuser/jupyterhub:/persist
     environment:             # Env variables passed to the Hub process.
       DOCKER_NETWORK_NAME: jupyter_hub
     ports:
       - 8000:8000
     restart: unless-stopped  # Configuration for the single-user servers
   jupyterlab:
     image: jupyter/tensorflow-notebook
     command: echo
     
  volumes:
    jupyterhub_data:

第二個是最主要的設定檔jupyterhub_config.py:

第一段設定了如何產生使用者真正的要使用的開發環境,我們使用DockerSpawner來產生環境,且在c.DockerSpawner.allowed_images設定一些環境可供選擇,例如:”tensorflow-cpu”: “jupyter/tensorflow-notebook”。”tensorflow-cpu”是選單中的名稱,“jupyter/tensorflow-notebook”則是docker image的名稱,其實也就是官方的image

 import os
 import sys
 import shutil
 
 c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
 c.DockerSpawner.network_name = os.environ['DOCKER_NETWORK_NAME']
 
 c.DockerSpawner.allowed_images = {
     "tensorflow-cpu": "jupyter/tensorflow-notebook",
     "tensorflow-gpu (Tensorflow 2.8)": "harbor.aif.tw/env/tensorflow-notebook",
     "pytorch-gpu (Pytorch 1.10)": "harbor.aif.tw/env/pytorch-notebook"
 }
 c.DockerSpawner.remove_containers = True
 c.DockerSpawner.extra_host_config = {'runtime': 'nvidia'}
 c.Spawner.environment = {'GRANT_SUDO': 'yes'}
 c.Spawner.default_url = '/lab'

供使用者選擇的開發環境選單

接下來,指定admin為管理者帳號;設定使用FirstUseAuthenticator,我們直接使用內建的帳號管理系統,從admin介面新增使用者之後,使用者在第一次登入時必須自行設定密碼。

 from jupyter_client.localinterfaces import public_ips
 c.JupyterHub.hub_ip = public_ips()[0]
 c.JupyterHub.admin_access = True
 c.Authenticator.admin_users = {'admin'}
 
 c.JupyterHub.authenticator_class = 'firstuseauthenticator.FirstUseAuthenticator'
 c.LocalAuthenticator.create_system_users = True

設定idle多久自動關閉server,這裡指的server是使用者開的開發環境。

 #shutdown the server after no activity for an hour
 c.ServerApp.shutdown_no_activity_timeout = 60 * 60
 #shutdown kernels after no activity for 30 minutes
 c.MappingKernelManager.cull_idle_timeout = 30 * 60
 #check for idle kernels every two minutes
 c.MappingKernelManager.cull_interval = 2 * 60

設定建立新使用者的家目錄,並且與docker外的本機目錄連結,家目錄的owner ID/group ID需要與本機的user ID/group ID相同,否則會發生無法編輯的錯誤。

 def create_dir_hook(spawner):
     """ Create directory """
     username = spawner.user.name  # get the username
     home_path = os.path.join('/persist/', username)
     if not os.path.exists(home_path):
         os.mkdir(home_path)
         os.chown(home_path, 1000, 1000)  # Same UID/GID as in local machine
 
 c.Spawner.pre_spawn_hook = create_dir_hook
 notebook_dir = os.environ.get('DOCKER_NOTEBOOK_DIR') or '/home/jovyan/work'
 c.DockerSpawner.notebook_dir = notebook_dir
 c.DockerSpawner.volumes = {'/home/aifuser/jupyterhub/{username}': '/home/jovyan/work'}

JupyterHub的設定與使用者資料會存在/persist下,以免JupyterHub重啟後全部消失。

 c.JupyterHub.cookie_secret_file = '/persist/jupyterhub_cookie_secret'
 c.JupyterHub.db_url = '/persist/jupyterhub.sqlite'

第三個設定檔是Dockerfile:

 FROM jupyterhub/jupyterhub
 
 #Copy the JupyterHub configuration in the container
 COPY jupyterhub_config.py .
 
 #Install dependencies (for advanced authentication and spawning)
 RUN pip install dockerspawner
 RUN pip install jupyter-client
 RUN pip install jupyterhub-firstuseauthenticator

我們直接使用JupyterHub官方提供的docker image,放入jupyterhub_config.py,並且加入指令安裝其他需要使用到的package。

建立開發環境

如先前所說,我們會以docker image方式提供Tensorflow, Pytorch等幾種不同環境,因為jupyter的tensorflow-notebook已經幫我們安裝設定好了大部分使用Python做資料分析與處理的套件與tensorflow,我們直接使用下列指令拉下image做為CPU版tensorflow環境:

 $ docker pull jupyter/tensorflow-notebook

要讓tensorflow支援GPU,所以接下來就以tensorflow-notebook為基礎來做修改;首先是將安裝的tensorflow改成tensorflow-gpu,如下:

 #Install Tensorflow
 RUN pip install — quiet — no-cache-dir \
  ‘tensorflow-gpu’ && \
  mamba clean — all -f -y && \
  fix-permissions “${CONDA_DIR}” && \
  fix-permissions “/home/${NB_USER}”

除了安裝tensorflow-gpu,docker image所使用的OS也要能支援GPU;從tensorflow-notebook的Dockerfile中可以看到,tensorflow-notebook是基於scipy-notebook,scipy-notebook是基於minimal-notebook,minimal-notebook是基於base-notebook,於是我們找到base-notebook原本使用的是ubuntu:focal的image,配合我們GPU server現在的設定和CUDA/cudnn版本,將它改成nvidia/cuda:11.2.2-cudnn8-devel-ubuntu20.04

 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
 
 # Ubuntu 20.04 (focal)
 # https://hub.docker.com/_/ubuntu/?tab=tags&name=focal
 # ARG ROOT_CONTAINER=ubuntu:focal
 ARG ROOT_CONTAINER=nvidia/cuda:11.2.2-cudnn8-devel-ubuntu20.04
 
 FROM $ROOT_CONTAINER
 
 LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
 ARG NB_USER="jovyan"
 ARG NB_UID="1000"
 ARG NB_GID="100"

使用下列指令建立上述docker images

 $ docker build -t harbor.aif.tw/env/tensorflow-notebook:lastest .
 $ docker push harbor.aif.tw/env/tensorflow-notebook:lastest

Pytorch的環境也同樣藉由修改tensorflow-notebook的Dockerfile得來,修改如下:

 ARG OWNER=harbor.aif.tw/env
 ARG BASE_CONTAINER=$OWNER/scipy-notebook
 FROM $BASE_CONTAINER
 
 # Fix DL4006
 SHELL ["/bin/bash", "-o", "pipefail", "-c"]
 
 # Install Tensorflow
 RUN conda install --quiet --yes \
 'pytorch' \
 'torchvision' \
 'torchaudio' \
 'cudatoolkit=11.3' \
 -c pytorch && \
 mamba clean --all -f -y && \
 fix-permissions "${CONDA_DIR}" && \
 fix-permissions "/home/${NB_USER}"

使用下列指令建立上述docker images

 $ docker build -t harbor.aif.tw/env/pytorch-notebook:lastest .
 $ docker push harbor.aif.tw/env/pytorch-notebook:lastest

JupyterHub~啟動!

設定檔準備好了,開發環境的image也準備好了,回到JupyterHub設定檔的目錄下,準備啟動!

 $ docker-compose build
 $ docker-compose up -d

連線到http://localhost:8000,便能看到JupyterHub的登入畫面;因為設定檔中已有指定admin為管理者帳號,只要設定密碼後便能登入。

(撰稿工程師:張嘉哲

參考資料
https://hackmd.io/@DanielChen/Sy81P-Aw4?type=view

https://medium.com/analytics-vidhya/jupyterhub-docker-31b7a3469872

https://towardsdatascience.com/tutorial-stop-running-jupyter-notebooks-from-your-command-line-b3af93265230

(原文連結:多人AI開發環境JupyterHub安裝