K3S存储Longhorn

针对K3S集群来说,存储肯定不能直接使用本地磁盘,因为一旦这台机器故障,pod漂移走了,那么数据就没了,还有很多分布式存储,比如Cept(功能强,但是维护复杂),NFS(性能差),如果公司已有 SAN、分布式存储,直接用其 CSI 驱动,最稳,K3S推荐的官方存储是Longhorn

Longhorn也是Rancher开发的存储,跟K3S是同一家公司开发,完美适配K3S和K8S,优点如下:

  • 分布式块存储
  • 多副本(1/2/3 副本)
  • 节点挂了,Pod 飘到别的节点,数据自动挂载
  • 自带备份、快照、恢复
  • 有 Web UI
  • 资源占用低,适合内网

Longhorn原理:

Longhorn 本质就是用各个节点的本地磁盘,拼成一个分布式高可用存储

  • 它会把 K3s 集群里每台机器的空闲磁盘 / 分区拿出来
  • 统一管理,变成一个虚拟的分布式块存储池
  • 创建 PVC 时,它就在这些本地盘里划空间给 Pod

注:要完整的阅读后面的所有内容,才能更好的使用Longhorn

部署

1、先给所有的k3s-master01节点打上污点,禁止调度,如下:

kubectl taint nodes k3s-master01 node-role.kubernetes.io/control-plane=:NoSchedule
kubectl taint nodes k3s-master02 node-role.kubernetes.io/control-plane=:NoSchedule
kubectl taint nodes k3s-master03 node-role.kubernetes.io/control-plane=:NoSchedule
#kubectl describe node k8s-master03 | grep Taint   查看污点打成功了没

2、在worker节点安装依赖,如下:

apt update
apt install -y open-iscsi nfs-common xfsprogs
systemctl enable --now iscsid

3、给worker节点打上标签,如下:

kubectl label node k3s-node01 k3s-node02 node-role.kubernetes.io/worker=storage

4、使用官方yaml一键安装Longhorn,如下:

kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.7.2/deploy/longhorn.yaml

执行后验证是否启动正常,如下:

kubectl get pods -n longhorn-system

注:很多镜像需要单独下载回来的

查看自动创建的StorageClass,如图:

  • (default) 代表:集群默认存储类,没指定 sc 时自动用 longhorn

开启Longhorn web

K3s 默认集成了 Traefik 作为 Ingress Controller,通过 Ingress 暴露 Longhorn UI 是最标准的做法。因此不需要修改 Longhorn 的任何服务配置,直接创建一条路由规则即可

1、在创建 Ingress 前,先确认 longhorn-frontend服务状态正常:

kubectl get svc -n longhorn-system longhorn-frontend

预期输出(Type 应为 ClusterIP,Port 为 80):

NAME                TYPE        CLUSTER-IP      PORT(S)
longhorn-frontend   ClusterIP   10.43.xxx.xxx   80/TCP

2、编辑ingress文件longhorn-ingress.yaml,配置域名规则,如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: longhorn-ingress
  namespace: longhorn-system
  annotations:
    # 可选:如果你希望访问时带基本认证(用户名/密码),取消注释以下两行
    # ingress.kubernetes.io/auth-type: "basic"
    # ingress.kubernetes.io/auth-secret: longhorn-auth
spec:
  ingressClassName: traefik  # K3s 默认的 Ingress Class
  rules:
  - host: longhorn.oa.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: longhorn-frontend
            port:
              number: 80

执行:

kubectl apply -f longhorn-ingress.yaml

配置域名解析到192.168.51.72(Metallb分配的IP),最后通过域名访问,如下:

3、修改Longhorn的数据目录,默认情况下使用的是系统跑的/var/lib/longhorn,现在修改为数据盘,首先创建目录:

mkdir -p /data/longhorn && chmod 755 /data/longhorn  #两个worker节点都要执行

4、登录web ui界面,点击Node,先勾选一个节点,然后点击右侧的Edit node and disks,如图:

将之前调度到系统盘的位置修改为Disable,然后点击Add Disk,如图:

新增磁盘设置预留空间,并开启调度:

  • Storage Reserved:存储预留的,Longhorn不会动,给其他系统用的
  • Scheduling:开启调度

最后查看节点信息,总量199.9G,可使用149.9,已使用0,如图:

注:Longhorn 是集群级别的应用,它实际上运行的是 DaemonSet(一种每个节点跑一个实例的控制器)。只要新节点加入集群,Longhorn 的 Agent(longhorn-manager)就会自动调度到新节点上运行,前提是要规划好资源,比如,创建好磁盘目录,安装好依赖iSCSI

取消默认存储类local-path

Kubernetes 有一个“兜底”机制:当你创建 PVC(申请存储)时,如果没有写 storageClassName字段,系统会自动找一个带有 (default)标记的 StorageClass 来用,查看当前存储类,如图:

查看local-path是否是默认类,也是true,如图:

从上图看出,两个都是default,因此如果在申请PVC的时候没有指定stroageClassName字段,那就可能会走默认的local-path,这个是本地路径,如果pod漂移了,数据就没有了,因此需要将这个默认存储类取消,只保留一个longhorn,执行如下命令:

kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'

附加:如果longhorn不是默认存储类,可以通过命令设置让它默认,如下:

kubectl patch storageclass longhorn -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

修改vloumebindingmode

Longhorn 的 volumeBindingMode 控制何时创建并绑定 PV 到 PVC,两种模式核心差异是:Immediate 马上绑WaitForFirstConsumer 等 Pod 调度时再绑

Immediate(立即绑定,Longhorn 默认)

  • 触发时机:PVC 创建后立刻动态创建 Longhorn 卷并绑定 PV
  • 调度逻辑:绑定不关心未来 Pod 跑在哪,先选节点建卷、绑定 PVC
  • 主要问题:卷可能建在与 Pod 亲和性(nodeSelector/taint)冲突的节点,导致 Pod 调度失败、卡在 Pending,卷建好就占副本空间,资源占用更早,此模式适合测试和开发环境

WaitForFirstConsumer(延迟绑定)

  • 触发时机:等到第一个使用该 PVC 的 Pod 开始调度时,才创建卷并绑定
  • 调度逻辑:Pod 调度器先筛选符合条件的节点,在候选节点上创建 Longhorn 卷,绑定 PV 到 PVC,再挂载到 Pod
  • 核心优势:卷与 Pod 节点强匹配,彻底避免因节点不匹配导致的 Pod Pending,按需建卷,PVC 存在但没 Pod 用时不占存储,更省资源
  • 适合环境:生产环境,有节点亲和、污点、拓扑约束(如多可用区)

定义longhorn-new.yaml文件,创建新的存储类,修改为WaitForFirstConsumer,如下:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: longhorn-new
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"   #设置默认
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
parameters:
  numberOfReplicas: "2"
  staleReplicaTimeout: "30"
  fromBackup: ""
  fsType: "ext4"
  dataLocality: "disabled"
  unmapMarkSnapChainRemoved: "ignored"
  disableRevisionCounter: "true"
  dataEngine: "v1"

注:numberOfReplicas参数要修改为2,如果longhorn只有2个节点,就要修改为2,如果有三个节点就要修改为3,默认值是3

在longhorn页面,点击设置–General-修改默认副本数量为当前节点数(默认也是3),如图:

测试

定义一个测试pod来验证下longhorn是否能正常工作,如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: longhorn-test-pvc
spec:
  accessModes:
    - ReadWriteOnce       # Longhorn 块存储通常只支持单节点读写[citation:8]
  storageClassName: longhorn-new   #Longhorn StorageClass 名字
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: longhorn-test-pod
spec:
  restartPolicy: Never      # 验证结束后不需要重启
  volumes:
    - name: test-storage
      persistentVolumeClaim:
        claimName: longhorn-test-pvc
  containers:
    - name: writer
      image: registry.ywdevops.cn/alpine:latest 
      command: ["/bin/sh"]
      args: ["-c", "echo 'Hello Longhorn' > /data/hello.txt && echo 'Data written successfully!' && tail -f /dev/null"]
      volumeMounts:
        - name: test-storage
          mountPath: /data

创建后,在longhorn的volume界面可以看到状态为healthy,如图:

注:longhorn存储的数据是以块方式存储,因为在本地磁盘的longhorn中看不到明文数据的

如果pod被删除了,后续重新创建了pod,只要重新指定之前的pvc就行,就是参数claimName: longhorn-test-pvc,这样数据还在,不会因为pod删除了数据就没了

标签