1. 程式人生 > >用Docker部署自己的JupyterHub

用Docker部署自己的JupyterHub

【話在前頭】

用 Docker 部署 JupyterLab 感覺是部署 JupyterLab 最方便的方式了,官方提供了很多可選的映象,也可以自己從 jupyter/base-notebook 中繼續打包,映象啟動命令加上“--NotebookApp.password”就可以直接用密碼登入用了。雖然只是自己一個人用,但是如果放在網際網路上訪問的話,總感覺不是那麼安全,還是希望能像其他服務一樣,能獨立管理使用者資訊,能設定二次驗證(2FA)。不過搜了下網上關於 JupyterHub 的資料比較少,甚至於官方的說明文件寫的也不是很詳細,有些配置和引數只能去原始碼裡扒。

 

【文章索引】

  1. 打包 JupyterHub 映象
  2. 配置 JupyterHub 啟動引數
  3. 啟動 JupyterHub
  4. 隔離 JupyterHub/JupyterLab 網路

 

【一、打包 JupyterHub 映象】

JupyterHub 架構的介紹和原理官方文件中描述的非常清楚了,這裡不再贅述了,簡單說就是 JupyterHub 把 認證 和 單使用者 JupyterLab 的管理 分別拆成了 Authenticator 和 Spawner 模組,可以根據不同的需要配置不同的認證方式或管理方式。不過官方的 JupyterHub 映象只包含了 JupyterHub 專案 本身,只有最基本的認證和管理(如通過 Linux 下 PAM 進行認證、通過本地程序執行 JupyterLab 等)。如果想通過自定義賬號密碼、並且開啟 2FA 的話,JupyterHub 其實也已經實現了一個官方的 NativeAuthenticator 模組,官方文件還是比較詳細的,預設使用者資訊儲存在 JupyterHub 的 sqlite 資料庫中,可以通過資料來源配置改成 Mysql,如果需要連線 Mysql 的話,官方的映象也不包含相關模組,也需要自行安裝。

除此之外,如果 JupyterHub 管理的 JupyterLab 也想在 docker 中執行的話,還需要使用官方提供的 DockerSpawner 進行管理,不過官方文件不是特別詳細,好在程式碼不多,扒扒程式碼也能看明白具體應該怎麼配置。

所以,如果我們需要實現能獨立管理的使用者資訊、支援2FA、使用Mysql資料庫儲存使用者資料,使用者的 JuyterLab 也通過 docker 映象進行執行和管理的話,我們可以通過如下的 Dockerfile 在官方映象之上打一個更完整的映象。

 1 ARG BASE_IMAGE=jupyterhub/jupyterhub:1.2
 2 FROM $BASE_IMAGE
 3 
 4 LABEL maintainer="MaysWind <[email protected]>"
 5 
 6 # Install Dependencies
 7 RUN apt-get update \
 8   && apt-get install -y --no-install-recommends unzip \
 9   && rm -rf /var/lib/apt/lists/* \
10   && rm -rf /tmp/*
11 
12 # Install Mysql
13 RUN pip3 --no-cache-dir install mysql-connector \
14   && rm -rf /tmp/*
15 
16 # Install NativeAuthenticator
17 RUN curl "https://github.com/jupyterhub/nativeauthenticator/archive/master.zip" -L -o /tmp/nativeauthenticator.zip \
18   && unzip /tmp/nativeauthenticator.zip -d /tmp \
19   && mv /tmp/nativeauthenticator-master /usr/local/bin/nativeauthenticator \
20   && pip --no-cache-dir install -e /usr/local/bin/nativeauthenticator \
21   && rm -rf /tmp/*
22 
23 # Install DockerSpawner
24 RUN pip --no-cache-dir install dockerspawner \
25   && rm -rf /tmp/*

注:寫這篇部落格的時候,JupyterHub 的最新 Release 版本是 1.1.0,但是 1.1.0 的 docker 映象存在問題(靜態資源沒有編譯等),所以這裡使用的是還在開發中的映象(1.2 tag 目前與 1.2.0dev tag 一致)。

 

【二、配置 JupyterHub 啟動引數】

打完映象後後其實就可以啟動了,不過通常還有些配置需要調整下。我通過 docker-compose 啟動 JupyterHub 容器,所有配置引數都通過引數或環境變數進行配置,同時由於 JupyterHub 在 docker 容器中,還需要把宿主機的 docker.sock 掛載到容器內,以便 JupyterHub 能夠管理 JupyterLab 容器。並且為 JupyterHub 和之後的 JupyterLab 建了一個單獨的網路,方便之後對 JupyterLab 的請求進行隔離,如果沒有需求的話實際上按預設的網路配置也是可以的,相關的 yml 示例配置如下

 1 version: "2"
 2 networks:
 3   jupyter-network:
 4     driver: bridge
 5     ipam:
 6       config:
 7         - subnet: 192.168.254.0/24
 8           gateway: 192.168.254.1
 9 services:
10   jupyterhub:
11     image: 你的 JupyterHub 映象名稱 
12     container_name: jupyterhub
13     hostname: "jupyterhub"
14     networks:
15       - "jupyter-network"
16     command:
17       - "jupyterhub"
18       - "--JupyterHub.hub_bind_url='http://:8081'" # JupyterHub 預設繫結 127.0.0.1,需要改成繫結所有 IP 使 JupyterLab 能跨容器訪問
19       - "--JupyterHub.db_url='mysql+mysqlconnector://Mysql使用者名稱:Mysql密碼@資料庫地址/資料庫名稱'" # 設定 Mysql 資料庫,如果使用預設 Sqlite,可以掛載目錄到 /srv/jupyterhub 實現資料庫持久化
20       - "--JupyterHub.authenticator_class='nativeauthenticator.NativeAuthenticator'" # 使用 NativeAuthenticator
21       - "--JupyterHub.spawner_class='dockerspawner.DockerSpawner'" # 使用 DockerSpawner
22       - "--JupyterHub.admin_access=True" # 啟用管理員功能
23       - "--Authenticator.admin_users={'管理員賬戶名稱'}" # 管理員名稱
24       - "--Authenticator.allow_2fa=True" # 開啟 2FA 功能
25       - "--DockerSpawner.remove_containers=True" # 每次啟動 JuypyterLab 容器時都刪除之前的容器,如果通過 docker-compose 設定的網路,docker-compose 重新配置網路後一定要重新建立容器才能啟動
26       - "--DockerSpawner.notebook_dir='/home/jovyan/work'" # 設定筆記本預設目錄(預設是 ~)
27       - "--DockerSpawner.image='你的 JupyterLab 映象名稱'"
28       - "--DockerSpawner.network_name='JupyterLab 網路名稱'" # 如果是通過 docker-compose 設定的網路,與第3行可能不一致,需要通過 docker network ls 檢視
29       - "--DockerSpawner.args=['--Application.log_level=WARN']" # 設定日誌預設輸出級別
30       - "--DockerSpawner.environment={\
31              'JUPYTER_ENABLE_LAB': 'yes'\ # 開啟 JupyterLab
32            }"
33       - "--DockerSpawner.volumes={\
34              '/etc/localtime': {'bind': '/etc/localtime', 'mode': 'ro'},\
35              '本機 Jupyter 筆記儲存路徑': '/home/jovyan/work'\  # 可以使用 “{username}” 佔位,表示使用者名稱,如 '/mnt/data1/jupyter/{username}/work': '/home/jovyan/work'
36            }"
37     volumes:
38       - "/etc/localtime:/etc/localtime:ro"
39       - "/var/run/docker.sock:/var/run/docker.sock"
40     restart: on-failure

其中,JupyterHub 配置檔案中的配置都可以通過啟動引數的方式進行配置,如上述配置中 command 中的配置項,所有 JupyterHub 配置可以參考官方文件。對於 NativeAuthenticator,也額外提供了一些其他引數,如自己註冊完賬號,可以設定“Authenticator.open_signup”引數為 False,關閉開放註冊功能,“Authenticator.ask_email_on_signup” 註冊時需要提供郵箱賬號等,這些引數可以如上附到啟動引數中,或者也可寫入到配置檔案中,更多引數和用法可以參考官方文件。對於 DockerSpawner,有些引數是實現了基礎類 Spawner 中定義的,可以查閱 Spawner 的定義文件 進行配置,也有部分是其本身單獨實現的,可以查閱其原始碼,例如其支援限制記憶體 “DockerSpawner.mem_limit”、限制CPU “DockerSpawner.cpu_limit”等引數,都是實現基礎類 Spawner 中定義的,Docker 網路名稱 “DockerSpawner.network_name ”、啟動容器前刪除已有容器的引數 “DockerSpawner.remove_containers” 等都是其本身自己實現的。

如果之前也是通過 docker 部署的 JupyterLab,可能下述幾個引數能遷移大部分之前的個性化配置,

  • DockerSpawner.args 可以追加 JupyterLab 容器的啟動引數,預設啟動命令是“start-notebook.sh --ip=0.0.0.0 --port=8888”,可以追加多個引數(如上述設定了配置了日誌輸出級別為WARN,JupyterLab 配置檔案中的配置都可以使用此方式進行配置,相關配置可以參考官方文件),引數格式是 python 的 dict。
  • DockerSpawner.environment 可以設定 JupyterLab 容器的環境變數,如上述設定了開啟 JupyterLab 功能,容器所有環境變數可以參考官方文件,引數格式是 python 的 dict。
  • DockerSpawner.volumes 可以設定 JupyterLab 容器的掛載配置,提供了兩種配置方式(讀寫模式:'source_path': 'target_path',或自定義讀寫模式(如只讀):'source_path': {'bind': 'target_path', 'mode': 'ro'}),格式是 python 的 dict。

 

【三、啟動 JupyterHub】

根據第二步的配置,就可以通過 docker-compose 或者其他方式啟動 JupyterHub 的 docker 映象了,只不過很有可能會失敗,主要是由於 NativeAuthenticator 對 Mysql 的相容性問題,用於管理註冊使用者資訊的那張表沒有自動建立成功,不過我們可以幫他完成這個任務,即編寫類似如下的SQL(具體儲存引擎、編碼可以根據自己實際情況調整)。

CREATE TABLE `users_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `password` blob NOT NULL,
  `is_authorized` bit(1) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `has_2fa` bit(1) DEFAULT NULL,
  `otp_secret` varchar(16) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

建立完 users_info 表後 JupyterHub 應該就能正常啟動了,接下來就可以自己進行註冊賬號了,如果沒有關閉開放註冊功能或者註冊的賬號名在配置中的管理員使用者名稱中的話,賬號直接就可以登入,否則需要自行去資料庫中找到自己註冊的記錄,並將 “is_authorized” 欄位設定為1。

登入後應該會預設啟動 JupyterLab,或者也可以自行選擇啟動,啟動成功後會自動跳轉到 JupyterLab,下次訪問時直接就會訪問 JupyterLab,而不會再顯示 JupyterHub 的介面了。如果啟動失敗,也可以通過 docker 檢視 JupyterLab 的容器情況。

 

【四、隔離 JupyterHub/JupyterLab 網路】

JupyterLab 裡什麼都能幹,能執行程式碼,能執行指令碼,總覺得部署了 JupyterLab 後,直接把內網環境對外打開了,所以還想再對 JupyterHub/JupyterLab 的網路進行隔離,不允許其訪問內網。這塊通過 iptables 就可以實現,比如上述我定義了 jupyter-network 網路,IP 是 192.168.254.0/24,我內網 IP 是 192.168.1.0/24,路由(閘道器)是 192.168.1.1,所以我在宿主機上定義如下 iptables,禁止來自 jupyter-network 的 IP 請求內網 IP(但允許通過路由訪問網際網路)。當然,如果 Mysql 伺服器不與 JupyterHub/JupyterLab 在一臺宿主機上的話,別忘了允許 JupyterHub 的 IP 地址訪問 Mysql 埠。

iptables -I DOCKER-USER -s 192.168.254.0/24 -d 192.168.1.0/24 -j DROP
iptables -I DOCKER-USER -s 192.168.254.0/24 -d 192.168.1.1 -j ACCEPT

此外,如果宿主機上還有其他服務或 docker 例項,如果需要禁止 JupyterHub/JupyterLab 訪問他們,還需要再定義一條

iptables -I INPUT -s 192.168.254.0/24 -p tcp -j DROP

 

這樣,應該就相對安全了一些