从零开发无服务函数管理器: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

参考