gVisor 与 Knative 集成:让 Kubernetes 上的 Serverless 工作负载运行在 gVisor 沙箱中
【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor
本文基于 gVisor 仓库中的 Knative 教程(g3doc/user_guide/tutorials/knative.md)展开,讲解如何让 Knative 创建的 Serverless 服务默认运行在 gVisor 沙箱中:从集群 RuntimeClass 准备,到 Knative 部署配置中runtime-class-name的完整写法与例外规则,再到服务部署与沙箱生效验证,并结合仓库中 containerd shim 的源码路径说明整条“RuntimeClass → runsc”的落地链路。读完本文,你可以将任意 Knative Serving 工作负载批量迁入 gVisor 隔离环境,并按标签粒度控制例外。
前提条件:一个能运行 gVisor 工作负载的集群
本教程假设你拥有一个能够运行 gVisor 工作负载的 Kubernetes 集群。原文档给出了两条典型路径:
GKE Sandbox 集群:在 Google Cloud 上使用启用了 GKE Sandbox(gVisor)的节点池,gvisor 的
RuntimeClass会在节点创建时自动实例化。验证方式:$ kubectl get runtimeclass/gvisor NAME HANDLER AGE gvisor gvisor 1h自建集群(containerd + gVisor shim):在自建节点上通过 containerd 的 gVisor 运行时处理器接入,完整步骤见仓库内的 Containerd Quick Start。其核心是在
/etc/containerd/config.toml中注册运行时:version = 2 [plugins."io.containerd.runtime.v1.linux"] shim_debug = true [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"其中
runtime_type = "io.containerd.runsc.v1"指向 gVisor 提供的 containerd shim 二进制containerd-shim-runsc-v1。该 shim 的入口即仓库中的 shim/main.go,它调用gvisor.dev/gvisor/shim/v1/cli的Main函数;shim 主体逻辑(容器生命周期管理、与 runsc 的交互)位于 pkg/shim/v1/ 下的runsc/、proc/、runtimeoptions/等子包。shim 的示例配置文件见 shim/runsc.toml,更多运行参数(如log_path、[runsc_config]透传给 runsc 的 flag)可参考 Containerd Advanced Configuration。注册完运行时后,为集群安装
gvisor的RuntimeClass(handler 指向 runsc 运行时):cat <<EOF | kubectl apply -f - apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: name: gvisor handler: runsc EOF安装 Knative:按 Knative 官方的 YAML 安装指南在其 Serving 组件上安装 Knative(会创建
knative-serving命名空间及config-deployment等 ConfigMap)。二进制与集群组件的安装细节另见 安装指南。
配置 Knative 的 runtime-class-name 部署项
Knative 允许通过部署配置(deployment configs,即knative-serving命名空间下的 ConfigMap)为它创建的 Pod 设置各种参数,其中runtime-class-name用于控制 Knative 生成的 Deployment 中的 Pod 运行在哪种运行时上。
编辑 Knative 的部署 ConfigMap:
kubectl edit configmap config-deployment -n knative-serving该配置项的语义是“按 Pod 标签选择器(label selector)配置 Pod 的runtimeClassName字段”。原文档给出两种典型写法。
写法一:强制所有 Knative Pod 使用 gVisor
apiVersion: v1 kind: ConfigMap metadata: name: config-deployment namespace: knative-serving data: runtime-class-name: | gvisor: {}runtime-class-name的值是一个 YAML 映射:键为运行时类名,值为匹配的标签选择器。这里gvisor: {}表示空选择器(无标签约束),即所有经 Knative 创建的 Pod 都会被强制使用gvisor作为 Runtime Class。
写法二:按标签允许例外 Pod 不使用 gVisor
apiVersion: v1 kind: ConfigMap metadata: name: config-deployment namespace: knative-serving data: runtime-class-name: | "": selector: no-isolation-here: "true" gvisor: {}这里引入了两个条目:
""(空字符串键)表示使用集群默认运行时(即不设置 gVisor Runtime Class),并只在 Pod 带有no-isolation-here: "true"标签时才命中;gvisor: {}仍然是兜底规则,命中其余所有 Pod。
因此,带有no-isolation-here: "true"标签的 Knative 服务可以绕过沙箱直接跑在默认运行时上(例如某些与沙箱兼容性不佳、或 I/O 密集的工作负载),其余服务仍然全部进入 gVisor。注意选择器按“更具体者优先”的方式匹配,兜底规则应放在无法被具体选择器命中的位置(本文档示例中即空选择器条目)。
部署 Knative Service
设置好 Runtime Class 部署配置后,就可以创建 Knative Service 了。原文档使用经典的helloworld-go示例镜像,并通过TARGET环境变量定制输出内容:
cat <<EOF | kubectl apply -f - apiVersion: serving.knative.dev/v1 kind: Service metadata: name: helloworld-go spec: template: spec: containers: - image: gcr.io/knative-samples/helloworld-go env: - name: TARGET value: "gVisor User" EOF注意整个 Service 定义中没有任何 gVisor 相关的显式字段——沙箱归属完全由上一步config-deployment中的runtime-class-name规则决定。这正是 Knative 集成方式的价值:无需修改业务服务清单,平台层配置即可全局生效。
验证 Pod 的 Runtime Class
用自定义列查看 Pod 及其runtimeClassName:
kubectl get pods -o=custom-columns='NAME:.metadata.name,RUNTIME CLASS:.spec.runtimeClassName,STATUS:.status.phase'输出应类似:
NAME RUNTIME CLASS STATUS helloworld-go-00002-deployment-646c87b7f5-5v68s gvisor Running两个需要留意的现象:
- 缩容到零:Knative 的核心特性是空闲时把副本缩到 0,因此执行命令时可能看不到任何 Pod;通过其 URL 访问服务会触发一个全新 Pod 被拉起,该 Pod 同样会带有
gvisorRuntime Class。 - 版本后缀:Pod 名中的
00002是 Knative 的 Revision 序号,每次模板变更都会产生新 Revision,新 Pod 的运行时归属仍由 ConfigMap 规则决定。
看到RUNTIME CLASS列为gvisor且状态Running,即表示 Knative 服务已运行在 gVisor 沙箱中。
底层链路:runtimeClassName 如何一路到达 runsc
从源码结构看,Knative 配置中的runtime-class-name最终落到 Pod 的spec.runtimeClassName字段,后续链路与普通 Kubernetes Pod 完全一致,gVisor 仓库可以佐证每一环:
- kubelet / CRI:kubelet 将 Pod 交给容器运行时,运行时名对应 containerd 配置中的 handler(
runsc/io.containerd.runsc.v1); - containerd shim:containerd 依据
runtime_type = "io.containerd.runsc.v1"启动containerd-shim-runsc-v1。该 shim 实现 containerd v2 shim API(兼容 v1 任务服务),仓库中 pkg/shim/v1/manager.go 负责沙箱内多容器的生命周期管理,pkg/shim/v1/runsc/ 封装对 runsc 的调用(api.go、container.go等); - runsc 沙箱:shim 通过 pkg/shim/v1/runsccmd/ 调用 runsc 二进制真正创建 Sentry 沙箱,Pod 内所有容器的应用进程都在沙箱内执行;
- 运行时参数:如需给 runsc 传 flag,可通过 shim/runsc.toml 所示的
[runsc_config]段配置(flag = "value"会被转换为runsc --flag="value"),配置文件默认路径为/etc/containerd/runsc.toml,机制说明见 Containerd Advanced Configuration。
也就是说,Knative 教程只是在这条通用链路的“源头”换了一个配置入口:由 Knative Serving 控制面统一给每个 Revision 的 Pod 打上runtimeClassName: gvisor,而不是要求每个开发者在 Service 清单里手写。
生产环境注意事项
- I/O 开销:gVisor 的沙箱机制会带来一定的 I/O 开销,仓库的 Production Guide 对生产部署给出了系统性的建议;同样的取舍在 WordPress 教程 中也有体现——沙箱化对外暴露面最大的前端组件收益最大,而数据库类组件通常不建议放入沙箱。Knative 场景下,可借助上文“按标签例外”的写法,为少数不适合沙箱的 Service 打上
no-isolation-here标签放行。 - 冷启动与缩容到零:Knative 的 scale-to-zero 意味着首次请求会额外承担沙箱创建(runsc 启动、Sentry 初始化)的开销,容量规划时应结合访问模式评估。
- 适用范围:本教程的前提是集群节点已具备 gvisor RuntimeClass(GKE Sandbox 节点池或按 Containerd Quick Start 配置的自建节点);
config-deployment的runtime-class-name写法取决于 Knative Serving 版本对“可选择 RuntimeClass”特性的支持,以你所用 Knative 版本的部署配置文档为准。
【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考