无容器!如何使用 Rust 在 Kubernetes 上运行 WebAssembly 工作负载
Kubernetes 上的 WebAssembly
为什么使用 Rust?
进入 Krustlet
使用 Krustlet 在 Kubernetes 上运行 WebAssembly 工作负载
那么,我们准备好用 WebAssembly 替换容器了吗?
了解有关 Kubernetes 和 WebAssembly 的更多信息
WebAssembly (Wasm) 是近年来最令人兴奋却又被低估的软件技术之一。它是一种基于堆栈的虚拟机的二进制指令格式,旨在通过内存安全且可靠的沙盒以原生速度执行。Wasm 具有可移植性、跨平台性和语言无关性,旨在作为多种语言的编译目标。虽然 Wasm 最初是开放 Web 平台的一部分,但它已在 Web 领域之外找到了应用场景。WebAssembly 目前已应用于浏览器、Node.js、Deno、Kubernetes 和物联网平台。
您可以在WebAssembly.org上了解有关 WebAssembly 的更多信息。
Kubernetes 上的 WebAssembly
尽管 WebAssembly 最初是为Web设计的,但它已被证明是编写与平台和语言无关的应用程序的理想格式。您可能知道容器世界中存在类似的容器——Docker 容器。包括 Docker 联合创始人 Solomon Hykes 在内的许多人都认识到了这种相似性,并认为 WebAssembly 效率更高,因为它快速、可移植、安全,并且能够以原生速度运行。这意味着您可以将 WebAssembly 与容器一起用作 Kubernetes 上的工作负载。另一项名为WebAssembly 系统接口 (WASI)的 WebAssembly 计划以及Wasmtime项目使这成为可能。
Kubernetes 上的 WebAssembly 相对较新,目前还存在一些缺陷,但它已被证明具有革命性。Wasm 工作负载可以非常快速,因为它们的执行速度比容器的启动速度更快。这些工作负载被沙盒化,因此比容器更安全;由于采用二进制格式,它们的大小也比容器小得多。
如果您想了解有关 WASI 的更多信息,请查看Mozilla 的原始公告。
为什么使用 Rust?
我之前写过一篇博文,解释了为什么 Rust 是一门面向未来的优秀语言。简而言之,Rust 安全快速,且没有大多数现代语言的妥协,而且 Rust 拥有WebAssembly 的最佳生态系统和工具。因此,Rust + Wasm 使其超级安全快速。
进入 Krustlet
Krustlet是一个用 Rust 编写的Kubelet,适用于 WebAssembly 工作负载(可以使用任何语言编写)。它根据tolerations
清单中的指定内容监听新的 Pod 分配并运行它们。由于默认的 Kubernetes 节点无法原生运行 Wasm 工作负载,因此您需要一个能够运行 Wasm 工作负载的 Kubelet,而这正是 Krustlet 的用武之地。
使用 Krustlet 在 Kubernetes 上运行 WebAssembly 工作负载
今天,您将使用 Krustlet 在 Kubernetes 上运行用 Rust 编写的 Wasm 工作负载。
先决条件
准备集群
首先,你需要准备一个集群,并在集群上安装 Krustlet 以在其上运行 WebAssembly。我使用kind来运行本地 Kubernetes 集群;你也可以使用MiniKube、MicroK8s或其他 Kubernetes 发行版。
第一步是使用以下命令创建集群:
kind create cluster
现在你需要启动 Krustlet。为此,你需要kubectl
安装一个 kubeconfig,该 kubeconfig 有权Secrets
在kube-system
命名空间中创建并批准CertificateSigningRequests
。你可以使用 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
注意:如果您使用的是 Mac 版 Docker,则 node-ip 会有所不同。请按照Krustlet 文档中的说明来获取 IP 地址。如果出现错误,您可以使用macOS系统偏好设置>安全和隐私>常规中的“仍然允许”按钮krustlet-wasi cannot be opened because the developer cannot be verified
来允许该错误。
您应该会看到一个手动批准 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
按如下所示运行,您应该会看到 pod 在 Krustlet 节点上运行。
kubectl get pods --field-selector spec.nodeName=krustlet
# Output
NAME READY STATUS RESTARTS AGE
hello-wasm 0/1 ExitCode:0 0 71m
不必担心状态。正常终止的工作负载出现 是正常的ExitCode:0
。让我们通过运行 来检查 Pod 的日志kubectl logs
。
kubectl logs hello-wasm
# Output
Hello, World!
您已成功设置可以在集群上运行 Wasm 工作负载的 Kubelet。
为 WebAssembly 设置 Rust
现在让我们用 Rust 为 WebAssembly 准备一个环境。请确保你使用的是稳定的 Rust 版本,而不是夜间版本。
首先,您需要添加wasm32-wasi
Rust 目标,以便将 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在本地运行工作负载,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 Package Registry,因为它最容易上手,而且大多数人可能已经拥有 GitHub 帐户。
首先,您需要使用 登录 GitHub Package Registry 。在 GitHub 上docker login
创建具有作用域的个人访问令牌,并使用它来登录注册表。write:packages
export CR_PAT=<your-token>
echo $CR_PAT | docker login ghcr.io -u <Your GitHub username> --password-stdin
现在,您需要将 Wasm 二进制文件推送为 OCI 工件;为此,您可以使用wasm-to-oci CLI。使用以下命令将其安装到您的计算机上。请下载适合您操作系统的版本。
# 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 Packages 页面,您应该会看到列出的工件。
默认情况下,该工件是私有的,但您需要将其公开,以便可以从 Krustlet 集群访问它。单击包名称,然后单击“包设置”按钮,向下滚动,单击“更改可见性”,然后更改为“公共”。
您可以使用以下命令拉取工件来检查这一点:
wasm-to-oci pull ghcr.io/<your GitHub user>/rust-wasm:latest
耶!您已成功将第一个 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"
注意<your GitHub user>
:请记住用您自己的 GitHub 用户名替换。
和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 代码库。
那么,我们准备好用 WebAssembly 替换容器了吗?
Kubernetes 上的 WebAssembly 尚未达到生产环境要求,因为许多支持生态系统仍处于试验阶段,而 WASI 本身也仍在不断完善中。网络功能尚不稳定,库生态系统也才刚刚起步。Krustlet 也仍处于测试阶段,目前尚无直接运行网络工作负载(尤其是在其上运行服务器)的方法。WasmEdge是一个更成熟的网络工作负载替代方案,但与 Kubernetes 上的 Krustlet 相比,它的设置和运行要复杂得多。WasmCloud是另一个值得关注的项目。因此,就目前而言,Krustlet 适合运行作业的工作负载以及涉及集群监控等用例。无论如何,这些领域都可以利用其额外的性能。
因此,尽管 Kubernetes 上的 Wasm 令人兴奋,而且 Kubernetes 上的无容器化也已近在眼前,但容器化应用仍然是生产环境的主流选择。对于微服务和 Web 应用等网络工作负载尤其如此。但是,鉴于生态系统的快速发展,尤其是在 Rust + Wasm + WASI 领域,我预计我们很快就能在 Kubernetes 上使用 Wasm 工作负载进行生产。
了解有关 Kubernetes 和 WebAssembly 的更多信息
如果您想了解有关 Kubernetes 和安全性的更多信息,请查看这些附加资源。
- 如何使用 OpenID Connect 和 RBAC 保护 Kubernetes 集群
- 如何利用最佳实践保护你的 Kubernetes 集群
- 如何使用 Blazor WebAssembly (WASM) 进行安全构建
- 保护集群
- RBAC 与 ABAC:定义和何时使用
- 管理员安全访问 AWS EKS 集群
如果您喜欢本教程,那么您很可能会喜欢我们发布的其他教程。请在 Twitter 上关注 @oktadev,并订阅我们的 YouTube 频道,以便在我们发布新的开发者教程时收到通知。
文章来源:https://dev.to/oktadev/containerless-how-to-run-web assembly-workloads-on-kubernetes-with-rust-2j8f