zoukankan      html  css  js  c++  java
  • Docker部署jupyterhub及自定制(亲测)

    Docker 安装自定制 jupyterhub

    • 官方最新 jupyterhub 镜像存在问题,这里使用1.0.0版本
    • 默认使用 linux 用户体系进行用户认证,需要在 jupyterhub 的 Docker 容器中,/home 路径需要加创建文件夹的权限

    部署流程

    1. 拉取镜像 
      docker pull jupyterhub/jupyterhub:1.0.0 
      docker pull jupyterhub/singleuser:1.0.0
    2. 创建 jupyterhub_network 网络 
      docker network create --driver bridge jupyterhub_network
    3. 创建 jupyterhub 的 volume  
      mkdir -pv /data/jupyterhub/jupyterhub-custom  # 用于创建自定制的文件
      mkdir -pv /data/jupyterhub/jupyterhub-docker-con # 用于映射docker容器内部的路径,如/home
      chmod -R 777 /data/jupyterhub
    4. 在 /data/jupyterhub/jupyterhub-custom 下创建 jupyterhub_config.py 文件
      # coding:utf-8
      
      from tornado import gen
      from jupyterhub.auth import Authenticator
      import os
      import pwd
      import requests
      
      class MyAuthenticator(Authenticator):
      
          def system_user_exists(self, username):
              """Check if the user exists on the system"""
              try:
                  self.log.info('create user: %s' % username)
                  pwd.getpwnam(username)
              except Exception as e:
                  self.log.error('create password for user error: %s' % e)
                  return False
              else:
                  return True
      
          def add_system_user(self, username, password):
              """Create a new local UNIX user on the system.
              Tested to work on FreeBSD and Linux, at least.
              """
              res = os.system('useradd  %(name1)s ' % {'name1': username})
              if res:
                  self.log.warn('user %s create failure: %s' % (username, res))
                  return False
      
              # res = os.system('echo %(pass)s |passwd --stdin %(name1)s' % {'name1': username, 'pass': password})
              res = os.system('echo %(name1)s:%(pass)s | chpasswd' % {'name1': username, 'pass': password})
      
              if res:
                  self.log.warn('user %s password create failure: %s' % (username, res))
                  return False
              return True
      
          def check_token_local(self, token):
              sec = 'l55cj)hh95jorr6!vmhleo0tuyors)xy@@+jaj-^l6wp)))=d$'
              algorithm = 'HS256'
              try:
                  d = jwt.decode(token, sec, algorithm)
                  return d.get('user_id')
              except:
                  return None
      
          @gen.coroutine
          def authenticate(self, handler, data):
              '''
      
              :param handler:
              :param data:
              :return: 成功:username,失败:None
              '''
              self.log.warn(data)
              token = data.get('token')
              self.log.warn('request token is: %s' % token)
              if not token:
                  return None
      
              # 验证token
              user_id, username = self.check_token_local(token)
              self.log.warn('--- current user id: %s' % user_id)
      
              if not user_id or not username:
                  return None
      
              user = 'user_%s' %user_id
              password = 'deault_jupyter_pwd_random_string_for_user'
      
              if not self.system_user_exists(user):
                  if self.add_system_user(user, password):
                      return user
                  else:
                      return None
      
              return user
      
      
              #user = handler.request.headers.get("User_info")
              #if user is not None:
              #    user = json.loads(user)
              #    username = user.get("username")
              #    return username
      
      c.JupyterHub.authenticator_class = MyAuthenticator
      
      c.PAMAuthenticator.encoding = 'utf8'
      
      # 指定cookie secret的文件,内容必须是64位哈希字符串,如6dd65ff19de7b8cb6d53031b0ad940e7379e15cf7ab612094d19e8b5141cc52c
      # c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/jupyterhub_cookie_secret'
      
      #创建用户时已经开指定的目录,这里就不需要在指定工作目了
      #c.Spawner.notebook_dir = '/data/file'
      
      #开启管理员用户
      c.JupyterHub.admin_access = True
      c.JupyterHub.admin_users = {"jupyterhub", "root"}
      
      # 白名单
      # c.Authenticator.whitelist = {}
      
      # Jupyterhub service setting
      # c.JupyterHub.spawner_class = 'sudospawner.SudoSpawner'
      c.JupyterHub.base_url = '/jupyter/'
      c.JupyterHub.cookie_max_age_days = 1  # cookie有效期为1天,默认值14为2周
      
      # customer templstes path, default is []
      c.JupyterHub.template_paths = ["templates"]
      使用jwt对进行自定义token认证
    5. 在 /data/jupyterhub/jupyterhub-custom 下创建userlist文件,写入admin用户,该用户是容器的管理员用户
      jupyterhub admin
      root admin
    6. 在 /data/jupyterhub/jupyterhub-custom 下创建 Dockerfile
      ARG BASE_IMAGE=jupyterhub/jupyterhub:1.0.0
      FROM ${BASE_IMAGE}
      
      ADD templates /srv/jupyterhub/templates
      ADD jupyterhub_config.py /srv/jupyterhub
      ADD userlist /srv/jupyterhub
      
      RUN echo "[global]
      index-url = https://mirrors.aliyun.com/pypi/simple/" > /etc/pip.conf &&
          pip install --no-cache --upgrade jupyter &&
          pip install --no-cache dockerspawner &&
          pip install --no-cache oauthenticator  &&
          chmod -R 777 /home
      EXPOSE 8000
      
      USER root
    7. 执行 build 命令构建镜像 
      docker build -t custom/jupyterhub .
    8. 在 /data/jupyterhub/jupyterhub-custom 下创建 singleuser 文件夹,在该文件夹下创建 Dockerfile
      ARG BASE_IMAGE=jupyterhub/singleuser:1.0.0
      FROM ${BASE_IMAGE}
      
      RUN pip install jupyterlab &&
          jupyter serverextension enable --py jupyterlab --sys-prefix
      
      USER root
    9. 在 /data/jupyterhub/jupyterhub-custom/singleuser 下执行build命令构建镜像
       docker build -t custom/jupyter_lab_singleuser .
    10. 创建/data/jupyterhub/jupyterhub-docker-con/docker-home用于映射容器内部的/home路径
    11. 开启容器
      docker run -d --name jupyterhub -p18000:8000  
      --network jupyterhub_network 
      -v /var/run/docker.sock:/var/run/docker.sock  
      -v /data/jupyterhub/jupyterhub-custom:/srv/jupyterhub 
      -v /data/jupyterhub/jupyterhub-docker-con/docker-home:/home  
      jupyterhub/jupyterhub:latest 
    12. 进入容器,修改 /home路径在的权限
      docker exec -it jupyterhub bash
      chmod -R 777 /home

    前端自定制

      jupyterhub 内核使用 Tornado 框架开发,前后端不分离,使用的是后端 render 或者 redirect 配合前端 jinja2 模板引擎渲染的方式实现,类似于 Django。

      默认支持自定制几个基本的前端页面,自定制的 HTML 文件需要放在上述 jupyterhub-custom 路径的 template 文件夹下(template 文件夹需自行创建),然后在 jupyterhub_config.py 中加入一行 c.JupyterHub.template_paths = ["templates"]

      jupyterhub_config.py 为 jupyterhub 的配置文件,在服务中有一份默认的配置,用户自己创建的 jupyterhub_config.py 中的配置优先级会大于默认配置,如:

    # 默认jinja模板路径配置
    c.JupyterHub.template_paths = []  
    # 自定义配置
    c.JupyterHub.template_paths = ["templates"]  
    # 如果不进行自定义配置,即使有HTML文件,服务也找不到
    • 支持自定义的 HTML 文件如下:
      • login.html:登录页面
      • home.html:个人主页
      • token.html:token页面
      • 404.html
      • admin.html
      • error.html
      • logout.html
      • page.html:其他 html 的基类模板
      • not_running.html
      • oauth.html
      • spawm.html
      • spawn_pending.html
      • stop_pending.html
    • 其他的深度自定制则需要进入容器中修改源码,如
      • 自定制后端登录功能:需要修改 /opt/conda/lib/python3.6/site-packages/jupyterhub/login.py 
      • 自定制 notebook 页面的导航条:需要修改 /opt/conda/lib/python3.6/site-packages/jupyterhub/singleuser.py 
      • 深度自定制 notebook 页面:需要修改 /opt/conda/lib/python3.6/site-packages/notebook/templates/tree.html

    HTML 代码来源

      jupyterhub 的 jinja 模板文件(即那些HTML文件)用了大量的模板继承(extend语法),修改这些文件前需要先明白模板的继承顺序。

    1.  /opt/conda/sharejupyterhub/templates:支持被自定义的HTML文件,也就是说想修改这些文件不需要修改源码,只需要在 jupyterhub-custom 路径的 template 文件夹下有同名文件就可覆盖
    2.  /opt/conda/lib/python3.6/site-packages/notebook/templates:不支持被自定义,也就是说想修改这些文件需要直接修改源码
    3. 后端代码:其他

    模板继承、通过后端自定制前端

    • “1”中有一个 page.html 是 “1” 中其他 HTML 文件的基板,也就是说 “1” 中其他 HTML 文件都继承了 page.html。
    • “2”中有一个 page.html 是 “2” 中其他 HTML 文件的基板,也就是说 “2” 中其他 HTML 文件都继承了 page.html。
    • “2”中有的 page.html 也是 “1”中 page.html 的基板,也就是说 “1”中的 page.html 继承了 “2” 中的 page.html。
    • 后端代码中有很多 HTML 格式的字符串直接 render 到前端,需要自行研究。如:
      • “2”中的 page.html 页面的导航条(也是所有其他页面的导航条),自定制该导航条需要修改 /opt/conda/lib/python3.6/site-packages/jupyterhub/singleuser.py 的 page_template 变量
      • .......

    其他自定制

       主要是修改源码,未完待续。。

                             

  • 相关阅读:
    49. 字母异位词分组
    73. 矩阵置零
    Razor语法问题(foreach里面嵌套if)
    多线程问题
    Get json formatted string from web by sending HttpWebRequest and then deserialize it to get needed data
    How to execute tons of tasks parallelly with TPL method?
    How to sort the dictionary by the value field
    How to customize the console applicaton
    What is the difference for delete/truncate/drop
    How to call C/C++ sytle function from C# solution?
  • 原文地址:https://www.cnblogs.com/zhuminghui/p/13542094.html
Copyright © 2011-2022 走看看