基本
在使用云服务的时候,我们创建磁盘,需要在存储服务中提交一个请求,里面包含了磁盘类型,磁盘大小.之后,我们再把这个申请的磁盘挂载到所需的计算资源上即可.
而在k8s中. 当使用存储的时候,我们会涉及到以下主要概念
构建存储的概念:
- volume 声明存储服务,例如公有云的云盘(aws-ebs,阿里云-云盘),也可以是nfs/cephfs。
- pv 声明volume的信息,否则,k8s就不知道如何使用 volume。
使用存储的概念:
-
pvc 通过PV声明【期望】的存储。例如期望得到的磁盘大小、访问模式。
-
spec.template.spec.volumes 通过 PVC 或者 CM 创建 pod 所需的卷。如同你在aws中你将EBS绑定到EC2。
-
spec.template.spec.containers.volumeMounts 声明 pod 如何挂载 volumes。如同你登陆到aws的EC2中,并使用mount命令进行实际挂载。
你可能会发现,podtemplate.spec.volumes
和podtemplate.spec.containers.volumeMounts
之间缺少了一个格式化文件系统的过程,这是因为k8s已经在你通过pv资源帮你创建好了文件系统。当然你也可以通过将pv的模式改为块设备,不过这样一来,程序就需要确认是否可以识别块设备了。我想一般的应用程序是用不上这个类型的。https://kubernetes.io/docs/concepts/storage/persistent-volumes/#volume-mode
最后,pod 与 pv 与 pvc 之间是一对一强依赖关系。三者之间,在A资源对象【被B调用】的过程中,任何一个删除A资源对象的操作都不会立即执行,而是在【B调用方】生命周期结束之后才会执行。
volume卷
支持的存储类型
https://kubernetes.io/docs/concepts/storage/volumes/#volume-types
在使用某一个存储服务类型之前,详细的阅读存储服务的限制很有必要。
存储类型支持的模式:
https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes
-
ReadWriteOnce(单节点挂载读写,单节点内多pod读写)
-
ReadOnlyMany(多节点挂载只读,多pod只读)
-
ReadWriteMany(多节点挂载读写,多pod读写)
-
ReadWriteOncePod(单Pod读写)
⚠️ReadWriteOncePod 是新增,仅支持 csi + k8s 1.22+
NFS
使用NFS之前,需要先搭建NFS服务端。
例如云服务已有的NFS服务,或者自行搭建。
下面是k8s官方给出的自行搭建:
https://github.com/kubernetes/examples/tree/master/staging/volumes/nfs
本地
local volume 特点:
- 当前只支持静态PV。
- 显式的添加节点亲和性,系统通过PV的节点亲和性将Pod调度到本地卷所在节点上。
- 建立一个动态PV,并通过设置模式延迟绑定直到Pod调度到本地卷所在节点上。
一个例子:
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
capacity:
storage: 100Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- example-node
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
PV持久卷
状态
kubectl get pv -n <ns>
- Available(可用):表示可用状态,还未被任何 PVC 绑定
- Bound(已绑定):表示 PV 已经被 PVC 绑定
- Released(已释放):PVC 被删除,但是数据资源还在
- Failed(失败): 表示该 PV 的自动回收失败
类型
PV分为静态和动态。
静态pv: 需要先创建一定数量的pv, 然后才能通过pvc对应申请。若pvc无法匹配任意静态pv,则pvc会一直等待下去。
动态pv: 需要先构建供应商.主流的云商存储基本都有供应商, 区别在于是内置了,还是需要自行外部创建.然后再通过pvc去申请.
内置供应商列表: https://kubernetes.io/docs/concepts/storage/storage-classes/#provisioner
外置供应商部署: https://github.com/kubernetes-retired/external-storage
回收策略
spec.persistentVolumeReclaimPolicy
定义 PVC 被删除的时候,集群如何处理PV。
Retain 不处理PV。但 PV 默认不能直接再次复用,因为里面还有之前Pod的数据。
-
复用PV:
-
在静态PV下,PV的名字是固定的,因此删除PVC之后,通过删除
pv.spec.claimRef.uid
解除PV的Released
状态,就可以采用原有的PVC配置分配到。 -
在动态PV下,PV的名字是动态生成的, 因此删除PVC之后, 当PVC重新申请资源的时候, PV默认将会分配到一个新的资源路径。
-
Delete 同时删除PVC和Volume。只不过Volume是否删除取决于每一个StorageClass中parameters的定义。
- 当StorageClass是nfs-client时,开启
parameters.archiveOnDelete=true
,就可以定义不删除而是在nfs中将其数据目录重命名归档。 - 当StorageClass是微软/aws/阿里云的云盘的时候,一般默认是删除云盘。从而避免产生大量闲置云盘。
Recycle 删除PV。
- 当前只有nfs和hostPath支持.
- 已废弃。建议通过动态PV+Delete去控制。
静态PV
静态PV,必须由管理员用户先创建好pv.就如同你想构建一个云服务器,但需要先创建好一个云盘。
一个静态pv的例子, 这里以 nfs 为例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nas-nginx-pv # pv 名字,会被 pvc 引用
spec:
claimRef:
name: nas-nginx-pvc
namespace: default
capacity:
storage: 10Gi # 定义本持久卷大小
accessModes:
- ReadWriteMany # 定义本持久卷访问模式
persistentVolumeReclaimPolicy: Retain # 定义pvc删除后的策略
nfs:
path: /volume1/k8s # 定义 nfs 共享路径
server: 10.200.10.4 # 定义 nfs 服务器地址
-
claimRef 显式的指定允许绑定的PVC。
-
accessModes 访问模式。
-
persistentVolumeReclaimPolicy 详见PV回收策略。
静态PV对应的PVC
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nas-nginx-pvc
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
volumeName: nas-nginx-pv
- spec.volumeName 显式的指定要绑定的pv
动态PV
动态PV目的在于用户可以自行通过PVC申请资源,无需提前通过管理员创建PV。
动态PV的构建需要一个新的资源对象StroageClass,它通过provisioner
指定卷插件,从而对接不同的储存。卷插件其实就是一个存储类型的客户端。
卷插件都会拥有自己的一些特定参数,从而满足向卷供应商提供创建卷时所需要的信息。
当前支持的volume类型以及对应的卷插件参数:
https://kubernetes.io/docs/concepts/storage/storage-classes/#provisioner
⚠️ k8s并不提供所有的卷插件。
基于nfs构建StroageClass
以网络存储nfs为例。nfs属于外置供应商,因此没有内置卷插件。因此需要先创建。创建文档:
⚠️ 需要注意的是,nfs-client正常运行条件:
- 所有节点安装nfs-utils。
yum install nfs-utils -y
安装命令:
- 国外源
#############################################国外源###################################################
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm repo update
helm search repo --max-col-width 200 | grep nfs
helm install nfs-client nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--set storageClass.name=nfs-client \
--set nfs.server=10.200.10.4 \
--set nfs.path=/volume1/k8s \
--set storageClass.reclaimPolicy=Delete \
--set storageClass.archiveOnDelete=true \
--set storageClass.allowVolumeExpansion=true \
--set storageClass.defaultClass=true
- 阿里源(版本可能陈旧)
#############################################阿里源###################################################
helm repo add apphub https://apphub.aliyuncs.com
helm repo update
helm install nfs-client apphub/nfs-client-provisioner \
--set storageClass.name=nfs-client \
--set nfs.server=10.200.10.4 \
--set nfs.path=/volume1/k8s \
--set storageClass.reclaimPolicy=Delete \
--set storageClass.archiveOnDelete=true \
--set storageClass.allowVolumeExpansion=true \
--set storageClass.defaultClass=true
-
parameters.archiveOnDelete 当回收策略在被执行的时候(persistentVolumeReclaimPolicy),pv删除的同时申请的存储资源是否要归档。false就是不归档直接删除。这里归档的意思就是将申请的存储资源的物理路径前加一个archived前缀。最终格式:
archived-${NS_NAME}-${PVC_NAME}-${PV_NAME}
-
storageClass.allowVolumeExpansion 开启卷扩展。这个选项可以让你动态的调整 pvc 请求的对象大小。
其实这个参数没用,因为k8s目前不支持nfs动态扩展。
nfs-client会自动帮你创建一个 StroageClass。
kubectl get StorageClass/nfs-client -o yaml
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
meta.helm.sh/release-name: nfs-client
meta.helm.sh/release-namespace: default
storageclass.kubernetes.io/is-default-class: "true"
creationTimestamp: "2021-12-27T06:25:24Z"
labels:
app: nfs-subdir-external-provisioner
app.kubernetes.io/managed-by: Helm
chart: nfs-subdir-external-provisioner-4.0.14
heritage: Helm
release: nfs-client
......
name: nfs-client
parameters:
archiveOnDelete: "true"
provisioner: cluster.local/nfs-client-nfs-subdir-external-provisioner
reclaimPolicy: Delete
volumeBindingMode: Immediate
动态PV对应的PVC
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nas-vsftpd-pvc
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
在这里,我们通过 spec.storageClassName
显式指定StroageClass。
Pod中的PVC模板
PVC模板可以动态的创建PVC,然后PVC再通过StorageClass动态创建PV。
💥 volumeClaimTemplates 不支持 Deployment.spec
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: app-demo
spec:
...
spec:
containers:
...
volumeMounts:
- name: appdata
mountPath: /app/data
volumeClaimTemplates:
- metadata:
name: appdata # appdata 即 volumeMounts.name
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
在线扩展(仅支持动态pv)
https://kubernetes.io/docs/concepts/storage/persistent-volumes/#expanding-persistent-volumes-claims 文档中列举了支持扩展的存储类。
💔NFS是不被支持的。
另外,还需要满足下列条件:
-
feature-gates的
ExpandInUsePersistentVolumes
被开启。它在1.15版本以后默认开启。 -
StorageClass的
allowVolumeExpansion
被开启。 -
文件系统是XFS, Ext3, or Ext4。这个需要StorageClass的支持。一般来说volume属于块设备类型的资源才支持,比如aws的ebs。
你可以在这里确认StorageClass是否支持fstype属性。https://kubernetes.io/docs/concepts/storage/storage-classes/#parameters
满足上述条件后,你就可以通过修改pvc提升存储大小了。即使 pvc 正在被使用。
局限性
当一个 pod 同时申请多个 volume 的时候,可能会出现 volume-A 申请成功,volume-B 因物理容量不够申请失败的问题,此时 pod 将卡住。这种情况下,需要手动介入去清理。
PVC申请失败
需要我们手动的时候,都是资源申请失败或者不小心删除了PVC之类的。而不管是什么,我们第一目的是数据不丢。
因此,我们在清理故障对象资源的时候,应该遵循下列步骤:
- 将pv的回收策略
persistentVolumeReclaimPolicy
定义为Retain
- 删除pvc,这个时候 pv 对象将会保留,但是 pv 状态会变成 Released,这个状态下 pv 无法再被使用
- 删除pv对象字段
spec.claimRef.uid
,从而将pv与pvc解绑,从而使 pv 状态变为 Available - 将pvc对象字段
volumeName
设置为pv的名字,重建pvc - 恢复pv的回收策略。
Pod使用PVC
上述例子中的 nfs 卷类型为例。
Deployment配置:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vsftpd
namespace: it
labels:
app: vsftpd
spec:
serviceName: vsftpd
revisionHistoryLimit: 10
replicas: 1
selector:
matchLabels:
app: vsftpd
template:
metadata:
labels:
app: vsftpd
spec:
hostNetwork: true
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s01
containers:
- name: vsftp
image: fauria/vsftpd
ports:
- containerPort: 20
- containerPort: 21
env:
- name: LOG_STDOUT
value: STDOUT
resources:
requests:
memory: "128Mi"
cpu: "125m"
limits:
memory: "512Mi"
cpu: "250m"
volumeMounts:
- name: vsftpd-vol
subPath: vsftpd/data
mountPath: /home/vsftpd
- name: vsftpd-vc
subPath: vsftpd.conf
mountPath: /etc/vsftpd/vsftpd.conf
readOnly: true
- name: vsftpd-vc
subPath: virtual_users.txt
mountPath: /etc/vsftpd/virtual_users.txt
readOnly: true
- name: vsftpd-vc
subPath: admin
mountPath: /etc/vsftpd/virtual/admin
readOnly: true
volumes:
- name: vsftpd-vol
persistentVolumeClaim:
claimName: vsftpd-pvc
- name: vsftpd-vc
configMap:
name: vsftpd-cm
-
spec.template.spec.volumes
:定义Pod所用的卷 -
spec.template.spec.containers.volumeMounts
:定义nas-vsftpd-vol
如何挂载这里将 nfs 共享目录
<nfs_root>/vsftpd/data
挂载到容器里的/home/vsftpd
路径。subPath不存在的时候,nfs会自动创建。 -
configMap 参见临时卷
⚠️ subPath 方式的挂载无法接收数据更新。
临时卷
https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#types-of-ephemeral-volumes
k8s用来进行临时的数据存储或者临时调用数据。例如缓存/会话/密码/配置一类的。
ℹ️ 他们无需人为的提前创建PVC去绑定,而是直接在Pod中去调用。
临时卷均通过节点的kubelet自动管理,实现临时卷的PVC将随着POD的删除而自动删除。
-
emptyDir
apiVersion: v1 kind: Pod metadata: name: test-pd spec: containers: - image: k8s.gcr.io/test-webserver name: test-container volumeMounts: - mountPath: /cache name: cache-volume volumes: - name: cache-volume emptyDir: {}
emptyDir支持构建一个内存层缓存,也就是 tmpfs。你可以通过
emptyDir.medium: "Memory"
来开启。 -
configMap、secret
配置卷和密码卷,很重要的类型。当然,这只是ConfigMap和secret对象的其中一种使用方式。
⚠️ 需要先创建ConfigMap和secret对象,才能在Pod中引用他们。
-
Downward API
常用来给容器提供Pod信息。
-
generic ephemeral volumes 这一种是更加灵活的emptyDir,可以是网络存储,如果插件支持,则各种持久卷的特性它都有可能支持。从1.19开始支持,1.19也是alpha,默认不开启。
具体使用看https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#generic-ephemeral-volumes
Projected Volumes
可实现将volumes的所有列表项挂载在Pod同一个位置上。
详细使用见【k8s☞10应用配置与密码与信息提供】