WebAssembly 与 Kubernetes双剑合璧

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年月,为了进一步推动模块化 WebAssembly 生态系统,Mozilla、Fastly、英特尔和红帽公司携手成立了字节码联盟(Bytecode Alliance),共同领导 WASI 标准、 WebAssembly 运行时、语言工具等工作。

WebAssembly 与 Kubernetes双剑合璧

原图:https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/

WASM与容器相爱相杀

WebAssembly是否会取代容器?

正因为 WebAssembly 所具备的的安全、可移植、高效率,轻量化的特点,非常适于应用安全沙箱场景。WASM得到了容器、函数计算、IoT/边缘计算等社区的广泛关注。Docker创始人Solomon Hykes在WASI发布之际的一句Twitter,更是成为了去年容器和WebAssembly社区引用频率最高的一句话之一。

WebAssembly 与 Kubernetes双剑合璧

Fastly, Cloudflare等CDN厂商基于WebAssembly技术实现了更加轻量化的应用安全沙箱,可以在一个进程内部运行多个独立的用户应用。阿里云CDN团队EdgeRoutine也实现了类似技术。与容器技术相比,WASM可以实现毫秒级冷启动时间和极低的资源消耗。

WebAssembly 与 Kubernetes双剑合璧

原图: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应用镜像的构建和分发。

我提供了一个技术原型示例项目,https://github.com/denverdino/wasm-container-samples,大家可以参考其中的例子来构建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应用而言,如果它需要需要访问指定文件等系统资源,需要从外部显式传入加有权限的文件描述符引用,而不能访问任何其他未授权资源。这中依赖注入的方式可以避免传统安全模型的潜在风险。一个示意图如下

WebAssembly 与 Kubernetes双剑合璧

原图: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年初发布了一个实验项目, https://github.com/deislabs/krustlet 来利用 Virtual Kubelet类似的架构调度 WebAssembly 应用。但是这个方式有很多局限,无法借助容器方式进行应用分发,也无法利用 K8s 的语义进行资源编排。 

难得有一个春节假期可以宅在家里间,我基于Derek McGowan去年的一个实验性项目https://github.com/dmcgowan/containerd-wasm,完善了containerd的WASM shim实现。可以让containerd支持WASM container,并且可以利用Kubernetes集群管理和调度 WASM container。
项目的代码实现: https://github.com/denverdino/containerd-wasm

注:这个项目更多是概念验证,进程管理、资源限制,性能优化等的细节并没未完整实现。

整个系统的架构设计如下,“container-shim-wasm-v1”做为Containerd的扩展,利用 wasmer 作为WASM应用运行时环境,可以实现与runc容器一致的用户体验。

WebAssembly 与 Kubernetes双剑合璧

我们还会将其注册为 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 <<EOF | sudo tee -a /etc/containerd/config.toml
disabled_plugins = ["restart"]
[plugins.cri.containerd.runtimes.wasm]
  runtime_type = "io.containerd.wasm.v1"
EOF

$ sudo systemctl restart containerd

测试 Hello World WASM容器应用

$ sudo ctr image pull docker.io/denverdino/hellowasm:latest
docker.io/denverdino/hellowasm:latest:                                            resolved       |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:cadcc8b07eb82b18db2c8f500fa2b11e5ebf2e9054cfa687e4ffe44861860132: done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:ecda28441283ecf01d35bca0361f2c1ef26a203454a06789ee5ce71ba1e32ca3:    done           |++++++++++++++++++++++++++++++++++++++|
config-sha256:57974480d640c8d60d254a8b0fa4606b2c7107fe169bc3ddd455091277c3a5e4:   done           |++++++++++++++++++++++++++++++++++++++|
elapsed: 3.0 s                                                                    total:   0.0 B (0.0 B/s)
unpacking linux/amd64 sha256:cadcc8b07eb82b18db2c8f500fa2b11e5ebf2e9054cfa687e4ffe44861860132...
done
$ sudo ctr run --rm --runtime io.containerd.wasm.v1 docker.io/denverdino/hellowasm:latest test1
Hello world

测试 Nginx的WASM容器应用

$ sudo ctr image pull docker.io/denverdino/nginxwasm:latest
docker.io/denverdino/nginxwasm:latest:                                            resolved       |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:8735c82524a463b842b7c79f2c1be8094ee1c57cfd34154f68752fbe79c25998: exists         |++++++++++++++++++++++++++++++++++++++|
layer-sha256:27f4d8ad067fbb709d18ea5acd7a5ddfb85851e5d9f030636e9da3d16cc4bd07:    done           |++++++++++++++++++++++++++++++++++++++|
config-sha256:a55bd3bdb9d00fdac5ee2f64bfc1856e58e8bb90587943969ad3d8115f4ced70:   done           |++++++++++++++++++++++++++++++++++++++|
elapsed: 3.0 s                                                                    total:   0.0 B (0.0 B/s)
unpacking linux/amd64 sha256:8735c82524a463b842b7c79f2c1be8094ee1c57cfd34154f68752fbe79c25998...
done
$ sudo ctr run --rm --runtime io.containerd.wasm.v1 docker.io/denverdino/nginxwasm:latest test2
2020/02/01 07:01:21 [notice] 30672#0: using the "select" event method
2020/02/01 07:01:21 [notice] 30672#0: nginx/1.15.3
2020/02/01 07:01:21 [notice] 30672#0: built by clang 6.0.1  (emscripten 1.38.11 : 1.38.11)
2020/02/01 07:01:21 [notice] 30672#0: OS: Linux 4.19.81
2020/02/01 07:01:21 [notice] 30672#0: getrlimit(RLIMIT_NOFILE): 1024:1024

在 Minikube 外部,可以用如下方式获得 nginx 应用的访问地址

$ echo http://$(minikube ip):8080
http://192.168.64.13:8080

利用浏览器打开上述地址,显示如下

WebAssembly 与 Kubernetes双剑合璧

创建WASM容器的RuntimeClass CRD

为了将WASM容器可以被Kubernetes所调度,我们需要创建一个RuntimeClass CRD

下载示例文件

$ git clone https://github.com/denverdino/wasm-container-samples
$ cd wasm-container-samples

注册 RuntimeClass “wasm”,这个值

$ cat wasm-runtimeclass.yaml
apiVersion: node.k8s.io/v1beta1
kind: RuntimeClass
metadata:
  name: wasm
handler: wasm

$ kubectl apply -f wasm-runtimeclass.yaml
runtimeclass.node.k8s.io/wasm created

$ kubectl get runtimeclass
kubectl get runtimeclass
NAME   CREATED AT
wasm   2020-02-01T06:24:12Z

在K8s中运行WASM容器应用

在K8s应用的yaml manifest中,我们可以在Pod Spec上指明所需 runtimeClassName。下面我们就用K8s来部署一个nginx的WASM容器。

$ cat nginx-wasm.yaml
apiVersion: v1
kind: Pod
metadata:
 name: nginx-wasm
spec:
 runtimeClassName: wasm
 containers:
 - name: nginx
   image: denverdino/nginxwasm
   ports:
     - containerPort: 8080

$ kubectl apply -f nginx-wasm.yaml
pod/nginx-wasm created

$ kubectl get pod
NAME         READY   STATUS    RESTARTS   AGE
nginx-wasm   1/1     Running   0          9s

新机遇、新希望

目前为止,WebAssembly 技术仍处于初期阶段,WASI也有很多局限性。但是社区的进展非常快,SIMD 指令支持,多线程处理等规范也正在快速演进中。WebAssembly已经打破次元壁,将高性能的计算能力带领到Web浏览器端,越来越多的计算密集型的游戏、AI模型预测、和数据处理应用被移植到浏览器端,可以为应用提供更加优化的用户体验。

WebAssembly更广阔的空间在云计算领域、区块链等分布式计算领域。WebAssembly 轻量、敏捷、安全的特性,可以有效降低Serverless应用启动速度和资源消耗。同时WebAssembly的可移植,可以让应用一致运行在从云端服务器到边缘IoT设备等不同平台环境中,让计算无处不在。

利用containerd的扩展机制,可以为WebAssembly应用提供与其他容器应用一致的、抽象的、应用分发、交付和运维模型,可以在Kubernetes集群中进行统一调度和管理。希望通过类似的探索可以简化基于WebAssembly的分布式应用管理和运维。

后记

本文写在2020年的春节期间,这个春节注定将会被所有人铭记。众志成城,抗击疫情!天佑中华,武汉加油!

上一篇:川普无法结束俄罗斯对美国的软件战争


下一篇:简单几招助您加速 ARM 容器应用开发和测试流程