k8s 存储初始化之树莓派外接USB硬盘

之前的文章中我们在 4 台树莓派 model 4b 上搭建了 k8s 集群. 但是这台集群有个小缺陷, 那就是我们为每块树莓派仅仅置备了 64 GB 的 sdcard 做为系统盘以及存储. 这点点空间实在是捉襟见肘, 更不用说后面还要跑一些 BT 下载, 私有云盘之类的应用了. 恰好家里有一块闲置的 3.5 寸 2T 硬盘, 所以就用它来做为 k8s 的存储空间也能算物尽启用吧. 当然如果大家家里有 NAS 设备, 比如群晖, 也完全可以利用其做为 NFS 服务器. 而且因为 NAS 一般都有多硬盘 RAID 方案, 所以数据的存放也更加可靠.

这块硬盘是放置在 ORICO 的硬盘盒中, 硬盘盒自带外接电源而且支持 USB 3.0. 这里遇到一个小插曲, 由于搞错了节点的排序, 所以将硬盘插错了节点… 就这个低级错误白白浪费了几个小时的时间!!! 不过其间也发现了树莓派外接 USB 硬盘的一个坑 - 由于树莓派的 usb 口供电有限, 似乎不少人都遇到无自带电源的 usb 硬盘在树莓派里无法识别. 不过好在我这块是外置电源的硬盘盒, 所以把硬盘插入到正确的节点后树莓派可以正常识别硬盘设备. 所以接下来我们就来研究下如何将这块外接 USB 硬盘变为 k8s 的存储资源.

准备工作

我们大致的计划是在 k8s 运行一个 nfs 服务器的 pod, 这个 pod 会让其固定在 node03 上并将外接硬盘 mount 的目录做为 NFS 存储资源发布到集群中供其他 pod 使用.

这块硬盘已经被格式化为 ext4 并 mount 到 /mnt/data 这个目录. 这里就不再赘述关于如何格式化磁盘并挂载的细节, 以下是目前磁盘的挂载路径情况:

1
2
3
4
5
6
7
8
9
10
$ blkid |grep sda
/dev/sda: UUID="adc0b8fe-2b19-4954-99bc-069e7a3461c2" TYPE="ext4"

$ cat /etc/fstab
LABEL=writable / ext4 defaults 0 1
LABEL=system-boot /boot/firmware vfat defaults 0 1
UUID=adc0b8fe-2b19-4954-99bc-069e7a3461c2 /mnt/data ext4 defaults 0 1

$ df -h | grep data
/dev/sda 1.8T 6.0G 1.7T 1% /mnt/data

准备 PV

在 k8s 中一个 pod 可以挂载外部存储的方法有很多, 但是比较好的做法是通过 PV 和 PVC 进行挂载. 对这两个概念不熟悉的同学先不用担心, 我们先来了解下为 k8s 中的 pod 提供存储空间需要考虑到的问题. 首先一个 pod 的生命周期非常短暂, 但是 pod 所产生的数据可能需要持久化保存. 比如我们后面会展现的一个例子是在 k8s 中运行一个 BT下载和网盘的服务. 我们希望这个 pod 停止运行后其下载的数据是可以在硬盘上的. 而且运行下载服务的 pod 可能会跑在 k8s 的任意一个节点上, 所以如何让其”活着的时候”有存储用, 而”消亡了后”之前下载的数据又得到了保存就是一个很实际的问题. PV 和 PVC 就能很好的满足我们的需求.

PV (Persistent Volume) 是全局级别的预挂载空间. 它支持与很多类型存储的对接, 比如 NFS, ISCSI, CephFS 等等. 我们可以把 PV 理解为一个在 k8s 世界中提供存储资源的大仓库, 比如你可以有很多个不同类型的 PV; 而 pod 在启动时通过声明 PVC 来获取这些 PV 资源的分配. 我们来看一个实际的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: v1
kind: Namespace
metadata:
name: storage
labels:
app: storage
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv
namespace: storage
spec:
capacity:
storage: 1500Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/data/
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: hdd
operator: In
values:
- enabled

为了后期管理的方便我们把这个NFS 服务相关的 pod, service, pv, pvc 都指派到同一个的 namespace - storage 中:

  • 这个 pv 资源我们取名为local-pv并分配 1500 GB 的空间
  • 这个 PV 资源的访问模式为ReadWriteOnce: 这个存储卷可以被一个节点以读写方式挂载
  • persistentVolumeReclaimPolicy 里指定的是回收策略, 这里我们使用 Retain 表示手动回收, 也就是说如果 pod 消亡, 其上的数据得以保留.
  • storageClassName 指定这个 PV 在 local-storage 这个逻辑组中, 主要是为了后面 PVC 可以直接引用这个 PV
  • local: 节点上挂载的本地存储设备. 路径是我们移动硬盘挂载在 node03 上的 /mnt/data
  • nodeAffinity 指定这个 PV 所在的节点. 因为我们的移动硬盘连接在node03上, 所以这个 PV 资源会绑定在 node03. 我们之前为 node03 打上了 hdd=enabled 的 label

总结一下: 我们在 node03 创建了一个 pv. 其使用本地目录空间 /mnt/data (实际是外接usb硬盘盒) 做为存储空间; 分配大小为 1500 GB 并且其上的数据只能由管理员手动删除(Retain)!

准备 PVC

PVC (Persistent Volume Claim) 是对于存储空间的请求声明. 不过这里需要注意的是 PVC 属于名称空间级别. 也就是说 PVC 必须和调用它的 pod 处于同一个 namespace 中. 我们来看下 PVC 的配置:

1
2
3
4
5
6
7
8
9
10
11
12
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: local-claim
namespace: storage
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 1500Gi
  • 这个 pvc 我们取名为local-claim 并将上面 PV 的所有空间 1500 GB 分配给它.
  • accessModes 使用和 pv 一致的 ReadWriteOnce
  • storageClassName 中指定与上述 pv 相同的 local-storage 实现与 pv 绑定.

准备 nfs pod

我们只需要完成 nfs 服务 pod 资源的准备并在其调用上面的 pvc 即可. nfs 服务器的 deployment 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-server
namespace: storage
labels:
app: nfs-server
spec:
replicas: 1
selector:
matchLabels:
app: nfs-server
template:
metadata:
labels:
app: nfs-server
name: nfs-server
spec:
containers:
- name: nfs-server
image: itsthenetwork/nfs-server-alpine:latest-arm
env:
- name: SHARED_DIRECTORY
value: /exports
ports:
- name: nfs
containerPort: 2049
- name: mountd
containerPort: 20048
- name: rpcbind
containerPort: 111
securityContext:
privileged: true
volumeMounts:
- mountPath: /exports
name: mypvc
volumes:
- name: mypvc
persistentVolumeClaim:
claimName: local-claim
nodeSelector:
hdd: enabled
  • 我们 nfs 应用 pod 名字为nfs-server 并打上 app: nfs-server 的标签. 它运行在和 pv, pvc 相同的 namespace 中
  • 将带有app: nfs-server标签的 pod 绑定到这个 deployment 资源里
  • env 中传入环境变量指定 nfs 服务器将存储挂载到自己的 /exports 目录下
  • ports 中暴露服务端口
  • securityContext 比较重要. 这个docker image 需要这样设置才能在 k8s 中工作
  • volumeMounts: 将 pod 内的 /exports 目录取名 mypvc 做为挂载点
  • volumes: 把 local-claim 这个 pvc 挂载到 mypvc 这个 pod 中的挂载点上
  • nodeSelector再次指定这个pod 需要运行在 node03 上

Service 准备

最后我们准备 service 资源将这个 nfs 应用发布出去. 我们为这个 service 分配了固定 IP: 10.96.0.100. 同时这个service 资源绑定到有app: nfs-server的pod 上.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kind: Service
apiVersion: v1
metadata:
name: nfs-server
namespace: storage
spec:
ports:
- name: nfs
port: 2049
- name: mountd
port: 20048
- name: rpcbind
port: 111
clusterIP: 10.96.0.100 this.
selector:
app: nfs-server

这样我们就完成了这个 nfs pod 的配置. 如果要在 k8s 上运行可以将上述配置放入同一个 yaml 文件中然后运行:

1
kubectl apply -f nfs.yml

另外请确保所有的 k8s 节点上都安装有 nfs 的client 程序. 因为后续的 pod 其实都是通过 mount 来调用这个 nfs 共享出的存储资源. 安装 nfs 客户端可以在各台node 主机上运行:

1
sudo apt update &&& sudo apt-get install nfs-common -y

小结

为 k8s 添加了存储资源后, 我们后续就可以方便的为 pod 分配空间并且数据会存放在 nfs 上进行持久存储. 我们下一步就是搭建一个家用的文件下载服务器以及私有云盘. 敬请期待把!