一、PV和PVC的引入

Volume 提供了非常好的数据持久化方案,不过在可管理性上还有不足。

要使用 Volume,Pod 必须事先知道如下信息:

  • 当前 Volume 来自哪一台机器。
  • EBS Volume 已经提前创建,并且知道确切的 volume-id

Pod 通常是由应用的开发人员维护,而 Volume 则通常是由存储系统的管理员维护。开发人员要获得上面的信息:

要么询问管理员。
要么自己就是管理员。

这样就带来一个管理上的问题:应用开发人员和系统管理员的职责耦合在一起了。如果系统规模较小或者对于开发环境这样的情况还可以接受。但当集群规模变大,特别是对于生成环境,考虑到效率和安全性,这就成了必须要解决的问题。

Kubernetes 给出的解决方案是 PersistentVolume (PV)和 PersistentVolumeClaim(PVC)。

PersistentVolume (PV) 是外部存储系统中的一块存储空间,由管理员创建和维护。与 Volume 一样,PV 具有持久性,生命周期独立于 Pod。

PersistentVolumeClaim (PVC) 是对 PV 的申请 (Claim)。PVC 通常由普通用户创建和维护。需要为 Pod 分配存储资源时,用户可以创建一个 PVC,指明存储资源的容量大小和访问模式(比如只读)等信息,Kubernetes 会查找并提供满足条件的 PV。

有了 PersistentVolumeClaim,用户只需要告诉 Kubernetes 需要什么样的存储资源,而不必关心真正的空间从哪里分配,如何访问等底层细节信息。这些 Storage Provider 的底层信息交给管理员来处理,只有管理员才应该关心创建 PersistentVolume 的细节信息。

二、通过NFS实现持久化存储

1、配置nfs

需要安装

k8s-master:nfs-server

k8s-node1:nfs-client

k8s-node2:nfs-client

所有节点安装nfs

yum install -y nfs-common nfs-utils

在master节点创建共享目录

mkdir /nfsdatachmod 666 /nfsdata

编辑exports文件

cat /etc/exports/nfsdata *(rw,no_root_squash,no_all_squash,sync)

启动rpc和nfs(注意顺序)

systemctl start rpcbindsystemctl start nfs

作为准备工作,我们已经在 k8s-master 节点上搭建了一个 NFS 服务器,目录为 /nfsdata:

2、创建PV

下面创建一个 PV mypv,配置文件 nfs-pv.yml 如下:

vim nfs-pv1.ymlapiVersion: v1kind: PersistentVolumemetadata:name: mypvspec:capacity:storage: 1GiaccessModes:- ReadWriteOncepersistentVolumeReclaimPolicy: RecyclestorageClassName: nfsnfs:path: /nfsdataserver: 10.128.1.117

① capacity 指定 PV 的容量为 1G。

② accessModes 指定访问模式为 ReadWriteOnce,支持的访问模式有:

ReadWriteOnce:PV 能以 read-write 模式 mount 到单个节点。
ReadOnlyMany:PV 能以 read-only 模式 mount 到多个节点。
ReadWriteMany :PV 能以 read-write 模式 mount 到多个节点。

③ persistentVolumeReclaimPolicy 指定当 PV 的回收策略为 Recycle,支持的策略有:

Retain: 需要管理员手工回收。
Recycle:清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*。
Delete: 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、- OpenStack Cinder Volume 等。

④ storageClassName 指定 PV 的 class 为 nfs。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。

⑤ 指定 PV 在 NFS 服务器上对应的目录。

创建 mypv:

kubectl apply -f nfs-pv.yml

 STATUS 为 Available,表示 mypv 就绪,可以被 PVC 申请。

3、创建PVC

接下来创建 PVC mypvc,配置文件 nfs-pvc.yml 如下:

vi nfs-pvc.ymlapiVersion: v1kind: PersistentVolumeClaimmetadata:name: mypvcspec:accessModes:- ReadWriteOnceresources:requests:storage: 1GistorageClassName: nfs

部署pvc

kubectl apply -f nfs-pvc.yml

 4、创建pod

上面已经创建好了pv和pvc,pod中直接使用这个pvc即可

vi pod.ymlapiVersion: v1kind: Podmetadata:name: mypodspec:containers:- name: mypodimage: busyboxargs:- /bin/sh- -c- sleep 30000volumeMounts:- mountPath: "/mydata"name: mydatavolumes:- name: mydatapersistentVolumeClaim:claimName: mypvc

与使用普通 Volume 的格式类似,在 volumes 中通过 persistentVolumeClaim 指定使用 mypvc 申请的 Volume。

通过命令创建mypod:

kubectl apply -f pod.yml

在这里,可以尝试在任何一方删除文件,文件在两端都会消失;

三、PV的回收

当 PV 不再需要时,可通过删除 PVC 回收。未删除pvc之前 pv的状态是Bound

删除pod

kubectl delete pod mypod

删除pvc

kubectl delete pvc mypvc

再次查看pv的状态

kubectl get pv

删除pvc之后pv的状态变为Available,,此时解除绑定后则可以被新的 PVC 申请。

/nfsdata文件中的文件被删除了

因为 PV 的回收策略设置为 Recycle,所以数据会被清除,

但这可能不是我们想要的结果。如果我们希望保留数据,可以将策略设置为 Retain

虽然 mypv 中的数据得到了保留,但其 PV 状态会一直处于 Released,不能被其他 PVC 申请。为了重新使用存储资源,可以删除并重新创建 mypv。删除操作只是删除了 PV 对象,存储空间中的数据并不会被删除。

PV 还支持 Delete 的回收策略,会删除 PV 在 Storage Provider 上对应存储空间。NFS 的 PV 不支持 Delete,支持 Delete 的 Provider 有 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。

四、PV的动态供给

前面的例子中,我们提前创建了 PV,然后通过 PVC 申请 PV 并在 Pod 中使用,这种方式叫做静态供给(Static Provision)。

与之对应的是动态供给(Dynamical Provision),即如果没有满足 PVC 条件的 PV,会动态创建 PV。相比静态供给,动态供给有明显的优势:不需要提前创建 PV,减少了管理员的工作量,效率高。

基于NFS的PV动态供给(StorageClass)

静态:pod–>pvc–>pv

动态:pod –>pvc–>storageclass

去官网下载三个文件

这三个文件去网上下载 https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client/deploy 

使用脚本批量下载:

for file in class.yaml deployment.yaml rbac.yaml; do wget https://raw.githubusercontent.com/kubernetes-incubator/external-storage/master/nfs-client/deploy/$file ; done

其中deployment.yaml需要修改一下挂载的地址,目录,镜像版本

apiVersion: apps/v1kind: Deploymentmetadata:name: nfs-client-provisionerlabels:app: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: defaultspec:replicas: 1strategy:type: Recreateselector:matchLabels:app: nfs-client-provisionertemplate:metadata:labels:app: nfs-client-provisionerspec:serviceAccountName: nfs-client-provisionercontainers:- name: nfs-client-provisionerimage: gcr.io/k8s-staging-sig-storage/nfs-subdir-external-provisioner:v4.0.0 #这里需要修改,因为最新版本存在 SelfLink 问题volumeMounts:- name: nfs-client-rootmountPath: /persistentvolumesenv:- name: PROVISIONER_NAMEvalue: fuseim.pri/ifs- name: NFS_SERVERvalue: 10.128.1.117 #这里需要修改- name: NFS_PATHvalue: /nfsdata #这里需要修改volumes:- name: nfs-client-rootnfs:server: 10.128.1.117 #这里需要修改path: /nfsdata #这里需要修改

然后分别去应用这三个文件

kubectl create -f rbac.yamlkubectl create -f class.yamlkubectl create -f deployment.yaml

创建pod进行测试 

vi nginx.yamlapiVersion: v1kind: Servicemetadata:name: nginxlabels:app: nginxspec:type: NodePortports:- port: 80nodePort: 30012name: webselector:app: nginx---apiVersion: apps/v1kind: StatefulSetmetadata:name: webspec:selector:matchLabels:app: nginxserviceName: "nginx"replicas: 3template:metadata:labels:app: nginxspec:terminationGracePeriodSeconds: 10containers:- name: nginximage: wangyanglinux/myapp:v1 ports:- containerPort: 80name: webvolumeMounts:- name: wwwmountPath: /usr/share/nginx/htmlvolumeClaimTemplates:- metadata:name: wwwspec:accessModes: [ "ReadWriteMany" ]storageClassName: "managed-nfs-storage"resources:requests:storage: 1Gi

查看pv和pvc

四、K3S/K8S 中动态创建 PVC 时 SelfLink 问题解决

在部署 statefulset 类型的工作负载时,动态创建 PV/PVC 是一种比较常用的配置方式,动态创建 PV/PVC 的方法基本如下:

  • 1、创建自己的 StorageClass 备用。
  • 2、创建 statefulset ,在 yaml 文件的 volumeClaimTemplates 块,添加 StorageClass 的名字。

一直启动不起来,查看 pvc 和 pods 信息如下:

  • 1、PVC 一直处于 pending 状态;
  • 2、mysql pods 显示:0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims.

原因分析

从上边的现象来看,是 PVC 没有创建成功,动态 PVC 中,是 provisioner 中来负责创建,查看其日志,看到如下错误信息:

I0214 10:22:35.436913 1 controller.go:1068] scheduleOperation[provision-mysql-sts/mysql-pvc-mysql-0[ac333031-e705-48d9-8180-4d2d583bb559]]E0214 10:22:35.444757 1 controller.go:766] Unexpected error getting claim reference to claim "mysql-sts/mysql-pvc-mysql-0": selfLink was empty, can't make reference

Google 之后,找到主要原因是,官方在 k8s 1.20 中基于对性能和统一apiserver调用方式的初衷,移除了对 SelfLink 的支持,而 nfs-provisioner 需要 SelfLink 该项功能。具体计划和原因可查看这个issue[2] 和 KEP[3]

K3S 为兼容 K8S 应该也继承了该项修改,按 K8S 的方式修改测试了下,完美解决。

解决方案

解决问题主要有下边两种方式:

1、修改 apiserver 的配置文件,重新启用 SelfLink 功能。针对 K8S,可添加如下配置:

# /etc/kubernetes/manifests/kube-apiserver.yamlspec:containers:- command:- kube-apiserver...- --feature-gates=RemoveSelfLink=false # 增加

K3S 中没有 apiserver 的配置文件,可通过 systemd 的启动文件添加该参数,如下:

# /etc/systemd/system/k3s.serviceExecStart=/usr/local/bin/k3s \server \...'--kube-apiserver-arg' \ # 新增'feature-gates=RemoveSelfLink=false' \# 新增

若为新安装,可如下启用:

$ curl -sfL https://get.k3s.io | sh -s - --kube-apiserver-arg "feature-gates=RemoveSelfLink=false"

2、使用新的不基于 SelfLink 功能的 provisioner 镜像,重新创建 provisioner 容器。

若你能科学上网,可使用这个镜像:

gcr.io/k8s-staging-sig-storage/nfs-subdir-external-provisioner:v4.0.0

国内可使用这个镜像【若不可用自己查找】:

registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0