集群级别资源,StorageClass 是 Kubernetes 中的一种资源对象,它定义了创建 Persistent Volume (PV) 的策略和方法。StorageClass 主要用于实现 PV 的动态供应,这意味着当用户创建了一个 Persistent Volume Claim (PVC) 时,Kubernetes 会根据所指定的 StorageClass 自动创建一个符合要求的 PV 并将其绑定到 PVC 上
StorageClass
作为对存储资源的抽象定义,对用户设置的 PVC
申请屏蔽后端存储的细节,一方面减少了用户对于存储资源细节的关注,另一方面减轻了管理员手工管理 PV
的工作,由系统自动完成 PV
的创建和绑定,实现动态的资源供应。基于 StorageClass
的动态资源供应模式将逐步成为云平台的标准存储管理模式。
StorageClass 资源对象的定义主要包括:名称、后端存储的提供者 (provisioner)、后端存储的相关参数配置(parameters)和回收策略(reclaimPolicy)、卷绑定模式(volumeBindingMode)
StorageClass 的名称很重要,将在创建 PVC 时引用,管理员应该准确命名具有不同存储特性的 StorageClass。
StorageClass 一旦被创建,则无法修改,如需更改,则只能删除原 StorageClass 资源对象并重新创建。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
# 存储类的名称,用户在 PVC 中引用
name: example-storage-class
storageclass.kubernetes.io/is-default-class: "true" # 设置为默认的 StorageClass
# 动态制备器的名称,需要与已安装的制备器匹配
provisioner: kubernetes.io/aws-ebs # 例子中使用的是 AWS EBS 制备器
# reclaimPolicy 定义了当 PVC 被删除时,PV 应该如何处理
reclaimPolicy: Delete # 可选值为 Retain 或 Delete
# 允许卷扩展
allowVolumeExpansion: true # 可选值为 true 或 false
# 定义了卷绑定到 Pod 的模式
volumeBindingMode: Immediate # 可选值为 Immediate 或 WaitForFirstConsumer
# 定义了存储系统需要的参数,这些参数会传递给制备器
parameters:
# 存储类型,根据制备器和存储系统的要求设置
type: gp2 # AWS EBS 的例子,对于其他系统可能有不同的值
# 存储 IOPS 性能,某些存储系统可能需要这个参数
iopsPerGB: "10" # 例子,具体值根据需求和制备器支持设置
# 存储的最小 IOPS 值,某些存储系统可能需要这个参数
minimumIOPS: "1000" # 例子,具体值根据需求和制备器支持设置
# 存储的最大 IOPS 值,某些存储系统可能需要这个参数
maximumIOPS: "20000" # 例子,具体值根据需求和制备器支持设置
# 存储的加密选项,某些存储系统可能支持加密
encrypted: "true" # 例子,具体值根据需求和制备器支持设置
# 存储的区域,对于跨区域存储系统可能需要这个参数
availabilityZone: "us-east-1a" # 例子,具体值根据制备器和存储系统的要求设置
# 存储的性能等级,某些存储系统可能提供不同的性能等级
performance: "high" # 例子,具体值根据制备器和存储系统的要求设置
# 定义了挂载选项,这些选项会在 PV 被挂载到节点时使用
mountOptions:
- debug # 例子,具体值根据需求设置,可能包括 "debug", "defaults", "ro" 等
# - other-option # 可以添加额外的挂载选项
# 允许的拓扑约束,定义了存储可以被哪些节点访问
allowedTopologies:
- matchLabelExpressions:
- key: topology.kubernetes.io/region
values:
- us-east-1
- key: topology.kubernetes.io/zone
values:
- us-east-1a
provisioner 指定了用于动态创建 PersistentVolume (PV) 的制备器(Provisioner)。制备器是一个插件,它负责在后端存储系统中根据 PersistentVolumeClaim (PVC) 的请求来创建存储资源,不同的存储插件支持不同的存储后端和服务提供商。当 PVC 被创建并且与之关联的 StorageClass 指定了一个制备器时,Kubernetes 会调用这个制备器来自动创建相应的 PV。
卷插件 | 内置制备器 | 配置示例 |
---|---|---|
AWSElasticBlockStore | √ | AWS EBS |
AzureFile | √ | Azure文件(已弃用) |
AzureDisk | √ | Azure Disk |
CephFS | - | - |
Cinder | √ | Open Stack Cinder |
FC | - | - |
FlexVolume | - | - |
GCEPersistentDisk | √ | gcePD |
Glusterfs | √ | GlusterFS |
iSCSI | - | - |
Local | - | Local |
NFS | - | NFS |
PortworxVolume | √ | portworx-volume |
RBD | √ | ceph-rbd |
VsphereVolume | √ | Vsphere |
Kubernetes 内置支持的 Provisioner 的命名都以 "kubernetes.io/" 开头
为了符合 StorageClass 的用法,自定义 Provisioner 需要符合存储卷的开发规范,外部存储供应商的作者对代码、提供方式、运行方式、存储插件(包括Flex)等具有完全的自由控制权。
代码仓库 kubernetes-sigs/sig-storage-lib-external-provisioner 包含一个用于为外部制备器编写功能实现的类库。你可以访问代码仓库 kubernetes-sigs/sig-storage-lib-external-provisioner 了解外部驱动列表。
例如,对NFS类型,Kubernetes没有提供内部的Provisioner,但可以使用外部的Provisioner。也有许多第三方存储提供商自行提供外部的Provisioner。
通过动态资源供应模式创建的PV将继承在StorageClass资源对象上设置的回收策略,配置字段名称为“reclaimPolicy“,可以设置的选项包括Delete(删除)和 Retain(保留)。
- 如果StorageClass没有指定reclaimPolicy,则默认值为Delete。
- 对于管理员手工创建的仍被StorageClass管理的PV,将使用创建PV时设置的资源回收策略。
PV 可以被配置为允许扩容,当 StorageClass 资源对象的 allowVolumeExpansion字段被设置为true时,将允许用户通过编辑PVC的存储空间自动完成PV的扩容。
下表描述了支持存储扩容的Volume类型和要求的Kubernetes最低版本:
支持存储扩容的 Volume 类型 | Kubernetes 最低版本 |
---|---|
gcePersistentDisk | 1.11 |
awsElasticBlock Store | 1.11 |
Cinder | 1.11 |
glusterfs | 1.11 |
RBD | 1.11 |
Azure File | 1.11 |
Azure Disk | 1.11 |
Portworx | 1.13 |
FlexVolume | 1.14(Alpha) |
CSI | 1.16(Beta) |
此功能仅可用于扩容卷,不能用于缩小卷。
通过StorageClass资源对象的mountOptions字段,系统将为动态创建的PV设置挂载选项。
并不是所有 PV类型都支持挂载选项,如果 PV不支持但 StorageClass 设置了该字段,则 PV将会创建失败。另外,系统不会对挂载选项进行验证,如果设置了错误的选项,则容器在挂载存储时将直接失败。
StorageClass 资源对象的 volumeBindingMode 字段设置用于控制何时将 PVC 与动态创建的 PV 绑定。
目前支持的绑定模式包括: Immediate 和 WaitForFirstConsumer。
存储绑定模式的默认值为 Immediate,表示当一个PersistentVolumeClaim (PVC)创建出来时,就动态创建PV并进行PVC与PV的绑定操作。
需要注意的是,对于拓扑受限 (Topology-limited) 或无法从全部Node访问的后端存储,将在不了解Pod调度需求的情况下完成PV的绑定操作,这可能会导致某些Pod无法完成调度。
WaitForFirstConsumer绑定模式表示PVC与PV的绑定操作延迟到第一个使用 PVC的Pod创建出来时再进行。
系统将根据Pod的调度需求,在Pod所在的Node上创建PV,这些调度需求可以通过以下条件(不限于)进行设置:
Pod对资源的需求
Node Selector
Pod亲和性和反亲和性设置
Taint和Toleration设置
目前支持 WaitForFirstConsumer 绑定模式的存储卷包括:
另外,有些存储插件通过预先创建好的PV绑定支持WaitForFirstConsumer模式,比如:
在使用WaitForFirstConsumer模式的环境中,如果仍然希望基于特定拓扑信息(Topology)进行PV绑定操作,则在StorageClass的定义中还可以通过 allowedTopologies字段进行设置。
下面的例子通过 matchLabelExpressions 设置目标 Node 的标签选择条件 (zone=us-central1-a或 us-central1-b) PV 将在满足这些条件的 Node 上允许创建
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/example
parameters:
type: pd-standard
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
- key: topology.kubernetes.io/zone
values:
- us-central-1a
- us-central-1b
后端存储资源提供者的参数设置,不同的 Provisioner 可能提供不同的参数设置。某些参数可以不显示设定,Provisioner 将使用其默认值。
目前 StorageClass 资源对象支持设置的存储参数最多为 512个,全部 key 和 value 所占的空间不能超过 256KiB。
下面举常见存储提供商(Provisioner)提供的 StorageClass 存储参数示例(以AWSElasticBlockStore存储卷为例子):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
csi.storage.k8s.io/fstype: xfs
type: io1
iopsPerGB: "50"
encrypted: "true"
allowedTopologies:
- matchLabelExpressions:
- key: topology.ebs.csi.aws.com/zone
values:
- us-east-2c
type (必需)
iopsPerGB (可选,仅当 type 为 io1 或 io2 时有效)
iops (可选,仅当 type 为 io1 时有效)
throughput (可选,仅当 type 为 st1 时有效)
encrypted (可选)
kmsKeyId (可选)
fsType (可选)
volumeSize (可选)
availabilityZone (可选)
multiAttachEnabled (可选)
snapshotId (可选)
tags (可选)
定义一组键值对,用于标记 EBS 卷。例如:
tags:
- key: "project"
value: "myproject"
- key: "owner"
value: "myteam"
在创建 SC 的 YAML 文件时,需要在 metadata 部分添加一个注解,以标记该 SC 为默认。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: low-latency
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: csi-driver.example-vendor.example
reclaimPolicy: Retain # 默认值是 Delete
allowVolumeExpansion: true
mountOptions:
- discard # 这可能会在块存储层启用 UNMAP/TRIM
volumeBindingMode: WaitForFirstConsumer
parameters:
guaranteedReadWriteLatency: "true" # 这是服务提供商特定的
kubectl patch storageclass <sc-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
kubectl patch storageclass <sc-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
如果你在集群中的多个 StorageClass 上将 storageclass.kubernetes.io/is-default-class 注解设置为 true,然后创建一个未设置 storageClassName 的 PersistentVolumeClaim (PVC), Kubernetes 将使用最近创建的默认 StorageClass。
角色 | 主机名 | ip地址 |
---|---|---|
nfs 服务端 + master 节点 | k8s-master1 | 192.168.112.10 |
nfs 客户端 + node 节点 | k8s-node1 | 192.168.112.20 |
nfs 客户端 + node 节点 | k8s-node2 | 192.168.112.30 |
yum install -y nfs-utils
mkdir -pv /data/nfs
echo "/data/nfs 192.168.112.0/24(rw,sync,no_root_squash)" > /etc/exports
exportfs -arv
systemctl restart nfs && systemctl enable nfs
showmount -e localhost
所有 node 节点
yum install -y nfs-utils
mkdir -pv /mnt/nfs
mount -t nfs 192.168.112.10:/data/nfs /mnt/nfs
如果在执行 mount -t nfs
命令时出现了错误,比如使用了错误的参数或者路径,你可以通过卸载当前的挂载点然后再重新挂载来修正错误。
确认当前的挂载状态
使用 mount
命令查看当前的所有挂载情况,找到错误挂载的条目。
mount
卸载错误的挂载点
# 知道错误的挂载点
sudo umount /mnt/nfs
# 知道 nfs 服务端 IP 地址和共享目录路径
sudo umount 192.168.112.10:/data/nfs
卸载时遇到设备/文件正在使用中
umount -f /mnt/nfs
强制卸载可能会导致数据丢失,因此只有在确定没有数据写入的情况下才这样做。
systemctl restart nfs && systemctl enable nfs
echo '192.168.112.10:/data/nfs /mnt/nfs nfs defaults 0 0' >> /etc/fstab
mount | grep 192.168.112.10
cat >> nfs-storage.yaml << EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
namespace: default
labels:
environment: test
provisioner: fuseim.pri/ifs
reclaimPolicy: Retain
volumeBindingMode: Immediate
EOF
kubectl apply -f nfs-storage.yaml
cat >> rbac.yaml << EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: default
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
EOF
kubectl apply -f rbac.yaml
部署 NFS Client Provisioner,这是一个 Kubernetes 外部存储插件,用于动态创建 NFS PV。
cat >> nfs-provisioner.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
namespace: default
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: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs # 这里必须要填写storageclass中的PROVISIONER名称信息一致
- name: NFS_SERVER
value: 192.168.112.10 # 指定NFS服务器的IP地址
- name: NFS_PATH
value: /data/nfs # 指定NFS服务器中的共享挂载目录
volumes:
- name: nfs-client-root # 定义持久化卷的名称,必须要上面volumeMounts挂载的名称一致
nfs:
server: 192.168.112.10 # 指定NFS所在的IP地址
path: /data/nfs # 指定NFS服务器中的共享挂载目录
EOF
kubectl apply -f nfs-provisioner.yaml
cat >> nfs-pvc.yaml << EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
namespace: default
labels:
environment: test
app: nginx
spec:
storageClassName: nfs-storage
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Mi
EOF
kubectl apply -f nfs-pvc.yaml
kubectl get pvc,pv,sc
发现使用 NFS Client Provisioner 可以动态创建 pv 并与 PVC 绑定处于 Bound 状态
cat >> nfs-pod1.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
name: nfs-pod1
spec:
containers:
- name: container1
image: nginx:1.16.0
volumeMounts:
- name: shared-data
mountPath: /usr/share/nginx/html
volumes:
- name: shared-data
persistentVolumeClaim:
claimName: nginx-pvc
EOF
cat >> nfs-pod2.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
name: nfs-pod2
spec:
containers:
- name: container2
image: nginx:1.16.0
volumeMounts:
- name: shared-data
mountPath: /usr/share/nginx/html
volumes:
- name: shared-data
persistentVolumeClaim:
claimName: nginx-pvc
EOF
kubectl apply -f nfs-pod1.yaml -f nfs-pod2.yaml
kubectl exec -it nfs-pod1 -- /bin/bash
echo "hello from nfs-pod1" > /usr/share/nginx/html/index.html
exit
kubectl exec -it nfs-pod2 -- /bin/bash
cat /usr/share/nginx/html/index.html
exit
tree /data/nfs
tree /mnt/nfs
这个新出现的目录(PV 目录) default-nginx-pvc-pvc-c8a8b825-1577-4f76-ba1f-a1302941b333 用于映射 PersistentVolume (PV) 到 NFS 服务器上的具体路径
/<共享目录>/<命名空间>-<PVC名称>-<PV名称>
kubectl get pvc nginx-pvc -o custom-columns='PVC-NAMESPACE:.metadata.namespace,PVC-NAME:.metadata.name'
kubectl get pv pvc-c8a8b825-1577-4f76-ba1f-a1302941b333 -o custom-columns='PV-NAME:.metadata.name'
可以发现也是同步更新的
cd /data/nfs/default-nginx-pvc-pvc-c8a8b825-1577-4f76-ba1f-a1302941b333
echo "hello from nfs-server" > index.html
kubectl get pods -o wide
curl 10.244.1.3
curl 10.244.2.8
cat >> nginx-headless.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
EOF
kubectl apply -f nginx-headless.yaml
kubectl get svc -l app=nginx -o wide
cat >> sts.yaml << EOF
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.16.0
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # 通过模板化方式绑定
- metadata:
name: www # 指定pvc的名字
annotations:
volume.beta.kubernetes.io/storage-class: "nfs-storage" # 只指定了storageClass
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 5Mi
EOF
kubectl apply -f sts.yaml
由于是 statefulset 控制器所以 pod 是按顺序创建的
kubectl get pods -l app=nginx -w
kubectl get pvc -l 'app=nginx,environment!=test'
kubectl get pv | grep -v "default/nginx-pvc"
kubectl get pod web-0 -o custom-columns='PVC-NAME:.spec.volumes[*].persistentVolumeClaim.claimName'
kubectl get pod web-1 -o custom-columns='PVC-NAME:.spec.volumes[*].persistentVolumeClaim.claimName'
kubectl get pod web-2 -o custom-columns='PVC-NAME:.spec.volumes[*].persistentVolumeClaim.claimName'
kubectl get pod web-3 -o custom-columns='PVC-NAME:.spec.volumes[*].persistentVolumeClaim.claimName'
kubectl get pod web-4 -o custom-columns='PVC-NAME:.spec.volumes[*].persistentVolumeClaim.claimName'