Deno 中的不可变脚本如何助力 Windmill.dev (YC S22) 构建生产级运维
本篇客座博客由 Ruben Fiszel 撰写,他是 Windmill.dev 的创始人兼首席执行官。
Windmill.dev 是一个开源开发者平台,公司可以在其中从脚本构建内部工作流和 UI。超过 300 家公司,包括 PhotoRoom 等企业客户,将 Windmill 作为其生产基础设施的核心部分,用于各种操作,例如 ETL 管道、整合内部和外部 API 等。
由于公司依赖 Windmill 来处理由脚本组成的任务关键型工作流,因此我们的技术必须具备高性能、高安全性和灵活性
- 脚本的最小冷启动时间
- 安全地运行不受信任的任意代码
- 共享和组合脚本的简单方法
Windmill 可以运行 Python、Typescript、Bash 和 Go 代码。对于 TypeScript,我们选择 Deno 是因为它具备高性能、高安全性和创建独立脚本的能力。
脚本作为一级原生类型
脚本是构建工作流和 UI 的最小“单元”,因为它们具有灵活性和可控性。Windmill 工作流由各种脚本组成。
一个简单的单分支流程示例:当 HackerNews 消息包含提及内容时发布到 Slack,并进行情感分析。
上述示例表明,工作流不仅由现成脚本组成,还使用不同语言的脚本,这些脚本具有各种输入和资源。
由于脚本用于各种工作流,因此它们的共享性和一致性非常重要。此外,Windmill 是社区驱动的:脚本在 Hub 上共享,所有经批准的脚本都集成到产品中。
基于这些要求,脚本必须可靠、安全且高性能。只有 Deno 能将这一切集于一身。
Deno 使脚本不可变
Windmill 要求每个脚本公开一个主函数,并在同一文件中声明其依赖项。主函数的参数被解析,以推断触发此类脚本的有效负载对应的 JSON Schema。这种模型与 Deno 完美契合。
Deno 的依赖项要求可以在使用它们的同一文件中声明。此外,导入语句可以指定所使用的精确版本,即使是 npm 导入也不例外
import mysql from "npm:mysql2@^2.3.3/promise";
import * as wmill from "https://deno.land/x/windmill@v1.101.1/mod.ts";
当指定依赖项版本时,我们可以保证脚本执行的可重现性,无论环境如何。最重要的是,共享 Deno 脚本就像共享一个文件一样简单——无需单独的依赖项清单,例如 `package.json`。
脚本创建或更新后,其新版本将与一个不可变且永久的哈希值相关联,然后连同相关元数据一起存储在 Postgres 中。
借助 Deno,Windmill 上的脚本是不可变的,并且是一级原生类型——一个可共享、可组合的构建块,供整个社区使用。
使用 Deno 安全地运行任意代码
Windmill 在多租户环境中运行用户生成的代码,因此必须隔离每个脚本的执行(对于自托管安装,为方便起见可以禁用这些安全措施)。Deno 的选择性权限模型使我们能够精细地控制每次执行的访问权限。
对于 Windmill 中其他语言的脚本,沙盒机制通过 NSJail 实现,这会带来很大的性能开销,并且使用和配置复杂。Deno,由于其默认安全的特性,在多租户环境中具有性能优势。
Deno 的最小冷启动
借助 Deno,我们实现了 15 毫秒的冷启动时间,这得益于它与 V8 的集成以及依赖项的不可变缓存
缓存的依赖项还会通过 S3 支持的同步系统在集群的工作节点之间传播,因此任何引用缓存依赖项的 Deno 脚本都不需要下载它,从而获得性能优势。
执行 Deno 脚本的冷启动时间约为 15 毫秒,这意味着大多数轻量级脚本的端到端运行时间为 30 毫秒。
未来展望
我们很高兴在 Windmill 的生产环境中使用了 Deno——它简化了开发,增加了一层安全性,并确保了我们企业客户的最佳性能。
未来,我们计划通过更深入地集成 Deno 并在进程内运行它,以达到无服务器 / AWS Lambda 的性能。
不要错过任何更新 — 在 Twitter 上关注我们。