一、认识PV、PVC
PersistentVolume(PV)是指集群管理员配置提供的某存储系统上的一段存储空间,它是对底层共享存储的抽象,将共享存储作为一种可由用户申请使用的资源,实现了"存储消费"机制。通过存储插件,PV支持使用多种网络存储或云端存储等多种后端存储系统,例如,前面使用到的NFS、还有其他的如RBD、Cinder等。PV是集群级别的资源,不属于任何的名称空间,用户对PV资源的使用需要通过PersistentVolumeClaim(PVC)提出的使用申请(或称为声明)来完成绑定,PVC是PV资源对象的消费者,它向PV申请特定大小的空间及访问模式(如读写或只读),从而创建出PVC存储卷,而后再由Pod资源通过PersistentVolumeClaim存储卷关联使用。
尽管PVC使得用户可以以抽象的方式访问存储资源,但是很多时候还是会涉及到PV的不少属性,例如,用于不同场景时设置的性能参数等。为此,集群管理员不得不通过多种方式提供多种不同的PV以满足用户不同的使用需求,两者衔接上的偏差必然会导致用户的需求无法全部及时有效的得到满足。Kubernetes自1.4版本起引入了一个新的资源对象StorageClass,可用于将存储资源定义为具有显着特性的类别(Class)而不是具体的PV,例如"fast"、“slow"或"glod”、“silver”、"bronze"等等。用户通过PVC直接向意向的类别发出申请,匹配由管理员事先创建的PV,或者由其按需为用户动态创建PV,这样做甚至免去了需要事先创建PV的过程。
二、Kubernetes的存储系统
Kubernetes挂载Volume的过程:
1)用户创建一个好汉PVC的Pod(使用动态存储卷);
2)PV Controller发现这个PVC处于待绑定状态,调用Volume-Pliugin(in-tree或out-of-tree)创建存储卷;并创建PV对象,并将创建的PV与PVC绑定;
3)Scheduler根据Pod配置、节点状态、PV配置等信息,把Pod调度到Worker节点Node上;
4)AD Controller发现Pod处于待挂载状态,调用Volume Plugin(in-tree或out-of-tree)实现设备挂载到目标节点(/dev/sdb);
5)在Worker节点上,Kubelet(Volume Manager)等待设备挂载完成,通过Volume Plugin将设备挂载至指定目录:/var/lib/kubelet/pods/f9433060-ba17-4ab5-96b3-4aacd1dc0dd6/volumes/
6)Kubelet在被告知挂载目录准备好后,启动Pod中的containers,用Docker -v方式(bind)将已经挂载到本地的卷映射到容器中;
三、创建PV
PersistentVolume Spec主要支持以下几个通用字段,用于定义PV的容量、访问模式和回收策略。
Capacity:当前PV的容量;目前,Capacity仅支持空间设定,将来应该还可以指定IOPS和throughput。
访问模式:尽管在PV层看起来并无差别,但是存储设备支持及启用的功能特性却可能不尽相同。例如NFS存储支持多客户端同时挂载及读写操作,但是也可能是在共享时仅启用了只读模式,其他存储系统也存在类似的可配置特性。因此,PV底层的设备或许存在其特有的访问模式下,用户使用时必须在其特定范围内设定其功能,具体各PV的支持访问模式如下图:
ReadWriteOnce:仅可被单个节点读写挂载;命令行中可简写为RWO。
ReadOnlyMany:可被多个节点同时只读挂载;命令行中可简写为ROX。
ReadWriteMany:可被多个节点同时读写挂载;命令行中可简写为RWX。
persistentVolumeReclaimPolicy:PV空间被释放时的处理机制;可用类型仅为Retain(默认)、Recycle或Delete,具体说明如下:
Retain:保持不动,由管理员随后手动回收。
Recycle:空间回收,即删除存储卷目录下的所有文件(包括子目录和隐藏文件),目前只支持NFS和hostPath支持此操作。
Delete:删除存储卷,仅部分支持云端存储系统支持,如AWS EBS、GCE PD、Azure Disk和Cinder。
volumeMode:卷模型,用于指定此卷可被用作文件系统还是裸格式的块设备;默认为Filesystem。
storageClassName:当前PV所属的StorageClass的名称;默认为空值,即不属于任何的StorageClass。
mountOptions:挂载选项组成的列表,如ro、soft和hard等等。
1)创建PV
]# cat my-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-001
labels:
release: stable
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
nfs:
path: "/data/"
server: 172.16.2.250
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-002
labels:
release: stable
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
nfs:
path: "/data/"
server: 172.16.2.250
]# kubectl apply -f my-pv.yaml
persistentvolume/pv-nfs-001 created
persistentvolume/pv-nfs-002 created
2)查看PV资源对象
]# kubectl get pv -o wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
pv-nfs-001 5Gi RWX Recycle Available slow 73s Filesystem
pv-nfs-002 10Gi RWX Recycle Available slow 73s Filesystem
]# kubectl describe pv pv-nfs-001
Name: pv-nfs-001
Labels: release=stable
Annotations: Finalizers: [kubernetes.io/pv-protection]
StorageClass: slow
Status: Available
Claim:
Reclaim Policy: Recycle
Access Modes: RWX
VolumeMode: Filesystem
Capacity: 5Gi
Node Affinity: <none>
Message:
Source:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 172.16.2.25
Path: /data/
ReadOnly: false
Events: <none>
.
通过查看PV的详细信息可以看见,当前PV的状态为Available,即为"可用状态",表示目前尚未被PVC所关联绑定;创建完成的PV资源可能处于下列四种状态中的某一种,它们代表着PV资源生命周期中的各个阶段:
Available:可用状态,尚未被PVC所绑定。
Bound:已经被绑定至某个PVC。
Released:绑定的PVC已经被删除了,但是资源尚未被集群回收。
Failed:因自动回收资源失败而处于的故障状态。
四、创建PVC
PersistentVolumeClaim是存储卷类型的资源,它通过申请占用某个PersistentVolume而创建,它与PV是一对一的关系,用户无须关心底层的实现细节。申请时,用户只需要指定目标空间的大小、访问模式、PV标签选择器和StorageClass等相关信息即可。PVC的Spec字段的可嵌套字段具体如下:
accessMode:当前PVC的访问模式,其可用模式与PV相同。
resource:当前PVC存储卷要占用的资源量最小值;目前,PVC的资源限定仅指的是其空间大小。
selector:绑定时对PV应用的标签选择器(matchLabels)或者是匹配条件表达式(matchEx-pressions),用于挑选要绑定的PV;如果同时指定了两种挑选机制,则需要同时满足两种选择机制的PV才能被选择出来。
storageClassName:所依赖的存储类名称。
volumeMode:卷模型,用于指定此卷可被用作文件系统还是裸格式的块设备;默认为"Filesystem"。
volumeName:用于直接指定要绑定的PV的卷名。
1)创建PVC
]# cat my-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs-001
labels:
release: "stable"
spec:
accessModes:
- ReadWriteMany
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs-002
labels:
release: "stable"
spec:
accessModes:
- ReadWriteMany
volumeMode: Filesystem
resources:
requests:
storage: 10Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
]# kubectl apply -f my-pvc.yaml
persistentvolumeclaim/pvc-nfs-001 created
persistentvolumeclaim/pvc-nfs-002 created
2)查看PVC资源对象详细信息
]# kubectl get pvc -o wide
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE
pvc-nfs-001 Bound pv-nfs-001 5Gi RWX slow 6s Filesystem
pvc-nfs-002 Bound pv-nfs-002 10Gi RWX slow 6s Filesystem
]# kubectl get pv -o wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
pv-nfs-001 5Gi RWX Recycle Bound default/pvc-nfs-001 slow 21m Filesystem
pv-nfs-002 10Gi RWX Recycle Bound default/pvc-nfs-002 slow 21m Filesystem
# kubectl describe pvc pvc-nfs-001
Name: pvc-nfs-001
Namespace: default
StorageClass: slow
Status: Bound
Volume: pv-nfs-001
Labels: release=stable
Annotations: pv.kubernetes.io/bind-completed: yes
pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pvc-protection]
Capacity: 5Gi
Access Modes: RWX
VolumeMode: Filesystem
Mounted By: <none>
Events: <none>
.
可以看到当前PV、PVC的状态都已经处于"Bound"状态,说明PV与PVC已经建立了绑定关系。创建好PVC资源之后,即可在Pod资源中通过persistenVolumeClain存储卷引用它,而后挂载于容器中进行数据持久化。需要注意的是,PV是集群级别的资源,而PVC则隶属于名称空间,因此,PVC在绑定目标PV时不受名称空间的限制,但是Pod引用PVC时,则只能是属于同一名称空间中的资源。
五、在Pod中使用PVC
在Pod资源中调用PVC资源,只需要在定义volumes时使用persistentVolumeClaims字段嵌套指定两个字段即可,具体如下:
claimName:要调用的PVC存储卷的名称,PVC卷要与Pod在同一个名称空间下。
readOnly:是否将存储卷强制挂载为只读模式,默认为false。
1)编写创建Pod的yaml文件
]# cat my-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: vol-nfs-pod01
labels:
app: myapp
release: "stable"
spec:
containers:
- name: redis
image: redis:4-alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 6379
name: redisport
volumeMounts:
- mountPath: /data-redis
name: redis-nfs-vol
volumes:
- name: redis-nfs-vol
persistentVolumeClaim:
claimName: pvc-nfs-001
---
apiVersion: v1
kind: Pod
metadata:
name: vol-nfs-pod02
labels:
app: myapp
release: "stable"
spec:
containers:
- name: redis
image: redis:4-alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 6379
name: redisport
volumeMounts:
- mountPath: /data-redis
name: redis-nfs-vol
volumes:
- name: redis-nfs-vol
persistentVolumeClaim:
claimName: pvc-nfs-002
]# kubectl apply -f my-pod.yaml
pod/vol-nfs-pod01 created
pod/vol-nfs-pod02 created
2)查看Pod资源的详细信息
]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
vol-nfs-pod01 1/1 Running 0 82s 10.244.1.109 node1 <none> <none>
vol-nfs-pod02 1/1 Running 0 82s 10.244.1.110 node1 <none> <none>
]# kubectl describe pods vol-nfs-pod01
Name: vol-nfs-pod01
Namespace: default
Priority: 0
Node: node1/172.16.2.101
Start Time: Sun, 30 Aug 2020 17:34:17 +0800
Labels: app=myapp
release=stable
Annotations: Status: Running
IP: 10.244.1.109
IPs:
IP: 10.244.1.109
Containers:
redis:
Container ID: docker://7ed7da0a023622f21f31c254ebfb1b3321ad9f4414f9cd294609894921cbb602
Image: redis:4-alpine
Image ID: docker-pullable://redis@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20
Port: 6379/TCP
Host Port: 0/TCP
State: Running
Started: Sun, 30 Aug 2020 17:34:19 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/data-redis from redis-nfs-vol (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-47pch (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
redis-nfs-vol:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: pvc-nfs-001
ReadOnly: false
default-token-47pch:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-47pch
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned default/vol-nfs-pod01 to node1
Normal Pulled <invalid> kubelet, node1 Container image "redis:4-alpine" already present on machine
Normal Created <invalid> kubelet, node1 Created container redis
Normal Started <invalid> kubelet, node1 Started container redis
3)查看Pod资源的存储空间
]# kubectl exec -it vol-nfs-pod01 -- df -h | grep /data-redis
172.16.2.250:/data 50.0G 14.7G 35.3G 29% /data-redis
4)报错及解决办法:
如果在创建Pod时该Pod状态一直处于"CreateContainer",且查看详细信息报如下错误:
Mounting arguments: --description=Kubernetes transient mount for /var/lib/kubelet/pods/51bb0c63-053d-4fd6-aa61-e5f552cd0915/volumes/kubernetes.io~nfs/pv-nfs-001 --scope -- mount -t nfs -o hand,nfsvers=4.1 172.16.2.25:/data/ /var/lib/kubelet/pods/51bb0c63-053d-4fd6-aa61-e5f552cd0915/volumes/kubernetes.io~nfs/pv-nfs-001
Output: Running as unit run-112794.scope.
mount.nfs: an incorrect mount option was specified
Warning FailedMount <invalid> kubelet, node1 MountVolume.SetUp failed for volume "pv-nfs-001" : mount failed: exit status 32
Mounting command: systemd-run
请在Pod被调度的节点上安装NFS相关的软件包:
yum install nfs-utils -y