Kubernetes关于StorageClass的应用——Flare

前言

最近在折腾NAS和虚拟机的时候发现一个问题,部署的应用和服务太多了,有点记不住地址和端口了,所以需要一个导航页来展示这一切。

本想自己开发一个单页面应用呢,但是想到vue或react,对于前端的东西能不碰就不想碰,所以找到了一个开源的项目——flare。恰巧搭建了一个Kubernetes集群,正好练习一下。

技术点

Storage Class

什么是Storage Class

Kubernetes提供了一套可以自动创建PV的机制,即:Dynamic Provisioning。而这个机制的核心在于StorageClass这个API对象。

StorageClass对象会定义下面两部分内容:

  1. PV的属性。比如,存储类型,Volume的大小等。
  2. 创建这种PV需要用到的存储插件,即存储制备器。

有了这两个信息之后,Kubernetes就能够根据用户提交的PVC,找到一个对应的StorageClass,之后Kubernetes就会调用该StorageClass声明的存储插件,进而创建出需要的PV。

为什么用Storage Class

在一个大规模的Kubernetes集群里,可能有成千上万个PVC,这就意味着运维人员必须实现创建出这个多个PV,此外,随着项目的需要,会有新的PVC不断被提交,那么运维人员就需要不断的添加新的,满足要求的PV,否则新的Pod就会因为PVC绑定不到PV而导致创建失败。而且通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求。

而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。

pvc是无法直接去向nfs-client-provisioner申请使用的存储空间的,这时,就需要通过SC这个资源对象去申请了,SC的根本作用就是根据pvc定义的来动态创建pv,不仅节省了我们管理员的时间,还可以封装不同类型的存储供pvc选用。

Storage Class资源

每个sc都包含以下三个重要的字段,这些字段会在sc需要动态分配pv时会使用到:

  • Provisioner(供给方):提供了存储资源的存储系统。
  • ReclaimPolicy:pv的回收策略,可用的值有Delete(默认)和Retiain
  • Parameters(参数):存储类使用参数描述要关联到存储卷。

每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV。 该字段必须指定。

插件 内置制备器 配置例子
AWSElasticBlockStore AWS EBS
AzureFile Azure File
AzureDisk Azure Disk
CephFS - -
Cinder OpenStack Cinder
FC - -
FlexVolume - -
Flocker -
GCEPersistentDisk GCE PD
Glusterfs Glusterfs
iSCSI - -
Quobyte Quobyte
NFS - -
RBD Ceph RBD
VsphereVolume vSphere
PortworxVolume Portworx Volume
ScaleIO ScaleIO
StorageOS StorageOS
Local - Local

回收策略,由 StorageClass 动态创建的 PersistentVolume 会在类的 reclaimPolicy 字段中指定回收策略,可以是 Delete 或者 Retain。如果 StorageClass 对象被创建时没有指定 reclaimPolicy,它将默认为 Delete。

flare

轻量、快速、美观的个人导航页面,适用于 HomeLab 或其他注重私密的场景。
支持 x86 以及常见的 ARM (ARM32v6、ARM32v7、ARM64v8)设备,应用资源消耗非常低:

  • CPU: < 1%
  • MEM: < 30M
  • Docker Image: < 10M

使用方式很简单:

  1. 下载Git仓库
  2. 使用docker启动,注意挂载/app目录

部署

创建Storage class

要使用 StorageClass,我们就得安装对应的自动配置程序,比如我们这里存储后端使用的是 nfs,那么我们就需要使用到一个 nfs-client 的自动配置程序,我们也叫它 Provisioner(制备器),这个程序使用我们已经配置好的 nfs 服务器,来自动创建持久卷,也就是自动帮我们创建 PV。

  1. 自动创建的 PV 以${namespace}-${pvcName}-${pvName}这样的命名格式创建在 NFS 服务器上的共享数据目录中
  2. 而当这个 PV 被回收后会以archieved-${namespace}-${pvcName}-${pvName}这样的命名格式存在 NFS 服务器上。

1669884658926.png

注意在所有节点安装yum -y install nfs-utils.

创建rbac权限:

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
43
44
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-provisioner-runner
namespace: default
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get","create","list", "watch","update"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["nfs-provisioner"]
verbs: ["use"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io

执行yaml文件。我们新建的一个名为nfs-provisioner的ServiceAccount,然后绑定了一个名为nfs-provisioner-runner的ClusterRole,而该ClusterRole声明了一些权限,其中就包括对pv的增,删,改,查等权限,所以我们可以利用该serviceAccount来自动创建pv。

创建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
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccount: nfs-provisioner
containers:
- name: nfs-client-provisioner
image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: nfs-deploy #供给方的名称(自定义)
- name: NFS_SERVER
value: 192.168.31.98 #nfs服务器的ip地址
- name: NFS_PATH
value: /mnt/free/nfs #nfs共享的目录
volumes:
- name: nfs-client-root
nfs:
server: 192.168.31.98
path: /mnt/free/nfs

执行yaml文件,检查pod

1
2
3
[root@master-51 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-7d9b557c55-9vs77 1/1 Running 0 16m

创建storage class:

1
2
3
4
5
6
7
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: stateful-nfs
namespace: default
provisioner: nfs-deploy
reclaimPolicy: Retain

我们声明了一个名为statefu-nfs的sc对象,注意下面的provisioner字段对应的值一定要和上面的nfs的Deployment下面的PROVISIONER_NAME这个环境变量的值一样。

执行yaml文件,检查该资源对象。

1
2
3
[root@master-51 ~]# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
stateful-nfs nfs-deploy Retain Immediate false 1m

动态创建PVC

1
2
3
4
5
6
7
8
9
10
11
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: flare
spec:
accessModes:
- ReadWriteMany
storageClassName: stateful-nfs
resources:
requests:
storage: 100Mi

执行yaml文件,检查是否创建了对应的PVC。

1
2
3
[root@master-51 flare]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
flare Bound pvc-ed851458-fe37-4095-a6e5-4838e46a58d0 100Mi RWX stateful-nfs 1m

部署flare

编辑yaml文件:

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
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: flare
name: flare
spec:
replicas: 1
selector:
matchLabels:
app: flare
template:
metadata:
labels:
app: flare
spec:
containers:
- image: soulteary/flare
name: flare
volumeMounts:
- name: flare-app
mountPath: /app
volumes:
- name: flare-app
persistentVolumeClaim:
claimName: flare

执行yaml文件,检查pod文件nfs。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@master-51 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
flare-5c57955b54-rj6hr 1/1 Running 0 2m
nfs-client-provisioner-7d9b557c55-9vs77 1/1 Running 0 7m

# 检查nfs目录

root@truenas[...-ed851458-fe37-4095-a6e5-4838e46a58d0]# ll
total 39
drwxrwxrwx 2 nobody 5 Dec 1 14:55 ./
drwxrwxrwx 3 nfs 6 Dec 1 14:42 ../
-rwxr-xr-x 1 nobody 418 Dec 1 15:51 apps.yml*
-rwxr-xr-x 1 nobody 733 Dec 1 15:51 bookmarks.yml*
-rwxr-xr-x 1 nobody 467 Dec 1 14:58 config.yml*
root@truenas[...-ed851458-fe37-4095-a6e5-4838e46a58d0]# pwd
/mnt/free/nfs/default-flare-pvc-ed851458-fe37-4095-a6e5-4838e46a58d0

临时使用nodeport的方式访问:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: flare
spec:
type: NodePort
ports:
- port: 35005
targetPort: 5005
nodePort: 35005
selector:
app: flare

之后可以考虑使用ingress或者其他方式。

访问页面,很美好:

1669886114176.png

总结

遇到的问题:

问题 1.:unexpected error getting claim reference: selfLink was empty, can’t make reference
解决 Kubernetes 1.20及以后版本禁用了 selfLink 所致。修改 /etc/kubernetes/manifests/kube-apiserver.yaml,添加 - –feature-gates=RemoveSelfLink=false 后重新部署。

问题 2.:waiting for a volume to be created, either by external provisioner “fuseim.pri/ifs” or manually created by system administrator
解决 Kubernetes 1.20及以后版本禁用了 selfLink 所致。修改 /etc/kubernetes/manifests/kube-apiserver.yaml,添加 - –feature-gates=RemoveSelfLink=false 后重新部署。

参考

------ 本文结束 ------

版权声明

Medivh's Notes by Medivh is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Medivh创作并维护的Medivh's Notes博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Medivh 博客( http://www.mknight.cn ),版权所有,侵权必究。