WebAssembly(Wasm)是近来发明的最令人兴奋和被低估的软件技术之一。它是一种基于堆栈的虚拟机的二进制指令格式,旨在以内存安全和安全的沙盒以本地速度执行。Wasm是可移植的、跨平台的、与语言无关的--被设计为语言的编译目标。虽然最初是开放网络平台的一部分,但它已经发现了网络以外的使用情况。WebAssembly现在被用于浏览器、Node.js、Deno、Kubernetes和IoT平台。
你可以在WebAssembly.org了解更多关于WebAssembly的信息。
Kubernetes上的WebAssembly
虽然最初是为网络设计的,但WebAssembly被证明是编写平台和语言无关的应用程序的理想格式。你可能知道容器世界中的类似东西--Docker容器。人们,包括Docker的联合创始人Solomon Hykes,认识到了这种相似性,并承认WebAssembly更加有效,因为它是快速、可移植和安全的,以本地速度运行。这意味着你可以在Kubernetes上将WebAssembly与容器一起作为工作负载使用。另一个被称为WebAssembly系统接口(WASI)的WebAssembly倡议与Wasmtime项目一起使这成为可能。
如果WASM+WASI在2008年就存在,我们就不需要创建Docker了。这就是它的重要性。服务器上的Webassembly是计算的未来。一个标准化的系统接口是缺失的环节。让我们希望WASI能够胜任这项工作!t.co/wnXQg4kwa4
- Solomon Hykes(@solomonstre)2019年3月27日
Kubernetes上的WebAssembly相对较新,目前有一些粗糙的边缘,但它已经被证明是革命性的。Wasm的工作负载可以非常快,因为它们的执行速度比容器的启动速度快。工作负载是经过沙盒处理的,因此比容器更安全;由于是二进制格式,它们的体积比容器小得多。
如果你想了解更多关于WASI的信息,请查看Mozilla的原始公告。
为什么是Rust?
我以前写过一篇博文,讲述了为什么Rust是一种未来的伟大语言,Tl;Dr; Rust是安全和快速的,没有大多数现代语言的妥协,而且Rust有最好的生态系统和WebAssembly的工具。因此,Rust + Wasm使得它超级安全和快速。
进入Krustlet
Krustlet是一个用Rust编写的Kubelet,用于WebAssembly工作负载(用任何语言编写)。它根据清单上指定的tolerations ,监听新的pod任务,并运行它们。由于默认的Kubernetes节点不能原生运行Wasm工作负载,你需要一个可以运行的Kubelet,这就是Krustlet的作用。
用Krustlet在Kubernetes上运行WebAssembly工作负载
今天,你将使用Krustlet在Kubernetes上运行一个用Rust编写的Wasm工作负载。
准备集群
首先,你需要准备一个集群,并在集群上安装Krustlet,在上面运行WebAssembly。我使用kind来运行一个本地的Kubernetes集群;你也可以使用MiniKube、MicroK8s或其他Kubernetes分布。
第一步是用下面的命令创建一个集群:
kind create cluster
现在你需要启动Krustlet。为此,你需要安装kubectl ,以及一个有权限在kube-system 命名空间中创建Secrets ,并且可以批准CertificateSigningRequests 的kubeconfig。你可以使用Krustlet的这些方便的脚本,为你的操作系统下载并运行适当的设置脚本:
# Setup for Linux/macOS
curl https://raw.githubusercontent.com/krustlet/krustlet/main/scripts/bootstrap.sh | /bin/bash
现在你可以安装和运行Krustlet了。
从发布页面下载一个二进制版本并运行它。为你的操作系统下载合适的版本。
# Download for Linux
curl -O https://krustlet.blob.core.windows.net/releases/krustlet-v1.0.0-alpha.1-linux-amd64.tar.gz
tar -xzf krustlet-v1.0.0-alpha.1-linux-amd64.tar.gz
# Install for Linux
KUBECONFIG=~/.krustlet/config/kubeconfig \
./krustlet-wasi \
--node-ip=172.17.0.1 \
--node-name=krustlet \
--bootstrap-file=${HOME}/.krustlet/config/bootstrap.conf
注意:如果你使用Docker for Mac,node-ip会有所不同。请按照Krustlet文档中的说明来计算IP。如果你遇到错误krustlet-wasi cannot be opened because the developer cannot be verified ,你可以在macOS上的系统偏好>安全与隐私>常规中找到允许按钮,允许它。
你应该看到一个手动批准TLS证书的提示,因为Krustlet使用的服务证书必须是手动批准的。打开一个新的终端,运行下面的命令。主机名将显示在Krustlet服务器的提示中。
kubectl certificate approve <hostname>-tls
只有在第一次启动Krustlet的时候才需要这样做。保持Krustlet服务器的运行。你可能会看到一些错误被记录下来,但我们暂时不要理会它,因为Krustlet还在测试阶段,有一些粗糙的边缘。
让我们看看这个节点是否可用。运行kubectl get nodes ,你应该看到像这样的东西。
kubectl get nodes -o wide
# Output
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
kind-control-plane Ready control-plane,master 16m v1.21.1 172.21.0.2 <none> Ubuntu 21.04 5.15.12-200.fc35.x86_64 containerd://1.5.2
krustlet Ready <none> 12m 1.0.0-alpha.1 172.17.0.1 <none> <unknown> <unknown> mvp
现在让我们通过应用下面的Wasm工作负载来测试Krustlet是否像预期的那样工作。正如你所看到的,我们定义了tolerations ,所以这不会被安排在正常节点上。
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: hello-wasm
spec:
containers:
- name: hello-wasm
image: webassembly.azurecr.io/hello-wasm:v1
tolerations:
- effect: NoExecute
key: kubernetes.io/arch
operator: Equal
value: wasm32-wasi # or wasm32-wasmcloud according to module target arch
- effect: NoSchedule
key: kubernetes.io/arch
operator: Equal
value: wasm32-wasi
EOF
一旦应用,运行kubectl get pods ,如下图所示,你应该看到Krustlet节点上运行的pod。
kubectl get pods --field-selector spec.nodeName=krustlet
# Output
NAME READY STATUS RESTARTS AGE
hello-wasm 0/1 ExitCode:0 0 71m
不要担心这个状态。对于一个正常终止的工作负载来说,有ExitCode:0 ,这很正常。让我们通过运行kubectl logs ,检查pod的日志。
kubectl logs hello-wasm
# Output
Hello, World!
你已经成功设置了一个Kubelet,可以在你的集群上运行Wasm工作负载。
为WebAssembly设置Rust
现在让我们用Rust为WebAssembly准备一个环境。确保你使用的是稳定的Rust版本,而不是夜间版本。
首先,你需要为Rust添加wasm32-wasi 目标,这样你就可以把Rust应用程序编译成WebAssembly。运行下面的命令。
rustup target add wasm32-wasi
现在你可以用Cargo创建一个新的Rust应用程序。
cargo new --bin rust-wasm
在你喜欢的IDE中打开创建的rust-wasm 文件夹。我使用Visual Studio Code和奇妙的rust-analyzer和CodeLLDB插件来开发Rust。
创建WebAssembly的工作负载
让我们写一个小服务,在控制台打印随机的猫的事实。为此,你可以使用一个免费的公共API,提供随机的猫的事实。
编辑cargo.toml ,并添加以下依赖项:
[dependencies]
wasi-experimental-http = "0.7"
http = "0.2.5"
serde_json = "1.0.74"
env_logger = "0.9"
log = "0.4"
然后编辑src/main.rs ,并添加以下代码:
use http;
use serde_json::Value;
use std::{str, thread, time};
fn main() {
env_logger::init();
let url = "https://catfact.ninja/fact".to_string();
loop {
let req = http::request::Builder::new()
.method(http::Method::GET)
.uri(&url)
.header("Content-Type", "text/plain");
let req = req.body(None).unwrap();
log::debug!("Request: {:?}", req);
// send request using the experimental bindings for http on wasi
let mut res = wasi_experimental_http::request(req).expect("cannot make request");
let response_body = res.body_read_all().unwrap();
let response_text = str::from_utf8(&response_body).unwrap().to_string();
let headers = res.headers_get_all().unwrap();
log::debug!("{}", res.status_code);
log::debug!("Response: {:?} {:?}", headers, response_text);
// parse the response to json
let cat_fact: Value = serde_json::from_str(&response_text).unwrap();
log::info!("Cat Fact: {}", cat_fact["fact"].as_str().unwrap());
thread::sleep(time::Duration::new(60, 0));
}
}
该代码很简单。它向API发出一个GET请求,每60秒解析并打印一次响应。现在你可以使用这个Cargo命令将其构建为一个Wasm二进制文件:
cargo build --release --target wasm32-wasi
就这样了。你已经成功地使用Rust创建了一个WebAssembly二进制文件。
在本地运行工作负载(可选)
让我们使用Wasmtime在本地运行工作负载,这是Wasm和WASI的一个小型JIT风格的运行时间。由于Wasmtime不支持开箱即用的网络,我们需要使用wasi-experimental-http提供的包装器。你可以使用下面的命令从源码构建它。
git clone https://github.com/deislabs/wasi-experimental-http.git
# Build for your platform
cargo build
# move to any location that is added to your PATH variable
mv ./target/debug/wasmtime-http ~/bin/wasmtime-http
现在从rust-wasm 项目文件夹中运行以下命令。
wasmtime-http target/wasm32-wasi/release/rust-wasm.wasm -a https://catfact.ninja/fact -e RUST_LOG=info
在Kubernetes中运行工作负载
在Kubernetes中运行工作负载之前,你需要将二进制文件推送到支持OCI工件的注册中心。符合OCI标准的注册中心可用于任何OCI工件,包括Docker镜像、Wasm二进制文件等。Docker Hub目前不支持OCI工件;因此你可以使用其他注册表,如GitHub Package Registry、Azure Container Registry或Google Artifact Registry。我将使用GitHub软件包注册处,因为它是最简单的入门方法,而且你们大多数人可能已经有了GitHub账户。
首先,你需要使用docker login 登录到GitHub包注册中心。在GitHub上用 write:packages 范围创建一个个人访问令牌,然后用它来登录注册表。
export CR_PAT=<your-token>
echo $CR_PAT | docker login ghcr.io -u <Your GitHub username> --password-stdin
现在你需要把你的Wasm二进制文件作为OCI工件推送;为此,你可以使用wasm-toociCLI。使用下面的命令将其安装在你的机器上。为你的操作系统下载适当的版本。
# Install for Linux
curl -LO https://github.com/engineerd/wasm-to-oci/releases/download/v0.1.2/linux-amd64-wasm-to-oci
# move to any location that is added to your PATH variable
mv linux-amd64-wasm-to-oci ~/bin/wasm-to-oci
现在你可以把你之前构建的二进制文件推送到GitHub软件包注册中心。在rust-wasm 文件夹中运行下面的命令。
wasm-to-oci push target/wasm32-wasi/release/rust-wasm.wasm ghcr.io/<your GitHub user>/rust-wasm:latest
你应该看到一个成功的消息。现在在你的个人主页上查看GitHub包的页面,你应该看到列出的工件。
默认情况下,该工件是私有的,但你需要将其公开,以便你可以从Krustlet集群中访问它。点击软件包名称,点击软件包设置按钮,向下滚动,点击改变可见性,然后改变为公开。
你可以通过使用下面的命令拉动工件来检查这一点。
wasm-to-oci pull ghcr.io/<your GitHub user>/rust-wasm:latest
Yay!你已经成功地将你的第一个Wasm工件推送到一个OCI注册表。现在让我们把它部署到你之前创建的Kubernetes集群。
创建一个YAML文件,比方说k8s.yaml ,内容如下。
apiVersion: v1
kind: Pod
metadata:
name: rust-wasi-example
labels:
app: rust-wasi-example
annotations:
alpha.wasi.krustlet.dev/allowed-domains: '["https://catfact.ninja/fact"]'
alpha.wasi.krustlet.dev/max-concurrent-requests: "42"
spec:
automountServiceAccountToken: false
containers:
- image: ghcr.io/<your GitHub user>/rust-wasm:latest
imagePullPolicy: Always
name: rust-wasi-example
env:
- name: RUST_LOG
value: info
- name: RUST_BACKTRACE
value: "1"
tolerations:
- key: "node.kubernetes.io/network-unavailable"
operator: "Exists"
effect: "NoSchedule"
- key: "kubernetes.io/arch"
operator: "Equal"
value: "wasm32-wasi"
effect: "NoExecute"
- key: "kubernetes.io/arch"
operator: "Equal"
value: "wasm32-wasi"
effect: "NoSchedule"
注意:记得用你自己的GitHub用户名替换<your GitHub user> 。
annotations 和tolerations 是重要的。注释用于允许从krustlet中进行外部网络调用,而容忍度限制了pod只能在Wasm节点上调度/运行。我们还传递了一些环境变量,应用程序将使用这些变量。
现在使用下面的命令应用清单,并检查pod状态。
kubectl apply -f k8s.yaml
kubectl get pods -o wide
# Output
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
rust-wasi-example 1/1 Running 0 6m50s <none> krustlet <none> <none>
你应该看到Wasm工作负载在Krustlet节点上成功运行。让我们检查一下日志。
kubectl logs rust-wasi-example
# Output
[2022-01-16T11:42:20Z INFO rust_wasm] Cat Fact: Polydactyl cats (a cat with 1-2 extra toes on their paws) have this as a result of a genetic mutation. These cats are also referred to as 'Hemingway cats' because writer Ernest Hemingway reportedly owned dozens of them at his home in Key West, Florida.
[2022-01-16T11:43:21Z INFO rust_wasm] Cat Fact: The way you treat a kitten in the early stages of its life will render its personality traits later in life.
真棒。你已经成功地使用Rust创建了一个Wasm工作负载,并在不使用容器的情况下将其部署到Kubernetes集群中。 如果你想看一下这个解决方案的全文,请查看GitHub repo。
那么,我们准备好用WebAssembly取代容器了吗?
Kubernetes上的WebAssembly还没有为生产做好准备,因为很多支持的生态系统仍然是实验性的,而WASI本身仍然是成熟的。网络还不稳定,库的生态系统也只是刚刚出现。Krustlet也仍然处于测试阶段,而且没有直接的方法来运行网络工作负载,特别是服务器。WasmEdge是一个更成熟的网络工作负载的替代解决方案,但它的设置和运行比Krustlet在Kubernetes上要复杂得多。WasmCloud是另一个值得关注的项目。因此,就目前而言,Krustlet适用于运行工作负载的作业和涉及集群监控的用例等。这些都是你可以使用额外性能的领域,无论如何。
因此,虽然Kubernetes上的Wasm令人振奋,而Kubernetes上的无容器也肯定会出现。 容器化应用仍然是生产使用的方式。这对于微服务和网络应用等网络工作负载来说尤其如此。但是,考虑到生态系统的发展速度,特别是在Rust + Wasm + WASI领域,我预计很快我们就能在Kubernetes上使用Wasm工作负载进行生产。