1.存储卷概述
存储卷是定义在Pod资源之上、可被其内部的所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久能力则取决于存储卷自身是否支持持久机制。
1.2 Kubernete支持的存储卷类型
Kubernetes支持非常丰富的存储卷类型,包括本地存储(节点)和网络存储系统中的诸多存储机制,甚至还支持secret和ConfigMap这样的特殊存储资源。对于Pod来说,卷类型主要是为关联相关的存储系统时提供相关的配置参数。
emptyDir与hostPath属于节点级别的卷类型,emptyDir的生命周期与Pod资源相同,而使用了hostPath卷的Pod一旦被重新调度至其他节点,那么它将无法再使用此前的数据。因此,这两种类型都不具有持久性。要想使用持久类型的存储卷,就得使用网络存储系统,如NFS、Ceph、GlusterFS等,或者云端存储,如gcePresistentDisk、awsElasticBlockStore等。
然后网络存储系统通常都不太容易使用,有的甚至很复杂,k8s为此专门设计了一中集群级别的资源PersistentVolume(PV),它借由管理员配置存储系统,而后由用户通过PersistentVolumeClaim(PVC)存储卷直接申请使用的机制大大简化了终端存储用户的配置过程,有效降低了使用难度。
再者,Secret和ConfigMap算得上是两种特殊的类型
- Secret用于向Pod传递敏感信息,如密码、私钥、证书文件等,这些信息如果直接定义在镜像中很容易导致泄露,有了Secret资源,用户可以将这些信息存储于集群中而后由Pod进行关在,从而实现将敏感数据与系统解耦。
- ConfigMap资源则用于向Pod注入非敏感数据,使用时,用户将数据直接存储于ConfigMap对象中,而后直接在Pod中使用ConfigMap卷引用它接口,它可以实现容器配置文件集中化定义和管理。
另外,k8s还引入了容器存储接口(Container Storage Interface,CSI),能够将插件的安装流程简化至与创建Pod相当,并允许第三方存储供应商在无须修改K8s代码库的前提下提供自己的解决方案。
1.3 存储卷的使用方式
在Pod中定义使用存储卷的配置由两部分组成:一是通过.spec.volumes字段定义在Pod之上的存储卷列表,其支持使用多种不同类型的存储卷且配置参数差别很大;另一个是通过.spec.containers.volumeMounts字段在容器上定义的存储卷挂载列表,它只能挂载当前Pod资源中定义的具体存储卷。
在Pod级别定义存储卷时,.spec.volumes字段的值时对象列表格式,每个对象一个存储卷的定义,它由存储卷名称(.spec.volumes.name)或存储卷对象(.spec.volumes.VOL_TYPE)组成,VOL_TYPE时使用的存储卷类型名称,它的内嵌字段随类型的不同而不同。
下面的资源清单片段定义了由两个存储卷组成的卷列表,一个是emptyDir类型,一个是gitRepo类型:
spec:...volumes:- name: logdataemptyDir: {}- name: examplegitRepo:repository: https://...gitrevision: masterdirectory: .
定义好的存储卷可由当前Pod资源内的各容器进行挂载。事实上,也只有多个容器挂载同一个存卷卷时,共享才有了具体的意义。当Pod中只有一个容器时,使用存储卷的目的通常在于数据持久化。.spec.containers.volumeMounts字段的值也是对象列表格式,由一到多个存储卷挂载定义组成。
spec:...containers:- name: <String>...volumeMounts:- name <string> -required-mountPath <string> -required-readOnly <boolean>subPath <string>mountPropagation <string>
- name: 指定要挂载的存储名称,必选字段
- mountPath: 挂载点路径,容器文件系统上的路径,必选字段
- readOnly: 是否挂载为只读卷
- subPath: 挂载存储卷时使用的子路径,即在mountPath指定的路径下使用一个子路径作为其挂载点
一个挂载示例,容器myapp将logdata存储卷挂载于/var/log/myapp,将example挂载到/webdata/example目录:
spec:containers:- name: myappimage: ikubernetes/myapp:v1volumeMounts:- name: logdatamountPath: /var/log/myapp- name: examplemountPath: /webdata/example
2.临时存储卷
k8s支持存储卷类型中,emptyDir存储卷的生命周期与其所属的Pod对象相同,它无法脱离Pod对象的生命周期提供数据存储功能,因此emptyDir通常仅用于数据缓存或临时存储。
2.1 emptyDir存储卷
emptyDir存储卷是Pod对象生命周期中的一个临时目录,类似于Docker上的docker挂载卷,在Pod对象启动时即被创建,而在Pod对象被移除时会被一并删除。不具有持久能力的emptyDir存储卷只能用于某些特殊场景中,例如,同一Pod内的多个容器间文件的共享,或者作为容器数据的临时存储目录用于数据缓存系统等。
emptyDir存储卷则定义于.spec.volumes.emptyDir嵌套字段中,可用字段主要包含两个:
- medium: 此目录所在的存储介质的类型,可取值为”default”或”Memory”,默认为default,表示使用节点的默认存储介质;”Memory”表示使用基于RAM的临时文件系统tmpfs,空间受限于内存,但性能非常好,通常用于为容器中的应用提供缓存空间。
- sizeLimit: 当前存储卷的空间限额,默认值为nil,表示不限制;不过,在medium字段为Memory时建议无比定义此限额。
示例vol-emptydir.yaml:
apiVersion: v1kind: Podmetadata:name: vol-emptydir-podspec:volumes:- name: htmlemptyDir: {}containers:- name: nginximage: nginx:1.18.0-alpinevolumeMounts:- name: htmlmountPath: /usr/share/nginx/html- name: pagegenimage: alpinevolumeMounts:- name: htmlmountPath: /htmlcommand: [/bin/sh, -c]args:- while true; doecho $(hostname) $(date) >> /html/index.html;sleep 10;done
上面的示例中定义的存储卷名称为html,挂载于容器nginx的/usr/share/nginx/html目录,以及容器pagegen的/html目录,容器pagegen每隔10秒向存储卷上的index.html文件中追加一行信息,而容器nginx中的nginx进程则以其为站点主页。
Pod资源的详细信息中会显示存储卷的相关状态,包括其是否创建成功(在Events字段中)、相关的类型及参数(在Volumes字段中)以及容器中的挂载状态等信息(Containers字段中):
# kubectl describe pod vol-emptydir-podContainers:nginx:...Mounts:/usr/share/nginx/html from html (rw)/var/run/secrets/kubernetes.io/serviceaccount from default-token-rhlmd (ro)pagegen:...Mounts:/html from html (rw)/var/run/secrets/kubernetes.io/serviceaccountVolumes:html:Type: EmptyDir (a temporary directory that shares a pod's lifetime)Medium:SizeLimit: <unset>default-token-rhlmd:Type: Secret (a volume populated by a Secret)SecretName: default-token-rhlmdOptional: false...Events:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled <unknown> default-scheduler Successfully assigned default/vol-emptydir-pod to k8s-test02Normal Pulling 48s kubelet, k8s-test02 Pulling image "nginx:1.18.0-alpine"Normal Pulled 4s kubelet, k8s-test02 Successfully pulled image "nginx:1.18.0-alpine"Normal Created 4s kubelet, k8s-test02 Created container nginxNormal Started 4s kubelet, k8s-test02 Started container nginxNormal Pulling 4s kubelet, k8s-test02 Pulling image "alpine"
2.2 节点存储卷hostPath
hostPath类型的存储卷是指将工作节点上某文件系统的目录或文件挂载于Pod中的一种存储卷,它可独立于Pod资源的生命周期,因而具有持久性。但它是工作节点本地的存储空间,仅适用于特定情况下的存储卷使用需求,例如,将工作节点上的文件系统关联为Pod的存储卷,从而使得容器访问节点文件系统上的数据。这一点在运行有管理任务的系统级Pod资源需要访问节点上的文件时尤为有用。
hostPath存储卷的嵌套字段共有两个:一个用于指定工作节点上的目录路径的必选字段path;另一个是指定存储卷类型的type,type支持使用的卷类型包含以下几种:
- DirectoryOrCreate: 指定的路径不存在时自动将其创建为权限是0755的空目录,属主属组均为kubelet;
- Diretory: 必须存在的目录路径;
- FileOrCreate: 指定的路径不存在时自动将其创建为权限是0644的空文件,属主属组同是kubelet;
- File: 必须存在的文件路径;
- Socket:必须存在的Socket文件路径
- CharDevice:必须存在的字符设备文件路径;
- BlockDevice:必须存在的块设备文件路径;
示例vol-hostpath.yml,它运行着日志收集代理应用filebeat,负责收集工作节点及容器相关的日志信息发往redis服务器:
apiVersion: v1kind: Podmetadata:name: vol-hostpath-podspec:containers:- name: filebeatimage: ikubernetes/filebeat:5.6.7-alpineenv:- name: REDIS_HOSTvalue: redis.cluster.local:6379- name: LOG_LEVELvalue: infovolumeMounts:- name: varlogmountPath: /var/log- name: socketmountPath: /var/run/docker.sock- name: containersmountPath: /var/lib/docker/containersreadOnly: trueterminationGracePeriodSeconds: 30volumes:- name: varloghostPath:path: /var/log- name: containershostPath:path: /var/lib/docker/containers- name: sockethostPath:path: /var/run/docker.sock
注意:
- 不同节点上的文件或许并不完全相同,于是,那些要求事先必须存在的文件或目录的满足状态也可能会有所不同;
- 基于资源可用状态的调度器调度Pod时,hostPath资源的可用性状态不会被考虑在内;
- 在节点中创建的文件或目录默认仅有root可写,若期望容器内的进程拥有写权限,则要么将它运行为特权容器,要么修改节点上的目录路径的权限。
3.网络存储卷
K8s拥有众多类型的用于适配专用存储系统的网络存储卷。包括传统的NAS或SAN设备(如NFS、iSCSI、fc)、分布式存储(如GlusterFS、RBD)、云端存储(如gcePersistentDisk、azureDisk、cinder)以及建构在各类存储系统之上的抽象管理层(如flocker、portworxVolume和vsphereVolume)等。
3.1 NFS存储卷
K8s的NFS存储卷用于将某事先存在的NFS服务器上导出(export)的存储空间挂载到Pod中以供容器使用。与emptyDir不同的是,NFS在Pod终止后仅是被卸载而非删除。另外,NFS是文件系统级共享服务,它支持同时存储的多路挂载请求。定义NFS存储卷时常用到以下字段:
- server: NFS服务器的IP地址或主机名,必选;
- path: NFS服务器导出(共享)的文件系统路径,必须;
- readOnly: 是否以只读方式挂载,默认为false。
下面是简单使用Redis的一个示例:
apiVersion: v1kind: Podmetadata:name: vol-nfs-podlabels:app: redisspec:containers- name: redisimage: redis:4-alpineports:- containerPort: 6379name: redisportvolumeMounts:- mountPath: /dataname: redisdatavolumes:- name: redisdatanfs:server: nfs.ilinux.iopath: /data/redisreadOnly: false
上面示例中的Pod拥有一个关联至NFS服务器nfs.ilinux.io的存储卷,Redis容器将其挂载于/data目录上,它是运行于容器中redis-server数据的持久保存位置。
3.2 RBD存储卷
Ceph是一个专注于分布式的、弹性可扩展的、高可靠的、性能优异的存储系统平台,同时支持提供块设备、文件系统和REST三种存储接口。它是一个高度可配置的系统,并提供了一个命令行界面用于监控和控制其存储集群。Ceph还包含鉴证和授权功能,可兼容多种存储网关接口,如OpenStack Swift。K8s支持通过RBD卷类型使用Ceph存储系统为Pod提供存储卷。
要配置Pod使用RBD存储卷,需要事先满足如下条件:
- 存在某可用的Ceph RBD存储集群,否则就需要创建一个;
- 在Ceph RBD集群中创建一个能满足Pod资源数据存储需要的存储映像(image);
- 在k8s集群内的各节点上安装Ceph客户端程序包(ceph-common);
在配置RBD的存储卷时,需要指定要连接的目录服务器和认证信息等,这一点通常使用以下嵌套字段进行定义:
- monitors: Ceph存储监视器,逗号分隔的字符串列表,必选;
- image:rados image的名称,必选;
- pool:rados存储池名称,默认为RBD;
- user:rados用户名,默认为admin;
- keyring: RBD用户认证时使用的keyring文件路径,默认为/etc/ceph/keyring;
- secretRef: RBD用户认证时使用的保存有相应认证信息的Secret对象,会覆盖由keyring字段提供的密钥信息;
- readOnly:是否以只读的方式进行访问;
- fsType: 要挂载的存储卷的文件系统类型,至少应该是节点操作系统支持的文件系统,如ext4、xfs、ntfs等,默认为ext4
Pod示例:
apiVersion: v1kind: Podmetadata:name: vol-rbd-podspec:containers:- name: redisimage: redis:4-alpineports:- containerPort: 6379name: redisportvolumeMounts:- mount: /dataname: redis-rbd-volvolumes:- name: redis-rbd-volrbd:monitors:- '172.16.0.56:6789'- '172.16.0.57:6789'- '172.16.0.58:6789'pool: kubeimage: redisfsType: ext4readOnly: falseuser: adminsecretRef:name: ceph-secret
3.3 GlusterFS存储卷
Gluster(Gluster File System)是一个开源的分布式文件系统,是水平扩展存储解决方案Gluster的核心,具有强大的横向扩展能力,GlusterFS通过扩展能够支持数PB存储容量和处理数千客户端。Gluster借助TCP/IP或InfiniBand RDMA网络将物理分布的存储资源聚集在一起,使用单一全局命名空间来管理数据。另外,GlusterFS基于可堆叠的用户空间设计,可为各种不同的数据负载提供优异的性能,是另一种流行的分布式存储解决方案。
要配置Pod使用GlusterFS存储卷,需事先满足以下前提:
- 存在某可用的GlusterFS存储集群,否则就要创建一个;
- 在GlusterFS集群中创建一个能满足Pod资源数据存储需要的卷;
- 在k8s集群内的各节点上安装GlusterFS客户端程序包(glusterfs和glusterfs-fuse);
若要基于GlusterFS使用存储卷的动态供给机制,还需要实现部署heketi,它用于为GlusterFS集群提供RESTful风格的管理接口。
常用的配置字段包含如下几个:
- endpoins:Endpoints资源的名称,此资源需要事先存在,用于提供Gluster集群的部分节点信息作为其访问入口,必选;
- path:用到的GlusterFS集群的卷路径,如kube-redis,必选;
- readOnly: 是否为只读卷;
示例:
apiVersion: v1kind: Podmetadata:name: vol-glusterfs-podlabels:app: redisspec:containers- name: redisimage: redis:alpineports:- containerPort: 6379name: redisportvolumeMounts:- mountPath: /dataname: redisdatavolumes:- name: redisdataglusterfs:endpoints: glusterfs-endpointspath: kube-redisreadOnly: false
用于访问Gluster集群的相关节点信息要事先保存于某特定的Endpoints资源中,例如上面示例中调用的glusterfs-endpoints。此类的Endpoints资源可按需求手动创建,例如下面的glusterfs-endpoints.yaml:
apiVersion: v1kind: Endpointsmetadata:name: glusterfs-endpointssubsets:- addresses:- ip: gfs01.ilinux.ioports:- port: 24007name: blusterd- addresses:- ip: gfs02.ilinux.ioports:- port: 24007name: glusterd
3.4 Cinder存储卷
Cinder是OpenStack Block Storage的项目名称,用来为虚拟机(VM)实例提供持久块存储。Cinder通过驱动架构支持多种后端(back-end)存储方式,包括LVM、NFS、Ceph和其他诸如EMC、IBM等,其提供了调用度来调度卷创建的请求,能合理优化存储资源的分配,而且还拥有REST API。将K8s集群部署于OpenStack构建的IaaS环境中时,Cinder的块存储功能可为Pod资源提供外部持久存储的有效方式。
在Pod使用Cinder存储卷时,其可用的嵌套字段包含如下几个:
- volumeID: 用于标识Cinder中的存储卷的卷标识符,必选
- readOnly: 是否以只读方式访问;
- fsType:要挂载的存储卷的文件系统类型,至少应该时节点操作系统支持的文件系统,如ext4、xfs、ntfs等,默认为ext4;
示例vol-cinder.yaml:
apiVersion: v1kind: Podmetadata:name: vol-cinder-podspec:containers:- name: mysqlimage: mysqlargs:- "--ignore-db-dir"- "lost+found"env:- name: MYSQL_ROOT_PASSWORDvalue: YOUR_PASSports:- containerPort: 3306name: mysqlportvolumeMounts:- name: mysqldatamountPath: /var/lib/mysqlvolumes:- name: mysqldatacinder:volumeID: xxx-xxx-xxfsType: ext4
4.持久存储卷
PersistentVolume(PV)是指由集群管理员配置提供的某存储系统上的一段存储空间,它是对底层共享存储的抽象,将共享存储作为一种可由用户申请使用的资源,实现了“存储消费”机制。通过存储插件机制,PV支持使用多种网络存储系统或云端存储等多种后端存储系统,例如,前面使用的NFS、RBD和Cinder等。
PV是集群级别的资源,不属于任何名称空间,用户对PV资源的使用需要通过PersistentVolumeClaim(PVC)提出的使用申请来完成绑定,是PV资源的消费者,它向PV申请特定大小的空间及访问模式(如rw或ro),从而创建出PVC存储卷,而后再由Pod资源通过PVC存储卷关联使用。
StorageClass资源对象可用于将存储资源定义为具有显著特性的类别(Class)而不是具体的PV,例如”fast” “slow” “glod”等。用户通过PVC直接向意向的类别发出申请,匹配由管理员事先创建的PV,或者由其按需为用户动态创建PV,这样做甚至免去了需要事先创建PV的过程。
4.1 创建PV
PersistentVolume Spec主要支持以下几个通用字段,用于定义PV的容量、访问模式和回收策略。
- Capacity: 当前PV的容量;目前,Capacity仅支持空间设定,将来应该还可以指定IOPS和throughput;
访问模式:尽管在PV层看起来并无差别,但存储设备支持及启用的功能特性却可能不尽相同。例如NFS存储支持多客户端同时挂载及读写操作,但也可能是在共享时仅启用了只读操作,其他存储系统也存在类似的可配置特性。因此,PV底层的设备或许存储其特有的访问模式,用户使用时必须在其特性范围内设定其功能:
- ReadWriteOnce(RWO): 仅可被单个节点读写挂载;
- ReadOnlyMany(ROX): 可被多个节点同时只读挂载;
- ReadWriteMany(RWX): 可被多个节点同时读写挂载;
persistentVolumeReclaimPolicy: PV空间被释放时的处理机制;可用类型仅为Retain(默认)、Recycle或Delete
- Retain:用户可以手动回收资源。当PersistentVolumeClaim对象被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为”已释放(released)”。 由于卷上仍然存在这前一申领人的数据,该卷还不能用于其他申领。
- Recycle: 空间回收,即删除存储卷目录下的所有文件(包括子目录和隐藏文件),目前Recycle已被废弃。
- Delete: 删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。
- volumeMode:卷模型,用于指定此卷可被用做文件系统还是裸格式的块设备,默认为Filesystem;
- storageClassName: 当前PV所属的StorageClass的名称;默认为空值,即不属于任何StorageClass;
- mountOptions: 挂载选项组成的列表,如ro、soft和hard等;
下面的资源清单配置示例中定义了一个使用NFS存储后端的PV,空间大小为10GB,支持多路的读写操作:
apiVersion: v1kind: PersistentVolumemetadata:name: pv-nfs-001labels:release: stablespec:capacity:storage: 5GivolumeMode: FilesystemaccessModes:- ReadWriteManypersistentVolumeReclaimPolicy: DeletestorageClassName: slowmountOptions:- hard- nfsvers=4.1nfs:path: "/webdata/htdocs"server: nfs.ilinux.io
PV资源生命周期有四种状态:
- Available: 可用状态的自由资源,尚未被PVC绑定;
- Bound:已经绑定至某PVC;
- Released: 绑定的PVC已经被删除,但资源尚未被集群回收;
- Failed:因自动回收资源失败而处于的故障状态;
4.2 创建PVC
PersistentVolumeClaim是存储卷类型的资源,它通过申请占用某个PersistentVolume而创建,它与PV是一对一的关系,用户无须关心底层实现细节。申请时,用户只需要指定目标空间的大小、访问模式、PV标签选择器和StorageClass等相关信息即可。
PVC的Spec字段的可嵌套字段具体如下:
- accessMode: 当前PVC的访问模式,其可用模式与PV相同;
- resources:当前PVC存储卷需要占用的资源量最小值;目前,PVC的资源限定仅指其空间大小;
- selector:绑定时对PV应用的标签选择器(matchLabels)或匹配条件表达式(matchExpressions),用于挑选要绑定的PV;如果同时指定了两种挑选机制,则必须同时满足两种选择机制的PV才能被选出;
- storageClassName: 所依赖的存储类的名称;
- volumeMode:卷模型,用于指定此卷可被用作文件系统还是裸格式的块设备,默认为Filesystem;
- volumeName: 用于直接指定要绑定的PV的卷名;
下面的配置清单pvc-nfs-001.yaml定义了一个PVC示例,其选择PV的选择机制是使用了标签选择器,适配的标签是release:stable,存储类为slow,这会关联到前面创建PV示例pv-nfs-001:
apiVersion: v1kind: PersistentVolumeClaimmetadata:name: pvc-nfs-001lables:release: stablespec:selector:matchLabels:release: stableaccessModes:- ReadWriteManyvolumeMode: Filesystemresources:requests:storage: 5GistorageClassName: slow
创建好PVC资源之后,即可在Pod中通过persistenVolumeClain引用它,而后挂载于容器中进行数据持久化。需要注意的是,PV是集群级别的资源,而PVC则隶属于名称空间,因此,PVC在绑定目标PV时不受名称空间的限制,但Pod引用PVC时,则只能是属于同一名称空间中的资源。
4.3 在Pod中使用PVC
在Pod调用PVC,只需要在定义volumes时使用persistentVolumeClaims字段嵌套指定两个字段即可:
- claimName: 要调用的PVC名称,PVC卷要与Pod在同一名称空间中;
- readOnly: 是否将存储卷强制挂载为只读模式,默认为false;
下面示例定义了一个Pod,它将调用pvc-rbd-001的PVC:
apiVersion: v1kind: Podmetadata:name: vol-rbd-podspec:containers:- name: redisimage: redis:4-alpineports:- containerPort: 6379name: redisportvolumeMounts:- mountPath: /dataname: redis-rbd-volvolumes:- name: redis-rbd-volpersistentVolumeClaim:claimName: pvc-rbd-001
4.4 存储类(StorageClass)
StorageClass是k8s资源类型的一种,它是由管理员为管理PV之便而按需创建的类别,例如可按存储系统的性能高低分类,或者根据其综合服务质量级别进行分类、依照备份策略分类等。
StorageClass的好处之一便是支持PV的动态创建。用户用到持久性存储时,需要通过创建PVC来绑定匹配的PV,此类操作需求量较大,或者当管理员手动创建的PV无法满足PVC的所有需求时,系统按PVC的需求标准动态创建适配的PV。
StorageClass的名称至关重要,它是用户调用的标识。创建存储类对象时,除了名称之外,还需要为其定义三个关键字段:provisioner、parameter和reclaimPolicy。
StorageClass其他一级字段:
- provisioner: 即提供了存储资源的存储系统,StorageClass要依赖Provisioner来判定要使用的存储插件以便适配到目标存储系统。k8s内建有多种供给方,这些供给方的名字都以kubernetes.io为前缀;
- parameters: StorageClass使用参数描述要关联到的存储卷,不过,不同的Provisioner可用的参数各不相同;
- reclaimPolicy: 为当前StorageClass动态创建的PV指定回收策略,可用值为Delete(默认)和Retain;不过,那些由管理员手工创建的PV的回收策略取决于它们自身的定义;
- volumeBindingMode:定义如何为PVC完成供给和绑定,默认值为VolumeBindingImmediate,此选项仅在启用了存储类调度功能时才能生效;
- mountOptions: 由当前类动态创建的PV的挂载选项列表;
下面示例使用nfs(10.151.30.57:/data/k8s),来自动自动创建pv:
- 配置Deployment,创建nfs-client,也就是Provisioner:
kind: DeploymentapiVersion: apps/v1metadata:name: nfs-client-provisionerspec:replicas: 1strategy:type: Recreateselector:matchLabels:app: nfs-client-provisionertemplate:metadata:labels:app: nfs-client-provisionerspec:serviceAccountName: nfs-client-provisionercontainers:- name: nfs-client-provisionerimage: quay.io/external_storage/nfs-client-provisioner:latestvolumeMounts:- name: nfs-client-rootmountPath: /persistentvolumesenv:- name: PROVISIONER_NAMEvalue: fuseim.pri/ifs- name: NFS_SERVERvalue: 10.151.30.57- name: NFS_PATHvalue: /data/k8svolumes:- name: nfs-client-rootnfs:server: 10.151.30.57path: /data/k8s
- 为nfs-client-provisioner创建serviceAccount,然后绑定对应的权限(nfs-client-sa.yml):
apiVersion: v1kind: ServiceAccountmetadata:name: nfs-client-provisioner---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:name: nfs-client-provisioner-runnerrules:- 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"]- apiGroup: [""]resources: ["events"]verbs: ["list", "watch", "create", "update", "patch"]- apiGroup: [""]resources: ["endpoints"]verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:name: run-nfs-client-provisionersubjects:- kind: ServiceAccountname: nfs-client-provisionernamespace: defaultroleRef:kind: ClusterRolename: nfs-client-provisioner-runnerapiGroup: rbac.authorization.k8s.io
我们这里新建的一个名为 nfs-client-provisioner 的ServiceAccount,然后绑定了一个名为 nfs-client-provisioner-runner 的ClusterRole,而该ClusterRole声明了一些权限,其中就包括对persistentvolumes的增、删、改、查等权限,所以我们可以利用该ServiceAccount来自动创建 PV。
- 创建StorageClass对象(nfs-client-class.yaml):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: course-nfs-storage
provisioner: fuseim.pri/ifs # 或者其他名字,但是必须和上边的deployment的PROVISIONER_NAME一致
- 创建一个PVC(test-pvc.yaml):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
annotations:
volume.beta.kubernetes.io/storage-class: "course-nfs-storage"
spec:
accessModes
- ReadWriteMany
resources:
requests:
storage: 1Mi
- 创建Pod来测试Storage(test-pod.yaml):
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: busybox
imagePullPolicy: IfNotPresent
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 | exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-pvc
- 在实际工作中,使用StorageClass更多的是StatefulSet类型的服务,StatefulSet类型的服务我们也可以通过一个volumeClaimTemplates属性来直接使用 StorageClass,如下(test-statefulset-nfs.yaml):
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nfs-web
spec:
serviceName: "nginx"
replicas: 3
selector:
matchLabels:
app: nfs-web
template:
metadata:
labels:
app: nfs-web
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
annotations:
volume.beta.kubernetes.io/storage-class: course-nfs-storage
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
5.PV和PVC的生命周期
5.1 存储供给
存储供给(Provision)是指为PVC准备可用PV的机制。K8s支持两种PV供给方式:
- 静态供给:静态供给是指由集群管理员手动创建一定数量的PV的资源供应方式。静态提供的PV可能属于某存储类(StorageClass),也可能没有存储类。
- 动态供给:不存在某静态的PV匹配到用户的PVC申请时,k8s集群会尝试为PVC动态创建符合需求的PV。这种方式依赖于StorageClass的辅助,PVC必须向一个事先存储的StorageClass发起动态分配PV的请求,没有指定StorageClass的PVC请求会被禁止使用动态创建PV的方式。
5.2 存储绑定
用户基于需求和访问模式定义好PVC后,k8s控制器为其查找匹配的PV,找到后它们二者之间的状态为Binding,若PV是动态创建的,该PV专用于其PVC。
若无法为PVC找到可匹配的PV,则PVC一直处于unbound状态,直到有符合条件的PV出现并完成绑定方才可用。
- 存储使用:一旦完成将存储卷挂载至Pod对象内的容器中,其应用即可使用关联的PV提供的存储空间;
- PVC保护:有用户删除了仍处于某Pod资源使用中的PVC时,k8s不会立即予以移除,而是推迟到不再被任何Pod资源使用后方才执行删除操作。处理此种阶段的PVC资源的status为Termination,并且其Finalizers字段中包含kubernetes.io/pvc-protection;
5.3 存储回收
- 留存(Retain): 在删除PVC之后,k8s不会自动删除PV,仅仅是将它置于释放(released)。不过,此种状态的PV尚且不能被其他PVC申请所绑定,因为此前申请生成的数据仍然存在。如果想再次使用此类的PV资源,需要由管理员手动执行删除操作
- 删除(Delete): 对于支持Deleted回收策略的存储插件来说,在PVC被删除后会直接移除PV对象,同时移除的还有PV相关的外部存储系统上的存储资产。动态创建的PV资源的回收策略取决于相关StorageClass上的定义,StorageClass上默认的策略为Delete。
