Kubernetes技术详解-从理论到实践-(7)-存储-Storage
本文介绍了Kubernetes中的存储机制,重点讲解了emptyDir、hostPath、nfs、persistentVolumeClaim、configMap和secret这六类常见的存储方式。并针对每种存储方式给出了运行示例。
1 存储简介
前面我们使用nginx镜像时,没有指定挂载目录,访问到的主页只是镜像自带的默认页面。这一章将介绍K8s的存储,通过存储,我们可以把自己的主页挂载到Pod中,这样就可以访问我们自己的主页了。
K8s存储的作用是数据持久化,虽然使用时相当于一块硬盘,但与硬盘相比,它还具有动态分配容量、网络存储、绑定、回收等功能。
K8s支持的存储方式非常多,使用kubectl explain命令可以看到FIELDS里面具体支持的存储类型。
[root@master ~]# kubectl explain pods.spec.volumes
KIND: Pod
VERSION: v1
FIELD: volumes <[]Volume>
DESCRIPTION:
List of volumes that can be mounted by containers belonging to the pod. More
info: https://kubernetes.io/docs/concepts/storage/volumes
Volume represents a named volume in a pod that may be accessed by any
container in the pod.
FIELDS:
awsElasticBlockStore <AWSElasticBlockStoreVolumeSource>
azureDisk <AzureDiskVolumeSource>
azureFile <AzureFileVolumeSource>
cephfs <CephFSVolumeSource>
cinder <CinderVolumeSource>
configMap <ConfigMapVolumeSource>
csi <CSIVolumeSource>
downwardAPI <DownwardAPIVolumeSource>
emptyDir <EmptyDirVolumeSource>
ephemeral <EphemeralVolumeSource>
fc <FCVolumeSource>
flexVolume <FlexVolumeSource>
flocker <FlockerVolumeSource>
gcePersistentDisk <GCEPersistentDiskVolumeSource>
gitRepo <GitRepoVolumeSource>
glusterfs <GlusterfsVolumeSource>
hostPath <HostPathVolumeSource>
iscsi <ISCSIVolumeSource>
name <string> -required-
nfs <NFSVolumeSource>
persistentVolumeClaim <PersistentVolumeClaimVolumeSource>
photonPersistentDisk <PhotonPersistentDiskVolumeSource>
portworxVolume <PortworxVolumeSource>
projected <ProjectedVolumeSource>
quobyte <QuobyteVolumeSource>
rbd <RBDVolumeSource>
scaleIO <ScaleIOVolumeSource>
secret <SecretVolumeSource>
storageos <StorageOSVolumeSource>
vsphereVolume <VsphereVirtualDiskVolumeSource>
本章我们将介绍这其中的六种。
- emptyDir
- hostPath
- nfs
- persistentVolumeClaim
- configMap
- secret
以上存储类型中,emptyDir和hostPath为本地存储,nfs为网络存储,PVC属于动态存储,configMap和secret属于配置类存储。
2 本地存储
本地存储有emptyDir和hostPath两种,它们的相同点是存储路径都是本地磁盘,区别如下:
emptyDir为K8s临时分配的存储目录,用户不可以手动指定,当Pod消亡后,存储目录也随之消失,且只有同一Pod内的容器才可以使用该方式共享数据,不在同一Pod无法使用该方式共享数据。
hostPath存储在本地,用户可以指定目录,当Pod消亡后,存储目录仍然存在,由于存储路径为本地,因此不在同一Pod也可以访问该目录,但不同节点的Pod无法访问到本目录。
2.1 emptyDir存储
下面是emptyDir的演示示例:
(1) 创建emptyDir资源清单文件
该资源清单创建一个名为storage-emptydir-pod的Pod,Pod内运行两个容器,一个为busybox,一个为nginx,两个容器使用emptyDir共享数据,busybox向这个临时目录写入index.html,nginx使用这个index.html作为主页。
[root@master 7-storage]# cat storage-emptydir-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: storage-emptydir-pod
spec:
restartPolicy: Always
containers:
- name: writer
image: busybox:1.34.1
command:
- /bin/sh
- -c
- |
echo '<h1>Hello from busybox via emptyDir!</h1>' > /shared/index.html
tail -f /dev/null
volumeMounts:
- name: shared-tmp
mountPath: /shared
- name: web
image: nginx:1.27.3
ports:
- containerPort: 80
volumeMounts:
- name: shared-tmp
mountPath: /usr/share/nginx/html # nginx默认根目录
volumes:
- name: shared-tmp
emptyDir: {}
(2) 创建Pod
创建pod,可以看到pod内有两个容器已经就绪READY 2/2,Pod的IP地址为10.42.0.70
[root@master 7-storage]# kubectl apply -f storage-emptydir-pod.yaml
pod/storage-emptydir-pod created
[root@master 7-storage]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
storage-emptydir-pod 2/2 Running 0 10s 10.42.0.70 master <none> <none>
(3) 访问服务
访问pod提供的nginx服务,可以看到正是busybox写入的内容。
[root@master 7-storage]# curl 10.42.0.70
<h1>Hello from busybox via emptyDir!</h1>
2.2 hostPath存储
下面是hostPath存储的演示示例:
(1) 创建hostPath存储资源清单文件
该资源清单文件是一个多资源yaml,也叫yaml文档流,包含两个资源的创建。采用hostPath方式创建两个Pod,一个busybox Pod向路径/root/k8s-test/data写入index.html,一个nginx Pod将该路径挂载到默认根目录下。当资源创建完成后,使用curl访问nginx Pod的IP,可以访问到busybox写入的index.html内容。当Pod被删除后,hostPath路径下的文件仍存在。
这里看到,spec节点下多了volumes子节点,containers节点下多了volumeMounts子节点。挂载类型为hostPath,将本机目录挂载到pod中。
由于我们这里演示环境为控制平面节点兼工作节点,这两个pod会调度到同一节点,如果集群有多个工作节点,则需要为pod设置节点亲和性,保证这两个pod调度到同一节点。节点亲和性的概念我们后续会讲到。
[root@master 7-storage]# cat storage-hostpath-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: storage-writer-hostpath-pod
spec:
restartPolicy: Always
containers:
- name: writer
image: busybox:1.34.1
command:
- /bin/sh
- -c
- |
mkdir -p /shared
echo '<h1>Hello from busybox via hostPath!</h1>' > /shared/index.html
tail -f /dev/null
volumeMounts:
- name: shared-host
mountPath: /shared
volumes:
- name: shared-host
hostPath:
path: /root/k8s-test/data
type: DirectoryOrCreate
---
apiVersion: v1
kind: Pod
metadata:
name: storage-reader-hostpath-pod
spec:
restartPolicy: Always
containers:
- name: web
image: nginx:1.27.3
ports:
- containerPort: 80
volumeMounts:
- name: shared-host
mountPath: /usr/share/nginx/html
volumes:
- name: shared-host
hostPath:
path: /root/k8s-test/data
type: DirectoryOrCreate
(2) 创建Pod
[root@master 7-storage]# kubectl get pod
NAME READY STATUS RESTARTS AGE
storage-emptydir-pod 2/2 Running 0 12m
[root@master 7-storage]# kubectl apply -f storage-hostpath-pod.yaml
pod/storage-writer-hostpath-pod created
pod/storage-reader-hostpath-pod created
可以看到两个pod已经创建成功,10.42.0.72是nginx容器。
[root@master 7-storage]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
storage-emptydir-pod 2/2 Running 0 13m 10.42.0.70 master <none> <none>
storage-reader-hostpath-pod 1/1 Running 0 4s 10.42.0.72 master <none> <none>
storage-writer-hostpath-pod 1/1 Running 0 4s 10.42.0.71 master <none> <none>
(3) 访问nginx容器
[root@master 7-storage]# curl 10.42.0.72
<h1>Hello from busybox via hostPath!</h1>
[root@master 7-storage]# cat /root/k8s-test/data/index.html
<h1>Hello from busybox via hostPath!</h1>
(4) 删除容器后挂载路径内容仍保留
[root@master 7-storage]# kubectl delete -f storage-hostpath-pod.yaml
pod "storage-writer-hostpath-pod" deleted
pod "storage-reader-hostpath-pod" deleted
[root@master 7-storage]# cat /root/k8s-test/data/index.html
<h1>Hello from busybox via hostPath!</h1>
3 网络存储
在前面介绍本地存储时,我们发现hostPath存储有一个问题,需要保证所有Pod都调度到同一个节点,否则本地存储无法共享目录,这不符合集群负载均衡要求,而且,所有文件都存储在本地,也有很大风险,因此本节介绍网络存储。网络存储有多种方式,常见的一种方式为nfs存储。
3.1 nfs存储
nfs(Network File System)是一种网络文件系统协议,允许不同机器通过网络像访问本地磁盘一样读写远程服务器(nfs Server)上的文件。
在K8s中,它常被当做共享存储后端,供多个Pod跨节点读写同一份数据。
3.1.1 nfs存储演示
下面演示K8s的Deployment使用nfs存储。
(1) 搭建nfs服务
在另一台安装了openEuler 22.03 LTS的虚拟机上,配置IP地址为192.168.88.130,用于搭建nfs服务器。
nfs服务的搭建见前面环境准备一章。
(2) 向nfs目录写入index.html主页文件
本操作是在nfs服务器192.168.88.130上进行,创建/opt/nfs-root/nginx-html-nfs,并在该目录创建index.html文件,文件内容如下。
[root@nfs-server ] cat /opt/nfs-root/nginx-html-nfs/index.html
<html><body>Nginx! With k8s nfs storage!</body></html>
(3) 编写Deployment控制器资源清单文件
Deploy配置文件,这里为了和之前的hostpath区分,修改了Deploy的名称。
nfs的远端路径为/opt/nfs-root/nginx-html-nfs,要确保/etc/exports配置了该路径,并使用exportfs -avr命令使配置生效。
[root@master 7-storage]# cat nginx-nfs-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-nfs-deploy
labels:
app: nginx-nfs-deploy
spec:
selector:
matchLabels:
app: nginx-nfs-deploy
replicas: 3
template:
metadata:
labels:
app: nginx-nfs-deploy
spec:
containers:
- name: nginx
image: nginx:1.27.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: nfs-volume
mountPath: /usr/share/nginx/html
volumes:
- name: nfs-volume
nfs:
path: /opt/nfs-root/nginx-html-nfs
server: 192.168.88.130
(4)创建Service资源清单文件
新建一个Service,选择上面的Pod,暴露到外部的端口为30020
[root@master 7-storage]# cat nginx-nfs-deploy-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-nfs-deploy-svc
spec:
type: NodePort
selector:
app: nginx-nfs-deploy
ports:
- port: 80
targetPort: 80
nodePort: 30020
(5) 创建Deploy和Service
创建Deploy和Service,外部可以通过主机的30020端口访问nginx服务。
# 通过Deploy创建三个Pod
[root@master 7-storage]# kubectl apply -f nginx-nfs-deploy.yaml
deployment.apps/nginx-deploy-nfs created
[root@master 7-storage]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-nfs-deploy-cdcd8fd5b-2jhj7 1/1 Running 0 3s
nginx-nfs-deploy-cdcd8fd5b-4cfhk 1/1 Running 0 3s
nginx-nfs-deploy-cdcd8fd5b-b5sql 1/1 Running 0 3s
# 创建Service
[root@master 7-storage]# kubectl apply -f nginx-nfs-deploy-svc.yaml
service/nginx-nfs-deploy-svc created
[root@master 7-storage]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 15d
nginx-nfs-deploy-svc NodePort 10.43.223.238 <none> 80:30020/TCP 3s
(6) 访问Service服务
在集群外部使用NodePort Service的对外暴露端口可以访问到nginx服务,该页面存储在192.168.88.130的/opt/nfs-root/nginx-html-nfs目录,通过nfs提供服务。
[root@master 7-storage]# curl 192.168.88.130:30020
<html><body>Nginx! With k8s nfs storage!</body></html>
3.1.2 一个nfs存储报错示例及解决办法
在日常运维中,nfs存储共享路径不存在是一种常见的现象,我们通过一个例子演示这种现象,并给出解决问题的方法。
(1) 创建Deploy
删除之前创建的所有deploy,nfs服务的路径由/opt/nfs-root/nginx-html-nfs改为/root/data/notexist,再次创建deploy
[root@master 7-storage]# kubectl get pods
No resources found in default namespace.
[root@master 7-storage]# kubectl get deploy
No resources found in default namespace.
创建Deploy
[root@master 7-storage]# kubectl apply -f nginx-nfs-deploy.yaml
deployment.apps/nginx-nfs-deploy created
发现deploy未READY
[root@master 7-storage]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deploy-nfs 0/3 3 0 5s
POD处于ContainerCreating状态
[root@master 7-storage]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-nfs-deploy-cdcd8fd5b-f9t9l 0/1 ContainerCreating 0 7s
nginx-nfs-deploy-cdcd8fd5b-qfzjf 0/1 ContainerCreating 0 7s
nginx-nfs-deploy-cdcd8fd5b-xrbsk 0/1 ContainerCreating 0 7s
(2)查看容器日志
容器正处于创建状态,nginx服务还没起来。
[root@master 7-storage]# kubectl logs -f nginx-nfs-deploy-cdcd8fd5b-f9t9l
Error from server (BadRequest): container "nginx" in pod "nginx-nfs-deploy-cdcd8fd5b-f9t9l" is waiting to start: ContainerCreating
(3) 查看pod详情
使用kubectl describe命令查看pod详情,发现是nfs目录不存在
[root@master 7-storage]# kubectl describe pod nginx-nfs-deploy-cdcd8fd5b-f9t9l
Name: nginx-nfs-deploy-cdcd8fd5b-f9t9l
Namespace: default
(。。。部分输出略。。。)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 41s default-scheduler Successfully assigned default/nginx-nfs-deploy-cdcd8fd5b-f9t9l to master
Warning FailedMount 9s (x7 over 41s) kubelet MountVolume.SetUp failed for volume "nfs-volume" : mount failed: exit status 32
Mounting command: mount
Mounting arguments: -t nfs 192.168.88.130:/opt/nfs-root/nginx-html-nfs /var/lib/kubelet/pods/b8b7bb7f-a9f1-44eb-b14a-ed14308c3d76/volumes/kubernetes.io~nfs/nfs-volume
Output: mount.nfs: mounting 192.168.88.130:/opt/nfs-root/nginx-html-nfs failed, reason given by server: No such file or directory
(4) 提供正确的nfs服务
创建/root/data/notexist目录,或者修改/etc/exports文件,修改为一个已经存在的nfs路径(注意使用exportfs -avr命令使配置生效)。
(5) 调整Pod状态
提供了正确的nfs服务后,Pod不会自动更改为RUNNING状态,我们可以通过删除再重新创建,或者扩缩容方式使其恢复正常状态。下面是通过扩容方式使Pod正常。
# 可以看到Pod仍处于创建中状态
[root@master 7-storage]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-nfs-deploy-cdcd8fd5b-f9t9l 0/1 ContainerCreating 0 6m4s
nginx-nfs-deploy-cdcd8fd5b-qfzjf 0/1 ContainerCreating 0 6m4s
nginx-nfs-deploy-cdcd8fd5b-xrbsk 0/1 ContainerCreating 0 6m4s
# 扩容到5个副本
[root@master 7-storage]# kubectl scale deploy nginx-nfs-deploy --replicas=5
deployment.apps/nginx-nfs-deploy scaled
# Pod状态正常
[root@master 7-storage]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-nfs-deploy-cdcd8fd5b-f9t9l 1/1 Running 0 6m28s
nginx-nfs-deploy-cdcd8fd5b-hllwj 1/1 Running 0 3s
nginx-nfs-deploy-cdcd8fd5b-jrgsp 1/1 Running 0 3s
nginx-nfs-deploy-cdcd8fd5b-qfzjf 1/1 Running 0 6m28s
nginx-nfs-deploy-cdcd8fd5b-xrbsk 1/1 Running 0 6m28s
4 PV/PVC存储
PVC和PV是K8s提供的用于管理存储资源的两个概念。PV是Persistent Volume的缩写,中文翻译持久化卷,PVC是Persistent Volume Claim的缩写,中文翻译持久化卷声明。
PV是集群里用于存储的一块硬盘或目录,这个目录可以是本地的,也可以是网络的,只要在集群中,可以被访问到就可以。
PVC是使用PV的凭据,用户通过这个凭据告诉K8s需要多大容量、读写方式、存储方式的PV,当集群中有合适的PV,K8s会将PV和PVC自动绑定。
当PVC和PV绑定后,Pod就可以使用这个PVC,挂载PV。
PV是存储盘,PVC是存储盘申请凭据,Kubernetes通过PV/PVC把底层存储抽象成云资源,让使用存储盘的人不需要关注存储盘的物理位置,使底层存储细节和使用者彻底解耦。
4.1 使用PV/PVC存储示例
这里我们举个例子直观感受一下Deploy是如何使用PV/PVC这种存储方式的。
首先创建一个PV,容量200MB,底层使用hostPath方式存储,路径为/root/k8s-test/7-storage/data/hostpath
。然后创建一个PVC,与这个PV绑定,最后创建Deployment,将PVC绑定的PV挂载到Pod中。
操作步骤如下:
(1) 编写PV资源清单文件
这个PV底层存储方式为hostPath,我们将其命名为storage-hostpath-pv.yaml,PV容量为200MB
[root@master 7-storage]# cat storage-hostpath-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: storage-hostpath-pv
spec:
capacity:
storage: 200Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
hostPath:
path: /root/k8s-test/7-storage/data/hostpath
type: DirectoryOrCreate
(2) 编写PVC资源清单文件
这个PVC最重要的是声明需要200MB大小的一个PV
[root@master 7-storage]# cat storage-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: storage-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 200Mi
storageClassName: ""
(3) 编写Deployment资源清单
挂载卷为PVC,PVC名字为storage-pvc,这个需要与前面创建的PVC名字一致,否则就会找不到挂载路径。
[root@master 7-storage]# cat nginx-pvc-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-pvc-deploy
labels:
app: nginx-deploy
spec:
selector:
matchLabels:
app: nginx-deploy
replicas: 3
template:
metadata:
labels:
app: nginx-deploy
spec:
containers:
- name: nginx
image: nginx:1.27.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: pvc-volume
mountPath: /usr/share/nginx/html
volumes:
- name: pvc-volume
persistentVolumeClaim:
claimName: storage-pvc
(4) 创建PV资源
# 清空之前创建过的所有资源
[root@master 7-storage]# kubectl get pv,pvc,deploy
No resources found
# 创建PV
[root@master 7-storage]# kubectl apply -f storage-hostpath-pv.yaml
persistentvolume/storage-hostpath-pv created
# PV创建成功,容量为200MB,状态为Available,CLAIM为空
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-pv 200Mi RWO Retain Available <unset> 6s
(5) 创建PVC资源
# 创建PVC资源
[root@master 7-storage]# kubectl apply -f storage-pvc.yaml
persistentvolumeclaim/storage-pvc created
# 查看PVC,其STATUS为Bound,已绑定,VOLUME为绑定的PV,容量为200MB
[root@master 7-storage]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
storage-pvc Bound storage-hostpath-pv 200Mi RWO <unset> 4s
# 查看PV状态,发现与之前也不一样,STATUS和CLAIM与之前未绑定的时候都有更新
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-pv 200Mi RWO Retain Bound default/storage-pvc <unset> 22s
(6) 创建Deployment
# 创建Deployment
[root@master 7-storage]# kubectl apply -f nginx-pvc-deploy.yaml
deployment.apps/nginx-pvc-deploy created
# 创建成功,查看三个Pod的地址
[root@master 7-storage]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-pvc-deploy-6dcfc894c7-42kdl 1/1 Running 0 21s 10.42.0.77 master <none> <none>
nginx-pvc-deploy-6dcfc894c7-6q4zr 1/1 Running 0 21s 10.42.0.78 master <none> <none>
nginx-pvc-deploy-6dcfc894c7-nwwlw 1/1 Running 0 21s 10.42.0.76 master <none> <none>
(7) 访问Pod内的nginx服务
# 访问其中一个Pod,提示Forbidden,这是因为nginx的根目录是我们指定的路径,这里面还没有index.html主页文件。
[root@master 7-storage]# curl 10.42.0.76
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.27.3</center>
</body>
</html>
# 向挂载路径写入index.html文件
[root@master 7-storage]# echo "deploy use pvc storage, pv use hostpath" > /root/k8s-test/7-storage/data/hostpath/index.html
# 访问其中一个Pod,可以看到刚才写入的内容
[root@master 7-storage]# curl 10.42.0.76
deploy use pvc storage, pv use hostpath
# 换一个Pod,也可以访问
[root@master 7-storage]# curl 10.42.0.77
deploy use pvc storage, pv use hostpath
# 如果我们想在集群外访问这个服务,需要创建对应的Service,暴露外部端口。
4.2 PV和PVC介绍
单独讲解PV和PVC的知识会比较枯燥,我们将从PV和PVC的资源清单着手,对资源清单里重要字段逐一解释。
4.2.1 PV资源清单
PV可以看做一个数据盘,它底层可以使用hostPath本地存储,也可以使用nfs网络存储。这里我们展示的两种类型的资源清单,他们绝大多数的字段都是相同的。只是在spec字段最后有一点差异,hostPath表示本地存储,nfs表示nfs网络存储。
4.2.1.1 使用hostPath本地存储的PV资源清单
下面是一份使用hostPath方式本地存储的PV资源清单
[root@master 7-storage]# cat storage-hostpath-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: storage-hostpath-pv
spec:
capacity:
storage: 200Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
hostPath:
path: /root/k8s-test/7-storage/data/hostpath
type: DirectoryOrCreate
这份PV清单把宿主机目录/root/k8s-test/7-storage/data/hostpath注册成集群里的一块“持久盘”,供PVC绑定后给Pod使用。
这块PV盘的特点是:本地静态持久存储,容量标称200MB,单节点读写,Pod删除后数据仍保留
各行具体含义:apiVersion: v1、kind: PersistentVolume
声明这是一个 PersistentVolume 资源,即“集群级存储”定义。metadata.name: storage-hostpath-pv
PV 在集群里的唯一标识,PVC 会按名字或选择器去匹配它。spec.capacity.storage: 200Mi
对外宣称的容量,仅做配额校验,不会真正限制宿主机目录大小。accessModes: [ReadWriteOnce]
只能被单个节点挂载为读写;多节点同时读写需改为ReadWriteMany,但hostPath本身不支持跨节点共享。
accessModes取值:
ReadWriteOnce (RWO) – 单节点读写
ReadOnlyMany (ROX) – 多节点只读
ReadWriteMany (RWX) – 多节点读写persistentVolumeReclaimPolicy: Retain
当绑定的PVC被删除时,PV和数据保留,需手动清理/root/k8s-test/7-storage/data/hostpath
里的文件。如果该值为Delete,则PVC删除后PV连同底层存储一起被删除,数据不可恢复。storageClassName: ""
空字符串表示“静态供给”,不会触发StorageClass的动态provisioner,PVC必须同样留空才能匹配。hostPath.path: /root/k8s-test/7-storage/data/hostpath
真实落在当前节点根目录的绝对路径,所有使用此PV的Pod必须调度到该节点;否则挂载会失败。hostPath.type: DirectoryOrCreate
如果该目录不存在,kubelet会自动创建;目录权限继承宿主机,通常为root:root 0755。
4.1.1.2 使用nfs网络存储的PV资源清单
下面是一份使用nfs方式存储的PV资源清单
[root@master 7-storage]# cat storage-nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: storage-nfs-pv
spec:
capacity:
storage: 200Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
nfs:
server: 192.168.88.130
path: /opt/nfs-root
这份PV把远端NFS服务器的一个目录/opt/nfs-root注册成集群里的一块共享持久盘。
这块PV的特点是:静态绑定,NFS方式存储,真实目录为192.168.88.130:/opt/nfs-root,标称容量200MB,单节点可读写,Pod删除后数据仍保留
各行具体含义:
前面字段与本地存储的yaml资源清单内容完全一样,这里就不重复解释,请参考上一节对本地存储PV资源清单的解释。nfs.server: 192.168.88.130
远端NFS服务器IP。nfs.path: /opt/nfs-root
nfs服务器上的共享目录。集群节点需能mount -t nfs 192.168.88.130:/opt/nfs-root /mnt
成功。
4.2.2 PVC资源清单
我们在对PV资源清单命名使,会标明其底层存储方式,hostPath或nfs,如,使用hostPath存储,则命名为storage-hostpath-pv.yaml。
PVC不关心底层存储方式,只要符合绑定条件的PV,无论何种底层存储方式,都可以被匹配。
下面是一份PVC资源清单文件。
[root@master 7-storage]# cat storage-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: storage-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 200Mi
storageClassName: ""
这个PVC清单的含义是,在集群中申请一块容量不小于200MB,访问模式为单节点可读写,StorageClassName为空并且状态为Available的PV。找到符合这种条件的PV,K8s会将PVC和PV进行自动绑定。
各行具体含义:apiVersion: v1 & kind: PersistentVolumeClaim
声明这是一个PVC,即“用户对存储的申请单”。metadata.name: storage-pvc
该PVC在集群中的唯一名称。Pod通过claimName: storage-pvc来引用它。spec.accessModes: [ReadWriteOnce]
要求存储支持单节点读写(RWO)。只有满足此模式的PV才能与之匹配。spec.resources.requests.storage: 200Mi
申请200MiB的容量。系统只会绑定≥200Mi的PV。spec.storageClassName: ""
强制静态绑定,只匹配同样storageClassName为空的PV,不会触发任何StorageClass的动态供给。
4.3 PVC与PV绑定
前面我们介绍了两种底层存储方式不同的PV,这里分别演示一下PVC与这两种PV的绑定过程,并介绍这种绑定的背后机制。
4.3.1 PVC与存储方式为hostPath的PV绑定
# 创建底层存储为hostPath的PV
[root@master 7-storage]# kubectl apply -f storage-hostpath-pv.yaml
persistentvolume/storage-hostpath-pv created
# PV创建成功,容量为200MB,访问模式RWO,回收策略为Retain,状态为Available,CLAIM字段为空,还没有PVC与之绑定,STORAGECLASS为空,说明是静态创建的PV。
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-pv 200Mi RWO Retain Available <unset> 3s
# 创建PVC
[root@master 7-storage]# kubectl apply -f storage-pvc.yaml
persistentvolumeclaim/storage-pvc created
# PVC创建成功,状态为已绑定,绑定的PV为storage-hostpath-pv,这个PV的容量为200MB,访问模式为RWO。
注意这个200MB是PV的容量,并不一定是PVC要求的容量,因为只要PVC要求容量小于PV容量,两者就可以绑定成功。
如果PVC没有和任何PV绑定,该PVC的STATUS为Pending状态,后面的VOLUME、CAPACITY、ACCESS MODES都为空。
[root@master 7-storage]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
storage-pvc Bound storage-hostpath-pv 200Mi RWO <unset> 5s
# 再次查看PV状态,可以看到其STATUS字段和CLAIM字段已经更新。
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-pv 200Mi RWO Retain Bound default/storage-pvc <unset> 19s
4.3.2 PVC与存储方式为nfs的PV绑定
# 删除之前创建的pv和pvc,应该先删除PVC再删除PV,否则在执行PV删除时,命令执行完不会退出,直到PVC删除后才会退出该命令。
[root@master 7-storage]# kubectl delete pvc storage-pvc
persistentvolumeclaim "storage-pvc" deleted
# PVC删除之后,PV的状态变为Released,这种状态的PV是无法被重新绑定的,因为PVC只绑定状态为Availabel的PV。
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-pv 200Mi RWO Retain Released default/storage-pvc <unset> 3m4s
# 删除PV
[root@master 7-storage]# kubectl delete pv storage-hostpath-pv
persistentvolume "storage-hostpath-pv" deleted
# 确认PV和PVC都已经被删除
[root@master 7-storage]# kubectl get pv,pvc
No resources found
# 创建底层存储为nfs的PV
[root@master 7-storage]# kubectl apply -f storage-nfs-pv.yaml
persistentvolume/storage-nfs-pv created
# 创建成功
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-nfs-pv 200Mi RWO Retain Available <unset> 3s
# 创建PVC
[root@master 7-storage]# kubectl apply -f storage-pvc.yaml
persistentvolumeclaim/storage-pvc created
# PVC创建成功,并且和PV也已经绑定
[root@master 7-storage]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
storage-pvc Bound storage-nfs-pv 200Mi RWO <unset> 3s
# PV状态也已更新
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-nfs-pv 200Mi RWO Retain Bound default/storage-pvc <unset> 20s
4.3.3 PVC与PV绑定规则
前面我们直观看到了PVC与PV的绑定过程,那如果同时创建了nfs与hostPath这两种类型的PV,PVC会与哪个PV绑定呢?这取决于PVC与PV绑定规则。
PVC与PV绑定规则为先过滤后筛选。
(一) 过滤
Kubernetes在把PV(PersistentVolume)与PVC(PersistentVolumeClaim)绑定时,会按下面5个条件逐项检查PV,只有全部符合要求,才会进入被绑定的流程。
(1) 存储容量(Capacity)
规则:PV的spec.capacity.storage必须 ≥ PVC的spec.resources.requests.storage。
举例:
如果PV容量20GB,PVC需要10GB,符合绑定要求,继续检查下一个条件
如果PV容量5GB,PVC需要10GB,不符合绑定要求,该PV不会被PVC绑定
(2)访问模式(AccessModes)
规则:PV的spec.accessModes列表符合PVC的spec.accessModes列表要求。
访问模式常见取值:
ReadWriteOnce(RWO) – 单节点读写
ReadOnlyMany(ROX) – 多节点只读
ReadWriteMany(RWX) – 多节点读写
举例:
如果PV支持[RWO, ROX],PVC要求[RWO],符合绑定要求,继续检查下一个条件
如果PV支持[RWO],PVC要求[RWX],不符合绑定要求,该PV不会被PVC绑定
(3)存储类名字(StorageClassName)
规则:存储类名字需要一致才符合绑定关系,如果PVC的这个字段为空(“”),则只能匹配同样留空的PV。
举例:
如果PV的storageClassName为fast-ssd,PVC的storageClassName为fast-ssd,则符合绑定要求,继续检查下一个条件
如果PV的storageClassName为空(即"“),PVC的storageClassName也为空(即”“),则符合绑定要求,继续检查下一个条件
如果PV的storageClassName为(fast-ssd),PVC的storageClassName为空(即”"),则不符合绑定要求,该PV不会被PVC绑定
(4)选择器(Selector)
规则:如果PVC在资源清单中声明了spec.selector,PV的所有标签必须完全命中该selector。
举例:
有一个PVC,其selector为: { tier: database, env: prod },
如果PV labels为: { tier: database, env: prod, zone: east },则符合绑定要求,继续检查下一个条件
如果PV labels为: { tier: database },由于与PVC相比,缺少一个env: prod,不符合绑定要求,该PV不会被PVC绑定
(5)状态(Status)
规则:PV状态必须为Available才可以被绑定,已被绑定的PV(Bound)或者处于释放状态的PV(Released)不参与再匹配。
(二) 筛选
按照上面1到5的条件过滤出所有符合要求的PV,如果有多个,则按照如下原则进行筛选。
(1) 容量优先原则
选择能够满足PVC要求容量的最小容量的那个PV,避免资源浪费。
举例:如果有两个PV符合绑定条件,一个20GB,一个50GB,PVC要求的容量是10GB,则20GB容量的PV会被选中。
(2) 时间有限原则
按照时间原则,先创建的那个PV先被使用
举例:如果两个PV,一个先创建,一个后创建,则先创建的那个PV会被选中。
(3) 静态优先原则
在K8s中,PV可以手动创建,这个又叫静态创建,也可以由StorageClass的Provisioner动态创建。在PV选择过程中,会先找静态创建的PV,只有没有符合条件的才会继续寻找动态创建的PV。
举例:如果有两个PV,一个静态创建,一个动态创建,则静态创建的PV会被选中。
总结一下,PV和PVC绑定规则为,先过滤后筛选。
过滤的要点是容量够、模式对、类名同、标签中、状态闲。
筛选的要点是容量优先、时间优先和静态优先。
注:PV和PVC创建先后顺序并不影响两者绑定。
如果先创建PV再创建PVC,会按照先过滤后筛选的规则进行绑定。
如果先创建PVC再创建PV,则PVC状态处于Pending,一直处于挂起状态,这和和先创建PV再创建PVC但过滤筛选后没找到符合要求的PV效果相同。
4.4 PV访问模式
PV的accessMode字段描述了PV的访问模式,即这块存储支持怎么样的读写方式。
这个字段有三个取值:
- ReadWriteOnce(RWO):单节点读写,只能被一个节点以读写方式挂载,只能在该节点上的多个Pod间共享该存储区域。
- ReadOnlyMany(ROX): 多节点只读,可以被多个节点同时挂载,但只能读不能写。
- ReadWriteMany(RWX):多节点读写,可以被多个节点同时读写,这个需要底层存储本身支持并发写(NFS、CephFS等都支持这种模式)。
PV可以声明多个访问模式,这叫访问模式列表,如[ReadWriteOnce, ReadWriteMany],表示这块存储既支持单节点读写,也支持多节点读写,至于具体按哪种方式挂载,是PVC的事情。
如果PV声明了访问模式列表为[ReadWriteOnce, ReadOnlyMany],PVC也声明了一个访问模式列表为[ReadWriteOnce, ReadWriteMany],则最终按照两者的交集方式挂载,即ReadWriteOnce。
如果PV声明的访问模式列表为[ReadWriteOnce, ReadWriteMany],PVC声明的访问模式列表为[ReadWriteOnce, ReadWriteMany],两者的交集有两个,那具体实际挂载到Pod时,使用哪种方式呢?
K8s访问模式遵循最严格模式,以避免潜在的多节点写冲突,这三种访问模式的优先级为RWO > ROX > RWX。所以在RWO和RWX同时存在的情况下,优先按照RWO方式挂载。
4.5 PV回收策略
PV的persistentVolumeReclaimPolicy字段描述了PV的回收策略。
这个字段有三个取值:
- Retain:PVC删除后PV保留,数据仍在,需管理员手动清理并重新使PV可用。
- Delete:PVC删除后PV及底层存储(云盘、NFS 子目录等)一并自动删除。
- Recycle(已弃用):PVC删除后执行rm -rf /volume/*,清空后PV重新变为 Available,仅支持hostPath和nfs,仅用于老版本K8s,官方已不推荐。
下面我们以一个示例解释PV回收策略:
(1) 创建一个新的yaml文件,命名为storage-hostpath-delete-pv.yaml,其内容与storage-hostpath-pv.yaml相似,只是回收策略改为Delete
[root@master 7-storage]# cat storage-hostpath-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: storage-hostpath-pv
spec:
capacity:
storage: 200Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
hostPath:
path: /root/k8s-test/7-storage/data/hostpath
type: DirectoryOrCreate
[root@master 7-storage]# cat storage-hostpath-delete-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: storage-hostpath-delete-pv
spec:
capacity:
storage: 200Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: ""
hostPath:
path: /root/k8s-test/7-storage/data/hostpath-delete
type: DirectoryOrCreate
(2) 在这两个挂载目录分别创建两个文件,内容如下
[root@master 7-storage]# cat /root/k8s-test/7-storage/data/hostpath/index.html
deploy use pvc storage, pv use hostpath
[root@master 7-storage]# cat /root/k8s-test/7-storage/data/hostpath-delete/test.txt
hostpath-delete-pv test
(3) 创建两个PV
[root@master 7-storage]# kubectl get pvc
No resources found in default namespace.
[root@master 7-storage]# kubectl get pv
No resources found
[root@master 7-storage]# kubectl apply -f storage-hostpath-pv.yaml
persistentvolume/storage-hostpath-pv created
[root@master 7-storage]# kubectl apply -f storage-hostpath-delete-pv.yaml
persistentvolume/storage-hostpath-delete-pv created
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-delete-pv 200Mi RWO Delete Available <unset> 3s
storage-hostpath-pv 200Mi RWO Retain Available <unset> 9s
(4) 删除这两个PV
[root@master 7-storage]# kubectl delete pv storage-hostpath-pv storage-hostpath-delete-pv
persistentvolume "storage-hostpath-pv" deleted
persistentvolume "storage-hostpath-delete-pv" deleted
[root@master 7-storage]# kubectl get pv
No resources found
(5) 检查是否自动删除
[root@master 7-storage]# cat /root/k8s-test/7-storage/data/hostpath/index.html
deploy use pvc storage, pv use hostpath
[root@master 7-storage]# cat /root/k8s-test/7-storage/data/hostpath-delete/test.txt
hostpath-delete-pv test
可以发现,hostpath目录下的index.html文件仍存在,符合我们的预期。但hostpath-delete目录下仍有test.txt文件,这是为什么呢?
PV的回收策略只在PVC删除时触发,PV对象本身的删除并不会自动删除底层存储。
并且,只有动态生成的PV才会被自动删除,手动静态生成的PV也不会被自动清理掉底层资源。至于如何动态生成PV,涉及到我们本章后面讲到的存储类StorageClass。
4.6 PVC与PV重新绑定
PVC删除后,与之绑定的PV将处于Released状态,此时无法再被绑定,但我们可以通过kubectl edit调整PV的状态,使其重新变为Availabel。
下面是操作步骤:
(1) 创建PV和PVC,两者自动绑定
[root@master 7-storage]# kubectl get pvc
No resources found in default namespace.
[root@master 7-storage]# kubectl get pv
No resources found
[root@master 7-storage]# kubectl apply -f storage-hostpath-pv.yaml
persistentvolume/storage-hostpath-pv created
[root@master 7-storage]# kubectl apply -f storage-pvc.yaml
persistentvolumeclaim/storage-pvc created
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-pv 200Mi RWO Retain Bound default/storage-pvc <unset> 13s
[root@master 7-storage]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
storage-pvc Bound storage-hostpath-pv 200Mi RWO <unset> 10s
(2) 删除PVC
可以看到pvc被删除后,与其绑定的pv状态为Released。
[root@master 7-storage]# kubectl delete pvc storage-pvc
persistentvolumeclaim "storage-pvc" deleted
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-pv 200Mi RWO Retain Released default/storage-pvc <unset> 38s
(3) 再次创建PVC
再次创建pvc,可以看到,由于PV不是Available状态,不符合绑定规则的筛选条件,无法绑定。PVC处于Pending状态,等待符合条件的PV
[root@master 7-storage]# kubectl apply -f storage-pvc.yaml
persistentvolumeclaim/storage-pvc created
[root@master 7-storage]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
storage-pvc Pending <unset> 2s
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-pv 200Mi RWO Retain Released default/storage-pvc <unset> 52s
(4) 修改PV
我们先删除刚才创建的处于Pending状态的PVC,以防PV修改后立即被PVC绑定,看不到修改后的真实状态
[root@master 7-storage]# kubectl delete pvc storage-pvc
persistentvolumeclaim "storage-pvc" deleted
执行kubectl edit命令修改PV
[root@master 7-storage]# kubectl edit pv storage-hostpath-pv
persistentvolume/storage-hostpath-pv edited
刚才修改的内容为:删除spec属性下的claimRef字段及其子字段,像vim操作那样:wq保存退出。
(。。。部分内容略。。。)
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 200Mi
claimRef: # 删除这个字段及下面的子字段
apiVersion: v1
kind: PersistentVolumeClaim
name: storage-pvc
namespace: default
resourceVersion: "76133"
uid: f46c9d42-9522-41eb-a152-f94a1652674d
hostPath:
path: /root/k8s-test/7-storage/data/hostpath
type: DirectoryOrCreate
(。。。部分内容略。。。)
修改后查看pv状态,此时已经变为Availabel
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-pv 200Mi RWO Retain Available <unset> 10m
(5) 创建PVC
创建PVC后,可以发现PV又和PVC自动绑定。注意看这里的AGE,PVC生存时间7秒,PV生存时间为5分25秒,PVC为新创建,PV为之前创建的,kubectl edit不会改变资源生存时间。
[root@master 7-storage]# kubectl apply -f storage-pvc.yaml
persistentvolumeclaim/storage-pvc created
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
storage-hostpath-pv 200Mi RWO Retain Bound default/storage-pvc <unset> 5m25s
[root@master 7-storage]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
storage-pvc Bound storage-hostpath-pv 200Mi RWO <unset> 7s
4.7 存储类StorageClass和动态创建PV
前面我们提到过,PV有静态创建和动态创建这两种,静态创建很容易理解,就是使用yaml清单文件,kubectl apply,创建一个PV,那什么是动态创建PV呢?
在K8s中,当我们创建一个PVC,系统自动生成一个满足符合要求的PV,并自动绑定,这样的PV就是动态创建的PV。
前面我们写的PVC资源清单文件,storageClassName总是空,那这个StorageClass又是什么呢?
StorageClass中文名称存储类,是一种与存储相关的资源对象,描述了集群中的存储类型分类,它是一种定义存储卷的配置模板,K8s可以根据这个模板中的参数动态生成PV。
4.7.1 StorageClass资源清单
一个典型的StorageClass对象的资源清单文件如下:
[root@master dynamic-pv]# cat nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storageclass
provisioner: nfs-client-provisioner
parameters:
archiveOnDelete: "false"
allowVolumeExpansion: true
volumeBindingMode: Immediate
reclaimPolicy: Delete
这份YAML定义了一个存储类StorageClass,名字叫nfs-storageclass,用来告诉Kubernetes凡是引用这个StorageClass的PVC,都让nfs-client-provisioner去动态创建/删除PV,PVC被删除时,PV和nfs子目录一并清理。
各自段的具体含义如下:
字段 | 含义 |
---|---|
apiVersion: storage.k8s.io/v1 | 使用StorageClass官方API版本。 |
kind: StorageClass | 资源类型:存储类资源。 |
metadata.name: nfs-storageclass | 存储类名字,PVC里写storageClassName: nfs-storageclass即可引用。 |
provisioner: nfs-client-provisioner | 提供者,指定Provisioner插件,负责PV自动创建。 |
parameters.archiveOnDelete: “false” | 删除时是否归档:false表示PVC删除时直接删除对应PV及底层nfs目录, true表示PVC删除时会把PV底层的nfs目录改名为archived-xxx进行软归档。 |
allowVolumeExpansion: true | 允许在线扩容,如PVC申请1GB扩容到5GB时,直接修改PV容量,无需重建。 |
volumeBindingMode: Immediate | 绑定模式,PVC创建后立即绑定(Immediate)还是等Pod调度之后再绑定(WaitForFirstConsumer)。 |
reclaimPolicy: Delete | PVC被删除时,PV+nfs子目录一并自动清理;若用Retain则只删PV对象,目录保留。 |
这里面有几点需要特别注意的地方:
- provisioner,这个是PV创建的提供者,也叫供应商,它负责动态创建PV。本文为nfs-client-provisioner,是一个Deployment。不是系统默认的,需要我们自己去创建。点击这里下载本文配套资料包,获取离线版镜像文件:https://download.csdn.net/download/field1003/91965690
- volumeBindingMode,默认为Immediate,立即绑定,本地存储(local-path、local-volume-provisioner)通常显式设置为WaitForFirstConsumer,PVC创建之后不会绑定PV,只有真正使用时才会绑定。
- reclaimPolicy,值有Retain和Delete,如果是Retain,则PVC删除时,PV会被保留;如果是Delete,PVC删除时,PV以及其存储的内容也会被自动删除。
- 多个存储类可以共用一个provisioner,如,我们使用nfs这种类型的provisioner,创建两个StorageClass,一个reclaimPolicy为Retain,一个reclaimPolicy为Delete,则前者PVC删除时PV会保留,后者PVC删除时PV不会保留,底层存储数据也同时被删除。
4.7.2 使用local-path StorageClass(存储类)自动创建PV
前面我们熟悉了如何创建一个StorageClass,但这样创建的StorageClass是无法自动创建PV的,因为没有对应的provisioner,这个provisioner需要我们自己创建。
如果你是使用本教程搭建的环境,那它就已经自带了一个StorageClass,我们可以用这个StorageClass了解PV自动创建的过程。
通过kubectl get sc命令可以查看当前集群中有哪些StorageClass。
可以看到,目前集群中只有一个local-path存储类,后面的default表示如果有多个相同类型的存储类,默认使用它。
这里需要提到的是,PVC有一个storageClassName字段,storageClassName: ""和PVC资源清单没有这一行,含义完全不同。前者是使用名字为空的存储类,后者是使用默认存储类。
[root@master dynamic-pv]# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 2d21h
[root@master dynamic-pv]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 2d21h
由于这个默认的local-path PVC绑定模式为等待Pod调度绑定,所以我们再创建一个Deploy使用这个PVC。
(1) 创建PVC
这个PVC资源清单和之前的资源清单,不同之处在于这里指定了使用local-path的存储类动态创建PV
[root@master 7-storage]# cat storage-local-path-sc-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: storage-local-path-sc-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 200Mi
storageClassName: "local-path"
(2) 创建Deploy
这里挂载的PVC为storage-local-path-sc-pvc
[root@master 7-storage]# cat nginx-lsc-pvc-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-lsc-pvc-deploy
labels:
app: nginx-lsc-deploy
spec:
selector:
matchLabels:
app: nginx-lsc-deploy
replicas: 3
template:
metadata:
labels:
app: nginx-lsc-deploy
spec:
containers:
- name: nginx
image: nginx:1.27.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: pvc-volume
mountPath: /usr/share/nginx/html
volumes:
- name: pvc-volume
persistentVolumeClaim:
claimName: storage-local-path-sc-pvc
(3) 创建PVC
可以看到由于local-path这个StorageClass绑定模式为WaitForFirstConsumer,所以这里还没有绑定PV,PVC处于Pending状态。
[root@master 7-storage]# kubectl get pv,pvc
No resources found
[root@master 7-storage]# kubectl apply -f storage-local-path-sc-pvc.yaml
persistentvolumeclaim/storage-local-path-sc-pvc created
[root@master 7-storage]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
storage-local-path-sc-pvc Pending local-path <unset> 4s
[root@master 7-storage]# kubectl get pv
No resources found
(4) 创建Deploy
创建完Deploy,可以看到PVC已经自动绑定了一个PV
[root@master 7-storage]# kubectl apply -f nginx-lsc-pvc-deploy.yaml
deployment.apps/nginx-lsc-pvc-deploy created
[root@master 7-storage]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
storage-local-path-sc-pvc Bound pvc-54541543-ce4d-46f8-97f3-bb2ec903e20e 200Mi RWO local-path <unset> 16s
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-54541543-ce4d-46f8-97f3-bb2ec903e20e 200Mi RWO Delete Bound default/storage-local-path-sc-pvc local-path <unset> 3s
(5) 删除Deploy
删除Deploy并不影响PVC和PV的绑定关系
[root@master 7-storage]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-lsc-pvc-deploy 3/3 3 3 30s
[root@master 7-storage]# kubectl delete deploy nginx-lsc-pvc-deploy
deployment.apps "nginx-lsc-pvc-deploy" deleted
[root@master 7-storage]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
storage-local-path-sc-pvc Bound pvc-54541543-ce4d-46f8-97f3-bb2ec903e20e 200Mi RWO local-path <unset> 52s
[root@master 7-storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-54541543-ce4d-46f8-97f3-bb2ec903e20e 200Mi RWO Delete Bound default/storage-local-path-sc-pvc local-path <unset> 39s
使用kubectl describe命令可以看到PV的真实路径
[root@master 7-storage]# kubectl describe pv pvc-54541543-ce4d-46f8-97f3-bb2ec903e20e
Name: pvc-54541543-ce4d-46f8-97f3-bb2ec903e20e
Labels: <none>
(。。中间内容略。。)
Source:
Type: LocalVolume (a persistent volume backed by local storage on a node)
Path: /var/lib/rancher/k3s/storage/pvc-54541543-ce4d-46f8-97f3-bb2ec903e20e_default_storage-local-path-sc-pvc
Events: <none>
(6) 删除PVC
删除PVC后,PV也会被删除,由于StorageClass的RECLAIMPOLICY为Delete,PV和其子目录也会被删除。
PV未删除前存储目录存在
[root@master 7-storage]# ll /var/lib/rancher/k3s/storage/pvc-54541543-ce4d-46f8-97f3-bb2ec903e20e_default_storage-local-path-sc-pvc
total 8.0K
drwxrwxrwx. 2 root root 4.0K Aug 13 22:52 .
drwx------. 3 root root 4.0K Aug 13 22:52 ..
删除PVC,PV也被删除
[root@master 7-storage]# kubectl delete pvc storage-local-path-sc-pvc
persistentvolumeclaim "storage-local-path-sc-pvc" deleted
[root@master 7-storage]# kubectl get pvc
No resources found in default namespace.
[root@master 7-storage]# kubectl get pv
No resources found
PV被删除,对应的底层存储目录也已经被删除
[root@master 7-storage]# ll /var/lib/rancher/k3s/storage/pvc-54541543-ce4d-46f8-97f3-bb2ec903e20e_default_storage-local-path-sc-pvc
ls: cannot access '/var/lib/rancher/k3s/storage/pvc-54541543-ce4d-46f8-97f3-bb2ec903e20e_default_storage-local-path-sc-pvc': No such file or directory
4.7.3 创建及使用nfs StorageClass(存储类)
上一节我们看到local-path存储类可以自动创建销毁PV,但由于这个路径太深,不是我们想要的,并且它只能创建本地存储的PV,无法创建nfs存储的PV,所以我们这里自己创建一个存储类。
然后我们创建一个PVC资源,使用这个存储类自动创建和销毁PV。
(1) StorageClass资源清单
下面是一份存储类资源清单文件,直接kubectl apply -f执行就可以成功创建一个存储类,但它并不会真正创建成功PV,因为这个存储类并没有一个提供者provisioner。
[root@master dynamic-pv]# cat nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storageclass
provisioner: nfs-client-provisioner
parameters:
archiveOnDelete: "false"
allowVolumeExpansion: true
reclaimPolicy: Delete
(2) provisioner Deploy资源清单
我们再创建一个provisioner。provisioner是一个Deployment资源对象。
为了便于观察,我们将provisioner创建在了default命名空间下。
[root@master dynamic-pv]# cat nfs-client-provisioner.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-serviceaccount
containers:
- name: nfs-client-provisioner
#image: quay.io/external_storage/nfs-client-provisioner:latest
image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: "nfs-client-provisioner"
- name: NFS_SERVER
value: "192.168.88.130"
- name: NFS_PATH
value: "/root/data/nfs-client-root"
volumes:
- name: nfs-client-root
nfs:
server: 192.168.88.130
path: /root/data/nfs-client-root
注:
本Deployment所需镜像名为nfs-subdir-external-provisioner:v4.0.2,上面那个被注释的镜像nfs-client-provisioner:latest也可以。如因网络问题无法下载,可以通过docker load -i xxx.tar离线导入,离线镜像在本文配套资料包中。
点击下载资料包:https://download.csdn.net/download/field1003/91965690。
本Deploy需要特定账户执行,serviceAccountName:nfs-client-serviceaccount,后续需要创建对应的账户。
请确保nfs server的/root/data/nfs-client-root目录已添加到/etc/exports并且已经通过执行exportfs -arv命令使更改的配置生效。
nfs服务出现问题请参考之前环境部署一章。
(3) 角色及权限清单
镜像也下载成功了,Deployment资源也有了,但我们并不能通过kubectl apply -f nfs-client-provisioner.yaml创建这个Deployment,因为此时还缺少执行动态创建PV的操作的权限,这个权限需要赋给serviceAccountName对应的账户。
首先创建一个ServiceAccount账户资源清单,然后创建一个ClusterRole角色资源清单(这个角色资源里面有权限规则描述),最后创建ClusterRoleBinding资源清单,把角色和账户绑定。
我们把这三个资源清单写到一个文件中,并为其取名为nfs-storageclass-rbac.yaml。
[root@master dynamic-pv]# cat nfs-storageclass-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-serviceaccount
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-client-clusterrole
rules:
- apiGroups: [""]
resources: ["endpoints", "persistentvolumes", "persistentvolumeclaims"]
verbs: ["get", "list", "watch", "create", "delete", "update", "patch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: nfs-client-clusterrolebinding
subjects:
- kind: ServiceAccount
name: nfs-client-serviceaccount
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-clusterrole
apiGroup: rbac.authorization.k8s.io
(4) 创建资源对象
至此,我们有了三个配置文件:nfs-storageclass-rbac.yaml
nfs-client-provisioner.yaml
nfs-storageclass.yaml
这三个文件依赖关系为:nfs-storageclass.yaml
需要nfs-client-provisioner.yaml
这个提供者,nfs-client-provisioner.yaml
需要的权限在nfs-storageclass-rbac.yaml
中描述。
从上到下依次执行上面三个yaml文件。
# 查看执行rbac执行前的初始状态,只有一个default的ServiceAccount
[root@master dynamic-pv]# kubectl get serviceaccount
NAME SECRETS AGE
default 0 15d
[root@master dynamic-pv]# kubectl get sa
NAME SECRETS AGE
default 0 15d
[root@master dynamic-pv]# kubectl get clusterrole | grep 'NAME\|nfs'
NAME CREATED AT
[root@master dynamic-pv]# kubectl get clusterrolebinding | grep 'NAME\|nfs'
NAME ROLE AGE
# 创建rbac资源对象
[root@master dynamic-pv]# kubectl apply -f nfs-storageclass-rbac.yaml
serviceaccount/nfs-client-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nfs-client-clusterrole created
clusterrolebinding.rbac.authorization.k8s.io/nfs-client-clusterrolebinding created
# 再次查看serviceaccount,clusterrole,clusterrolebinding,可以看到全部创建成功
[root@master dynamic-pv]# kubectl get serviceaccount
NAME SECRETS AGE
default 0 15d
nfs-client-serviceaccount 0 9s
# sa是serviceaccout的缩写
[root@master dynamic-pv]# kubectl get sa
NAME SECRETS AGE
default 0 15d
nfs-client-serviceaccount 0 12s
[root@master dynamic-pv]# kubectl get clusterrole | grep 'NAME\|nfs'
NAME CREATED AT
nfs-client-clusterrole 2025-07-20T00:05:01Z
[root@master dynamic-pv]# kubectl get clusterrolebinding | grep 'NAME\|nfs'
NAME ROLE AGE
nfs-client-clusterrolebinding ClusterRole/nfs-client-clusterrole 26s
# 创建提供者
[root@master dynamic-pv]# kubectl apply -f nfs-client-provisioner.yaml
deployment.apps/nfs-client-provisioner created
# 提供者创建成功
[root@master dynamic-pv]# kubectl get deploy -n kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
coredns 1/1 1 1 2d22h
local-path-provisioner 1/1 1 1 2d22h
nfs-client-provisioner 0/1 0 0 14s
(5) 编写PVC资源清单
新建一个PVC配置文件,指定StorageClassName为刚才创建的StorageClass,读写模式为单节点读写,声明容量为300MB。
[root@master dynamic-pv]# cat nginx-autopv-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-autopv-pvc
spec:
storageClassName: "nfs-storageclass"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 300Mi
(6) 创建PVC
# 此时还没有pvc和pv
[root@master dynamic-pv]# kubectl get pvc,pv
No resources found in default namespace.
# 创建pvc
[root@master dynamic-pv]# kubectl apply -f nginx-autopv-pvc.yaml
persistentvolumeclaim/nginx-autopv-pvc created
# 可以看到已经成功创建了pvc,且自动创建了PVC所需要的PV,并且已经绑定。
[root@master dynamic-pv]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
nginx-autopv-pvc Bound pvc-722a351e-0dd9-4e60-a899-19069e8430a1 300Mi RWO nfs-storageclass <unset> 3s
# 这个PV从名字上看就是动态创建的,后面一串随机数字
[root@master dynamic-pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-722a351e-0dd9-4e60-a899-19069e8430a1 300Mi RWO Delete Bound default/nginx-autopv-pvc nfs-storageclass <unset> 6s
# 查看PV的底层存储路径,可以看到在192.168.88.130的/root/data/nfs-client-root下,自动创建了一个目录
[root@master dynamic-pv]# kubectl describe pv pvc-722a351e-0dd9-4e60-a899-19069e8430a1
Name: pvc-722a351e-0dd9-4e60-a899-19069e8430a1
Labels: <none>
Annotations: pv.kubernetes.io/provisioned-by: nfs-client-provisioner
Finalizers: [kubernetes.io/pv-protection]
StorageClass: nfs-storageclass
Status: Bound
Claim: default/nginx-autopv-pvc
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 300Mi
Node Affinity: <none>
Message:
Source:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 192.168.88.130
Path: /root/data/nfs-client-root/default-nginx-autopv-pvc-pvc-722a351e-0dd9-4e60-a899-19069e8430a1
ReadOnly: false
Events: <none>
(7) 删除PVC
# 删除PVC
[root@master dynamic-pv]# kubectl delete pvc nginx-autopv-pvc
persistentvolumeclaim "nginx-autopv-pvc" deleted
[root@master dynamic-pv]# kubectl get pvc
No resources found in default namespace.
# 可以看到PV也被删除,这是因为StorageClass的reclaimPolicy字段值为Delete,如果为Retain,PV及对应的nfs目录就不会被删除。
[root@master dynamic-pv]# kubectl get pv
No resources found
# 此时我们去nfs服务器192.168.88.130上查看,发现/root/data/nfs-client-root/default-nginx-autopv-pvc-pvc-722a351e-0dd9-4e60-a899-19069e8430a1目录已经被删除
4.7.4 综合示例:Deployment + PVC + 动态PV + NFS存储
我们将创建一个Deploy,PVC自动绑定动态创建的PV,PV底层存储为nfs。
下面是操作步骤:
(1) 创建Deployment资源清单
下面这个Deployment和之前的Deployment无任何不同,因为Deployment只和PVC打交道,至于PVC绑定的是动态创建的PV还是静态创建的PV,以及这个PV底层存储是nfs还是hostPath,都和Deployment无关。
[root@master dynamic-pv]# cat nginx-autopv-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-autopv-deploy
labels:
app: nginx-autopv-dm
spec:
selector:
matchLabels:
app: nginx-autopv-dm
replicas: 3
template:
metadata:
labels:
app: nginx-autopv-dm
spec:
containers:
- name: nginx
image: nginx:1.27.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: pvc-volume
mountPath: /usr/share/nginx/html
volumes:
- name: pvc-volume
persistentVolumeClaim:
claimName: nginx-autopv-pvc
(2) 创建PVC资源清单
创建一个PVC配置文件,指定StorageClassName为刚才创建的StorageClass,读写模式为单节点读写,声明容量为300MB。
[root@master dynamic-pv]# cat nginx-autopv-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-autopv-pvc
spec:
storageClassName: "nfs-storageclass"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 300Mi
(3) 创建PVC资源对象
[root@master dynamic-pv]# kubectl get pvc
No resources found in default namespace.
# 创建PVC
[root@master dynamic-pv]# kubectl apply -f nginx-autopv-pvc.yaml
persistentvolumeclaim/nginx-autopv-pvc created
# 可以看到PVC创建成功, 且自动绑定了一个动态创建的PV,创建PV使用的存储类为nfs-storageclass
[root@master dynamic-pv]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
nginx-autopv-pvc Bound pvc-06e97a51-db92-45de-9b6d-fcb46545449a 300Mi RWO nfs-storageclass <unset> 3s
[root@master dynamic-pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-06e97a51-db92-45de-9b6d-fcb46545449a 300Mi RWO Delete Bound default/nginx-autopv-pvc nfs-storageclass <unset> 19s
(4) 向PV底层存储路径写入index.html主页
# 查看PVC的真正路径,可以看到在192.168.88.130的/root/data/nfs-client-root下,自动创建了一个目录
[root@master dynamic-pv]# kubectl describe pv pvc-06e97a51-db92-45de-9b6d-fcb46545449a
Name: pvc-06e97a51-db92-45de-9b6d-fcb46545449a
Labels: <none>
Annotations: pv.kubernetes.io/provisioned-by: nfs-client-provisioner
Finalizers: [kubernetes.io/pv-protection]
StorageClass: nfs-storageclass
Status: Bound
Claim: default/nginx-autopv-pvc
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 300Mi
Node Affinity: <none>
Message:
Source:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 192.168.88.130
Path: /root/data/nfs-client-root/default-nginx-autopv-pvc-pvc-06e97a51-db92-45de-9b6d-fcb46545449a
ReadOnly: false
Events: <none>
# 这时我们在本地创建一个index.html主页,并通过ssh远程拷贝到nfs服务器。
# 当然,也可以直接在192.168.88.130这台nfs服务器的PV目录,手动创建这么一个主页文件。
[root@master dynamic-pv]# echo "<html><body>Nginx! With k8s nfs pvc dynamic pv!</body></html>" > index.html
[root@master dynamic-pv]# cat index.html
<html><body>Nginx! With k8s nfs pvc dynamic pv!</body></html>
[root@master dynamic-pv]# scp index.html root@192.168.88.130:/root/data/nfs-client-root/default-nginx-autopv-pvc-pvc-06e97a51-db92-45de-9b6d-fcb46545449a/
Authorized users only. All activities may be monitored and reported.
root@192.168.88.130's password:
index.html 100% 62 7.9KB/s 00:00
(5) 创建Deployment
[root@master dynamic-pv]# kubectl apply -f nginx-autopv-deploy.yaml
deployment.apps/nginx-autopv-deploy created
[root@master dynamic-pv]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nfs-client-provisioner 1/1 1 1 20m
nginx-autopv-deploy 3/3 3 3 3s
第一个deploy是动态创建PV的供应商deploy,后面三个是我们刚才创建的deploy
[root@master dynamic-pv]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-775bc8c8b5-687lq 1/1 Running 0 20m 10.42.0.223 master <none> <none>
nginx-autopv-deploy-689dfdb48b-8vjnr 1/1 Running 0 10s 10.42.0.224 master <none> <none>
nginx-autopv-deploy-689dfdb48b-js54v 1/1 Running 0 10s 10.42.0.226 master <none> <none>
nginx-autopv-deploy-689dfdb48b-rwxjb 1/1 Running 0 10s 10.42.0.225 master <none> <none>
(6) 访问nginx服务
由于没有创建Service,这里直接访问Pod的地址,可以看到能访问到正确的页面。
[root@master dynamic-pv]# curl 10.42.0.224
<html><body>Nginx! With k8s nfs pvc dynamic pv!</body></html>
思考:
那么,能不能更进一步,创建Deployment的时候,让PVC也动态创建呢?
答案是可行的,这里就需要用到volumeClaimTemplates了。
volumeClaimTemplates只能用在有状态服务控制器上,即StatefulSet控制器,下一章我们将介绍这个控制器。
5 配置类存储
配置类存储(Configuration Storage)在Kubernetes中指专门用来保存“非二进制数据”的两种对象:ConfigMap与Secret。它们以key-value形式挂载到Pod,供容器读取配置信息或敏感凭据,而不需要把数据打包进镜像或放到持久卷里。
5.1 ConfigMap
ConfigMap是Kubernetes用来集中存放“非机密”配置数据的一种键值对象。可以把配置文件、环境变量、命令行参数等从镜像里抽出来,放到ConfigMap,再由Pod按需注入或挂载,实现“改配置不用重新打镜像”。
下面介绍两种常见的使用方式,注入环境变量和作为挂载文件。
5.1.1 ConfigMap注入环境变量
下面是将ConfigMap资源对象的key-value值注入到环境变量的操作步骤。
(1) 创建ConfigMap资源清单
定义了两个环境变量,log-path和custom.conf,custom.conf有多行
[root@master configmap-as-env]# cat nginx-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-cm
labels:
app: nginx-cm
data:
log-path: "/var/log/nginx/"
custom.conf: |
[nginx-custom-conf]
workers: 10
error_level: info
other_custom_conf: other
(2) 创建Pod资源清单
在pod清单中使用valueFrom.ConfigMapKeyRef,将configMap注入Pod环境变量
[root@master configmap-as-env]# cat nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.27.3
env:
- name: log-path
valueFrom:
configMapKeyRef:
name: nginx-cm
key: log-path
- name: conf-content
valueFrom:
configMapKeyRef:
name: nginx-cm
key: custom.conf
restartPolicy: Never
(3) 创建资源前查看目前的ConfigMap和Pod对象
# 目前的这个是集群安装时默认的
[root@master configmap-as-env]# kubectl get configmap
NAME DATA AGE
kube-root-ca.crt 1 15d
# cm是configmap的缩写
[root@master configmap-as-env]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 15d
[root@master configmap-as-env]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-775bc8c8b5-687lq 1/1 Running 0 10h
(4) 创建ConfigMap和Pod
# 创建cm资源
[root@master configmap-as-env]# kubectl apply -f nginx-configmap.yaml
configmap/nginx-cm created
# 创建pod
[root@master configmap-as-env]# kubectl apply -f nginx-pod.yaml
pod/nginx-pod created
(5) 查看环境变量
进入pod
[root@master configmap-as-env]# kubectl exec -it nginx-pod -- /bin/bash
root@nginx-pod:/# env
conf-content=[nginx-custom-conf]
workers: 10
error_level: info
other_custom_conf: other
log-path=/var/log/nginx/
KUBERNETES_SERVICE_PORT_HTTPS=443
(。。。部分内容略。。。)
5.1.2 ConfigMap作为挂载文件
ConfigMap挂载文件一般都是作为配置文件,这里为了演示更直观,直接将这个文件作为nginx的index.html主页。
下面是操作步骤:
(1) 创建ConfigMap资源清单
data为index.html,是一个文件
[root@master configmap-as-file]# cat nginx-file-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-file-configmap
data:
index.html: |
<html><body>Nginx! With k8s pod file from file configMap!</body></html>
(2) 创建Pod资源清单
把ConfigMap作为file挂载到pod
[root@master configmap-as-file]# cat nginx-file-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-file-pod
spec:
containers:
- name: nginx
image: nginx:1.27.3
fileMounts:
- name: data-file
mountPath: /usr/share/nginx/html
files:
- name: data-file
configMap:
name: nginx-file-configmap
(3) 创建ConfigMap
查看现有cm
[root@master configmap-as-file]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 15d
nginx-cm 2 9m9s
创建cm
[root@master configmap-as-file]# kubectl apply -f nginx-file-configmap.yaml
configmap/nginx-file-configmap created
[root@master configmap-as-file]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 15d
nginx-cm 2 9m23s
nginx-file-configmap 1 3s
(4) 创建Pod
[root@master configmap-as-file]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-775bc8c8b5-687lq 1/1 Running 0 10h
nginx-pod 1/1 Running 0 9m41s
创建pod
[root@master configmap-as-file]# kubectl apply -f nginx-file-pod.yaml
pod/nginx-file-pod created
[root@master configmap-as-file]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-775bc8c8b5-687lq 1/1 Running 0 10h 10.42.0.223 master <none> <none>
nginx-pod 1/1 Running 0 9m58s 10.42.0.26 master <none> <none>
nginx-file-pod 1/1 Running 0 7s 10.42.0.27 master <none> <none>
(5) 查看挂载文件
# 在容器外,可以访问到挂载路径上的主页内容
[root@master configmap-as-file]# curl 10.42.0.27
<html><body>Nginx! With k8s pod file from file configMap!</body></html>
# 进入容器内,可以看到ConfigMap已经作为文件挂载到指定目录了。
[root@master configmap-as-file]# kubectl exec -it nginx-file-pod -- /bin/bash
root@nginx-file-pod:/# cat /usr/share/nginx/html/index.html
<html><body>Nginx! With k8s pod file from file configMap!</body></html>
5.2 Secret
Secret是Kubernetes用来专门存储敏感数据(密码、Token、证书、SSH 密钥等)的键值对象,与镜像解耦,并支持加密落盘、挂载文件、注入环境变量。
下面演示注入环境变量效果。
5.2.1 Secret注入环境变量
下面是将Secret资源对象注入到环境变量的操作步骤。
(1) 创建Secret资源清单文件
创建secret资源的yaml文件,username和password字段的值经过了Base64编码
[root@master secret]# cat nginx-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: nginx-sec
labels:
app: nginx-sec
type: Opaque
data:
username: dXNlcm5hbWU=
password: cGFzc3dvcmQ=
(2) 创建Pod资源清单
创建Pod资源的yaml文件,valueFrom和ConfigMap的相同,读取键时,使用secretKeyRef,注入到环境变量。
[root@master secret]# cat nginx-sec-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-sec-pod
spec:
containers:
- name: nginx
image: nginx:1.27.3
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: nginx-sec
key: username
- name: PASSWORD
valueFrom:
secretKeyRef:
name: nginx-sec
key: password
(3) 创建Secret资源对象
# 当前集群还没有Secret对象
[root@master secret]# kubectl get secret
No resources found in default namespace.
# 创建secret
[root@master secret]# kubectl apply -f nginx-secret.yaml
secret/nginx-sec created
[root@master secret]# kubectl get secret
NAME TYPE DATA AGE
nginx-sec Opaque 2 2s
(4) 创建Pod
# 没有与Secret相关的Pod
[root@master secret]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-775bc8c8b5-687lq 1/1 Running 0 11h
# 创建Pod
[root@master secret]# kubectl apply -f nginx-sec-pod.yaml
pod/nginx-sec-pod created
# Pod创建成功
[root@master secret]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-775bc8c8b5-687lq 1/1 Running 0 11h
nginx-sec-pod 1/1 Running 0 2s
(5) 查看环境变量
进入Pod,查看env。此时的值,为Base64解码后的可读值。
[root@master secret]# kubectl exec -it nginx-sec-pod -- /bin/bash
root@nginx-sec-pod:/# env | grep 'USERNAME\|PASSWORD'
USERNAME=username
PASSWORD=password

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。
更多推荐
所有评论(0)