引子
在前面的一系列文章中,我们知道了要想在 riscv 开发板上适配 k3s,我们要做以下两件事
- 系统级别的重要镜像进行适配并可以成功拉取到开发板
- 执行 make 或者 download,build,package-cli 脚本
对于第一件事,已经有人做过了,我们只需要使用他做好的镜像,并将其推送到我们的私有仓库.

对于第二件事,在操作过程中我们可能会遇到大大小小的问题,其实在之前的文章操作中我们都遇到过,这里汇总一下.
构建过程
由于 make 过程会执行 dapper 的容器构建,会涉及到下载,由于众所周知的网络问题(我不可能给开发板上代理吧),所以我们放弃 make 过程.
直接采用执行三个脚本的方法,在此过程中我们需要修改一些部分的代码.
避免Golang版本检测
由于在脚本执行过程中,会去检测 当前代码中 k8s 的 Golang 的版本,具体代码在 /scripts/validate 如下:
echo Running: go version
if ! go version | grep -s "go version ${VERSION_GOLANG} "; then
echo "Unexpected $(go version) - Kubernetes ${VERSION_K8S} should be built with go version ${VERSION_GOLANG}"
exit 1
fi
这个 VERSION_GOLANG 来自于 /scripts/version.sh
DEPENDENCIES_URL="https://raw.githubusercontent.com/kubernetes/kubernetes/${VERSION_K8S}/build/dependencies.yaml"
VERSION_GOLANG="go"$(curl -sL "${DEPENDENCIES_URL}" | yq e '.dependencies[] | select(.name == "golang: upstream version")
故它会去使用 curl 命令查找这个 yaml 文件,接着 yq 读取其中 golang 的字段,具体内容如下:
# Golang
- name: "golang: upstream version"
version: 1.22.8
refPaths:
- path: .go-version
- path: build/build-image/cross/VERSION
- path: staging/publishing/rules.yaml
match: 'default-go-version\: \d+.\d+(alpha|beta|rc)?\.?(\d+)?'
- path: test/images/Makefile
match: GOLANG_VERSION=\d+.\d+(alpha|beta|rc)?\.?\d+
而这个检查 golang 版本的行为会出现在我们运行 k3s 的过程中,源码中 server 和 agent 的启动第一步就是 cmds.MustValidateGolang()
但是,由于众所周知的网络问题, curl 不动,😅;如果你不管这个问题就会出现以下现象:
May 14 18:16:01 revyos-lpi4a k3s[1447]: time="2025-05-14T18:16:01+08:00" level=fatal msg="Failed to validate golang version: incorrect golang build version - kubernetes v1.31.2 should be built with go, runtime version is go1.23.6"
可以发现,should built with go这是错误的,至少后面也应该是 goxxx,而不是 go——证明其没有读取到 yaml 文件中的内容.
这也就导致运行 k3s 时第一步的检查版本代码过不去,即下方这里的代码: pkg/cli/agent/agent.go
func ValidateGolang() error {
k8sVersion, _, _ := strings.Cut(version.Version, "+")
if version.UpstreamGolang == "" {
return fmt.Errorf("kubernetes golang build version not set - see 'golang: upstream version' in https://github.com/kubernetes/kubernetes/blob/%s/build/dependencies.yaml", k8sVersion)
}
if v, _, _ := strings.Cut(runtime.Version(), " "); version.UpstreamGolang != v {
return fmt.Errorf("incorrect golang build version - kubernetes %s should be built with %s, runtime version is %s", k8sVersion, version.UpstreamGolang, v)
}
return nil
}
所以我们最后干了什么:
- 将 validate 中关于检验 Golang 版本的代码注释掉
- 将 server 与 agent 中的
cmds.MustValidateGolang()注释掉 - 在 yaml 文件中找到适合的 golang 版本作为我们开发板上要用的版本,见上一篇文章环境配置.
避免下载过程过慢
在执行 download 过程中,其实并不需要在开发板上执行,这一点我们在这篇文章中提过,具体做法如下:
# 在我们的主机上执行
export GOARCH=riscv64
export ARCH=riscv64
export OS=linux
./scripts/download
# 然后拷贝build目录到开发板的k3s源码目录中
scp build/ root@ip:/root/k3s
开发板 airgap 安装
在开发板上执行 build 与 package-cli 脚本,build过程中可能会遇到网络问题,速度较慢,但是只要我们的环境,工具都正确,不会出现其他问题.
执行完两个脚本后会在 /dist/artifacts目录下产生一个 k3s-riscv 可执行二进制文件,将作为我们后续安装的文件.
接着我们进行 airgap 安装 server 节点,利用上一篇文章中的安装脚本即可完成安装.
安装后检查是否成功:
sudo systemctl status k3s
sudo kubectl get nodes
看到自己的节点即为成功.
手动拉取镜像
在 2025-05-14 实验版本中,系统镜像并没有自动拉取,这点我们还在尝试,因为官网还有一种为其设置配置文件的方式 /etc/rancher/k3s/config.yaml
所以我们使用 sudo kubeclt get pods -A 来查看这些系统镜像,并用sudo kubectl describe pod <podName> -n kube-system 来查看其错误原因
错误原因大部分都来自于镜像拉取失败,因为它还在拉取默认的 rancher 镜像. 所以我们需要修改 registries.yaml 文件,为其添加 rewrite,使其将默认镜像改为我们私有仓库中的镜像.
mirrors:
docker.io:
endpoint:
- "https://jimlt.bfsmlt.top"
# 这里根据上
rewrite:
"^library/pause$": "pause"
"^rancher/mirrored-library-traefik$": "traefik"
"^rancher/mirrored-metrics-server$": "metrics-server"
"^rancher/klipper-helm$": "klipper-helm"
"^library/klipper-lb$": "klipper-lb"
"^rancher/local-path-provisioner$": "local-path-provisioner"
"^rancher/mirrored-coredns-coredns$": "coredns/coredns"
"^busybox$": "riscv64/busybox"
# 注意,因为我们采用了 TLS 的私有仓库,所以这里需要添加 configs
configs:
"jimlt.bfsmlt.top": # 你的私有仓库地址
auth:
username: xxx
password: yyyyyyyy
然后需要手动拉取这些镜像,利用 sudo crictl pull 命令拉取.
最终结果如下:
sudo kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-56f6fc8fd7-28mn7 1/1 Running 1 (18h ago) 19h
kube-system helm-install-traefik-crd-98v8n 0/1 Completed 0 19h
kube-system helm-install-traefik-j7czp 0/1 Completed 0 19h
kube-system local-path-provisioner-5cf85fd84d-hznms 1/1 Running 0 19h
kube-system metrics-server-5985cbc9d7-v7dch 1/1 Running 0 19h
kube-system svclb-traefik-699324d1-48lvn 2/2 Running 0 93m
kube-system traefik-57b79cf995-xnv79 1/1 Running 0 93m
这些系统镜像都已经在成功运行.
测试pod
在主机上构建一个小的镜像推送到私有仓库中去
package main
import (
"fmt"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
message := os.Getenv("MESSAGE")
if message == "" {
message = "Hello from RISC-V!"
}
fmt.Fprintf(w, "%s\n", message)
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("Listening on :7080")
http.ListenAndServe(":7080", nil)
}
FROM golang:1.22.12-alpine3.20 AS builder
WORKDIR /app
COPY . .
RUN go build -o hello-k8s main.go
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/hello-k8s .
EXPOSE 7080
ENTRYPOINT ["./hello-k8s"]
构建并推送,注意设置 platform 与 network
docker build --platform=linux/riscv64 --network host -t jimlt.bfsmlt.top/hello-kubernetes:1.0.1 .
docker push jimlt.bfsmlt.top/hello-kubernetes:1.0.1
这里看着 push 的结果可以思考一下为什么 push 的时候显示 traefik?
The push refers to repository [jimlt.bfsmlt.top/hello-kubernetes]
f8b25a4c3a38: Pushed
ecbb5a20f6d5: Pushed
7df33f7ad8be: Mounted from traefik
4f4fb700ef54: Layer already exists
1.0.1: digest: sha256:78107dc8255d5b7eb09031d1c26fee9b10ee896237ac8b8acc1914e83b704325 size: 857
- 因为私有仓库中的 traefik 镜像已经有了 7d 这个镜像层(Docker 镜像的基本概念,镜像由层组成)
- 故这里复用了这个镜像层,就不用再重新构建这层了
在开发板上写一个 hello-deploy.yaml 进行部署测试
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
type: ClusterIP
ports:
- port: 7080
targetPort: 7080
selector:
app: hello
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
replicas: 1
selector:
matchLabels:
app: hello
template: # 注意这里在 spec 下,不是在 selector 下
metadata:
labels:
app: hello
spec:
containers:
- name: hello-kubernetes
image: jimlt.bfsmlt.top/hello-kubernetes:1.0.0
env:
- name: MESSAGE
value: "Hello RISC-V!"
ports:
- containerPort: 7080
运行此 deployment 并执行测试
sudo kubectl apply -f hello-deploy.yaml
sudo kubectl port-forward svc/hello 7080:7080
curl http://localhost:7080
# 输出结果
Hello RISC-V!
总结
至此,我们的移植过程暂时告一段落了,可以运行起一个简单的小应用,接下来我们会将另一个开发板作为 agent 加入其中进行集群测试.
不过其中对源码的修改方式(避免检测)也是一种无奈的尝试,后续看看有没有什么其他的好的方式;还有手动拉取系统镜像这一步也不应该这样做,再看吧.
最后问自己一句:问题解决了吗?这里是LTX,感谢您阅读这篇博客,人生海海,和自己对话,像只蝴蝶纵横四海。
