從零開發無服務函數管理器:jupyter lab 插件

本文介紹如何製作一個 jupyter lab 的插件。作為例子,我們將製作一個運行在 jupyter 中的serveless 函數的管理插件。和各種其他無服務函數不同的是:這是一個極其輕量級的 無服務函數 管理插件,不依賴任何其他組件,所有組件都會允許在 jupyter lab 內部。

1. 創建開發環境

1.1 安裝 conda/miniconda

1.2 創建開發環境,裝各種庫

conda create -n jupyterlab-ext --override-channels --strict-channel-priority -c conda-forge -c anaconda jupyterlab cookiecutter nodejs git  conda activate jupyterlab-ext

2. 創建 repo

mkdir jupyter-lab-serverless

3. 創建插件項目

3.1 使用 cookiecutter 創建項目模板

➜ cookiecutter https://github.com/jupyterlab/extension-cookiecutter-ts --checkout v1.0  author_name []: u2takey  extension_name [myextension]: jupyter-lab-serverless  project_short_description [A JupyterLab extension.]: Create And Run Serverless Function in JupyterLab  repository [https://github.com/my_name/myextension]: https://github.com/u2takey/jupyter-lab-serverless

可以發現一些模板文件已經創建出來了

➜ cd jupyter-lab-serverless  ➜ ll  .rw-r--r--  475 leiwang  6 Dec 19:16 README.md  .rw-r--r-- 1.2k leiwang  6 Dec 19:16 package.json  drwxr-xr-x    - leiwang  6 Dec 19:16 src  drwxr-xr-x    - leiwang  6 Dec 19:16 style  .rw-r--r--  555 leiwang  6 Dec 19:16 tsconfig.json

3.2 直接 Build安裝 試一下

jlpm install  jupyter labextension install . --no-build

3.3 打開觀察 第一次 安裝的效果

jupyter lab --watch    # 打開 瀏覽器 console,可以看到  > JupyterLab extension jupyter-lab-serverless is activated!

4. 開始製作 severless 插件

這個插件將分為兩個部分,一部分是 server 部分,一部分是前端部分. 我們將先創建後端部分。

4.1 server 插件部分

server 插件本質是一個 tornado handler,首先在 init 種實現load

def load_jupyter_server_extension(nb_server_app):      初始化

定義 API,我們的函數 Server API主要的作用是完成 無服務函數的 增刪查改,以及觸發.

為了讓實現更簡單,我們用 put/delete 帶函數名實現增刪改,post/get 帶函數名用於實現觸發,而get不帶函數名作為 查的實現,返回所有函數。同時為了 重啟後函數能得到保存,我們使用 sqite作為本地保存(jupyter lab serverside的 state保存可能有更好的辦法)。

class FunctionHandler(APIHandler):      """      A handler that manage serverless functions.      """        def initialize(self, app):          self.logger = app.log          self.db = app.db        @web.authenticated      @gen.coroutine      def get(self, function=''):          if function:              # 觸發          else:              # List          def trigger_function(self, function, method, query, body):          #  觸發實現        @gen.coroutine      def post(self, function=''):          #  觸法        @web.authenticated      @gen.coroutine      def put(self, function=''):          #  增        @web.authenticated      @gen.coroutine      def delete(self, function=''):          #  刪

函數的執行:

class Function(Base):      __tablename__ = 'functions'        # 各種字段略        def __call__(self, *args, **kwargs):          import imp          module = imp.new_module(self.name)          exec(self.script, module.__dict__)          module.handle.logger = self.logger          return module.handle(*args, **kwargs)

4.2 前端插件部分

在 index.js 中實現一個 xx Plugin 的繼承

增加前端依賴的辦法:

jlpm add @jupyterlab/apputils  jlpm add @jupyterlab/application  jlpm run build
/**   * Initialization data for the jupyter-lab-serverless extension.   */  const extension: JupyterFrontEndPlugin<void> = {    id: 'jupyter-lab-serverless',    requires: [IStateDB],    autoStart: true,    activate: activate  };

設計上,我們並沒有使用 jupyterlab 插件中常用的 platte,而是增加 toolbar 上的兩個 button。其中一個按鈕設計為增加函數,另一個函數用於管理包括刪除函數。

/**   * Save Current Script as A Serverless Function   */  export  class ButtonExtensionAdd implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {      createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {      let callback = () => {        // 保存函數      };      let button = new ToolbarButton({        className: 'serverless-add',        iconClassName: 'fa fa-desktop',        onClick: callback,        tooltip: 'Save as Serverless Functions'      });        panel.toolbar.addItem('severless-add', button);      return new DisposableDelegate(() => {        button.dispose();      });    }  }    /**   * Manager Serverless Function   */  export  class ButtonExtensionManager implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {    delete(name: string){      // 刪除函數    }    createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {      let callback = () => {        // 發送請求 獲取函數      };      let button = new ToolbarButton({        className: 'serverless-manager',        iconClassName: 'fa fa-tasks',        onClick: callback,        tooltip: 'Show Serverless Functions'      });        panel.toolbar.addItem('severless-manager', button);      return new DisposableDelegate(() => {        button.dispose();      });    }  }

在回調函數中,我們實現 request 發送到剛剛實現的 server 插件,獲取數據。

4.3 打包,發佈插件

python3 setup.py sdist    // 發佈後端插件  twine upload --skip-existing -u xx -p yy  dist/*    // 發佈前端插件  npm publish --access=public

5. 使用演示

5.1. 啟動

從鏡像啟動,鏡像中到 jupyter 已經安裝了 serveless 插件

docker run --rm -p 8888:8888 ccr.ccs.tencentyun.com/leiwang/jupterlab:serverless  /bin/bash  -c 'jupyter lab --ip=* --port=8888 --no-browser --allow-root'

5.2. 創建一個函數

函數需要有一個命名為 'handle' 的函數.

函數有個一個 logger,可以用於debug,logger輸出內容將輸出到 jupyter 後端。

def handle(event):      logger = handle.logger      logger.info(event)      return event

點擊保存按鈕, 保存函數

image.png

5.3. 本地測試

本地測試有兩種方式

一: 直接調用 handle 函數

二: 打開另一個 notebook,模擬 request 觸發函數,檢查效果

handle({})

注意 調用時需要帶上notebook的 Authorization,這個在 jupyter notebook 啟動時可以查看到。

import requests  headers = {'Authorization': 'token 4b917c156ea968fdafb81308324b06c5a9154596ebfcfd67'}  data = {"test": "testdata"}  r = requests.post('http://127.0.0.1:8888/function/test1.ipynb', json=data, headers=headers)    r.text  '{"code": "success", "data": {"method": "POST", "query": {}, "body": {"test": "testdata"}}}'

5.4. 管理函數

可以看到已經調用了一次

image.png

點擊刪除按鈕,可以把 函數刪除。

5.5. 定期執行函數

函數支持定期執行,schedule採用類似 https://schedule.readthedocs.io/en/stable/ 的語法表達方式

schedule 支持 'every'(默認1), 'unit'(默認為day), 'at' (默認為None) 三個參數

def handle(event):      logger = handle.__dict__.get('logger')      if logger:          logger.info(event)      return event    handle.schedule={'unit':'seconds'}

保存函數,觀察後端日誌,可以發現每秒被執行一次

image.png

5.6. 其他應用

可以用於接收 weekhook 做ci觸發,聊天機械人,定期執行腳步等等。

本文完整的代碼在 https://github.com/u2takey/jupyter-lab-serverless

參考