«

K8S 部署Longhorn(云原生分布式块存储解决方案)

myluzh 发布于 阅读:167 Kubernetes


0x00 前言

Longhorn 是 Kubernetes 里的分布式块存储,主要用来给 PVC 提供持久化存储。它不需要单独部署 Ceph 这种重型存储集群,直接使用各个 worker 节点上的本地磁盘,然后通过副本机制把数据分散到不同节点上。

我这里选择 Longhorn,主要是因为这几个点:

Longhorn 的核心不是“共享一个目录”,而是“每个卷有多个副本”。比如一个 5Gi 的 PVC,如果副本数是 2,Longhorn 会保存两份数据。最好让这两个副本落在不同节点或者不同磁盘上。

副本数可以简单这样理解:

副本数 实际占用 容错能力 适合场景
1 PVC 容量 x 1 没有节点级容错 测试环境
2 PVC 容量 x 2 允许丢一个副本 小集群常用
3 PVC 容量 x 3 容错更好 节点数和磁盘空间充足时

容量按副本数计算:

实际占用容量 = PVC 容量 x 副本数
可用容量约等于 = Longhorn 总磁盘容量 / 副本数

比如两个 worker 节点,每个节点给 Longhorn 准备 500Gi 磁盘:

Longhorn 总容量 = 500Gi x 2 = 1000Gi
副本数 = 2
理论可用容量约等于 1000Gi / 2 = 500Gi

实际可用容量还要扣掉系统保留空间、Longhorn 调度预留空间、快照、备份和临时重建副本需要的空间,所以不要按满盘规划。

副本数不要乱设。两个 worker 节点就不要强行设 3 副本,副本调度不满,卷容易长期处于 Degraded。如果只有一个 worker 节点,那 Longhorn 只能提供本地盘能力,不能提供节点级容错。

0x01 给 Longhorn 单独的数据盘

Longhorn 默认数据路径是:

/var/lib/longhorn

正式用不建议直接拿系统盘跑 Longhorn。最好提前准备一块单独的数据盘,挂载到 /var/lib/longhorn,然后再部署 Longhorn。这样系统盘和存储盘分开,后面排查、扩容、重装系统都更清楚。

每个需要承载 Longhorn 数据的 worker 节点都先准备好挂载目录:

mkdir -p /var/lib/longhorn

示例:把单独的数据盘挂载到 Longhorn 默认目录:

mount /dev/sdb1 /var/lib/longhorn

再写入 /etc/fstab 固化挂载:

blkid /dev/sdb1

vi /etc/fstab
UUID=这里替换成实际UUID  /var/lib/longhorn  ext4  defaults,noatime  0  2

确认挂载没问题:

df -h /var/lib/longhorn

如果 Longhorn 已经运行了一段时间,再去移动 /var/lib/longhorn,不要直接复制目录硬搬。应该先在 Longhorn UI 里禁用调度、驱逐副本,等数据迁移完成后再调整磁盘路径。

Longhorn 还依赖节点上的 iSCSI 工具。如果系统是 Debian/Ubuntu,可以先装:

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

如果系统是 CentOS/RHEL,可以先装:

yum install -y iscsi-initiator-utils nfs-utils
systemctl enable --now iscsid

其中 nfs-common / nfs-utils 主要用于 Longhorn RWX 卷。

0x02 Longhorn 架构和副本作用

先把 Longhorn 理清楚,后面看 Pod 和卷状态就不会乱。

Longhorn 分两层:

组件 作用
控制面 longhorn-manager 管理卷、节点、磁盘、副本、快照、备份,处理 UI 和 CSI 请求
数据面 longhorn-engine / instance manager 每个卷一个 engine,负责把数据读写转发到多个 replica
数据面 replica 真正保存卷数据的副本,数据落在节点的 /var/lib/longhorn 下面
Kubernetes 接入 CSI 组件 负责 PVC 创建、挂载、扩容、快照等 Kubernetes 存储动作
页面 longhorn-ui Web 管理界面

你看到的这些 Pod 大概可以这样理解:

Pod 作用
longhorn-manager-* 每个节点一个,负责 Longhorn 控制面
longhorn-ui-* Longhorn 页面
longhorn-driver-deployer-* 部署 CSI 相关组件
longhorn-csi-plugin-* 节点上的 CSI 插件,负责挂载卷到 Pod 所在节点
csi-attacher-* 处理卷 attach
csi-provisioner-* 根据 PVC 创建 Longhorn volume
csi-resizer-* 处理 PVC 扩容
csi-snapshotter-* 处理 CSI snapshot
engine-image-* Longhorn engine 镜像管理
instance-manager-* 管理每个卷对应的 engine 和 replica 实例

一个 PVC 从创建到 Pod 使用,大概是这个流程:

PVC
  -> StorageClass longhorn
  -> CSI provisioner 创建 Longhorn volume
  -> Longhorn manager 创建 engine 和 replicas
  -> Pod 调度到某个节点
  -> CSI plugin 把卷 attach/mount 到这个节点
  -> engine 在 Pod 所在节点运行
  -> engine 同步读写多个 replica
  -> replica 数据落到各节点 /var/lib/longhorn

这里最关键的是 enginereplica

engine 可以理解成这个卷的控制器。一个 Longhorn volume 会有自己的 engine。Pod 写数据时,不是直接写某个副本目录,而是先写到 engine。engine 再把写入同步到多个 replica。

replica 才是真正保存数据的地方。每个 replica 是同一个卷的一份数据副本,通常应该分散在不同节点或者不同磁盘上。比如副本数是 2,一个卷就会有两个 replica。只要还有一个健康 replica,这个卷就还有恢复机会。

副本的作用主要有这几个:

读写关系可以简单理解成:

Pod 写入数据
  -> engine
  -> replica-1
  -> replica-2

如果副本数是 3

Pod 写入数据
  -> engine
  -> replica-1
  -> replica-2
  -> replica-3

Longhorn 是同步复制,不是异步复制。也就是说,写入要同步到健康副本后才算完成。所以副本数越多,容错越好,但写入性能、网络压力、磁盘占用也会增加。

副本数和容错可以这样算:

最多可容忍副本故障数 = 副本数 - 1

比如:

副本数 最多可丢副本 说明
1 0 没有副本容错
2 1 可以丢一个副本
3 2 理论上可以丢两个副本

这里说的是副本,不是随便丢节点。如果多个副本被调度到同一个节点,这个节点坏了,多个副本会一起丢。所以生产环境要尽量让副本分散到不同节点和不同磁盘。

还有一点:Longhorn 的卷是块存储,不是共享文件系统。默认的 ReadWriteOnce 更适合一个节点挂载使用。如果要多个节点同时读写,需要用 RWX,Longhorn 会通过 share-manager 提供 NFS 方式访问。

0x03 部署

这边采用 kubectl YAML 方式,也可以用 Helm 方式部署。

下载 YAML

wget https://raw.githubusercontent.com/longhorn/longhorn/v1.11.2/deploy/longhorn.yaml

查看依赖镜像,修改成加速地址

grep -oE '[a-zA-Z0-9._-]+/[a-zA-Z0-9._/-]+:[a-zA-Z0-9._-]+' longhorn.yaml | sort -u
docker.io/longhornio/backing-image-manager:v1.11.2
docker.io/longhornio/csi-attacher:v4.11.0-20260428
docker.io/longhornio/csi-node-driver-registrar:v2.16.0-20260428
docker.io/longhornio/csi-provisioner:v5.3.0-20260428
docker.io/longhornio/csi-resizer:v2.1.0-20260428
docker.io/longhornio/csi-snapshotter:v8.5.0-20260428
docker.io/longhornio/livenessprobe:v2.18.0-20260428
docker.io/longhornio/longhorn-engine:v1.11.2
docker.io/longhornio/longhorn-instance-manager:v1.11.2
docker.io/longhornio/longhorn-manager:v1.11.2
docker.io/longhornio/longhorn-share-manager:v1.11.2
docker.io/longhornio/longhorn-ui:v1.11.2
docker.io/longhornio/support-bundle-kit:v0.0.84

替换成加速地址:
Rancher 社区已经将 Longhorn 的镜像同步到了国内的阿里云镜像仓库,包括 amd64 和 arm64 的镜像。
需要注意的是:Longhorn 镜像默认的命名空间为 “longhornio”,但上传到阿里云镜像仓库的命名空间为 “rancher”。以下是一个 Longhorn 镜像的示例:
longhornio/longhorn-manager:v1.5.5 --> registry.cn-hangzhou.aliyuncs.com/rancher/longhorn-manager:v1.5.5
因此,如果要在国内安装 Longhorn,只需要将镜像替换为国内的镜像名称即可。

sed -i 's|longhornio|registry.cn-hangzhou.aliyuncs.com/rancher|g' longhorn.yaml
# sed -i 's|docker\.io/longhornio/\([^: ]*\):\([^ "]*\)|swr.cn-east-3.myhuaweicloud.com/itho/docker.io/longhornio/\1:\2-amd64|g' longhorn.yaml

再检查一次:

grep -oE '[a-zA-Z0-9._-]+/[a-zA-Z0-9._/-]+:[a-zA-Z0-9._-]+' longhorn.yaml | sort -u
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/backing-image-manager:v1.11.2
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/csi-attacher:v4.11.0-20260428
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/csi-node-driver-registrar:v2.16.0-20260428
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/csi-provisioner:v5.3.0-20260428
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/csi-resizer:v2.1.0-20260428
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/csi-snapshotter:v8.5.0-20260428
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/livenessprobe:v2.18.0-20260428
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/longhorn-engine:v1.11.2
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/longhorn-instance-manager:v1.11.2
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/longhorn-manager:v1.11.2
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/longhorn-share-manager:v1.11.2
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/longhorn-ui:v1.11.2
docker.io/registry.cn-hangzhou.aliyuncs.com/rancher/support-bundle-kit:v0.0.84

开始部署

kubectl apply -f longhorn.yaml

查看 Pod:

kubectl -n longhorn-system get pod -o wide
NAME                                                READY   STATUS    RESTARTS        AGE   IP              NODE
csi-attacher-79b46ddbb5-8mxpf                       1/1     Running   1 (7m11s ago)   11m   10.244.36.206   k8s-worker-01
csi-attacher-79b46ddbb5-bfm5q                       1/1     Running   1 (7m11s ago)   11m   10.244.36.207   k8s-worker-01
csi-attacher-79b46ddbb5-zzxz7                       1/1     Running   4 (4m ago)      11m   10.244.118.79   k8s-worker-02
csi-provisioner-589b68df94-8jv2l                    1/1     Running   4 (3m53s ago)   11m   10.244.118.80   k8s-worker-02
csi-provisioner-589b68df94-m5rp8                    1/1     Running   4 (3m54s ago)   11m   10.244.118.81   k8s-worker-02
csi-provisioner-589b68df94-mbg2p                    1/1     Running   0               11m   10.244.36.208   k8s-worker-01
csi-resizer-6686cb7b68-5282c                        1/1     Running   4 (3m48s ago)   11m   10.244.118.82   k8s-worker-02
csi-resizer-6686cb7b68-7zl5v                        1/1     Running   0               11m   10.244.36.210   k8s-worker-01
csi-resizer-6686cb7b68-wb89s                        1/1     Running   0               11m   10.244.36.209   k8s-worker-01
csi-snapshotter-567bc6c575-5sn2z                    1/1     Running   0               11m   10.244.36.211   k8s-worker-01
csi-snapshotter-567bc6c575-78r9h                    1/1     Running   3 (4m33s ago)   11m   10.244.118.84   k8s-worker-02
csi-snapshotter-567bc6c575-jmncl                    1/1     Running   3 (4m35s ago)   11m   10.244.118.83   k8s-worker-02
engine-image-ei-89a2d474-5qfkt                      1/1     Running   0               12m   10.244.36.203   k8s-worker-01
engine-image-ei-89a2d474-5t482                      1/1     Running   0               12m   10.244.118.77   k8s-worker-02
instance-manager-39bcec98d10b689799eff1e45a19367f   1/1     Running   0               11m   10.244.118.78   k8s-worker-02
instance-manager-c25d748ecd3392e94e4597b302d0eeac   1/1     Running   0               11m   10.244.36.204   k8s-worker-01
longhorn-csi-plugin-jgz58                           3/3     Running   1 (5m44s ago)   11m   10.244.118.85   k8s-worker-02
longhorn-csi-plugin-n7zj5                           3/3     Running   0               11m   10.244.36.212   k8s-worker-01
longhorn-driver-deployer-66dc8cf958-hn22t           1/1     Running   0               13m   10.244.118.75   k8s-worker-02
longhorn-manager-2r4rs                              2/2     Running   0               13m   10.244.36.201   k8s-worker-01
longhorn-manager-lmh7c                              2/2     Running   0               13m   10.244.118.74   k8s-worker-02
longhorn-ui-66b5b4dc7d-8fl2p                        1/1     Running   0               13m   10.244.118.76   k8s-worker-02
longhorn-ui-66b5b4dc7d-9bxzl                        1/1     Running   0               13m   10.244.36.202   k8s-worker-01

查看 StorageClass:

kubectl get sc
NAME                 PROVISIONER          RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
csi-s3               ru.yandex.s3.csi     Delete          Immediate           false                  17h
longhorn (default)   driver.longhorn.io   Delete          Immediate           true                   12m
longhorn-static      driver.longhorn.io   Delete          Immediate           true                   12m

查看 Service:

kubectl get svc -n longhorn-system
NAME                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
longhorn-admission-webhook   ClusterIP   10.104.158.115   <none>        9502/TCP   15m
longhorn-backend             ClusterIP   10.96.13.116     <none>        9500/TCP   15m
longhorn-frontend            ClusterIP   10.104.253.143   <none>        80/TCP     15m
longhorn-recovery-backend    ClusterIP   10.97.147.135    <none>        9503/TCP   15m

0x04 暴露 Web UI

这里直接把 longhorn-frontend 改成 NodePort:

kubectl -n longhorn-system patch svc longhorn-frontend \
  -p '{"spec":{"type":"NodePort","ports":[{"port":80,"targetPort":8000,"nodePort":30880}]}}'

访问地址:

http://节点IP:30880

如果只是临时看一下,也可以用 port-forward

kubectl port-forward -n longhorn-system svc/longhorn-frontend 8080:80

然后访问:

http://localhost:8080

0x05 修改默认副本数

方法一:kubectl patch

kubectl -n longhorn-system patch settings.longhorn.io default-replica-count \
  --type='merge' \
  -p '{"value":"{\"v1\":\"2\",\"v2\":\"2\"}"}'

参数说明:

方法二:kubectl edit

kubectl -n longhorn-system edit settings.longhorn.io default-replica-count

方法三:Longhorn UI

浏览器打开 Longhorn UI,进入 Settings,找到 Default Replica Count,直接改成 2

常用查看命令

# 查看某个设置项的值
kubectl -n longhorn-system get settings.longhorn.io default-replica-count -o jsonpath='{.value}'

# 列出所有设置项
kubectl -n longhorn-system get settings.longhorn.io -o wide

Longhorn 的所有配置都是 settings.longhorn.io 资源,名称就是配置项,改 value 字段就行。

0x06 快速使用

直接创建 PVC

写一个 PVC,storageClassName 不填就会用默认的 longhorn

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 5Gi

在 Pod 中使用

apiVersion: v1
kind: Pod
metadata:
  name: test-longhorn
spec:
  containers:
    - name: nginx
      image: nginx
      volumeMounts:
        - mountPath: /data
          name: data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: my-pvc

在 Deployment 中使用

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          volumeMounts:
            - mountPath: /usr/share/nginx/html
              name: html
      volumes:
        - name: html
          persistentVolumeClaim:
            claimName: my-pvc

0x07 Longhorn 快照和定时快照

Longhorn 支持 PVC 快照。这里有两种常见用法:

方式 说明
Longhorn UI 快照 直接在 Longhorn 页面里对 volume 创建快照,适合手工回滚和临时保护
Kubernetes CSI 快照 通过 VolumeSnapshot / VolumeSnapshotClass 创建快照,适合写进 YAML 和自动化流程

快照和备份不是一回事:

类型 数据位置 作用
Snapshot 还在 Longhorn 本地卷和副本里 快速回滚、快速克隆
Backup 备份到 S3、NFS 这类外部存储 集群或者节点故障后的灾备恢复

所以快照不能代替备份。如果只是防止误操作,快照够用。如果要防止集群盘坏、节点全丢,必须把备份放到外部存储。

检查 CSI Snapshot 支持

Longhorn 里可以看到 csi-snapshotter Pod,但 Kubernetes 这边还需要有 Snapshot CRD 和 snapshot-controller。

先检查 CRD:

kubectl get crd | grep snapshot.storage.k8s.io

正常应该能看到这些:

volumesnapshotclasses.snapshot.storage.k8s.io
volumesnapshotcontents.snapshot.storage.k8s.io
volumesnapshots.snapshot.storage.k8s.io

再检查 snapshot-controller:

kubectl get pod -A | grep snapshot-controller

如果这些没有,就先安装 Kubernetes CSI Snapshot Controller。否则创建 VolumeSnapshot 时,资源可能能提交,但不会真正完成快照。

创建 VolumeSnapshotClass

先创建一个 Longhorn 的快照类:

vi longhorn-snapshotclass.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: longhorn-snapshot-vsc
driver: driver.longhorn.io
deletionPolicy: Delete
parameters:
  type: snap
kubectl apply -f longhorn-snapshotclass.yaml
kubectl get volumesnapshotclass

这里的 type: snap 表示创建 Longhorn 本地快照。如果要创建 Longhorn backup 类型,需要配置 backup target 后再用 type: bak

给 PVC 创建快照

假设前面已经有一个 PVC:

my-pvc

创建快照:

vi my-pvc-snapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: my-pvc-snapshot
  namespace: default
spec:
  volumeSnapshotClassName: longhorn-snapshot-vsc
  source:
    persistentVolumeClaimName: my-pvc
kubectl apply -f my-pvc-snapshot.yaml
kubectl -n default get volumesnapshot
kubectl get volumesnapshotcontent

看到 READYTOUSEtrue,说明快照已经可以使用。

从快照恢复 PVC

恢复时不是覆盖原 PVC,而是创建一个新的 PVC,然后把 dataSource 指向刚才的快照:

vi restored-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: restored-pvc
  namespace: default
spec:
  storageClassName: longhorn
  dataSource:
    name: my-pvc-snapshot
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
kubectl apply -f restored-pvc.yaml
kubectl -n default get pvc restored-pvc

恢复出来的是一个新的 PVC。后面 Pod 要不要切过去,需要自己改 Pod、Deployment 或 StatefulSet 的 PVC 引用。

定时快照

Longhorn 支持定时快照,用的是 RecurringJob

比如每天凌晨 2 点给卷打一次快照,保留最近 7 个:

vi daily-snapshot.yaml
apiVersion: longhorn.io/v1beta2
kind: RecurringJob
metadata:
  name: daily-snapshot
  namespace: longhorn-system
spec:
  task: snapshot
  cron: "0 2 * * *"
  retain: 7
  concurrency: 1
  groups:
    - default
  labels:
    type: daily
kubectl apply -f daily-snapshot.yaml
kubectl -n longhorn-system get recurringjobs.longhorn.io

然后把这个规则绑定到 PVC。Longhorn 这里用的是 label,不是 annotation:

kubectl -n default label pvc/my-pvc recurring-job.longhorn.io/source=enabled --overwrite
kubectl -n default label pvc/my-pvc recurring-job.longhorn.io/daily-snapshot=enabled --overwrite

也可以直接在 PVC YAML 里写:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
  labels:
    recurring-job.longhorn.io/source: enabled
    recurring-job.longhorn.io/daily-snapshot: enabled
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 5Gi

检查 label:

kubectl -n default get pvc my-pvc --show-labels

如果以后不想让这个 PVC 再跑这个定时快照,删掉 label:

kubectl -n default label pvc/my-pvc recurring-job.longhorn.io/daily-snapshot-

定时备份

定时备份也是 RecurringJob,只是把 task 改成 backup

apiVersion: longhorn.io/v1beta2
kind: RecurringJob
metadata:
  name: daily-backup
  namespace: longhorn-system
spec:
  task: backup
  cron: "30 2 * * *"
  retain: 7
  concurrency: 1
  groups:
    - default
  labels:
    type: daily

这个要先在 Longhorn 里配置好 Backup Target,比如 S3 或 NFS。没有外部备份目标,只创建 backup 任务没有意义。

快照使用注意点

0x08 常用说明

关键特性

功能 说明
默认副本数 这里改成 2,数据在两个 worker 间冗余
扩容 支持,PVC 中改 storage 大小即可,allowVolumeExpansion: true
快照/备份 通过 Longhorn UI 或 kubectl 操作
UI 面板 可以用 NodePort 或 port-forward 访问

两个 StorageClass 区别

几个注意点

k8s kubernetes Longhorn


正文到此结束
版权声明:若无特殊注明,本文皆为 Myluzh Blog 原创,转载请保留文章出处。
文章内容:https://itho.cn/k8s/607.html
文章标题:《K8S 部署Longhorn(云原生分布式块存储解决方案)