Volume - 卷
K8s 中的 卷
,是指包含可被 Pod 中容器访问的数据的目录。
Docker 也有 卷(Volume) 的概念,但对它只有少量且松散的管理。 Docker 卷是磁盘上或者另外一个容器内的一个目录。 Docker 提供卷驱动程序,但是其功能非常有限。
K8s 支持 很多类型的卷,比如:cephfs
、configMap
、emptyDir
、hostPath
、local
、nfs
、persistentVolumeClaim
等等。Pod 可以同时使用任意数目的多种类型的卷。所采用的特定的卷类型将决定该目录如何形成、使用何种介质保存数据以及目录中存放的内容。
使用卷时, 在 .spec.volumes
字段中设置为 Pod 提供的卷,并在 .spec.containers[*].volumeMounts
字段中声明卷在容器中的挂载位置。例如:
apiVersion: v1
kind: Pod
metadata:
name: multi-volumes-pod
spec:
containers:
- name: test
image: busybox
volumeMounts:
- name: config-vol
mountPath: /etc/config
- name: test-volume
mountPath: /test-pd
volumes:
- name: config-vol
configMap:
name: log-config
items:
- key: log_level
path: log_level
- name: test-volume
hostPath:
# 宿主上目录位置
path: /data
卷不能挂载到其他卷之上,也不能与其他卷有硬链接。 Pod 配置中的每个容器必须独立指定各个卷的挂载位置。
按照卷的生命周期,又可分为 临时卷
和 持久卷
。
- 临时卷类型的生命周期与 Pod 相同。
- 持久卷可以比 Pod 的存活期长,因此,持久卷的存在时间会超出 Pod 中运行的所有容器,并且在容器重新启动时数据也会得到保留。
当 Pod 不再存在时,临时卷也将不再存在。但是持久卷会继续存在。
有些应用程序需要额外的存储,但并不关心数据在重启后是否仍然可用,即是否被持久地保存。例如,缓存服务经常受限于内存大小,将不常用的数据转移到比内存慢、但对总体性能的影响很小的存储中。另有些应用程序需要以文件形式注入的只读数据,比如配置数据或密钥。临时卷(Ephemeral Volume)就是为此类用例设计的,emptyDir
、configMap
、secret
等都属于临时卷。
PersistentVolume,PV - 持久卷
存储的管理是一个与计算实例的管理完全不同的问题。持久卷(PersistentVolume,PV)是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样,也是使用 卷插件 来实现的(持久卷的卷插件类型是卷的类型的子集),只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。
持久卷是集群中的一块存储,可以由管理员提前创建好若干 PV 供 Pod 使用。例如:
apiVersion: v1
kind: PersistentVolume
metadata:
name: block-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
volumeMode: Block
persistentVolumeReclaimPolicy: Retain
fc:
targetWWNs: ["50060e801049cfd1"]
lun: 0
readOnly: false
但为了更好的配置可移植性,以及将卷的创建和使用分由集群中不同的角色来负责,更通用的做法是仅在 Pod 中描述其对存储资源的需求,如存储空间大小和访问模式等。此时便需要使用 persistentVolumeClaim
类型的卷。
PersistentVolumeClaim,PVC - 持久卷申领
persistentVolumeClaim
卷用来将持久卷(PV)挂载到 Pod 中。持久卷申领(PersistentVolumeClaim,PVC)是用户在不知道环境细节的情况下”申领”持久存储的一种方法。持久卷申领,表达的是用户对存储的请求。与 Pod 会耗用节点资源类似,PVC 会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 也可以请求特定的大小和访问模式(例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载)。例如:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Block
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: nginx
volumeMounts:
- mountPath: "/var/www/html"
name: mypd
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim
可以将 PVC 狭义的理解为一个对提前创建好的 PV 的筛选策略,找到与 PVC 匹配的 PV 后,二者会进行绑定。Pod 将 PVC 当作卷来使用,并藉此访问存储资源。PVC 必须位于使用它的 Pod 所在的同一命名空间内。 集群在 Pod 的命名空间中查找 PVC,并使用它来获得绑定的 PV。 之后,卷会被挂载到宿主上并挂载到 Pod 中。
如果找不到匹配的 PV,PVC 会无限期地处于未绑定状态。当与之匹配的 PV 可用时,PVC 会被绑定。例如,即使某集群上供应了很多 50 Gi 大小的 PV,也无法与请求 100 Gi 大小的存储的 PVC 匹配。当新的 100 Gi PV 卷被加入到集群时,该 PVC 才有可能被绑定。
可以看到上面所描述的场景,是依赖于 K8s 的集群管理员提前创建好若干 PV 的。这种 PV 的供应方式称为静态供应
。
除静态供应外,还有一种动态供应
,即使用存储类(Storage Class)来动态创建一个存储卷。
StorageClass,SC - 存储类
PV 的动态供应是基于存储类(StorageClass,SC)来实现的:PVC 必须请求某个存储类 SC,同时集群管理员必须已经创建并配置了该类,这样动态供应卷的动作才会发生。如果 PVC 指定存储类为 ""
,则相当于为自身禁止使用动态供应的卷。
每个 StorageClass 都包含 provisioner
、parameters
和 reclaimPolicy
字段,这些字段会在 SC 需要动态分配 PersistentVolume 时使用到。例如:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
- debug
volumeBindingMode: Immediate
StorageClass 对象的命名(.metadata.name
)很重要,用户使用这个 name 来请求指定的 SC。当 SC 一旦被创建了,就不能再对其更新。
PVC 不必一定要指定某个 SC:
- 如果 PVC 的
storageClassName
属性值设置为""
,则被视为要请求的是没有设置存储类的 PV 卷(未设置persistentVolume.storageClassName
注解或者注解值为""
的 PV,此类 PV 对象在系统中不会被删除,因为这样做可能会引起数据丢失)。 - 如果将
persistentVolumeClaim.storageClassName
留空(nil),集群会使用默认 StorageClass 为用户自动供应一个存储卷。很多集群环境都配置了默认的 StorageClass,或者管理员也可以自行创建默认的 StorageClass。