Kubernetes(k8s)存储管理之数据卷volumes(五):动态制备-存储类StorageClass
一.系统环境
服务器版本 | docker软件版本 | Kubernetes(k8s)集群版本 | CPU架构 |
---|---|---|---|
CentOS Linux release 7.4.1708 (Core) | Docker version 20.10.12 | v1.21.9 | x86_64 |
Kubernetes集群架构:k8scloude1作为master节点,k8scloude2,k8scloude3作为worker节点
服务器 | 操作系统版本 | CPU架构 | 进程 | 功能描述 |
---|---|---|---|---|
k8scloude1/192.168.110.130 | CentOS Linux release 7.4.1708 (Core) | x86_64 | docker,kube-apiserver,etcd,kube-scheduler,kube-controller-manager,kubelet,kube-proxy,coredns,calico | k8s master节点 |
k8scloude2/192.168.110.129 | CentOS Linux release 7.4.1708 (Core) | x86_64 | docker,kubelet,kube-proxy,calico | k8s worker节点 |
k8scloude3/192.168.110.128 | CentOS Linux release 7.4.1708 (Core) | x86_64 | docker,kubelet,kube-proxy,calico | k8s worker节点 |
二.前言
Kubernetes(k8s)数据卷volumes类型众多,本文介绍使用存储类StorageClass动态制备持久卷Persistent Volume,关于静态制备持久卷Persistent Volume,请查看博客《Kubernetes(k8s)存储管理之数据卷volumes(四):持久卷Persistent Volume》。
使用数据卷volumes的前提是已经有一套可以正常运行的Kubernetes集群,关于Kubernetes(k8s)集群的安装部署,可以查看博客《Centos7 安装部署Kubernetes(k8s)集群》。
三.静态制备和动态制备
创建持久卷Persistent Volume有两种方法:静态制备和动态制备 。
静态制备(集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息, 并且对集群用户可用。PV 卷对象存在于 Kubernetes API 中,可供用户消费使用),简言之就是需要先创建pv,然后才能创建PVC。
动态制备 (如果管理员所创建的所有静态 PV 卷都无法与用户的 PersistentVolumeClaim 匹配, 集群可以尝试为该 PVC 申领动态制备一个存储卷。 这一制备操作是基于 StorageClass 来实现的:PVC 申领必须请求某个 存储类, 同时集群管理员必须已经创建并配置了该类,这样动态制备卷的动作才会发生。 如果 PVC 申领指定存储类为 "",则相当于为自身禁止使用动态制备的卷)。
为了基于存储类完成动态的存储制备,集群管理员需要在 API 服务器上启用 DefaultStorageClass 准入控制器。 举例而言,可以通过保证 DefaultStorageClass 出现在 API 服务器组件的 --enable-admission-plugins 标志值中实现这点;该标志的值可以是逗号分隔的有序列表。
简言之就是我们可以使用存储类StorageClass实现动态制备,不需要提前创建PV,只要创建好存储类StorageClass,用户创建PVC之后,存储类StorageClass会自动创建一个PV和PVC进行绑定。
四.存储类StorageClass
4.1 存储类StorageClass概览
StorageClass 为管理员提供了描述存储 "类" 的方法。 不同的类型可能会映射到不同的服务质量等级或备份策略,或是由集群管理员制定的任意策略。 Kubernetes 本身并不清楚各种类代表的什么。这个类的概念在其他存储系统中有时被称为 "配置文件"。
4.2 StorageClass 资源
每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段, 这些字段会在 StorageClass 需要动态制备 PersistentVolume 时会使用到。
存储制备器provisioner:每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV。 该字段必须指定。
回收策略reclaimPolicy:由 StorageClass 动态创建的 PersistentVolume 会在类的 reclaimPolicy 字段中指定回收策略,可以是 Delete 或者 Retain。如果 StorageClass 对象被创建时没有指定 reclaimPolicy,它将默认为 Delete。通过 StorageClass 手动创建并管理的 PersistentVolume 会使用它们被创建时指定的回收策略。
允许卷扩展allowVolumeExpansion:PersistentVolume 可以配置为可扩展。将此功能设置为 true 时,允许用户通过编辑相应的 PVC 对象来调整卷大小。此功能仅可用于扩容卷,不能用于缩小卷。
挂载选项mountOptions:由 StorageClass 动态创建的 PersistentVolume 将使用类中 mountOptions 字段指定的挂载选项。如果卷插件不支持挂载选项,却指定了挂载选项,则制备操作会失败。 挂载选项在 StorageClass 和 PV 上都不会做验证,如果其中一个挂载选项无效,那么这个 PV 挂载操作就会失败。
卷绑定模式volumeBindingMode:volumeBindingMode 字段控制了卷绑定和动态制备应该发生在什么时候。
- Immediate 模式:默认情况下,Immediate 模式表示一旦创建了 PersistentVolumeClaim 也就完成了卷绑定和动态制备。 对于由于拓扑限制而非集群所有节点可达的存储后端,PersistentVolume 会在不知道 Pod 调度要求的情况下绑定或者制备。
- WaitForFirstConsumer 模式:集群管理员可以通过指定 WaitForFirstConsumer 模式来解决此问题。 该模式将延迟 PersistentVolume 的绑定和制备,直到使用该 PersistentVolumeClaim 的 Pod 被创建。 PersistentVolume 会根据 Pod 调度约束指定的拓扑来选择或制备。 这些包括但不限于资源需求、 节点筛选器、 Pod 亲和性和互斥性、 以及污点和容忍度。
注意:如果你选择使用 WaitForFirstConsumer,请不要在 Pod 规约中使用 nodeName 来指定节点亲和性。 如果在这种情况下使用 nodeName,Pod 将会绕过调度程序,PVC 将停留在 pending 状态。相反,在这种情况下,你可以使用节点选择器nodeSelector指定主机名。
参数parameters:Storage Classes 的参数描述了存储类的卷。取决于制备器,可以接受不同的参数。 例如,参数 type 的值 io1 和参数 iopsPerGB 特定于 EBS PV。 当参数被省略时,会使用默认值。一个 StorageClass 最多可以定义 512 个参数。这些参数对象的总长度不能超过 256 KiB, 包括参数的键和值。
五.创建存储类StorageClass
5.1 配置NFS服务端以及共享目录
在一台机器上安装NFS服务端,k8s的两个worker安装NFS客户端。
etcd1机器作为NFS的服务端,安装NFS。
[root@etcd1 ~]# yum -y install nfs-utils
[root@etcd1 ~]# rpm -qa | grep nfs
libnfsidmap-0.25-19.el7.x86_64
nfs-utils-1.3.0-0.68.el7.2.x86_64
启动NFS
#使nfs开机自启动并现在就启动
[root@etcd1 ~]# systemctl enable nfs-server --now
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.
#查看nfs状态
[root@etcd1 ~]# systemctl status nfs-server
● nfs-server.service - NFS server and services
Loaded: loaded (/usr/lib/systemd/system/nfs-server.service; enabled; vendor preset: disabled)
Active: active (exited) since 二 2022-01-18 17:24:24 CST; 8s ago
Process: 1469 ExecStartPost=/bin/sh -c if systemctl -q is-active gssproxy; then systemctl reload gssproxy ; fi (code=exited, status=0/SUCCESS)
Process: 1453 ExecStart=/usr/sbin/rpc.nfsd $RPCNFSDARGS (code=exited, status=0/SUCCESS)
Process: 1451 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
Main PID: 1453 (code=exited, status=0/SUCCESS)
CGroup: /system.slice/nfs-server.service
1月 18 17:24:24 etcd1 systemd[1]: Starting NFS server and services...
1月 18 17:24:24 etcd1 systemd[1]: Started NFS server and services.
先在NFS服务端创建/dongtaijuandongying,并把目录/dongtaijuandongying共享出去
#在NFS服务端创建共享目录/dongtaijuandongying
[root@etcd1 ~]# mkdir /dongtaijuandongying
[root@etcd1 ~]# vim /etc/exports
#把/dongtaijuandongying目录共享出去
[root@etcd1 ~]# cat /etc/exports
/dongtaijuandongying *(rw,async,no_root_squash)
[root@etcd1 ~]# exportfs -arv
exporting *:/dongtaijuandongying
5.2 配置NFS客户端
在k8s集群的worker节点安装nfs的客户端
[root@k8scloude3 ~]# yum -y install nfs-utils
#安装nfs的客户端
[root@k8scloude2 ~]# yum -y install nfs-utils
查看etcd1(192.168.110.133)机器共享出来的目录是哪个?
[root@k8scloude2 ~]# showmount -e 192.168.110.133
Export list for 192.168.110.133:
/dongtaijuandongying *
5.3 配置StorageClass支持NFS
NFS类型的StorageClass没有内部制备器provisioner,但可以使用外部制备器。 也有第三方存储供应商提供自己的外部制备器。Kubernetes 不包含内部 NFS 驱动。你需要使用外部驱动为 NFS 创建 StorageClass。
由于k8s默认不支持nfs类型的StorageClass,需要修改参数然后自定义nfs StorageClass
[root@k8scloude1 volume]# vim /etc/kubernetes/manifests/kube-apiserver.yaml
#添加参数如下
[root@k8scloude1 volume]# cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep RemoveSelfLink
- --feature-gates=RemoveSelfLink=false
重启kubelet使配置生效
[root@k8scloude1 volume]# systemctl restart kubelet
[root@k8scloude1 volume]# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)
Drop-In: /usr/lib/systemd/system/kubelet.service.d
└─10-kubeadm.conf
Active: active (running) since 三 2022-01-19 18:11:09 CST; 6s ago
Docs: https://kubernetes.io/docs/
Main PID: 89887 (kubelet)
Memory: 37.4M
CGroup: /system.slice/kubelet.service
├─89887 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --network-plugin=cni --pod-in...
└─90152 [find]
下载git
[root@k8scloude1 volume]# yum -y install git
下载扩展存储卷yaml文件
[root@k8scloude1 volume]# git clone https://github.com/kubernetes-incubator/external-storage.git
[root@k8scloude1 volume]# ls
external-storage
[root@k8scloude1 volume]# cd external-storage/nfs-client/
[root@k8scloude1 nfs-client]# cd deploy/
[root@k8scloude1 deploy]# ls
class.yaml deployment-arm.yaml deployment.yaml objects rbac.yaml test-claim.yaml test-pod.yaml
因为deployment.yaml文件里需要NFS制备器镜像:nfs-client-provisioner:latest镜像,可以提前下载下来镜像
[root@k8scloude1 deploy]# cat deployment.yaml | grep image
image: quay.io/external_storage/nfs-client-provisioner:latest
#在master和worker上都下载好镜像
[root@k8scloude1 deploy]# docker pull quay.io/external_storage/nfs-client-provisioner:latest
[root@k8scloude2 ~]# docker pull quay.io/external_storage/nfs-client-provisioner:latest
[root@k8scloude3 ~]# docker pull quay.io/external_storage/nfs-client-provisioner:latest
[root@k8scloude1 deploy]# docker images | grep nfs-client-provisioner
quay.io/external_storage/nfs-client-provisioner latest 16d2f904b0d8 3 years ago 45.5MB
安装NFS制备器
[root@k8scloude1 deploy]# pwd
/root/volume/external-storage/nfs-client/deploy
#当前的namespace为:volume
[root@k8scloude1 deploy]# kubens
default
kube-node-lease
kube-public
kube-system
ns1
ns2
pod
volume
[root@k8scloude1 deploy]# ls
class.yaml deployment-arm.yaml deployment.yaml objects rbac.yaml test-claim.yaml test-pod.yaml
#修改namespace
[root@k8scloude1 deploy]# sed -i 's/namespace: default/namespace: volume/g' rbac.yaml
[root@k8scloude1 deploy]# kubectl apply -f rbac.yaml
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
[root@k8scloude1 deploy]# grep image deployment.yaml
image: quay.io/external_storage/nfs-client-provisioner:latest
[root@k8scloude1 deploy]# vim deployment.yaml
#修改NFS的服务器地址和共享目录,设置镜像下载策略为: imagePullPolicy: IfNotPresent:本地有镜像就不下载
[root@k8scloude1 deploy]# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed,命名空间要修改
namespace: volume
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
#镜像下载策略要修改
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
#NFS服务器地址
- name: NFS_SERVER
value: 192.168.110.133
#NFS共享目录
- name: NFS_PATH
value: /dongtaijuandongying
volumes:
- name: nfs-client-root
nfs:
#NFS服务器地址
server: 192.168.110.133
#NFS共享目录
path: /dongtaijuandongying
[root@k8scloude1 deploy]# kubectl apply -f deployment.yaml
deployment.apps/nfs-client-provisioner created
#可以看到nfs制备器nfs-client-provisioner-76c576954d-5x7t2
[root@k8scloude1 deploy]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-76c576954d-5x7t2 1/1 Running 0 17s 10.244.112.129 k8scloude2 <none> <none>
5.4 创建StorageClass
查看StorageClass
[root@k8scloude1 deploy]# kubectl get sc
No resources found
配置StorageClass
[root@k8scloude1 deploy]# vim class.yaml
#archiveOnDelete参数表示:If it exists and has a false value, delete the directory. if onDelete exists, archiveOnDelete will be ignored.
[root@k8scloude1 deploy]# cat class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false"
创建storageclass
[root@k8scloude1 deploy]# kubectl apply -f class.yaml
storageclass.storage.k8s.io/managed-nfs-storage created
[root@k8scloude1 deploy]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage fuseim.pri/ifs Delete Immediate false 10s
5.5 创建持久卷申领PersistentVolumeClaim(PVC)
现在没有pv和PVC
[root@k8scloude1 deploy]# kubectl get pv
No resources found
[root@k8scloude1 deploy]# kubectl get pvc
No resources found in volume namespace.
配置PVC
[root@k8scloude1 deploy]# vim pvc1.yaml
#storageClassName要和刚才创建的storageClass一样
[root@k8scloude1 deploy]# cat pvc1.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 1Gi
storageClassName: managed-nfs-storage
创建PVC
[root@k8scloude1 deploy]# kubectl apply -f pvc1.yaml
persistentvolumeclaim/mypvc created
[root@k8scloude1 deploy]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc Bound pvc-4b73eeaa-1530-4599-b2c8-6057bb16658a 1Gi RWO managed-nfs-storage 6s
创建PVC之后,pv也自动创建了
[root@k8scloude1 deploy]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-4b73eeaa-1530-4599-b2c8-6057bb16658a 1Gi RWO Delete Bound volume/mypvc managed-nfs-storage 9s
查看pv的详细信息
[root@k8scloude1 deploy]# kubectl describe pv pvc-4b73eeaa-1530-4599-b2c8-6057bb16658a
Name: pvc-4b73eeaa-1530-4599-b2c8-6057bb16658a
Labels: <none>
Annotations: pv.kubernetes.io/provisioned-by: fuseim.pri/ifs
Finalizers: [kubernetes.io/pv-protection]
StorageClass: managed-nfs-storage
Status: Bound
Claim: volume/mypvc
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 1Gi
Node Affinity: <none>
Message:
Source:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 192.168.110.133
Path: /dongtaijuandongying/volume-mypvc-pvc-4b73eeaa-1530-4599-b2c8-6057bb16658a
ReadOnly: false
Events: <none>
删除pvc,PV也自动删除
[root@k8scloude1 deploy]# kubectl delete pvc mypvc
persistentvolumeclaim "mypvc" deleted
[root@k8scloude1 deploy]# kubectl get pv
No resources found
[root@k8scloude1 deploy]# kubectl get pvc
No resources found in volume namespace.
重新创建PVC
[root@k8scloude1 deploy]# kubectl apply -f pvc1.yaml
persistentvolumeclaim/mypvc created
[root@k8scloude1 deploy]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc Bound pvc-7d17891d-f95a-417f-abf4-06f9e84dc82e 1Gi RWO managed-nfs-storage 6s
[root@k8scloude1 deploy]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-7d17891d-f95a-417f-abf4-06f9e84dc82e 1Gi RWO Delete Bound volume/mypvc managed-nfs-storage 9s
六.把卷挂载到pod
配置pod把PVC挂载到容器的/xx目录
[root@k8scloude1 deploy]# vim pvcpod.yaml
[root@k8scloude1 deploy]# cat pvcpod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pvcshare
name: pvcshare
spec:
#nodeName指定pod运行在k8scloude3节点
nodeName: k8scloude3
terminationGracePeriodSeconds: 0
volumes:
- name: v1
#卷类型为PVC
persistentVolumeClaim:
claimName: mypvc
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: h1
resources: {}
volumeMounts:
- name: v1
mountPath: /xx
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
创建pod
[root@k8scloude1 deploy]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-76c576954d-5x7t2 1/1 Running 0 15m
[root@k8scloude1 deploy]# kubectl apply -f pvcpod.yaml
pod/pvcshare created
[root@k8scloude1 deploy]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-76c576954d-5x7t2 1/1 Running 0 15m 10.244.112.129 k8scloude2 <none> <none>
pvcshare 1/1 Running 0 5s 10.244.251.195 k8scloude3 <none> <none>
进入pod,创建文件
[root@k8scloude1 deploy]# kubectl exec -it pvcshare -- bash
root@pvcshare:/# ls /xx/
root@pvcshare:/# touch /xx/{1..10}.txt
root@pvcshare:/# ls /xx/
1.txt 10.txt 2.txt 3.txt 4.txt 5.txt 6.txt 7.txt 8.txt 9.txt
root@pvcshare:/# exit
exit
NFS服务器对应目录下也有文件
[root@etcd1 ~]# ls /dongtaijuandongying/volume-mypvc-pvc-7d17891d-f95a-417f-abf4-06f9e84dc82e/
10.txt 1.txt 2.txt 3.txt 4.txt 5.txt 6.txt 7.txt 8.txt 9.txt
删除pod
[root@k8scloude1 deploy]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-76c576954d-5x7t2 1/1 Running 0 17m
pvcshare 1/1 Running 0 2m9s
[root@k8scloude1 deploy]# kubectl delete -f pvcpod.yaml
pod "pvcshare" deleted
[root@k8scloude1 deploy]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-76c576954d-5x7t2 1/1 Running 0 18m
删除PVC
[root@k8scloude1 deploy]# kubectl delete -f pvc1.yaml
persistentvolumeclaim "mypvc" deleted
[root@k8scloude1 deploy]# kubectl get pv
No resources found
[root@k8scloude1 deploy]# kubectl get pvc
No resources found in volume namespace.
pvc被删除之后,NFS服务端文件也没了,是因为回收策略RECLAIM POLICY为Delete
[root@etcd1 ~]# ls /dongtaijuandongying/volume-mypvc-pvc-7d17891d-f95a-417f-abf4-06f9e84dc82e/
ls: 无法访问/dongtaijuandongying/volume-mypvc-pvc-7d17891d-f95a-417f-abf4-06f9e84dc82e/: 没有那个文件或目录
当配置了存储类StorageClass,PVC可以进行动态扩容,关于PVC动态扩容请查看博客《troubleshoot:PVC动态扩容报错》。