使用Jupyter API调度和调用Notebooks作为Web服务

使用Jupyter API调度和调用Notebooks作为Web服务' can be condensed to '使用Jupyter API调度和调用Notebooks作为Web服务

感谢像GCP CloudRunner和Cloud Functions这样的无服务器云服务,我们无需管理昂贵的虚拟机或服务器来部署和定期执行我们的笔记本。通过Jupyter API,您可以将笔记本迁移到云端,将其转化为Web服务,并与调度集成。

由MidJourney生成的云端定期执行的Python笔记本,作者指导

然而,最常用的方法(除非您使用像Vertex AI或SageMaker这样的云原生服务)是使用nbconvert将笔记本转换为Python代码,并将代码添加到全新引导的Tornado或Flask自定义Web应用程序中。

作者生成的没有传统容器化的Python笔记本,图像

这需要一些编码和外部库,但好消息是,我们可以将代码保留在Jupyter开发容器中,并直接从那里使用Jupyter Rest API触发它。

通过Web API访问笔记本

在我们深入了解如何使用Jupyter API的详细信息之前,我将演示该架构的工作原理。首先,让我们拿一个简单的笔记本用于测试。

如果一切正常,则返回“15”的简单测试笔记本

要在本地使用Jupyter运行它,最简单的方法是在Jupyter Lab容器中运行它:

# 下载测试工作簿wget https://raw.githubusercontent.com/tfoldi/vizallas/main/notebooks/JupyterAPI_Test.ipynb# 使用令牌身份验证在新的Jupyter lab实例中启动容器docker run -it --rm -p 8888:8888 \  -e JUPYTER_TOKEN=ab30dd71a2ac8f9abe7160d4d5520d9a19dbdb48abcdabcd \  --name testnb -v "${PWD}":/home/jovyan/work jupyter/base-notebook \  jupyter lab --ServerApp.disable_check_xsrf=true

一旦服务启动,您将能够通过在JUPYTER_TOKEN环境变量中传递的令牌,在http://127.0.0.1:8888/lab/tree/work访问该笔记本。

通过命令行调用笔记本

从命令行,您可以下载这个小脚本(它需要requestswebsocket-client包),或者通过Docker容器运行它:

# 检查我们先前启动的“testnb”容器的IP地址docker inspect testnb | grep IPAddress            "SecondaryIPAddresses": null,            "IPAddress": "172.17.0.2",                    "IPAddress": "172.17.0.2",# 调用我们的笔记本。将下面的IP替换为上一步的IP.docker run -it --rm \  -e JUPYTER_TOKEN=ab30dd71a2ac8f9abe7160d4d5520d9a19dbdb48abcdabcd \  tfoldi/jupyterapi_nbrunner 172.17.0.2:8888 /work/JupyterAPI_Test.ipynb在http://172.17.0.2:8888/api/kernels创建一个新内核发送每个单元格的执行请求{'data': {'text/plain': '15'}, 'execution_count': 3, 'metadata': {}}处理完成。关闭websocket连接删除内核

此脚本连接到我们新创建的JupyterLab服务器,执行我们的笔记本,返回最后一个单元格的结果,然后退出。整个过程在Web协议上进行,无需对笔记本代码或其他库进行任何修改。

底层实现

不幸的是,Jupyter API中没有单个端点可以完整执行一个notebook。首先,我们需要初始化一个新的内核(或使用现有的内核),检索notebook的元数据,获取所有代码单元格,并为每个代码单元格发送一个execute_request

要检索结果,我们需要在WebSocket通道中监听传入的消息。由于没有“所有代码执行结束”的消息,我们必须手动跟踪发送执行请求的代码块数量以及实际完成的代码块数量,通过计算所有类型为execute_reply的消息数量。一旦所有代码执行完毕,我们可以停止内核或将其保持在空闲状态以供将来使用。

下图详细说明了整个流程:

通过Rest API执行Jupyter Notebook的步骤。Notebook级别的操作使用Rest API,而单元格级别的调用使用WebSockets。作者提供的图片。

为了保持身份验证,我们必须在所有HTTP和WebSocket调用中传递Authorization头。

如果对于执行notebook来说,这些步骤似乎有些繁琐,我明白。我相信在Jupyter Server内部实现一个更高级的函数来减少复杂性会很有用。

完整的脚本在此处提供,可供您的应用程序使用。

几乎免费在GCP上安排我们的工作簿

虽然有很多托管notebook的选择,但最具成本效益的方式是利用Google Cloud的Cloud Run服务。使用Cloud Run,您只需支付作业的确切运行时间,这使其成为一种适用于不频繁触发的任务而无需额外包或其他SaaS供应商(除了Google)的经济型选择,而且不需要编写一行代码。

架构和调用流程如下所示:

我们将只使用无服务器服务以降低成本。作者提供的图片。

首先,我们需要在GCP Cloud Run中部署我们的notebook。有多种方法可以将文件添加到Cloud Run服务中,但最简单的方法之一是将我们的notebook复制到一个Docker容器中。

# 将notebook托管在Jupyter Server上的简单dockerfileFROM jupyter/base-notebookCOPY JupyterAPI_Test.ipynb /home/jovyan/workspaces/

要构建并在Cloud Run中提供容器,我们只需使用gcloud run deploy命令指定--source选项,将其指向包含我们的notebook和Dockerfile的目录。

# 获取Jupyter notebook和Dockerfile的源代码git clone https://github.com/tfoldi/jupyterapi_nbrunner.git# 在jupyter/base-notebook容器中部署测试notebook # Dockerfile和JupyterAPI_Test.ipynb文件位于tests/test_notebook文件夹中gcloud run deploy test-notebook --region europe-west3 --platform managed \  --allow-unauthenticated --port=8888 \  --source tests/test_notebook \  --set-env-vars=JUPYTER_TOKEN=ab30dd71a2ac8f9abe7160d4d5520d9a19dbdb48abcdabcd   [...]Service [test-notebook] revision [test-notebook-00001-mef] has been deployed and is serving 100 percent of traffic.Service URL: https://test-notebook-fcaopesrva-ey.a.run.app

JupyterLab将在服务URL上可用。Google Cloud Run将提供SSL证书和启动或暂停容器的机制,具体取决于访问部署的请求。

要从Cloud Scheduler触发我们新部署的笔记本,我们必须创建一个与PubSub主题关联的Cloud Function。下面的命令将从此存储库部署main.pyrequirements.txtmain.py是我们之前用于从命令行触发代码的相同脚本。

# 确保您在克隆https://github.com/tfoldi/jupyterapi_nbrunner.git的 # 内容的同一个目录中 gcloud functions deploy nbtrigger --entry-point main --runtime python311 \  --trigger-resource t_nbtrigger --trigger-event google.pubsub.topic.publish \  --timeout 540s --region europe-west3 \  --set-env-vars=JUPYTER_TOKEN=ab30dd71a2ac8f9abe7160d4d5520d9a19dbdb48abcdabcd

让我们通过向t_nbtrigger主题发送带有适当参数的消息来测试我们的新Cloud Function,就像我们在命令行中所做的那样:

gcloud pubsub topics publish t_nbtrigger \  --message="test-notebook-fcaopesrva-ey.a.run.app:443        /workspaces/JupyterAPI_Test.ipynb --use-https"

如果您检查nbtrigger Cloud Function的日志,您可能会注意到向主题发出记录成功触发了我们指定的笔记本的执行:

日志显示了我们笔记本的成功执行。作者提供的图像。

最后一步是创建一个在指定时间运行的计划。在这种情况下,我们将每小时运行我们的笔记本:

gcloud scheduler jobs create pubsub j_hourly_nbtrigger \  --schedule "0 * * * *" --topic t_nbtrigger --location europe-west3 \  --message-body "test-notebook-fcaopesrva-ey.a.run.app:443 /workspaces/JupyterAPI_Test.ipynb --use-https --verbose"   

您已经完成了 – 您刚刚以无服务器方式安排了第一个Jupyter Notebook。

CloudRun在作业执行后自动关闭我们的容器。在不指定最小实例的情况下,“空闲”状态也是免费的。

我们的笔记本每天只消耗几分钱,使得这种部署方式成为谷歌云中最具成本效益的方法之一。

几天后的执行成本约为三美分。

结论

我们过去常常依赖将Jupyter Notebook转换为Python代码,以使其可用于云原生工具,或者使用更复杂、更昂贵的服务,如Vertex AI或SageMaker。然而,通过利用Jupyter Rest API并将您的Notebooks与其“开发环境”一起部署,您可以跳过额外的步骤,并为您的Notebooks启用Web服务调用或调度。

尽管这种方法不一定适用于具有计算密集型长时间运行笔记本的大型项目,但对于您的家庭自动化或爱好项目来说,这是完全可以接受的——而无需在基础架构上(过度)消费。