3.1 StorageClass 详解
StorageClass 是用于对存储资源的抽象定义,与 PVC 申请绑定后,就可以屏蔽后端存储的细节,这样也可以减轻管理员手动创建 PV 的过程,交给集群自动创建并绑定 PV。由于这种方式更加方便,所以在实际的生产过程中应用地更加广泛。
示例
下面是一个配置示例:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: # StorageClass 的名称为 standard
name: standard
provisioner: kubernetes.io/aws-ebs # 存储资源提供者
parameters: # 参数
type: gp2
reclaimPolicy: Retain # 回收策略
allowVolumeExpansion: true # 允许容量扩展
mountOptions: # 挂载选项
- debug
volumeBindingMode: Immediate # 卷绑定模式
这其中涉及到了比较多的字段选项,下面来一一详解。
provisioner(存储资源提供者)
这是必填的字段,也可以认为这里需要指明的是后端存储驱动。集群支持的存储卷都以 kubernetes.io/ 开头,当然也可以自定义后端存储卷。
parameters(参数)
不同的存储资源提供者,需要设置不同的参数。如果不显性设置参数的话,就会使用这些参数的默认值。
reclaimPolicy(回收策略)
使用这个字段指定回收策略,可选的值有:
- Delete:删除。如果没有指定回收策略的话,默认为删除。
- Retain:保留。
allowVolumeExpansion(允许容量扩展)
PV 可以被设置为可扩展的。当设置这个值为 true 时,可以在 PVC 资源对象中调整卷的大小(只能增加,不能减少)。
支持扩展的存储卷有:gcePersistentDisk、awsElasticBlockStore、Cinder、glusterfs、rbd、Azure File、Azure Disk、Portworx、CSI。
mountOptions(挂载选项)
通过这个字段可以指定卷挂载选项。
需要注意的是:如果定义了挂载选项,但是卷插件不支持,那么挂载 PV 将会失败。
volumeBindingMode(卷绑定模式)
这个字段用于设置动态配置和卷绑定的发生时间。有两个可选值:
- Immediate:立即绑定模式。创建 PVC 以后立刻执行动态配置和卷绑定。但是如果集群中的存储卷不能全局访问所有的 Node 节点,PV 会在不知道 Pod 调度需求的情况下进行绑定,这可能导致 Pod 无法调度。
- WaitForFirstConsumer:延迟绑定模式。这个模式可以解决上面提到的问题,在这种模式下会延迟 PV 的配置和绑定,直到引用 PVC 的 Pod 被成功创建。
有这些存储插件支持 WaitForFirstConsumer 模式的配置:AWSElasticBlockStore、GCEPersistentDisk、AzureDisk、Local。
allowedTopologies(允许的拓扑)
通常情况下,如果指定了 WaitForFirstConsumer 卷绑定模式,就不需要再限制拓扑结构了。但是在某些特定的情况下依然可以使用这个字段进行限制,配置示例如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-standard
volumeBindingMode: WaitForFirstConsumer
allowedTopologies: # 限定允许的拓扑结构
- matchLabelExpressions:
- key: failure-domain.beta.kubernetes.io/zone
values:
- us-centrall-a
- us-centrall-b
3.2 不同存储卷的配置参数
由于不同的存储类需要配置的参数也是不同的,这里简单举例几个不同类型存储卷的参数配置。后续需要使用到的话,也可以再查询相关文档。
Local
Local 卷目前不支持动态配置,但是可以通过创建 StorageClass 来延迟卷绑定直到 Pod 调度。这样可以让 PVC 绑定 PV 时考虑到 Pod 的所有调度约束。
示例如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
AWS EBS
具体可以参考官方文档 Amazon EBS 卷类型。
示例如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/aws-ebs
parameters:
type: io1
iopsPerGB: "10"
fsType: ext4
其中参数代表的含义分别为:
- type:类型。可选项有:io1、gp2、sc1、st1,默认值为 gp2。
- iopsPerGB:这个参数只用于 io1 类型的卷,表示每秒每 GiB 的 I/O 操作数量。
- fsType:kubernetes 支持,默认值为:ext4。
- encrypted:卷是否加密,可选项为 "true" 或是 "false"。
- kmsKeyId:加密卷时使用的完整的 Amazon Resource Name。
GCE PD(Google Compute Engine Persistent Disk)
示例如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-standard
replication-type: none
其中参数代表的含义分别为:
- type:类型。可选项有:pd-standard 或 pd-ssd。默认值为:pd-standard。
- replication-type:复制类型。可选项有:none(提供一个普通 PD) 或 regional-pd(提供一个个区域性的 PD,然后使用 zones 字段指定区域)。
GlusterFS
具体可以查看官方文档 GlusterFS Documentation 和 RESTful based volume management framework for GlusterFS -- Heketi。
示例如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://127.0.0.1:8081"
clusterid: "630372ccdc720a92c681fb928f27b53f"
restauthenabled: "true"
restuser: "admin"
secretNamespace: "default"
secretName: "heketi-secret"
gidMin: "40000"
gidMax: "50000"
volumetype: "replicate:3"
其中参数代表的含义分别为:
- resturl:Gluster REST 服务(Heketi 服务)的 URL 地址,通常它的格式为 IP 地址:端口号,主要用于自动完成 GlusterFSvolume 的设置。
- clusterid:集群 ID。Heketi 在配置卷时会使用这个 ID。
- restauthenabled:是否对 Gluster REST 服务启用安全验证。
- restuser:访问 Gluster REST 服务的用户名。
- secretNamespace 和 secretName:标识 Secret 实例。其中保存的是访问 Gluster REST 服务密码的 Secret 资源对象名。
- gidMin 和 gidMax:StorageClass 的 GID 范围,用于动态资源供应时为 PV 设置 GID。
- volumetype:设置 GlusterFS 内部的 Volume 类型,有 3 个参数值可选:
- Replica 类型:replicate:3 表示有 3 份副本。
- Disperse 类型:disperse:4:2 表示有 4 份数据,其中 2 份属于冗余数据。
- Distribute 类型:none。
3.3 动态存储管理实例
StatefulSet 是用来部署有状态服务的,通常会使用本地持久存储卷用于保存服务的状态。
整个的流程为:
- 创建本地持久存储卷
- 将本地持久存储卷绑定到 Node 上
- 创建 StatefulSet
创建 local PV,新建 local-storage.yaml 文件,并向其中写入如下代码:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
# provisioner 的值为 kubernetes.io/no-provisioner,因为 local pv 不支持 Dynamic Provisioning,不能在创建 pvc 的时候自动创建 pv
provisioner: kubernetes.io/no-provisioner
# 使用延迟卷绑定功能,将 volume binding 延迟至 pod scheduling 阶段执行
volumeBindingMode: WaitForFirstConsumer
执行创建:
$ kubectl create -f local-storage.yaml
storageclass.storage.k8s.io/local-storage created
然后在每个 Node 上创建一个 PV,新建 local-pv.yaml 文件,并向其中写入如下内容:
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
# local.path 指明对应的磁盘路径
local:
path: /tmp
# spec.nodeAffinity 指定对应的 node
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- kubesphere02
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv2
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /tmp
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- kubesphere03
上面的配置会在 kubesphere02 和 kubesphere03 两个 Node 上分别创建一个 PV。其中,存储类 spec.storageClassName 用的是前面启用的 local-storage, spec.capacity.storage 指定了容量为 1G,挂载路径 spec.local.path 为 /tmp(该路径需要在 Node 上存在)。
执行创建:
$ kubectl create -f local-pv.yaml
persistentvolume/local-pv1 created
persistentvolume/local-pv2 created
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv1 1Gi RWO Retain Available local-storage 17s
local-pv2 1Gi RWO Retain Available local-storage 17s
可以看到两个 PV 都已创建成功,其状态为可用(Available),底层资源提供者为 local-storage。
创建有状态集合,新建 web.yaml 文件,并向其中写入如下代码:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: mirrorgooglecontainers/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 10Mi
其中 Service 的 clusterIP 指定为 None,表示它是一个 Headless Service,集群不用为它分配 IP。StatefulSet 包含 2 个 Pod 副本,它们的名字分别为 web-0 和 web-1,并且都挂载了一个 PV。不管 Pod 被重新调度到哪个节点,Pod 的名字和挂载的 PV 都将跟之前保持一致。
在执行创建之前可以监听 Pod 的创建过程,在一个新的终端执行如下命令:
$ kubectl get pods -w -l app=nginx
然后执行创建:
$ kubectl create -f web.yaml
service/nginx created
statefulset.apps/web created
这个时候可以看到整个的创建过程:
$ kubectl get pods -w -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 1s
web-0 0/1 ContainerCreating 0 1s
web-0 0/1 ContainerCreating 0 2s
web-0 1/1 Running 0 14s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 1s
web-1 0/1 ContainerCreating 0 1s
web-1 0/1 ContainerCreating 0 1s
web-1 1/1 Running 0 11s
查看创建的 Headless Service 和 StatefulSet:
$ kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 100s
$ kubectl get statefulset web
NAME READY AGE
web 2/2 108s
查看根据 volumeClaimTemplates 自动创建 PVC:
# PVC 名称的格式为:$(volumeClainTemplates.name)-(pod_name),这里 volumeClainTemplates.name 为 www,pod_name 为 web-[0-1]
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound local-pv2 1Gi RWO local-storage 2m43s
www-web-1 Bound local-pv1 1Gi RWO local-storage 2m29s
查看创建的 Pod,它们都是有序的:
# Pod 名称的格式为:$(StatefulSet 名称)-$(序号),这里为:web-0、web-1
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3m43s
web-1 1/1 Running 0 3m29s
评论区