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]\nindex-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 變量
    • .......

 

其他自定制

   主要是修改源碼,未完待續。。

 

 

                         

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM