一、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