- 文档
- 教程
- 云原生
【免费下载链接】docs
User documentation for Knative components.
本指南以 Knative docs 仓库中的社区示例 helloworld-r 为主线,完整演示如何将一个读取环境变量的 R 脚本封装成容器化 Web 服务,并部署到 Knative Serving 集群。读完本文,你将掌握 R 与 Go 桥接的容器多阶段构建方法、KnativeService资源的编写规范、镜像构建与推送流程,以及验证与清理服务部署的完整操作闭环。
示例概览:R 脚本 + Go Web 桥接
helloworld-r是一个"hello world"级别的入门示例,但它的架构思路值得关注:纯 R 脚本本身不擅长处理 HTTP 请求,因此示例采用 Go 编写一个轻量 Web 服务器,在收到 HTTP 请求时通过os/exec调用Rscript执行 R 脚本,并把脚本输出作为 HTTP 响应返回。
整个示例的目录结构如下(位于仓库code-samples/community/serving/helloworld-r/):
helloworld-r/ ├── HelloWorld.R # R 脚本:读取 TARGET 环境变量并输出问候语 ├── invoke.go # Go Web 服务:调用 Rscript 执行 R 脚本 ├── Dockerfile # 多阶段构建:Go 编译 + R 运行时镜像 ├── go.mod # Go module 定义 ├── service.yaml # Knative Service 声明 ├── README.md # 示例说明文档 └── index.md # 示例说明副本业务逻辑非常简单:R 脚本通过Sys.getenv("TARGET", "World")读取环境变量TARGET,输出Hello ${TARGET}!;如果未设置该环境变量,则默认输出Hello World!。而 Knative Serving 负责将这份工作负载暴露为可通过 HTTP 访问的 Serverless 服务,并按流量自动扩缩容。
开始前的准备
在动手之前,需要满足以下前置条件:
- 一个已安装 Knative 的 Kubernetes 集群。Knative Serving 以 CRD(自定义资源)方式管理 Serverless 工作负载,集群是部署的前提。若还没有集群,可以参考仓库中的 Knative Serving 安装文档(docs/versioned/install/README.md)完成安装与 DNS 配置。
- 本机安装并运行 Docker,同时配置好一个 Docker Hub 账户,用于把构建出的容器镜像推送到镜像仓库(本示例以 Docker Hub 为例)。
- 可选:
kubectl(Kubernetes 命令行工具)用于应用 YAML 资源;若偏好命令行交互,也可安装knCLI(参见 docs/versioned/client/install-kn.md)。
如果你希望直接使用仓库中已经写好的示例文件,而不是手动创建,可以克隆本仓库并进入示例目录:
git clone https://gitcode.com/gh_mirrors/docs1/docs.git knative-docs cd knative-docs/code-samples/community/serving/helloworld-r第一步:创建 R 脚本HelloWorld.R
新建文件HelloWorld.R,内容如下(与仓库中 HelloWorld.R 一致):
#!/usr/bin/Rscript TARGET <- Sys.getenv("TARGET", "World") message = paste("Hello ", TARGET, "!", sep = "") print(message)逐行解读:
#!/usr/bin/Rscript:shebang 声明,指明使用Rscript作为解释器,使得脚本可以被Rscript HelloWorld.R直接执行(在容器中Rscript位于/usr/bin/Rscript,与仓库示例中的写法一致)。Sys.getenv("TARGET", "World"):R 的内置函数,读取环境变量TARGET,若未设置则返回默认值"World"。这就是 Knative 部署时注入配置的"入口"——见后文service.yaml中的env配置。paste("Hello ", TARGET, "!", sep = ""):拼接字符串,sep = ""表示各段之间不加分隔符。print(message):将结果输出到标准输出。
用 R 在本地快速验证脚本行为:
TARGET <- Sys.getenv("TARGET", "World") print(paste("Hello ", TARGET, "!", sep = ""))在没有设置TARGET时输出[1] "Hello World!";设置后输出对应的问候语。
第二步:创建 Go Web 服务invoke.go
R 脚本本身没有 Web 能力,示例用 Go 编写了一个极简 HTTP 服务器来"驱动"它。新建文件invoke.go(与仓库中 invoke.go 一致):
package main import ( "fmt" "log" "net/http" "os" "os/exec" ) func handler(w http.ResponseWriter, r *http.Request) { cmd := exec.CommandContext(r.Context(), "Rscript", "HelloWorld.R") cmd.Stderr = os.Stderr out, err := cmd.Output() if err != nil { w.WriteHeader(500) } w.Write(out) } func main() { http.HandleFunc("/", handler) port := os.Getenv("PORT") if port == "" { port = "8080" } log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) }代码要点:
exec.CommandContext(r.Context(), "Rscript", "HelloWorld.R"):以请求上下文启动Rscript子进程执行 R 脚本。使用CommandContext而非Command的用意是:当 HTTP 请求被取消或连接断开时,子进程会随之终止,避免产生僵尸进程——这在 Serverless 高频启停的场景下尤为重要。cmd.Stderr = os.Stderr:将 R 脚本的 stderr 透传到容器日志,便于排错。cmd.Output():捕获子进程的标准输出,即 R 脚本print()的结果。- 错误处理:执行失败时返回 HTTP 500 状态码。
- 端口读取:
PORT环境变量是 Knative 注入约定——Knative Serving 会自动把监听端口注入到容器的PORT环境变量中,因此示例在PORT为空时默认回退到8080,这与 Knative 容器默认约定一致。
新建go.mod声明模块(仓库中 go.mod 使用 Go 1.22):
module knative.dev/docs/code-samples/community/serving/helloworld-r go 1.22本示例的 Go 代码只依赖标准库(net/http、os/exec等),因此无需第三方依赖。
第三步:编写多阶段构建 Dockerfile
新建Dockerfile。仓库当前版本的 Dockerfile 如下:
# 阶段一:使用官方 Golang 镜像构建 Go 可执行文件 FROM golang:latest AS builder ARG TARGETOS ARG TARGETARCH # 创建并切换工作目录 WORKDIR /app # 将本地代码复制进镜像 COPY . ./ # 整理 go.mod 依赖 RUN go mod tidy # 编译静态二进制,禁用 CGO 以便在 R 基础镜像中运行 RUN CGO_ENABLED=0 GOOS=linux GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -v -o invoke # 阶段二:使用官方 R 基础镜像作为运行时 FROM r-base:3.6.0 # 将 Go 二进制与 R 脚本复制进运行时镜像 COPY --from=builder /app/invoke /invoke COPY HelloWorld.R . # 容器启动时运行 Web 服务 CMD ["/invoke"]这份 Dockerfile 采用的是 Docker **多阶段构建(multi-stage build)**策略,是本节最值得学习的工程实践:
- 阶段一(builder):在
golang:latest镜像中编译 Go 程序。CGO_ENABLED=0保证产出静态链接的二进制,不依赖目标系统的动态库;GOOS/GOARCH由ARG TARGETOS/ARG TARGETARCH注入,支持docker buildx的跨平台构建(如同时产出linux/amd64与linux/arm64)。 - 阶段二(runtime):仅把编译产物和 R 脚本复制进
r-base:3.6.0官方 R 运行时镜像,不携带 Go 工具链,因此最终镜像体积大幅缩小。容器启动命令CMD ["/invoke"]直接运行 Go 二进制,由它在运行时按需调用镜像内的Rscript。
注意:示例 README 中展示的 Dockerfile 早期版本使用
FROM golang:1.12 as builder并把工作目录设为WORKDIR /go/src/github.com/knative/docs/helloworld-r;仓库当前版本已升级为golang:latest+WORKDIR /app并新增go mod tidy步骤。两者构建思路一致,建议以仓库当前版本为准。
第四步:编写 Knative Service 定义service.yaml
新建service.yaml(与仓库中 service.yaml 一致),注意把{username}替换为你自己的 Docker Hub 用户名:
apiVersion: serving.knative.dev/v1 kind: Service metadata: name: helloworld-r namespace: default spec: template: spec: containers: - image: docker.io/{username}/helloworld-r env: - name: TARGET value: "R Sample v1"这份声明体现了 Knative Serving 的核心资源模型——Service资源自动管理工作负载的完整生命周期。正如仓库文档 docs/versioned/serving/README.md 与 docs/snippets/about-serving.md 所述,Knative Serving 定义了Service、Route、Configuration、Revision四类核心 CRD:
- Service(
service.serving.knative.dev):管理整个工作负载生命周期的入口,提交Service后,Knative 会自动为你创建 Route、Configuration,并在每次更新时产生新的 Revision。本示例的kind: Service即这一层的声明。 - Configuration:维护部署的期望状态,修改配置即触发新 Revision 的创建。
- Revision:代码与配置的"时间点快照",不可变,可被自动扩缩容。
- Route:把网络端点映射到一个或多个 Revision,支持流量分配。
spec.template.spec.containers.env中的TARGET: "R Sample v1"正是第一步中 R 脚本Sys.getenv("TARGET", ...)读取的环境变量来源——这就是"Knative 向工作负载注入配置"的机制。如果你希望验证默认值行为,也可以不配置该环境变量,此时输出将是Hello World!。
第五步:构建镜像并推送到镜像仓库
在示例目录下执行以下命令,用 Docker Buildx 构建多架构镜像并直接推送:
# 在本地构建并推送容器镜像 docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-r" --push .参数说明:
--platform linux/arm64,linux/amd64:同时构建 amd64 与 arm64 两种架构的镜像,配合 Dockerfile 中的TARGETOS/TARGETARCH实现交叉编译,便于在异构集群上运行。-t "{username}/helloworld-r":镜像标签,{username}替换为你的 Docker Hub 用户名。--push:构建完成后直接推送到 Docker Hub。- 若你的机器未启用 Buildx,也可以退回到传统方式:
docker build -t "{username}/helloworld-r" .后执行docker push "{username}/helloworld-r"。
第六步:部署到 Knative 集群
镜像推送完成后,确保service.yaml中的镜像值与你构建的镜像完全一致,然后应用配置:
kubectl apply --filename service.yamlService创建完成后,Knative Serving 会在后台自动完成以下工作(参见 docs/versioned/serving/services/creating-services.md):
- 为当前版本的应用创建一个新的不可变 Revision;
- 执行网络编程,为应用创建 Route、Ingress、Service 与负载均衡;
- 根据流量自动扩缩容 Pod,包括在没有流量时缩容到 0 个活跃 Pod。
关于"缩容到零",可参考仓库文档 docs/versioned/serving/autoscaling/scale-to-zero.md:enable-scale-to-zero全局配置默认值为true,意味着 Knative 允许 Revision 的副本数缩放到 0;当请求到来时,Activator 会缓冲请求并触发"冷启动"拉起新 Pod。本示例在curl访问时看到的延迟,往往就包含这一从零扩容的过程。
第七步:获取服务 URL 并验证
运行以下命令获取服务的对外访问地址:
kubectl get ksvc helloworld-r --output=custom-columns=NAME:.metadata.name,URL:.status.url输出示例:
NAME URL helloworld-r http://helloworld-r.default.1.2.3.4.sslip.io然后使用curl发起请求(把 URL 替换为你实际获取到的值):
curl http://helloworld-r.default.1.2.3.4.sslip.io预期输出:
[1] "Hello R Sample v1!"[1]是 R 控制台输出的行号前缀,说明该响应确实来自 R 脚本的print()输出。你可以尝试修改service.yaml中TARGET的值(例如改为"R Sample v2")后重新kubectl apply,Knative 会创建新 Revision 并将流量切到新版本——这直观地演示了"配置即版本"的迭代方式。
提示:如果
curl失败,可以加上-v选项查看更详细的请求与响应信息,帮助定位问题。
清理部署
验证完成后,删除服务记录即可清理集群中的资源:
kubectl delete --filename service.yamlKnative 会随之回收该 Service 关联的 Route、Configuration 与 Revision 等资源;同时由于自动扩缩容机制,闲置期间服务本就可能已经缩容到零副本,清理成本很低。
深入理解:一次 HTTP 请求背后的 Knative 机制
部署一个helloworld-r之后,从curl到 R 脚本输出,中间发生了什么?结合仓库文档 docs/versioned/serving/request-flow.md 可以梳理出完整的调用链:
- **HTTP 路由器(可插拔的 Ingress 层)**在请求级做一次路由决策,将请求分配到对应 Revision。
- 若该 Revision 当前低流量或零流量(例如刚从零扩容),请求会被转发给Activator——它作为请求缓冲队列,向 Autoscaler 发出扩容信号,等待新 Pod 就绪后转发请求,这就是"从零扩容 / 冷启动"路径。
- 当 Revision 流量较高、超出
target-burst-capacity阈值时,Ingress 会绕过 Activator,直接路由到应用 Pod,降低延迟;此时queue-proxy仍在请求路径上,负责度量并发请求、执行containerConcurrency硬限制、处理优雅停机并向 Autoscaler 上报指标。 - 请求最终到达用户容器(即本示例的
/invokeGo 进程),Go 通过os/exec派生Rscript子进程执行HelloWorld.R,将print()输出写回 HTTP 响应。
整个流程中,Go 桥接层屏蔽了 R 与 HTTP 协议之间的鸿沟,而 Knative Serving 则屏蔽了集群编排的复杂度——开发者只需交付一个监听PORT的容器镜像,即可获得路由、扩缩容与版本管理的 Serverless 体验。
延伸阅读
- Knative Serving 概述:了解 Service/Route/Configuration/Revision 四大核心资源的关系。
- 创建 Service:YAML 与
knCLI 两种创建方式对比。 - 自动扩缩容 与 配置缩容到零:深入 Knative 的弹性伸缩机制。
- HTTP 请求流:Activator、Autoscaler、queue-proxy 的协作细节。
- 其他社区示例:code-samples/community/serving,以及 Knative 官方的 Hello World 系列(Go、Python、Node.js 等)示例,可对比不同语言接入 Knative Serving 的异同。
- 文档
- 教程
- 云原生
【免费下载链接】docs
User documentation for Knative components.
相关推荐
Knative Serving 实战:用 Elixir + Phoenix 构建并部署 Hello World 服务
Knative Serving 实战:用 Elixir + Phoenix 构建并部署 Hello World 服务 本篇技术指南以 Knative 官方代码示
文档教程云原生用 Clojure 编写并部署 Knative Serving Hello World 服务
用 Clojure 编写并部署 Knative Serving Hello World 服务 导读 本文基于 Knative 文档仓库中的社区示例 hellow
文档教程云原生用 Deno 编写 Hello World 应用并部署到 Knative Serving:社区示例完整实战
用 Deno 编写 Hello World 应用并部署到 Knative Serving:社区示例完整实战 本文基于 Knative 官方文档仓库中的社区示例
文档教程云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考