动态卷供应
一.简介
动态卷供应允许按需创建存储卷。
如果没有动态供应,集群管理员必须手动创建新的存储卷, 然后在 Kubernetes 集群创建
PersistentVolume
对象来表示这些卷。 动态供应功能消除了集群管理员预先配置存储的需要。 相反,它在用户请求时自动供应存储。
动态卷供应的实现基于
storage.k8s.io
API 组中的StorageClass
API 对象。集群管理员可以根据需要定义多个
StorageClass
对象,每个对象指定一个卷插件(又名 provisioner), 卷插件向卷供应商提供在创建卷时需要的数据卷信息及相关参数。集群管理员可以在集群中定义和公开多种存储(来自相同或不同的存储系统),每种都具有自定义参数集。 该设计也确保终端用户不必担心存储供应的复杂性和细微差别,但仍然能够从多个存储选项中进行选择。
动态与静态区别:
静态:Pod ——> PVC——> PV (如果没有匹配的PV,那么PVC会一直处于pending状态)
动态:Pod——> PVC——> StorageClass——> PV (当PVC创建时,会自动创建对应的PV去绑定)
二. StorageClass 资源
要启用动态供应功能,集群管理员需要为用户预先创建一个或多个
StorageClass
对象。每个 StorageClass 都包含
provisioner
、parameters
和reclaimPolicy
字段, 这些字段会在 StorageClass 需要动态分配 PersistentVolume 时会使用到。
StorageClass 对象的命名很重要,用户使用这个命名来请求生成一个特定的类。 当创建 StorageClass 对象时,管理员设置 StorageClass 对象的命名和其他参数,一旦创建了对象就不能再对其更新。
provisioner(存储制备器)
每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV。 该字段必须指定。
你可以指定"内置" 制备器(其名称前缀为 “kubernetes.io” 并打包在 Kubernetes 中)。也可以运行和指定外部制备器,这些独立的程序遵循由 Kubernetes 定义的 规范。
外部供应商的作者完全可以自由决定他们的代码保存于何处、打包方式、运行方式、使用的插件(包括 Flex)等。
代码仓库 kubernetes-sigs/sig-storage-lib-external-provisioner 包含一个用于为外部制备器编写功能实现的类库。你可以访问代码仓库 kubernetes-sigs/sig-storage-lib-external-provisioner 了解外部驱动列表。
例如,NFS 没有内部制备器,但可以使用外部制备器。 也有第三方存储供应商提供自己的外部制备器。
示例:
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 动态创建的 PersistentVolume 会在类的
reclaimPolicy
字段中指定回收策略,可以是Delete
或者Retain
。如果 StorageClass 对象被创建时没有指定
reclaimPolicy
,它将默认为Delete
。通过 StorageClass 手动创建并管理的 PersistentVolume 会使用它们被创建时指定的回收政策。
允许卷扩展
特性状态: Kubernetes v1.11 [beta]
PersistentVolume 可以配置为可扩展。将此功能设置为
true
时,允许用户通过编辑相应的 PVC 对象来调整卷大小。当下层 StorageClass 的
allowVolumeExpansion
字段设置为 true 时,以下类型的卷支持卷扩展。
卷类型 | Kubernetes 版本要求 |
---|---|
gcePersistentDisk | 1.11 |
awsElasticBlockStore | 1.11 |
Cinder | 1.11 |
glusterfs | 1.11 |
rbd | 1.11 |
Azure File | 1.11 |
Azure Disk | 1.11 |
Portworx | 1.11 |
FlexVolume | 1.13 |
CSI | 1.14 (alpha), 1.16 (beta) |
说明: 此功能仅可用于扩容卷,不能用于缩小卷。
挂载选项
由 StorageClass 动态创建的 PersistentVolume 将使用类中
mountOptions
字段指定的挂载选项。如果卷插件不支持挂载选项,却指定了挂载选项,则制备操作会失败。 挂载选项在 StorageClass 和 PV 上都不会做验证,如果其中一个挂载选项无效,那么这个 PV 挂载操作就会失败。
卷绑定模式
volumeBindingMode
字段控制了卷绑定和动态制备 应该发生在什么时候。默认情况下,
Immediate
模式表示一旦创建了 PersistentVolumeClaim 也就完成了卷绑定和动态制备。 对于由于拓扑限制而非集群所有节点可达的存储后端,PersistentVolume 会在不知道 Pod 调度要求的情况下绑定或者制备。集群管理员可以通过指定
WaitForFirstConsumer
模式来解决此问题。 该模式将延迟 PersistentVolume 的绑定和制备,直到使用该 PersistentVolumeClaim 的 Pod 被创建。 PersistentVolume 会根据 Pod 调度约束指定的拓扑来选择或制备。这些包括但不限于 资源需求、 节点筛选器、 pod 亲和性和互斥性、 以及污点和容忍度。
以下插件支持动态供应的 WaitForFirstConsumer
模式:
以下插件支持预创建绑定 PersistentVolume 的 WaitForFirstConsumer
模式:
- 上述全部
- Local
说明:
如果你选择使用
WaitForFirstConsumer
,请不要在 Pod 规约中使用nodeName
来指定节点亲和性。 如果在这种情况下使用nodeName
,Pod 将会绕过调度程序,PVC 将停留在pending
状态。相反,在这种情况下,你可以使用节点选择器作为主机名,如下所示
apiVersion: v1
kind: Pod
metadata:
name: task-pv-pod
spec:
nodeSelector:
kubernetes.io/hostname: kube-01
volumes:
- name: task-pv-storage
persistentVolumeClaim:
claimName: task-pv-claim
containers:
- name: task-pv-container
image: nginx
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: task-pv-storage
parameters(卷参数)
Storage Classes 的参数描述了存储类的卷。取决于制备器,可以接受不同的参数。
例如,参数 type 的值 io1 和参数 iopsPerGB 特定于 EBS PV。 当参数被省略时,会使用默认值。
AWS EBS
provisioner: kubernetes.io/aws-ebs
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/aws-ebs
parameters:
type: io1
iopsPerGB: "10"
fsType: ext4
GCE PD
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-standard
fstype: ext4
replication-type: none
NFS
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: example-nfs
provisioner: example.com/external-nfs
parameters:
server: nfs-server.example.com
path: /share
readOnly: "false"
server
:NFS 服务器的主机名或 IP 地址。path
:NFS 服务器导出的路径。readOnly
:是否将存储挂载为只读的标志(默认为 false)。
Kubernetes 不包含内部 NFS 驱动。你需要使用外部驱动为 NFS 创建 StorageClass。 这里有些例子:
Ceph RBD
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: kubernetes.io/rbd
parameters:
monitors: 10.16.153.105:6789
adminId: kube
adminSecretName: ceph-secret
adminSecretNamespace: kube-system
pool: kube
userId: kube
userSecretName: ceph-secret-user
userSecretNamespace: default
fsType: ext4
imageFormat: "2"
imageFeatures: "layering"
Local (本地)
特性状态:
Kubernetes v1.14 [stable]
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
本地卷还不支持动态制备,但是还是需要创建 StorageClass 以延迟卷绑定, 直到完成 Pod 的调度。这是由
WaitForFirstConsumer
卷绑定模式指定的。延迟卷绑定使得调度器在为 PersistentVolumeClaim 选择一个合适的 PersistentVolume 时能考虑到所有 Pod 的调度限制。
三.使用动态卷供应
启用动态卷供应
要启用动态供应功能,集群管理员需要为用户预先创建一个或多个
StorageClass
对象。 [详细见[StorageClass](#二. StorageClass 资源)]
StorageClass
对象定义当动态供应被调用时,哪一个驱动将被使用和哪些参数将被传递给驱动。以下清单创建了一个 “fast” 存储类,它提供类似 SSD 的永久磁盘。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
使用动态卷供应
用户通过在
PersistentVolumeClaim
中包含存储类来请求动态供应的存储。在 Kubernetes v1.9 之前,这通过
volume.beta.kubernetes.io/storage-class
注解实现。然而,这个注解自 v1.6 起就不被推荐使用了。用户现在能够而且应该使用
PersistentVolumeClaim
对象的storageClassName
字段。 这个字段的值必须能够匹配到集群管理员配置的StorageClass
名称(见[[StorageClass](#二. StorageClass 资源)])。例如,要选择 “fast” 存储类,用户将创建如下的 PersistentVolumeClaim:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: claim1
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast
resources:
requests:
storage: 30Gi
该声明会自动供应一块类似 SSD 的永久磁盘。 在删除该声明后,这个卷也会被销毁。
设置默认值的行为
可以在集群上启用动态卷供应,以便在未指定存储类的情况下动态设置所有声明。
集群管理员可以通过以下方式启用此行为:
- 标记一个
StorageClass
为 默认; - 确保
DefaultStorageClass
准入控制器在 API 服务端被启用。
管理员可以通过向其添加
storageclass.kubernetes.io/is-default-class
注解来将特定的StorageClass
标记为默认。当集群中存在默认的
StorageClass
并且用户创建了一个未指定storageClassName
的PersistentVolumeClaim
时,DefaultStorageClass
准入控制器会自动向其中添加指向默认存储类的storageClassName
字段。
请注意,集群上最多只能有一个 默认 存储类,否则无法创建没有明确指定
storageClassName
的PersistentVolumeClaim
。
拓扑感知
在多区域集群中,Pod 可以被分散到多个区域。 单区域存储后端应该被供应到 Pod 被调度到的区域。 这可以通过设置卷绑定模式来实现。
四. 创建NFS动态卷
使用 NFS 远程存储,为托管的 pod 提供了动态存储服务,pod 创建者无需关心数据以何种方式存在哪里,只需要提出需要多大空间的申请即可。
Kubernetes 不包含内部 NFS 驱动,使用github上提供的NFS subdir 外部驱动
总体流程是:
- 创建 NFS 服务器
- 创建 Service Account,用来管控 NFS provisioner 在k8s集群中运行的权限
- 创建 StorageClass,负责创建 PVC 并调用 NFS provisioner 进行预定的工作,并关联 PV 和 PVC
- 创建 NFS provisioner,有两个功能,一个是在NFS共享目录下创建挂载点(volume), 二是建立 PV 并将 PV 与 NFS 挂载点建立关联
创建RBAC授权
下载rbac资源清单
wget https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/rbac.yaml
查看 rbac.yaml 内容:
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- 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
# replace with namespace where provisioner is deployed
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
# replace with namespace where provisioner is deployed
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
# replace with namespace where provisioner is deployed
namespace: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
进行apply部署
kubectl apply -f rbac.yaml
创建nfs制备器
下载部署nfs制备器的 deployment 资源清单
wget https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/deployment.yaml
修改nfs server和path 参数,改为自己nfs服务器地址和共享目录
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
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: docker.io/simple11618/nfs-subdir-external-provisioner:v4.0.2 ## 改为国内服务器镜像地址
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner #此为storageclass中Provisioner 引用值
- name: NFS_SERVER
value: 10.42.0.1 #自己的nfs服务地址
- name: NFS_PATH
value: /nfs/share #自己的nfs共享目录
volumes:
- name: nfs-client-root
nfs:
server: 10.42.0.1 #自己的nfs服务地址
path: /nfs/share #自己的nfs共享目录
查看运行情况
kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65845c4f8-4vjgc 1/1 Running 0 4m4s
创建 Storageclass
下载 存储类清单
wget https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/class.yaml
查看内容
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false"
进行部署
kubectl apply -f class.yaml
查看部署情况
kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 2m21s
测试
创建PVC
vi pvc-test.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-test
spec:
storageClassName: nfs-client
accessModes: ReadWriteMany
resources:
requests:
storage: 1G
创建
kubectl apply -f pvc-test.yaml
查看 pv 和 pvc状态
# 使用动态供应,会自动创建pv与 pvc进行绑定
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-ef9ca3f1-110c-44bf-b1bf-a7548fa5801a 1G RWX Delete Bound default/pvc-test nfs-client 21s
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-test Bound pvc-ef9ca3f1-110c-44bf-b1bf-a7548fa5801a 1G RWX nfs-client 2m10s
创建POD
vi nfs-pod-test.yaml
# 将前面创建的pvc挂在到 容器内 /mnt下,并在挂在目录下创建一个 nfs-test 文件,之后到nfs目录下进行验证
apiVersion: v1
kind: Pod
metadata:
name: nfs-pod-test
spec:
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: pvc-test
containers:
- name: nfs-pod-test
image: busybox
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/nfs-test && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
验证
# 查看 pod状态
kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-pod-test 0/1 Completed 0 10s
# 查看nfs共享目录下是否有nfs-test文件
[root@tengxun nfs]# cd /nfs/share/
[root@tengxun share]# ls
default-pvc-test-pvc-ef9ca3f1-110c-44bf-b1bf-a7548fa5801a
[root@tengxun share]# cd default-pvc-test-pvc-ef9ca3f1-110c-44bf-b1bf-a7548fa5801a/
[root@tengxun default-pvc-test-pvc-ef9ca3f1-110c-44bf-b1bf-a7548fa5801a]# ls
nfs-test
# 查看到nfs-test 文件,表示nfs动态卷使用成功