K8s: PersistantVolume, PersistentVolumeClaim, Storage Class的相关概念与应用
注意,上面provisioner配置里的名称要和provisioner配置文件中的环境变量PROVISION_NAME保持一致。3 )PV, PVC,StorageClass 的协作流程。注意,要先由pv, 之后才能创建 pvc。
PersistantVolume
1 )概述
- PersistentVolume 持久化存储对象
- 在使用 nfs 的时候不是给一个项目使用的,而是全公司项目服务的
- 这就会涉及到一些项目隔离的问题,比如私有的目录,不被项目团队之外的人访问到
- 同时又不希望自己去维护那些目录,希望管理员给我做好这个事情
- 只要告诉管理员,用什么样的资源,要多大的空间内存空间,让管理员提供这么一个环境
- 相当于我把整个NFS这个目录把它拆分成了若干块这种隔离的存储
- 管理员事先提供这样的隔离存储,然后供给不同的团队来用
2 )应用
-
$
vi pv-demo1.yaml
apiVersion: v1 kind: PersistentVolume metadata: name: pv-demo spec: capacity: storage: 200Mi # 这里可以调大,比如 5Gi accessModes: - ReadWriteOnce # 一次只由一个 work node 去读写,保证读写不会冲突 persistentVolumeReclaimPolicy: Recycle nfs: path: /nfsdata server: master.k8s # master节点配置的hostname
-
$
kubectl apply -f pv-demo1.yaml
persistentvolume/pv-demo created
-
$
kubectl get pv pv-demo
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv-demo 200Mi RWO Recycle Available 81s
- 可以看到,这里的状态是 available 是可以申领的状态
PersistentVolumeClaim
1 )概述
- PVC (PersistentVolumeClaims),这里 claim,就是申领,申请的意思
- PV 表示:管理员事先划分好了一堆硬盘做在什么办公室等着大家来用。
- PVC 表示: 员工排的队过来领新的硬盘, 需要声明领取什么型号类型的硬盘,硬盘由哪些功能
- PV 和 PVC它的设计就是面向一个组织,一个集群模式下设计的
2 )应用
-
PVC 的申请
-
$
vi pvc-demo1.yaml
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvc-demo spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi # 申领 1G 的空间
-
注意,要先由pv, 之后才能创建 pvc
-
$
kubectl apply -f pvc-demo1.yaml
persistentvolumeclaim/pvc-demo created
-
$
kubectl get pv pv-demo
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv-demo 2Gi RWO Recycle Bound default/pvc-demo 2m23s
- 这里可以看到,STATUS 已经是 Bound 了
- 只要有人申领磁盘,这个状态就变为 绑定状态
-
$
kubectl get pvc pvc-demo
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc-demo Bound pv-demo 2Gi RWO 3m17s
- 现在不用关心底层的PV存储在哪里,我只需要知道有 pvc-demo 就可以调用了
- 这里屏蔽了对存储资源的细节的管理
3 ) 相关概念
- 访问模式
- Claim在请求具有特定访问模式的存储时,使用与卷相同的访问模式约定
- 卷模式
- Claim使用与卷相同的约定来表明是将卷作为文件系统还是块设备来使用
- 资源
- Claim和 Pod 一样,也可以请求特定数量的资源。在这个上下文中,请求的资源是存储
- 卷和Claim都使用相同的资源模型
- 选择算符
- Claim可以设置标签选择算符 来进一步过滤卷集合
- 只有标签与选择算符相匹配的卷能够绑定到Claim上
- 选择算符包含两个字段:
- matchLabels - 卷必须包含带有此值的标签
- matchExpressions - 通过设定键(key)、值列表和操作符(operator) 来构造的需求
- 合法的操作符有 In、NotIn、Exists 和 DoesNotExist
- 来自 matchLabels 和 matchExpressions 的所有需求都按逻辑与的方式组合在一起
- 这些需求都必须被满足才被视为匹配
- 类
- Claim可以通过为 storageClassName 属性设置 StorageClass 的名称来请求特定的存储类
- 只有所请求的类的 PV 卷,即 storageClassName 值与 PVC 设置相同的 PV 卷, 才能绑定到 PVC Claim
4 )总结
- PVC Claim不必一定要请求某个类
- 如果 PVC 的 storageClassName 属性值设置为 “”, 则被视为要请求的是没有设置存储类的 PV 卷
- 因此这一 PVC Claim只能绑定到未设置 存储类的 PV 卷
- (未设置注解或者注解值为 “” 的 PersistentVolume(PV)对象在系统中不会被删除,因为这样做可能会引起数据丢失。
- 未设置 storageClassName 的 PVC 与此大不相同,也会被集群作不同处理
- 具体筛查方式取决于 DefaultStorageClass 准入控制器插件是否被启用
- 当某 PVC 除了请求 StorageClass 之外还设置了 selector,则这两种需求会按 逻辑与关系处理
- 只有隶属于所请求类且带有所请求标签的 PV 才能绑定到 PVC, 参考如下:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 8Gi storageClassName: slow selector: matchLabels: release: "stable" matchExpressions: - {key: environment, operator: In, values: [dev]}
- 持久卷申领(PersistentVolumeClaim,PVC)表达的是用户对存储的请求,概念上与 Pod 类似
- Pod会耗用节点资源,而 PVC 申领会耗用 PV 资源
- Pod 可以请求特定数量的资源(CPU 和内存),同样 PVC 申领也可以请求特定的大小和访问模式
- 例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany
- 这些模式之一来挂载,参见访问模式
- 尽管 PersistentVolumeClaim 允许用户消耗抽象的存储资源
- 常见的情况是针对不同的问题用户需要的是具有不同属性(如,性能)的 PersistentVolume 卷
- 集群管理员需要能够提供不同性质的 PersistentVolume
- 并且这些 PV 卷之间的差别不 仅限于卷大小和访问模式,同时又不能将卷是如何实现的这些细节暴露给用户
- 为了满足这类需求,就有了 存储类(StorageClass) 资源
- 综上
- 存储的管理是一个与计算实例的管理完全不同的问题
- PersistentVolume 子系统为用户和管理员提供了一组 API,将存储如何供应的细节从其如何被使用中抽象出来
- 为了实现这点,引入了两个新的 API资源:PersistentVolume 和 PersistentVolumeClaim
存储类 Storage Class
1 ) 概述
- 回顾一下PV和PVC的关系
- PV是管理员买了一堆磁盘
- PVC是员工排着队,拿着这个PVC这个申请单找管理员要PV
- 要到PV之后,PV就会在底层的真正存储进行一个磁盘的一个创建
- 这个就是相关工作流程
- Storage Class 面向更大规模的开发
- 对于开发者来讲, 他其实只想知道pv的属性, volume的大小
- 如果在大规模的 K8s 集群里面可能有成千上万个PVC
- 这就意味着运维人员必须手动的创建这么多PV,然后会有新的PVC不断被提交
- 运维人员就不断的添加新的PV,这样的话存在一个人工操作的问题,就是效率非常低
- 对不同应用的存储性能要求可能不尽相同, 比如说独写速度,并发性能
- 所以 K8s 又为我们引用了新的一个资源,StorageClass 存储类型
- 这个就更抽象了,比PVC还要抽象一层
- Storage Class 构成
- PV的属性
- 比如:存储类型, Volume的大小等
- 创建这种PV需要用到的存储插件
- K8s提供自动创建PV的机制:Dynamic Provisioning
- 可以动态的发布创建存储
- 解决了管理员手工操作的问题
- PV的属性
2 )应用示例
-
$
vi sc-demo1.yaml
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage provisioner: qgg-nfs-storage # 将存储变为可用存储 parameters: archiveOnDelete: "false"
-
注意,上面provisioner配置里的名称要和provisioner配置文件中的环境变量PROVISION_NAME保持一致
3 )PV, PVC,StorageClass 的协作流程
- 现在分两个角色,一个是管理员,一个是用户
- K8s 管理员首先会安装 Provisioner
- 先看下什么是 provision,当一个机器买来,它是一台裸机,没有操作系统
- 我装了一个操作系统,这个过程就叫 provision,就是把东西变得可用
- 装好动态能够创建磁盘的这个工具之后,管理员还要定义有哪些 Storage Class
- 比如说我手里有大概五十TB的存储,这些存储应该怎么定义
- 有的存储是SSD的,有的是这个机械硬盘
- 那我就分两个类,一个是 高速存储,一个是 低速存储
- 通过 provision 能够动态的创建底层的这个PV的对象去调用实际的存储
- 对于用户来讲,用户比如说是开发者来说,我创建了一个pod
- 这个pod定义了PVC, PVC里面引入了一个 Storage Class
- 这个 类型的比如是 SSD的, Storage Class 就会通过我的申领请求
- 动态给我创建一块能用的磁盘来交付给我
- 这就是它整个自动创建的一个流程
综合应用
场景:基于 PV,PVC,StorageClass来挂载这个NFS进行文件创建
1 )环境准备
- 安装好 nfs 相关软件环境
- 参考:https://active.blog.csdn.net/article/details/138136289
2 )编写yaml文件进行应用的综合创建
$ vi app-nfs-demo1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default #与RBAC文件中的namespace保持一致
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
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: jmgao1983/nfs-client-provisioner
# image: registry.cn-beijing.aliyuncs.com/qingfeng666/nfs-client-provisioner:v3.1.0
image: dyrnq/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes # 容器中预置
env:
- name: PROVISIONER_NAME
value: qgg-nfs-storage #provisioner名称,请确保该名称与 nfs-StorageClass.yaml文件中的provisioner名称保持一致
- name: NFS_SERVER
value: master.k8s #NFS Server IP地址
- name: NFS_PATH
value: /nfsdata #NFS挂载卷
volumes:
- name: nfs-client-root
nfs:
server: master.k8s #NFS Server IP地址
path: /nfsdata #NFS 挂载卷
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default #根据实际环境设定namespace,下面类同
---
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
# 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
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
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: qgg-nfs-storage #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致
parameters:
archiveOnDelete: "false"
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
annotations:
volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" #与nfs-StorageClass.yaml metadata.name保持一致
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
---
kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: nginx
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1" #创建一个SUCCESS文件后退出
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim #与PVC名称保持一致
3 )关于yaml中配置的梳理
- 创建 Persistent Volume Provisioner
- 管理员预制一个Provisioner,是NFS客户端的一个provisier
- 相当于它能够通过脚本自动化的创建一些NFS目录
- 创建服务账号 (RBAC相关)
- 创建集群角色 (RBAC相关)
- 用户一定要有权限才能访问某一个空间的一些API接口
- 这里定义了ClusterRole这个角色
- 它能够获取存储的
["get", "list", "watch", "create", "delete"]
这样的权限 - 它能够调用
storage.k8s.io 下的 storageclasses
这个api接口
- 创建集群角色绑定 (RBAC相关)
- 绑定的是集群角色和服务账号 (nfs-client-provisioner)
- 绑定之后,用户服务账号就具备了Cluster的相关权限
- 创建普通角色 (RBAC相关)
- 以获取endpoint这个终端机器的
["get", "list", "watch", "create", "update", "patch"]
- 等信息权限
- 创建角色绑定 (RBAC相关)
- 这个是普通应用级别角色,和集群角色不一样
- 创建 Storage Class
- VOLUMEBINDINGMODE 默认的模式是 Immediate, 就是立刻绑定
- Storage Class 会调用 Provisioner 去创建 PV
- 可见这种不同于之前的PVC去已创建好的PV中去申请
- 这种就更为强大
- 创建 PVC
- 就是我的申请,代表我要申请一个什么样的资源
- 这个是给 Pod 用
- 可以看到 Storage Class 对应的是
managed-nfs-storage
- 引用的是 上面的 Storage Class
- 创建 Pod
- 这个Pod去调用PVC,PVC 调用 Storage Class
- Storage Class 对 PV Provisioner 请求资源
- PV Provisioner 自动创造 PV
- 当上述环境都搭建完成之后,基于以上
- Pod就可以从PVC中申请存储了,这样创造出来的 PV 就被提供给 Pod 使用了
4 )执行并验证
-
$
kubectl apply -f app-nfs-demo1.yaml
创建deployment.apps/nfs-client-provisioner created serviceaccount/nfs-client-provisioner created clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created storageclass.storage.k8s.io/managed-nfs-storage created persistentvolumeclaim/test-claim created pod/test-pod created
-
$
kubectl get all
查看具体部署状态NAME READY STATUS RESTARTS AGE pod/nfs-client-provisioner-cd49bdc7-ngnr4 1/1 Running 0 26m pod/test-pod 0/1 Completed 0 26m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 7d8h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/nfs-client-provisioner 1/1 1 1 26m NAME DESIRED CURRENT READY AGE replicaset.apps/nfs-client-provisioner-cd49bdc7 1 1 1 26m
-
$
kubectl logs pod/nfs-client-provisioner-cd49bdc7-ngnr4 | grep Provision
查看 ProvisionI0424 11:45:09.634105 1 event.go:278] Event(v1.ObjectReference{Kind:"PersistentVolumeClaim", Namespace:"default", Name:"test-claim", UID:"e325c456-f401-4f90-b684-5b06f2f100c2", APIVersion:"v1", ResourceVersion:"390293", FieldPath:""}): type: 'Normal' reason: 'Provisioning' External provisioner is provisioning volume for claim "default/test-claim" I0424 11:45:09.937907 1 event.go:278] Event(v1.ObjectReference{Kind:"PersistentVolumeClaim", Namespace:"default", Name:"test-claim", UID:"e325c456-f401-4f90-b684-5b06f2f100c2", APIVersion:"v1", ResourceVersion:"390293", FieldPath:""}): type: 'Normal' reason: 'ProvisioningSucceeded' Successfully provisioned volume pvc-e325c456-f401-4f90-b684-5b06f2f100c2
-
$
kubectl get storageclass
查看 Storage ClassNAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE managed-nfs-storage qgg-nfs-storage Delete Immediate false 5m45s
-
$
kubectl get pvc test-claim
查看 pvcNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE test-claim Bound pvc-e325c456-f401-4f90-b684-5b06f2f100c2 1Mi RWX managed-nfs-storage 18m
-
好,现在所有都已经部署完成,现在回到 master 节点进行检查,看是否同步创建
- $
cd /nfsdata && ls
default-test-claim-pvc-e325c456-f401-4f90-b684-5b06f2f100c2
- $
cd default-test-claim-pvc-e325c456-f401-4f90-b684-5b06f2f100c2 && ls
SUCCESS
- $
-
可见,文件已经同步写到 master 节点上了
5 )总结
- 整个流程,按照上面流程图来说是比较复杂的
- 生产环境中,都用 Storage Class 这样PVC操作就很简单了
更多推荐
所有评论(0)