PV与PVC介绍

PV(PersistentVolume)与PVC(PersistentVolumeClaim)就是在用户与存储服务之间添加一个中间层,管理员事先根据PV支持的存储卷插件以及适配器的存储方案细节定义好可以支撑存储卷的底层存储空间,而后由用户通过PVC声明要使用的存储特性来绑定符合条件的最佳PV定义存储卷,从而实现存储系统的使用与管理职能的解耦,大大简化了用户使用存储的方式。

PV和PVC的生命周期由ControllerManager中专用的PV控制独立管理,这种机制的存储卷不在依赖并受限于pod对象的生命周期,从而实现了用户和集群管理员的职责相分离,也充分体现了kubernetes把简单留给用户,把复杂留给自己的管理理念。

PV与PVC基础

PV是由集群管理员于全局级别配置的预挂载存储空间,它通过支持的存储卷插件及给定的配置参数关联至某个存储系统上可用数据存储的一段空间,这段存储空间可能是ceph存储系统上的一个存储映像、一个文件系统或其子目录,也可能是NFS存储系统上的一个导出目录等。pv将存储系统上的存储空间抽象为kubernetes系统全局级别的API资源,由集群管理员负责管理和维护。

将pv提供的存储空间用于pod对象的存储卷时,用户需要事先使用pvc在名称空间级别声明所需要的存储空间大小及访问模式并提交给kubernetes API Server,接下来由pv控制器负责查找与之匹配的pv资源并完成绑定。随后,用户在pod资源中使用persistentVolumeClaim类型的存储卷插件指明要使用的pvc对象的名称即可使用其绑定到的pv所指向的存储空间。

尽管pvc及pv将存储资源管理与使用的职责分离至用户和集群管理员两类不同的人群之上,简化了用户对存储资源的使用机制,但也对二者之间的协同能力提出了要求。管理员需要精心预测和规划集群用户的存储使用需求,提前创建出多种规格的pv,以便于在用户声明pvc后能够由pv控制器在集群中寻到合适的甚至是最佳匹配的pv进行绑定。这种通过管理员手动创建pv来满足pvc需求的静态预配存在着不少问题。

  1. 集群管理员难以预测出用户的真实需求,很容易导致某些类型的pvc无法匹配到pv而被挂起,直到管理员参与到问题的解决过程中。
  2. 那些能够匹配到pv的pvc也很有可能存在资源利用率不佳的状况,例如一个声明使用5G存储空间的pvc绑定到一个20G的pv之上。

更好的解决方案是一种称为动态预配、按需创建pv的机制。集群管理员要做的仅是事先借助存储类的API资源创建出一到多个pv模板,并在模板中定义好基于某个存储系统创建pv所依赖的存储组件时需要用到的配置参数。创建pvc时,用户需要为其指定要使用pv模板,而后pv控制器会自动连接相应存储类上定义的目标存储系统的管理接口,请求创建匹配该pvc需求的存储组件,并将该存储组件创建为kubernetes集群上可由该pvc绑定的pv资源。

需要说明的是,静态预配的pv可能属于某个存储类,也可能没有存储类,这取决于管理员的设定。但动态pv预配依赖存储类的辅助,pvc必须向一个事先存在的存储类发起动态分配pv的请求,没有指定存储类的pvc不支持使用动态预配pv的方式。

pv和pvc是一对一的关系:一个pvc技能绑定一个pv,而一个pv在某一时刻也仅可被一个pvc所绑定。为了能够让用户更精细地表达存储需求,pv资源对象的定义支持存储容量、存储类、卷模型和访问模式等属性维度的约束。相应地,pvc资源能够从访问模式、数据源、存储资源容器需求和限制、标签选择器、存储类名称、卷类型和卷名称等多个不同的维护向pv资源发起匹配请求并完成筛选。

PV的生命周期

从较为高级的实现上来讲,kubernetes系统与存储相关的组件主要有存储卷插件、存储卷管理器、pv/pvc控制器和AD控制器这4中。

  • 存储卷插件:kubernetes存储卷功能的基础设施,是存储任务相关操作的执行方,它是存储相关的扩展接口,用于对接各类存储设备。
  • 存储卷管理器:kubelet内置管理器组件之一,用于在当前节点上执行存储设备的挂载、卸载和格式化等操作;另外,存储卷管理器也可执行节点级别设备的附加(attach)及拆除(detach)操作。
  • pv控制器:负责pv及pvc的绑定和生命周期管理,并根据需求进行存储卷的预配和删除操作。
  • AD控制器:专用于存储设备的附加和拆除操作的组件,能够将存储设备管理至目标节点或从目标节点之上剥离。

这4个组件中,存储卷插件是其它3个组件的基础库,换句话说,pv控制器、AD控制器和存储卷管理器均构建于存储卷插件之上,以提供不同维度管理功能的接口,具体实现逻辑均由存储卷插件完成。

除了创建、删除pv对象,以及完成pv和pvc的状态迁移等生命周期管理之外,pv控制器还要负责绑定pvc与pv对象,而且pvc只能在绑定到pv之后方可由pod作为存储卷使用。创建后未能正确关联到存储设备的pv将处于Pending状态,知道成功关联后转为Available状态。而后一旦该pv被某个pvc请求并成功绑定,其状态也就顺应转为Bound,直到相应的pvc删除后而自动解除绑定,pv才会再次发生状态转变,此时的状态为Released,随后pv的去向将由其回收策略(reclaim policy)所决定,具体如下:

  1. Retain(保留):删除pvc后将保留其绑定的pv及存储的数据,但会把该pv置为Released状态,它不可在被其它pvc所绑定,且需要管理员手动进行后续的回收操作:首先删除pv,接着手动清理其关联的外部存储组件上的数据,最后手动删除该存储组件或基于该组件重新创建pv。
  2. Delete(删除):对于支持该回收策略的卷插件,删除一个pvc将同时删除其绑定的pv资源以及该pv关联的外部存储组件;动态的pv回收策略继承子StorageClass资源,默认为delete。多数情况下,管理员都需要根据用户的期望修改此默认策略,以免导致数据非计划内的删除。
  3. Recycle(回收):对于支持该回收策略的卷插件,删除pvc时,其绑定的pv所关联的外部存储组件上的数据会被清空,随后,该pv转为Available状态,可再次接收其它pvc的绑定请求。不过,该车库已被废弃。

相应地,创建后的pvc也将处于Pending状态,仅在遇到条件匹配、状态为Available的pv,且pvc请求绑定成功才会转为Bound状态。pv和pvc的生命周期存在以下几个关键阶段。

  1. 存储预配(provision):存储预配是指为pvc准备pv的途径,kubernetes支持静态和动态两种pv预配方式,前者是指由管理员以手动方式创建pv的操作,而后者则是由pvc基于StorageClass定义的模板,按需请求创建pv的机制。
  2. 存储绑定:用户基于一系列存储需求和访问模式定义好pvc后,pv控制器即会为其查找匹配的pv,完成关联后它们二者同时转为已绑定状态,而且动态预配的pv和pvc之间存在强关联关系。无法找到可满足条件的pv的pvc将一直处于Pending状态,直到有符合条件的pv出现并完成绑定为止。
  3. 存储使用:pod资源基于persistenVolumeClaim存储卷插件的定义,可将选定的pvc关联为存储卷并用于内部容器应用的数据存取。
  4. 存储回收:存储卷的使用目标完成之后,删除pvc对象可使得此前绑定的pv资源进入Released状态,并由pv控制器根据pv回收策略对pv做出相应的处置。目前,可用的回收策略有Retaine、Delete和Recycle这3种。

处于绑定状态的pvc删除后,相应的pv将装维Released状态,之后的处理机制依赖于其回收策略。而处于绑定状态的pv将会导致相应的pvc转为Lost状态,而无法再由pod正常使用,除非pvc在绑定至其它Available状态的pv之上,但应用是否能正常运行,则取决于对此前数据的依赖度。另一方面,为了避免使用中的存储卷被移除而导致数据丢失,kubernetes自1.9版本引入了pvc保护机制,其目的在于,用户删除了仍被pod对象使用的pvc时,kubernetes不会立即移除该pvc,而是会推迟带它不在被任何pod对象使用后方才真正的执行删除操作。处于保护阶段的pvc资源的status字段值为Termination,并且其Finalizers字段中包含有kubernetes.io/pvc-protection。

PV访问模式

  • ReadWriteOnce:该卷可以由单个节点以读写方式挂载。ReadWriteOnce 访问模式仍然可以允许多个 pod 在同一节点上运行时访问卷。
  • ReadOnlyMany:该卷可以被许多节点以只读方式挂载。
  • ReadWriteMany:该卷可以被许多节点以读写方式挂载。
  • ReadWriteOncePod:该卷可以由单个 Pod 以读写方式挂载。如果要确保整个集群中只有一个 pod 可以读取或写入 PVC,请使用 ReadWriteOncePod 访问模式。这仅支持 CSI 卷和 Kubernetes 1.22+ 版本。

在 CLI 中,访问模式缩写为:

  • RWO-ReadWriteOnce
  • ROX - ReadOnlyMany
  • RWX - ReadWriteMany
  • RWOP - ReadWriteOncePod

静态PV资源

PersistentVolume是隶属于kubernetes核心API群组中的标准资源类型,它的目的在于通过存储卷机制,将支持的外部存储系统上的存储组件定义为可被pvc声明所绑定的资源对象。但pv资源隶属于kubernetes集群级别,因而它只能由集群管理员进行创建。这种由管理员手动定义和创建的pv被人们习惯地称为静态pv资源。

pv支持的存储卷插件类型是pod对象支持的存储卷插件类型的一个子集,它仅涵盖pod支持的网络存储卷类别中的所有存储卷插件以及local卷插件。处理存储卷插件之外,PersistentVolume资源规范Spec字段主要支持以下几个通用字段,它们用于定义pv的容量、访问模式和回收策略等属性。

  • capacity <map [string]string>:指定pv的容量;目前,Capacity仅支持存储容量设定,将来应该还可以指定IOPS和吞吐量(throughput)。
  • accessModes <[]string>:指定当前pv支持的访问模式;存储系统支持的存取能力大体可分为ReadWriteOnce(单路读写)、ReadOnlyMany(多路只读)和ReadWriteMany(多路读写)3种类型,某个特定的存储系统可能会支持其中的部分或全部的能力。
  • PersistentVolumeReclaimPolicy <string>:pv空间被释放时的处理机制;可用类型仅为Retain(默认)、Recycle或Delete。目前,仅NFS和hostPath支持Recycle策略,也仅有部分存储系统支持Delete策略。
  • volumeMode <string>:该pv的卷模型,用于指定此存储卷被格式化为文件系统使用还是直接使用裸格式的块设备;默认值为Filesystem,仅块设备接口的存储系统支持该功能。
  • storageClassName <string>:当前pv所属的StorageClass资源的名称,指定的存储类需要事前存在;默认为空值,即不属于任何存储类。
  • mountOptions <string>:挂载选项组成的列表,例如ro、soft和hard等。
  • nodeAffinity <Object>:节点亲和性,用于限制能够访问该pv的节点,进而会影响与该pv管理的pvc的pod调度结果。

NFS PV示例

kubernetes集群各工作节点安装nfs服务客户端程序包nfs-common。

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: "/data/webapp/redis"
server: 192.168.174.120

查看nfs pv资源的详细信息

~# kubectl describe pv/pv-nfs
Name: pv-nfs
Labels: <none>
Annotations: <none>
Finalizers: [kubernetes.io/pv-protection]
StorageClass:
Status: Available #Available状态表示已经可以接受pvc的绑定请求,并在绑定完成后转为Bound状态。
Claim:
Reclaim Policy: Retain
Access Modes: RWX
VolumeMode: Filesystem
Capacity: 5Gi
Node Affinity: <none>
Message:
Source:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 192.168.174.120
Path: /data/webapp/redis
ReadOnly: false
Events: <none>

RBD示例

kubernetes集群各工作节点安装ceph客户端。

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-rbd
labels:
usedof: redisdata
spec:
capacity:
storage: 3Gi
accessModes:
- ReadWriteOnce
rbd:
monitors:
- 192.168.174.103
- 192.168.174.104
- 192.168.174.105
pool: wgsrbd
image: wgs-img01
user: wgsrbd
keyring: /etc/ceph/ceph.client.wgsrbd.keyring
fsType: xfs
readOnly: false
persistentVolumeReclaimPolicy: Retain

查看rbd pv信息

~# kubectl describe pv/pv-rbd
Name: pv-rbd
Labels: usedof=redisdata
Annotations: <none>
Finalizers: [kubernetes.io/pv-protection]
StorageClass:
Status: Available
Claim:
Reclaim Policy: Retain
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 3Gi
Node Affinity: <none>
Message:
Source:
Type: RBD (a Rados Block Device mount on the host that shares a pod's lifetime)
CephMonitors: [192.168.174.103 192.168.174.104 192.168.174.105]
RBDImage: wgs-img01
FSType: xfs
RBDPool: wgsrbd
RadosUser: wgsrbd
Keyring: /data/ceph/ceph.client.wgsrbd.keyring
SecretRef: nil
ReadOnly: false
Events: <none>

PVC资源

PersistentVolumeClaim也是kubernetes系统上标准的API资源类型之一,它位于核心API群组,属于名称空间级别。用户提交新建的PVC资源最初处于Pending状态,由pv控制器找寻最佳匹配的pv并完成二者绑定后,两者都将转让Bound状态,随后pod对象便可基于PersistentVolumeClaim存储卷插件配置使用该PVC对应的持久存储卷。

定义pvc时,用户可通过访问模式(accessModes)、数据源(DataSource)、存储资源空间需求和限制(resources)、存储类、标签选择器、卷模型和卷名称等匹配标准来筛选集群上的pv资源,其中resources和accessModes是最重要的筛选标准。pvc的Spec字段可嵌套字段有如下几个。

  • accessModes <[]string>:pvc的访问模式;它同样支持RWO、RWX和ROX这3种模式。
  • DataSources  <Object>:用于从指定的数据源恢复该pvc卷,它目前支持的数据源包括一个现存的卷快照对象、一个既有的pvc对象或一个既有的用于数据转存的自定义资源对象。
  • resources <Object>:声明使用的存储空间的最小值和最大值;目前,pvc的资源限定仅支持空间大小一个维度。
  • selector <Object>:筛选pv时额外使用的标签选择器或匹配条件表达式。
  • storageClassNmae <string>:该pvc资源隶属的存储类资源名称;指定了存储类资源的pvc仅能在同一个存储类下筛选pv资源,否则就只能从所有不具有存储类的pv中进行筛选。
  • volumeMode <string>:卷模型,用于指定此卷可被用作文件系统还是裸格式的块设备;默认值为Filesystem。
  • volumeName <string>:直接指定要绑定的pv资源名称。

NFS PVC示例

该示例仅定义了期望的存储空间范围、访问模式和卷模型以筛选集群上的pv资源。在集群上的pv资源数量很多时,用户可通过指定多维度的过滤条件来缩小pv资源的筛选范围,以获取到最佳配置。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-demo-0001
namespace: default
spec:
accessModes: ["ReadWriteMany"]
volumeMode: Filesystem
resources:
requests:
storage: 4Gi
limits:
storage: 10Gi

查看绑定信息

~# kubectl get pvc pvc-demo-0001
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-demo-0001 Bound pv-nfs 5Gi RWX 15m
~# kubectl get pv pv-nfs
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-nfs 5Gi RWX Retain Bound default/pvc-demo-0001 4h13m

RBD PVC示例

 该示例除了定义了期望访问模型、卷模型和存储空间容量之外,它使用了标签选择器来匹配pv资源的标签。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-demo-0002
spec:
accessModes: ["ReadWriteOnce"]
volumeMode: Filesystem
resources:
requests:
storage: 3Gi
limits:
storage: 5Gi
selector:
matchLabels:
usedof: "redisdata"

查看绑定信息

~# kubectl get pvc pvc-demo-0002
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-demo-0002 Bound pv-rbd 3Gi RWO 5m12s
~# kubectl get pv pv-rbd
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-rbd 3Gi RWO Retain Bound default/pvc-demo-0002 177m

删除一个pvc将导致其绑定的pv资源转入Released状态,并由相应的回收策略完成资源回收。反过来,直接删除一个仍由某pvc绑定的pv资源,会由pvc保护机制延迟该删除操作至相关的pvc资源被删除。

在pod中使用PVC

需要特别说明的是,pvc资源属于名称空间级别,它仅可被同一名称空间中的pod对象通过PersistentVolumeClaim插件所应用并作为存储卷使用,该存储卷插件可嵌套使用如下两个字段。

  • claimName:要调用的pvc存储卷的名称,pvc卷要以pod在同一名称空间中。
  • readOnly:是否强制将存储卷挂载为只读模式,默认为false。
apiVersion: v1
kind: Pod
metadata:
name: volumes-pvc-demo
namespace: default
spec:
containers:
- name: redis
image: redis:alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 6379
name: redisport
volumeMounts:
- mountPath: /data
name: redis-rbd-vol
volumes:
- name: redis-rbd-vol
persistentVolumeClaim:
claimName: pvc-demo-0002

查看pod挂载信息

~# kubectl describe pod volumes-pvc-demo
Name: volumes-pvc-demo
Namespace: default
Priority: 0
Node: 192.168.174.108/192.168.174.108
Start Time: Wed, 25 May 2022 09:59:26 +0800
Labels: <none>
Annotations: <none>
Status: Running
IP: 10.200.89.155
IPs:
IP: 10.200.89.155
Containers:
redis:
Container ID: docker://f743d89129a38bd0c2938714f5afc30f177885e7cbdd28e54915fe40791fe6a1
Image: redis:alpine
Image ID: docker-pullable://redis@sha256:4bed291aa5efb9f0d77b76ff7d4ab71eee410962965d052552db1fb80576431d
Port: 6379/TCP
Host Port: 0/TCP
State: Running
Started: Wed, 25 May 2022 10:00:08 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/data from redis-rbd-vol (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-47rn9 (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
redis-rbd-vol:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: pvc-demo-0002
ReadOnly: false
kube-api-access-47rn9:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s

存储类

存储类也是kubernetes系统上的API资源类型之一,它位于storage.k8s.io群组中。存储类通常由集群管理员为管理pv资源而按需创建的存储资源类别,例如可将存储系统按照其性能高低或者综合服务质量级别分类、依照备份策略分类,甚至直接按管理员自定义的标准分类等。存储类也是pvc筛选pv时的过滤条件之一,这意味着pvc仅能在其隶属的存储类之下寻找匹配的pv资源。不过,kubernetes系统自身无法理解类别到底意味着什么,它仅仅把存储类中的信息当中pv资源的特性描述使用。

存储类的最重要功能之一便是对pv资源动态预配机智的支持,它可被视作为动态pv资源的创建模板,能够让集群管理员从维护pvc和pv资源之间的耦合关系的束缚中解脱出来。需要用到具有持久化功能的存储卷资源时,用户只需要想满足其存储特性要求的存储类声明一个pvc资源,存储类将会根据该声明创建恰好匹配其需求的pv对象。

StorageClass资源

StorageClass资源的期望状态直接与apiVersion、kind和metadata定义在同一级别而无需嵌套在spec字段中,它支持使用的字段包括如下几个。

  • allowVolumeExpansion <boolean>:是否支持存储卷空间扩展功能。
  • allowedTopologies <[]Object>:定义可以动态配置存储卷的节点拓扑,仅启用了卷调度功能的服务器才会用到该字段;每个卷插件都有自己支持的拓扑规范,空的拓扑选择器表示无拓扑限制。
  • provisioner <string>:必选字段,用于指定存储服务方(provisioner,或称为预配器),存储类要基于该字段值来判断要使用的存储插件,以便适配到目标存储系统;kubernetes内置了支持许多的provisioner,它们的名字都以kubernetes.io/为前缀,例如kubernetes.io/glusterfs等。
  • parameters <map[string]string>:定义连接至指定的provisioner类别下的某特定存储时需要使用的各相关参数;不同provisioner的可用参数各不相同。
  • reclaimPolicy <string>:由当前存储类动态创建的pv资源的默认回收策略,可用值为Delete(默认)和Retain两个;但那些静态pv的回收策略则取决于它们自身的定义。
  • volumeBindingMode <string>:定义如何为pvc完成预配和绑定,默认值为VolumeBindingImmediate;该字段仅在启用了存储卷调度功能时才能生效。
  • mountOptions <[]string>:由当前类动态创建的pv资源的默认挂载选项列表。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-rbd
provisioner: kubernetes.io/rbd
parameters:
monitors: 192.168.174.103:6789,192.168.174.104:6789,192.168.174.105:6789
adminId: admin
adminSecretName: ceph-admin-secret
adminSecretNamespace: wgsrbd-system
pool: wgsrbd
userId: wgsrbd
userSecretName: ceph-wgsrbd-secret
userSecretNamespace: wgsrbd-system
fsType: xfs
imageFormat: "2"
imageFeatures: "layering"
reclaimPolicy: Retain

不同的provisioner的parameter字段中可嵌套使用的字段各有不同,Ceph RBD存储服务可使用的各字段如下。 其它存储系统可参考官方文档https://kubernetes.io/docs/concepts/storage/storage-classes/#parameters

  • monitors <string>:Ceph存储系统的监视器访问接口,多个套接字之间以逗号分割。
  • adminId  <string>:有权限在指定的存储池中创建image的管理员用户名,默认为admin。
  • adminSecretName <string>:存储有管理员账号认证密钥的Secret资源名称。
  • adminSecretNamespace <string>:管理员账号相关的Secret资源所在的名称空间。
  • pool <string>:ceph存储系统的RBD存储池名称,默认为rbd。
  • userId <string>:用于映射RBD镜像的ceph用户账号,默认同adminId字段。
  • userSecretName <string>:存储有用户账号认证密钥的Secret资源名称。
  • userSecretNamespace <string>:用户账号相关的Secret资源所在的名称空间。
  • fsType <string>:存储映像格式化的文件系统类型,默认为ext4。
  • imageFormat <string>:存储映像的格式,其可用值仅有1和2,默认为2。
  • mageFeatures <string>:2格式的存储映像支持的特性,目前仅支持layering,默认值为空值,并且不支持任何功能。

与pod或pv资源上的RBD卷插件配置格式不同的是,StorageClass上的RBD参数不支持Keying直接认证到Ceph,它仅能引用Secret资源中存储的认证密钥完成认证操作。

获取ceph认证用户密钥信息

admin认证信息

~# ceph auth get-key client.admin
AQDBP7BhKJevMRAAwD7yV3LH30SQmPCXCWtXiQ==

wgsrbd认证信息

~# ceph auth get-key client.wgsrbd
AQDUvLJhXTxNGxAAhYKUR84if5s97cjiE0iFbQ==

创建wgsrbd-system  namespace

apiVersion: v1
kind: Namespace
metadata:
name: wgsrbd-system

创建Secret资源

admin secret

~# kubectl create secret generic ceph-admin-secret --type="kubernetes.io/rbd" --from-literal=key='AQDBP7BhKJevMRAAwD7yV3LH30SQmPCXCWtXiQ==' --namespace=wgsrbd-system
secret/ceph-admin-secret created

wgsrbd secret

~# kubectl create secret generic ceph-wgsrbd-secret --type="kubernetes.io/rbd" --from-literal=key='AQDUvLJhXTxNGxAAhYKUR84if5s97cjiE0iFbQ==' --namespace=wgsrbd-system
secret/ceph-wgsrbd-secret created

查看sc信息

~# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
fast-rbd kubernetes.io/rbd Retain Immediate false 34s
~# kubectl describe sc fast-rbd

Name: fast-rbd

IsDefaultClass: No

Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"name":"fast-rbd"},"parameters":{"adminId":"admin","adminSecretName":"ceph-admin-secret","adminSecretNamespace":"wgsrbd-system","fsType":"xfs","imageFeatures":"layering","imageFormat":"2","monitors":"192.168.174.103:6789,192.168.174.104:6789,192.168.174.105:6789","pool":"wgsrbd","userId":"wgsrbd","userSecretName":"ceph-wgsrbd-secret","userSecretNamespace":"wgsrbd-system"},"provisioner":"kubernetes.io/rbd","reclaimPolicy":"Retain"}
Provisioner: kubernetes.io/rbd

Parameters: adminId=admin,adminSecretName=ceph-admin-secret,adminSecretNamespace=wgsrbd-system,fsType=xfs,imageFeatures=layering,imageFormat=2,monitors=192.168.174.103:6789,192.168.174.104:6789,192.168.174.105:6789,pool=wgsrbd,userId=wgsrbd,userSecretName=ceph-wgsrbd-secret,userSecretNamespace=wgsrbd-system

AllowVolumeExpansion: <unset>

MountOptions: <none>

ReclaimPolicy: Retain

VolumeBindingMode: Immediate

Events: <none>

PV动态预配

动态pv预配功能的使用有两个前提条件:支持动态pv创建功能的卷插件,以及一个使用了对应于该存储插件的后端存储系统的StorageClass资源。

RBD存储插件,关联至ceph RBD存储系统接口的存储资源fast-rbd就能实现pv动态预配功能,用户与该存储类中创建pvc资源后,运行于kube-controller-manager守护进程中的pv控制器会根据fast-rbd存储类的定义接入ceph存储系统创建出相应的存储映像,并在自动创建一个关联至该存储映像的pv资源后,将其绑定值pvc资源。

动态pv预配的过程中,pvc控制器会调用相关存储系统的管理接口API或专用的客户端工具来完成后端存储系统上的存储组件管理。以ceph rbd为例,pv控制器会以存储类型参数adminId中指定的用户身份调用rbd命令创建存储映像、然后,以kubeadm部署且运行为静态pod资源的kube-controller-manager容器并未自行附带此类工具,如ceph-common程序包。常见的解决办法有3种:在kubernetes系统上部署kubernetes-incubator/external-storage中的rbd-provisioner,从而以外置的方式提供相关工具程序,或基于CSI卷插件使用ceph-csi项目来支持更加丰富的卷功能,或定制kube-controller-manager的容器镜像,为其安装ceph-common程序包。

pvc示例

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-sc-rbd-demo
namespace: default
spec:
accessModes: ["ReadWriteOnce"]
volumeMode: Filesystem
resources:
requests:
storage: 3Gi
limits:
storage: 10Gi
storageClassName: fast-rbd

查看pvc信息

~# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-sc-rbd-demo Bound pvc-793c9f8c-4607-4e60-b1a1-016f9fba101d 3Gi RWO fast-rbd 40m

查了pv信息

~# kubectl describe pv pvc-793c9f8c-4607-4e60-b1a1-016f9fba101d
Name: pvc-793c9f8c-4607-4e60-b1a1-016f9fba101d
Labels: <none>
Annotations: kubernetes.io/createdby: rbd-dynamic-provisioner
pv.kubernetes.io/bound-by-controller: yes
pv.kubernetes.io/provisioned-by: kubernetes.io/rbd
Finalizers: [kubernetes.io/pv-protection]
StorageClass: fast-rbd
Status: Bound
Claim: default/pvc-sc-rbd-demo
Reclaim Policy: Retain
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 3Gi
Node Affinity: <none>
Message:
Source:
Type: RBD (a Rados Block Device mount on the host that shares a pod's lifetime)
CephMonitors: [192.168.174.103:6789 192.168.174.104:6789 192.168.174.105:6789]
RBDImage: kubernetes-dynamic-pvc-7492c079-112a-4b47-9f53-73c27b97e1b5
FSType: xfs
RBDPool: wgsrbd
RadosUser: wgsrbd
Keyring: /etc/ceph/keyring
SecretRef: &SecretReference{Name:ceph-wgsrbd-secret,Namespace:wgsrbd-system,}
ReadOnly: false
Events: <none>

查看ceph 映像

~# rbd ls --pool wgsrbd -l 
NAME SIZE PARENT FMT PROT LOCK
kubernetes-dynamic-pvc-7492c079-112a-4b47-9f53-73c27b97e1b5 3 GiB 2
wgs-img01 3 GiB 2

在pod中使用pvc

apiVersion: v1
kind: Pod
metadata:
name: volumes-pvc-demo
namespace: default
spec:
containers:
- name: redis
image: redis:alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 6379
name: redisport
volumeMounts:
- mountPath: /data
name: redis-rbd-vol
volumes:
- name: redis-rbd-vol
persistentVolumeClaim:
claimName: pvc-sc-rbd-demo
查看pod信息

查看代码

~# kubectl describe pod volumes-pvc-demo 
Name: volumes-pvc-demo
Namespace: default
Priority: 0
Node: 192.168.174.108/192.168.174.108
Start Time: Thu, 26 May 2022 17:28:21 +0800
Labels: <none>
Annotations: <none>
Status: Running
IP: 10.200.89.159
IPs:
IP: 10.200.89.159
Containers:
redis:
Container ID: docker://32d813db6571738842c5c0fa890d7c2634614bbff4c08f71d5291deefda5493a
Image: redis:alpine
Image ID: docker-pullable://redis@sha256:4bed291aa5efb9f0d77b76ff7d4ab71eee410962965d052552db1fb80576431d
Port: 6379/TCP
Host Port: 0/TCP
State: Running
Started: Thu, 26 May 2022 17:28:31 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/data from redis-rbd-vol (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-t4rmk (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
redis-rbd-vol:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: pvc-sc-rbd-demo
ReadOnly: false
kube-api-access-t4rmk:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m57s default-scheduler Successfully assigned default/volumes-pvc-demo to 192.168.174.108
Normal SuccessfulAttachVolume 2m57s attachdetach-controller AttachVolume.Attach succeeded for volume "pvc-793c9f8c-4607-4e60-b1a1-016f9fba101d"
Normal Pulled 2m47s kubelet Container image "redis:alpine" already present on machine
Normal Created 2m47s kubelet Created container redis
Normal Started 2m47s kubelet Started container redis
查看pod挂载信息

查看代码


~# kubectl -it exec volumes-pvc-demo -- df -h
Filesystem Size Used Available Use% Mounted on
overlay 18.6G 10.5G 7.1G 60% /
tmpfs 64.0M 0 64.0M 0% /dev
tmpfs 976.2M 0 976.2M 0% /sys/fs/cgroup
/dev/rbd0 3.0G 53.9M 2.9G 2% /data

容器存储接口CSI

存储卷管理器通过调用存储卷插件实现当前节点上存储卷相关的附加、分离、挂载、卸载等操作,对于未被kubernetes内置的卷插件所支持的存储系统或服务来说,扩展定义新的存储卷插件是解决问题的唯一途径。但将存储供应商提供的第三方存储代码打包到kubernetes的核心代码可能会导致可靠性及安全性方面的问题,因而这就需要一种简单、便捷的、外置于kubernetes代码树的扩展方式,FlexVolume和CSI就是这样的存储卷插件,换句话说,它们自身是内置的存储卷插件,但实现的却是第三方存储卷的扩展接口。

CSI基础

FlexVolume是kubernetes自V1.8版本引入GA阶段的一种存储插件扩展方式,它要求将外部插件的二进制文件部署在预先配置的路径中(例如/usr/libexec/kubernetes/kubelet-plugins/volume/exec/),并设定系统环境满足其正常运行所需要的全部依赖关系。事实上,一个FlexVolume类型的插件就是一款可被kubelet驱动的可执行文件,它实现了特定存储的挂载、卸载等存储插件接口,而对该类插件的调用相当于请求运行该程序文件,并要求返回JSON格式的相应内容。

而自kubernetes的v1.13版进入GA阶段的CSI是一种更加开放的存储卷插件接口标准,它独立于kubernetes,由CSI社区制定,可被Mesos和CloudFoundry等编排系统共同支持,而且能够以容器化形式部署,更加符合云原生的要以。除了允许第三方供应商外置实现存储插件之外,CSI支持使用存储类、pv和pvc等组件,因而它们与内置的存储卷插件具有一脉相承的功能和特性。

第三方需要提供的CSI组件主要是两个CSI存储卷驱动,一个是节点插件(Identity+Node),用于同kubelet交互实现存储卷的挂载和卸载等功能,另一个是自定义控制器(Identity+Controller),负载处理来自API Server的存储卷管理请求,例如,创建和删除等,它的功能类似于控制器管理器中的pv控制器。

kubelet对存储卷的挂载和卸载操作将通过UNIX Sock调用在同一主机上运行的外部CSI卷驱动程序完成。初始化外部CSI卷驱动程序时,kubelet必须调用CSI方法NodeGetInfo才能将kubernetes的节点名称映射为CSI的节点标识(NodeID)。于是,为了降低部署外部容器化的CSI卷驱动程序时的复杂度,kubernetes团队提供一个以Sidecar容器运行的应用--kubernetes CSI Helper,以辅助自动完成UNIX Sock套接字注册机NodeID的初始化。

不受kubernetes信任的第三方卷驱动程序运行为独立容器,它无法直接同控制器管理器通信,而是借助于kubernetes API Server进行,换句话说,CSI存储卷驱动需要注册监视API Server上的特定资源并针对存储卷管理器面向其存储卷请求执行预配,删除、附加和分离等操作。同样为了降低外部容器化CSI卷驱动及控制器程序部署的复杂度,kubernetes团体提供了一到多个以Sidecar容器运行的代理应用kubernetes to CSI来负责监视kubernetes API,并触发针对CSI卷驱动程序容器的相应操作。

  • external-privisioner:CSI存储卷的创建和删除。
  • external-attacher:CSI存储卷的附加和分离。
  • external-resizer:CSI存储卷的容量调整(扩缩容)。
  • external-snapshotter:CSI存储卷的快照管理(创建和删除等)。

尽快kubernetes并为指定CSI卷驱动程序的打包标准,但它提供了以下建议,以简化容器化CSI卷驱动程序的部署。

  1. 创建一个独立CSI卷驱动容器镜像,由其实现存储卷插件的标准行为,并在运行时通过UNIX Socket公开其API。
  2. 将控制器级别的各辅助容器(external-privisioner和external-attacher)以Sidecar的形式同带有自定义控制器功能的CSI卷驱动程序容器运行在同一个pod中,而后借助StatefulSet或Deployment控制器资源确保各辅助容器可正常运行相应数目的实例副本,将负责各容器间通信的UNIX Socket存储到共享的emptyDir存储卷上。
  3. 将节点上需要的辅助容器node-driver-registrar以Sidecar的形式与运行CSI卷驱动程序的容器运行在同一pod中,而后借助DaemonSet控制器资源确保辅助容器可在每个节点上运行一个实例。