作者 | 易立 阿里巴巴资深技术专家
导读:WebAssembly 技术已经走出浏览器,让计算无处不在。本文利用 containerd 的扩展机制,可以为 WebAssembly 应用提供与其他容器应用一致的、抽象的、应用分发、交付和运维模型,可以在 Kubernetes 集群中进行统一调度和管理。
无处不在的 WebAssembly
如果评选 2019 年编程技术的“网红”,无论是前端圈还是后端圈,WebAssembly (WASM) 都绝对能够高票入选。然而,如果评选最被“低估”的技术,我觉得 WebAssembly 也可以轻松入围。
借用伏尔泰曾评价神圣罗马帝国的句式 “既不神圣,也不罗马,更非帝国”,我们也可以说WebAssembly “既不限于 Web,更不是 Assembly(汇编语言)”。
在 2019 年 12 月,万维网联盟 (World Wide Web Consortium – W3C) 宣布 WebAssembly 核心规范正式成为 Web 标准, 这使得 WebAssembly 成为互联网上与 HTML, CSS, and JavaScript 并列的第四种官方语言,可以原生的运行在浏览器上。而更加重要的是,WebAssembly 作为一个安全的、可移植、高效率的虚拟机沙箱,可以在 Internet 的任何地方、任何平台(不同操作系统,不同 CPU 体系架构下)安全运行应用。WebAssembly 已得到了所有主流浏览器厂商的广泛支持(Google Chrome, Microsoft Edge, Apple Safari, Mozilla Firefox 等),然而它的影响已经远超 Web。
WebAssembly 的设计初衷之一是为了解决 JavaScript 的性能问题,使得 Web 网页应用有接近本机原生应用的性能。作为一个通用、开放、高效的底层虚拟机抽象,众多编程语言(如 C/C++, Rust 等)可以将现有应用编译成为 WASM 的目标代码,运行在浏览器中 。这让应用开发技术与运行时技术解耦,极大促进了代码复用。
Mozilla 在 2019 年 3 月推出了 WebAssembly System Interface(WASI),来标准化 WebAssembly 应用与系统资源的交互抽象,比如文件系统访问,内存管理,网络连接等,类似 POSIX 这样的标准 API。WASI 规范大大拓展了 WASM 应用的场景,可以让其可以超越浏览器环境,作为一个独立的虚拟机运行各种类型的应用。同时,平台开发商可以针对具体的操作系统和运行环境提供 WASI 接口不同的实现,可以在不同设备和操作系统上运行跨平台的 WebAssembly 应用。这可以让应用执行与具体平台环境实现解耦。这一切使得“Build Once, Run Anywhere”的理想逐渐形成现实。WASI 的示意图如下所示。2019 年 11 月,为了进一步推动模块化 WebAssembly 生态系统,Mozilla、Fastly、英特尔和红帽公司携手成立了字节码联盟(Bytecode Alliance),共同领导 WASI 标准、 WebAssembly 运行时、语言工具等工作。
图片来源:https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/
WASM 与容器相爱相杀
WebAssembly 是否会取代容器?
正因为 WebAssembly 所具备的的安全、可移植、高效率,轻量化的特点,非常适于应用安全沙箱场景。WASM 得到了容器、函数计算、IoT / 边缘计算等社区的广泛关注。Docker 创始人 Solomon Hykes 在 WASI 发布之际的一句 Twitter,更是成为了去年容器和 WebAssembly 社区引用频率最高的一句话之一。
Fastly, Cloudflare 等 CDN 厂商基于 WebAssembly 技术实现了更加轻量化的应用安全沙箱,可以在一个进程内部运行多个独立的用户应用。阿里云 CDN 团队 EdgeRoutine 也实现了类似技术。与容器技术相比,WASM 可以实现毫秒级冷启动时间和极低的资源消耗。
原图:https://blog.cloudflare.com/cloud-computing-without-containers/
当然,世界上没有完美的技术。任何沙箱技术不可能同时满足执行效率、安全隔离性和通用性这三个维度的要求。WASM 在安全隔离和通用性等方面与 Docker Container 等存在差距。虽然如此,我们还是看到了 WebAssembly 技术巨大的潜力。
WebAssembly 容器
我的理解是 WebAssmebly 可以成为一种容器类型,类似 Linux Container 或者 Windows Container 一样。成为一个跨平台的标准应用分发方式和运行时环境。
应用分发
Docker 容器的一个重要贡献是其标准化了容器化应用打包规范 Docker Image,而且它已经成为开放容器计划 (Open Container Initiative – OCI) 的镜像格式标准。Docker 镜像提供了自包含、自描述的镜像格式。它可以将应用以及其依赖的环境信息打包在一起,从而实现应用与运行环境解耦,让容器应用可以轻松运行在从本地开发环境到云端生产环境的不同场景中。并且社区围绕 Docker 镜像构建了繁荣的工具链生态,如 Docker Hub 可以进行应用分发和 CI / CD 协同,Nortary / TUF 项目可以保障应用可信地分发、交付。
对与 WebAssembly,目前社区提供了类似 NPM 的包管理实现 WAPM,可以较好地支持应用的分发。 为 WebAssembly 应用构建 Docker 镜像,可以实现双赢的局面。
- WebAssembly 开发者可以完全复用 Docker/OCI 镜像规范和工具链,进一步简化应用分发和交付。比如,我们可以将 Nginx 的 WASM 镜像作为基础镜像,基于这个镜像可以构建包含不同 Web 内容的应用镜像;我们可以利用 tag 对应用版本进行追踪;利用 Docker Registry 进行应用分发;在这个过程我们还可以进一步利用数字签名来保障安全的软件供应链;
- Docker 镜像规范支持 Multi-Arch 镜像,可以简化不同 CPU 体系架构(如 x86, ARM, RISC-V 等)的应用镜像的构建与分发。而 WebAssembly 天生具备可移植性,大大简化了跨平台 Docker 应用镜像的构建和分发。参考:利用 Docker 加速 ARM 容器应用开发和测试流程。
我提供了一个技术原型示例项目,大家可以参考其中的例子来构建 WASM 容器镜像。由于 WebAssembly 应用采用紧凑的二进制格式,而且没有任何操作系统依赖,WASM 应用可以构建出非常小的容器镜像。大家可以自行感受一下:
$ sudo ctr image ls
REF TYPE DIGEST SIZE PLATFORMS LABELS
docker.io/denverdino/c-http-server-wasm:latest application/vnd.docker.distribution.manifest.v2+json sha256:2efa759f46f901cda2e6a9b4228c423b17a960c06e957964e72c21dc5b42408f 29.2 KiB linux/amd64 -
docker.io/denverdino/hellowasm:latest application/vnd.docker.distribution.manifest.v2+json sha256:cadcc8b07eb82b18db2c8f500fa2b11e5ebf2e9054cfa687e4ffe44861860132 8.2 KiB linux/amd64 -
docker.io/denverdino/nginxwasm:latest application/vnd.docker.distribution.manifest.v2+json sha256:8735c82524a463b842b7c79f2c1be8094ee1c57cfd34154f68752fbe79c25998 582.7 KiB linux/amd64 -
安全隔离
WebAssembly 的最初设计目标是让应用可以安全运行在浏览器中。WASM 虚拟机提供的沙箱和内存隔离机制,可以有效减少安全攻击面。而当 WebAssembly 走出浏览器,面向更加通用的场景。WASM 也面对更加复杂的安全挑战。
WASI 提供了基于能力的安全模型。WASI 应用遵循最小权限原则,应用只能访问其执行所需的确切资源。传统上,如果应用需要打开文件,它会带路径名字符串调用系统操作 open。然后系统调用会检查应用是否具有访问该文件的相关权限,比如 Linux 实现了基于用户/组的权限模型。这样隐式的安全模型,依赖于正确的安全管理配置,比如一旦特权用户执行了一个恶意应用,它就可以访问系统中任意的资源。而对于 WASI 应用而言,如果它需要需要访问指定文件等系统资源,需要从外部显式传入加有权限的文件描述符引用,而不能访问任何其他未授权资源。这中依赖注入的方式可以避免传统安全模型的潜在风险。
一个示意图如下:
原图:https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/
我们可以看到 WASI 的安全模型与传统操作系统安全模型非常不同,而且还在持续演进中。比如字节码联盟提出了 nanoprocess 来解决应用模块间的安全协同和信任传递。
WebAssembly/WASI 的安全模型依然存在不足,比如:
- 资源隔离
对于内存资源,WebAssembly 实现了线性内存模型。WebAssembly 应用只能利用索引访问传入的一段逻辑线性内存。而 WASM 虚拟机负责确定内存的实际物理地址,WASM 应用无法获知内存的真实地址,也无法通过越界访问等方式发动攻击。所以理论上,可以对 WASM 应用进行资源容量限制。但是目前部分 WASM 虚拟机还无法对内存进行精确的隔离限制。
对于 CPU 资源,部分的 WASM 虚拟机实现可以对应用使用的 CPU 资源进行计量,但是大多无法实现精确的配额限制、优先级和抢占式调度。
I/O 资源,比如 IOPS 等,WASM 目前完全没有相关的隔离能力。
- 网络安全
WASI 的 Capability 模型对于文件系统访问相对比较容易保护。但是这个静态的安全模型无法适用于动态的网络应用场景。在微服务架构中,应用经常通过 Service Registry 进行服务发现,为服务的调用者和提供者实现动态的调用绑定。这个语义是无法用静态的 capability 模型描述和注入的。这也导致了 WASI 的网络部分 API 还处于讨论之中。现有的 WASI 网络安全模型,以及相关讨论。
Linux 操作系统和容器技术已经提供了非常完备的资源隔离和安全隔离实现。与 WebAssembly 结合在一起可以应对不同场景对不同隔离级别的需求。
- 共享进程资源 – 多个 WASM 应用模块运行在一个 WASM 虚拟机进程内部,依赖 WASM 运行时进行隔离。隔离级别低,控制粒度比较粗,资源开销极小。可以以较小代价保障系统安全。适合受限问题域的应用安全隔离;
- 独立进程资源 – 不同 WASM 应用模块运行在不同的 WASM 虚拟机进程中,可以复用操作系统的进程级隔离能力,比如 CGroup。此外,还可以利用类似 Kubernetes 中的 Network Policy (网络策略),或者服务网格(如Istio)等技术,对进程的网络访问进行细粒度的控制,甚至实现零信任网络。隔离级别比较高,控制粒度比较细,资源开销适中。可以应用于更加通用的场景。
注:当然利用安全沙箱如虚拟化等技术,结合 WebAssembly,可以进一步最小化安全攻击面,但是 ROI 不高。
调度与编排
在云时代,Kubernetes 已经成为分布式环境下资源调度和应用编排的事实标准。Kubernetes 可以屏蔽底层设施的差异性。可以在同一个 K8s 集群中包含 x86、ARM 等不同体系架构的节点,可以支持 Linux,Windows 等不同的操作系统。Kubernetes 和 WebAssembly 相结合可以进一步提升应用的可移植性。
微软的 Deis Labs 年初发布了一个实验项目,来利用 Virtual Kubelet 类似的架构调度 WebAssembly 应用。但是这个方式有很多局限,无法借助容器方式进行应用分发,也无法利用 K8s 的语义进行资源编排。
难得有一个春节假期可以宅在家里,在此期间我基于 Derek McGowan 去年的一个实验性项目,完善了 containerd 的 WASM shim 实现。可以让 containerd 支持 WASM container,并且可以利用 Kubernetes 集群管理和调度 WASM container。
项目的代码实现: https://github.com/denverdino/containerd-wasm
注:这个项目更多是概念验证,进程管理、资源限制,性能优化等的细节并没未完整实现。
整个系统的架构设计如下,“container-shim-wasm-v1”作为 Containerd 的扩展,利用 wasmer 作为 WASM 应用运行时环境,可以实现与 runc 容器一致的用户体验。
我们还会将其注册为 K8s 的一个 RuntimeClass ,允许用户利用 K8s 来交付和运维 WASM 应用。
注:RuntimeClass 是 Kubernetes v1.12 引入的新概念,可以让 Kubernetes 支持多种不同的容器运行时,比如 runc 容器、或者 Kata Containers,gVisor 等安全沙箱容器。更多细节可以参考:containerd 与安全沙箱的 Kubernetes 初体验。
Talk is Cheap, 放码过来
首先,我们将利用 Minikube 创建一个 K8s 测试环境,并将 Containerd 作为 Kubernetes 集群的容器运行时。
创建虚拟机测试环境
创建 Minikube K8s 集群,并将 Containerd 作为 Kubernetes 集群容器运行时。
minikube start --image-mirror-country cn \
--iso-url=https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/iso/minikube-v1.6.0.iso \
--registry-mirror=https://tgtsuwdg.mirror.aliyuncs.com \
--container-runtime=containerd
进入 Minikube 虚拟机:
$ minikube ssh
_ _
_ _ ( ) ( )
___ ___ (_) ___ (_)| |/') _ _ | |_ __
/' _ ` _ `\| |/' _ `\| || , < ( ) ( )| '_`\ /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )( ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)
配置环境所需依赖:
- wasmer 0.13;
- minikube 缺省安装了 container 1.2.x,需要升级 containerd 1.3.x;
- 我提供了一个预编译的 containerd-wasm-shim-v1,也可自己编译一个版本。
cd ~
# Install Wasmer 0.13.1
curl -L -O https://github.com/wasmerio/wasmer/releases/download/0.13.1/wasmer-linux-amd64.tar.gz
gunzip wasmer-linux-amd64.tar.gz
tar xvf wasmer-linux-amd64.tar
sudo cp bin/* /usr/bin/
# Upgrade containerd to v1.3.2
curl -L -O https://github.com/containerd/containerd/releases/download/v1.3.2/containerd-1.3.2.linux-amd64.tar.gz
gunzip containerd-1.3.2.linux-amd64.tar.gz
tar xvf containerd-1.3.2.linux-amd64.tar
sudo systemctl stop containerd
sudo cp bin/* /usr/bin/
sudo systemctl restart containerd
# Install containerd-wasm-shim
wget http://kubernetes.oss-cn-hangzhou.aliyuncs.com/containerd-wasm/containerd-shim-wasm-v1
chmod +x containerd-shim-wasm-v1
sudo mv containerd-shim-wasm-v1 /usr/bin/
配置 containerd 支持 WASM shim
在 containerd 配置文件中添加 wasm shim 相关配置,并重启 containerd。
$ cat