引子

接着上一篇文章,我们继续解决开发板上的k3s集群遇到的问题,回顾一下之前遇到的问题,使用命令查看当前所有 Pods。

kubectl get pods -A -o wide
NAMESPACE     NAME                                      READY   STATUS             RESTARTS        AGE    IP           NODE                NOMINATED NODE   READINESS GATES
default       redis-696579c6c8-v2wns                    1/1     Running            4 (4m10s ago)   7d6h   10.42.0.17   openeuler-riscv64   <none>           <none>
kube-system   helm-install-traefik-pv4hv                0/1     ImagePullBackOff   0               14d    10.42.0.20   openeuler-riscv64   <none>           <none>
kube-system   local-path-provisioner-7b7dc8d6f5-48jjf   0/1     ImagePullBackOff   0               14d    10.42.0.19   openeuler-riscv64   <none>           <none>
kube-system   helm-install-traefik-crd-ktfth            0/1     ImagePullBackOff   0               14d    10.42.0.21   openeuler-riscv64   <none>           <none>
kube-system   metrics-server-668d979685-jthzj           0/1     ImagePullBackOff   0               14d    10.42.0.22   openeuler-riscv64   <none>           <none>
kube-system   coredns-5f8bb7cf9f-5h5kj                  0/1     Running            0               11s    10.42.0.36   openeuler-riscv64   <none>           <none>

可以发现在 kube-system 命名空间下的这些 Pod 还是处于 ImagePullBackOff 阶段,只有我们上次解决的 CoreDNS 是 Running, 只是还存在网络问题没有解决,所以并没有 Ready.

所以这次我们来解决其他 local-path-provisioner 的问题。

什么是local-path-provisioner

官方的介绍是:Dynamically provisioning persistent local storage with Kubernetes

众所周知,如果我们起一个 Pod,里面的容器一定是需要挂载外部的存储目录的,否则容器一旦销毁那么相关的数据也不在了,无法做到持久化存储,而挂载外部存储目录本质上要求 PVC——持久化卷声明,举例说明:

volumeMounts:
  - mountPath: /data
    name: redis-storage
volumes:
  - name: redis-storage
    persistentVolumeClaim:
      claimName: redis-data

这里我们声明了具体的 pvc persistentVolumeClaim:claimName: redis-data,这是一个真正的磁盘路径,见下方 PVC 的声明写法。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: redis-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

一旦写了 PVC,local-path-provisioner 就会起作用:它自动为你的 PVC 创建一个目录, 如 /opt/local-path-provisioner/pvc-xxx/,将此目录映射给容器作为持久化的目录。 所以,这个镜像的修复是至关重要的一步,没有它我们就无法进行数据的持久化。

修复问题

与CoreDNS不同的是,这次在官方的 release 界面并没有发现其针对于riscv架构的二进制文件,所以我们得尝试自己构建了。

(后续更新,发现其已经支持了 RiscV 架构,只是没有跑通流水线部署,但是代码中已经 Commit 了,所以可以直接使用了)

拉取到其源代码之后在我的 mac 本机进行构建

1.构建自定义镜像

git clone git@github.com:rancher/local-path-provisioner.git
cd local-path-provisioner
GOOS=linux GOARCH=riscv64 CGO_ENABLED=0 go build -o local-path-provisioner

构建成功,这证明其中并没有不适配riscv的地方(例如coredns中构建时出现过的syscall不支持问题)得到一个local-path-provisioner的二进制文件,我们可以将这个二进制文件封装在一个Dockerfile中构建其自定义的镜像来代替默认的镜像。

dockerfile如下:注意拷贝的路径选择为/usr/bin

FROM alpine:latest

# 使用阿里源(可选)
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#http://mirrors.aliyun.com/alpine#g' /etc/apk/repositories

# 拷贝编译好的可执行文件
COPY local-path-provisioner-riscv64 /usr/bin/local-path-provisioner

# 启动命令
ENTRYPOINT ["local-path-provisioner"]

如果不使用/usr/bin的话会遇到问题:

Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  79s                default-scheduler  Successfully assigned kube-system/local-path-provisioner-5cdfcc7d9c-hpqnm to openeuler-riscv64
  Normal   Pulled     35s (x4 over 79s)  kubelet            Container image "ltx/local-path-provisioner:v0.0.21" already present on machine
  Normal   Created    35s (x4 over 79s)  kubelet            Created container local-path-provisioner
  Warning  Failed     35s (x4 over 79s)  kubelet            Error: failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "local-path-provisioner": executable file not found in $PATH: unknown
  Warning  BackOff    9s (x7 over 78s)   kubelet            Back-off restarting failed container

构建镜像并推送至本地的私有镜像仓库。

docker build --network host --platform=linux/riscv64 -f Dockerfile.local-path-provisioner-riscv64 -t 192.168.173.76:6000/riscv64/local-path-provisioner:1.1 .
docker push 192.168.173.76:6000/riscv64/local-path-provisioner:1.1

2.拉取镜像修改配置文件

在 riscv 开发板进行拉取镜像并打标签至默认 local-storage 中指明的镜像名称。

crictl pull riscv64/local-path-provisioner:1.1
ctr -n k8s.io images tag docker.io/riscv64/local-path-provisioner:1.1 docker.io/rancher/local-path-provisioner:v.0.0.21

可以使用 kubectl -n kube-system edit deployment local-path-provisioner 检查其Deployment文件;

或者与coreDNS相同,进入 /var/lib/rancher/k3s/server/manifests 目录下查看其 local-storage.yaml 文件. 这是local-path-provisioner的部署文件。

如果未能识别到本地镜像,根据这个回答,我们只要修改前缀不是rancher的就可以识别到本地的镜像

接下来与 coreDNS 相同,这是系统级别的镜像,为了防止每次重启都被覆盖,需要在k3s启动命令中添加--disable标签. 因为我们使用systemctl来管理k3s,所以需要修改/etc/systemd/system/k3s.service

在此之前我们要先拷贝一份manifests目录下的yaml文件,修改为我们自定义的custom-local-storage.yaml文件,让之后的k3s识别到这个文件进行容器的构建。

cp local-storage.yaml custom-local-storage.yaml

由于我们之前已经把标签打成了与默认 yaml 文件中相同名称的镜像,所以这个 custom 文件中不需要修改什么东西。

回到刚才,继续修改启动命令。

vi /etc/systemd/system/k3s.service
# 在ExecStart中添加 --disable
ExecStart=/usr/local/bin/k3s \
    server \
    --disable=local-storage,coredns \

这一方法来源于官方文档,或者这篇文章,加上这个标签会使k3s在启动时选择我们设定的自定义yaml文件并且删除默认的yaml文件与构建的容器。

你还可以选择文章中的方法二,在manifests目录下添加 local-storage.yaml.skip 文件,这样会让k3s跳过某个容器的构建,但我没有测试其是否会去选择我们自定义的配置文件。

接下来进行删除,检测。最好重启一下k3s,systemctl restart k3s

kubectl delete pod -n kube-system -l app=local-path-provisioner
kubectl get pods -n kube-system -l app=local-path-provisioner
kubectl describe pod <PodName> -n kube-system

总结

经历了 coreDNS 和 local-path-provisioner 这两个系统级别的镜像之后,我开始反思整个适配过程,我觉得应该先去看看如何从头适配,即使 OREV 的兴趣小组先进行了一个基础适配,使得我们可以先下载到 k3s。

google 后发现在官方的ISSUE中有这个适配问题,其中提到一个巨大挑战是rancher-mirrored 镜像中缺乏对 riscv 平台的支持。

这也正是我们之前处理的那些镜像,默认是拉取的 rancher 官方维护的镜像副本 rancher-mirrored,我们也对这些镜像做了适配。(后来知道可以修改默认镜像仓库,在启动命令时加上私有的仓库即可)

While we could simply add this to the arch list, this would change the multi-arch digest for any tags that we currently mirror that have this platform available.

下面也提到虽然可以简单地将 riscv 添加到镜像的架构列表中,但会导致当前镜像的多架构 digest 发生变化。

  • 镜像的 digest 是镜像的唯一标识,一个哈希值 sha256:xxx
  • 我们平时用的镜像名称,其实就是 tag,指向的是一个多架构的manifest,这个 manifest 会列出所有对应架构的镜像(见下方图像)
  • manifest list 本身是一个 JSON 文件,他也有 digest 标识;如果添加一个 riscv 字段进去,整个 JSON 内容变化导致 digest 变化
  • 很多生产系统,CI/CD 都依赖这个稳定可验证的 digest,这就导致牵一发而动全身, 会重新拉取镜像,打破缓存;某些系统会误以为内容被篡改或更新了,造成意外后果;不利于镜像的同步和管理
  • 更愿意的做法是:不改变现有版本(及之前版本)的镜像 manifest,在新的版本中添加对新架构的支持.

2

We should figure out how to add this platform to only new tags, in a way that is minimally disruptive to the list maintenance process, and can be reused when adding new platforms in the future.

这是他们提出的解决方法,只将 RISC-V 平台添加到**新标签(新的 manifest,新的镜像)**中,这样对镜像列表的维护干扰最小。

为了保证镜像的稳定性,K3s 团队不修改已有镜像的多架构配置,而是只给新添加的镜像构建 RISC-V 版本,靠 git blame + 时间区间控制,确保 digest 不变且流程可复用。

但是后续的讨论就围绕着该如何支持新的架构,官方开发者更倾向于对新的镜像加入新的架构而不去改动老的镜像,这是很好理解的策略。

接下来我们将探索其他系统级别镜像,例如 Helm,Traefik

这里是LTX,感谢您阅读这篇博客,人生海海,和自己对话,像只蝴蝶纵横四海。

后续更新

可以发现 rancher 官方提供了关于 local-path-provisioner 在 riscv 上的镜像.

3

所以大佬在 commit 中只是修改了其版本到29.

故我们只需要将其拉取到本地并推送到私有镜像仓库即可.