【话在前头】html
用 Docker 部署 JupyterLab 感受是部署 JupyterLab 最方便的方式了,官方提供了不少可选的镜像,也能够本身从 jupyter/base-notebook 中继续打包,镜像启动命令加上“--NotebookApp.password”就能够直接用密码登陆用了。虽然只是本身一我的用,可是若是放在互联网上访问的话,总感受不是那么安全,仍是但愿能像其余服务同样,能独立管理用户信息,能设置二次验证(2FA)。不过搜了下网上关于 JupyterHub 的资料比较少,甚至于官方的说明文档写的也不是很详细,有些配置和参数只能去源码里扒。python
【文章索引】mysql
JupyterHub 架构的介绍和原理官方文档中描述的很是清楚了,这里再也不赘述了,简单说就是 JupyterHub 把 认证 和 单用户 JupyterLab 的管理 分别拆成了 Authenticator 和 Spawner 模块,能够根据不一样的须要配置不一样的认证方式或管理方式。不过官方的 JupyterHub 镜像只包含了 JupyterHub 项目 自己,只有最基本的认证和管理(如经过 Linux 下 PAM 进行认证、经过本地进程运行 JupyterLab 等)。若是想经过自定义帐号密码、而且开启 2FA 的话,JupyterHub 其实也已经实现了一个官方的 NativeAuthenticator 模块,官方文档仍是比较详细的,默认用户信息存储在 JupyterHub 的 sqlite 数据库中,能够经过数据源配置改为 Mysql,若是须要链接 Mysql 的话,官方的镜像也不包含相关模块,也须要自行安装。github
除此以外,若是 JupyterHub 管理的 JupyterLab 也想在 docker 中运行的话,还须要使用官方提供的 DockerSpawner 进行管理,不过官方文档不是特别详细,好在代码很少,扒扒代码也能看明白具体应该怎么配置。sql
因此,若是咱们须要实现能独立管理的用户信息、支持2FA、使用Mysql数据库存储用户数据,用户的 JuyterLab 也经过 docker 镜像进行运行和管理的话,咱们能够经过以下的 Dockerfile 在官方镜像之上打一个更完整的镜像。docker
1 ARG BASE_IMAGE=jupyterhub/jupyterhub:1.2 2 FROM $BASE_IMAGE 3 4 LABEL maintainer="MaysWind <i@mayswind.net>" 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 一致)。数据库
打完镜像后后其实就能够启动了,不过一般还有些配置须要调整下。我经过 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,可能下述几个参数能迁移大部分以前的个性化配置,
根据第二步的配置,就能够经过 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 的容器状况。
【4、隔离 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
这样,应该就相对安全了一些吧。