共计 7114 个字符,预计需要花费 18 分钟才能阅读完成。
今天就跟大家聊聊有关详解 Kubernetes 存储体系,可能很多人都不太了解,为了让大家更加了解,丸趣 TV 小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
Volume、PV、PVC、StorageClass 由来
先思考一个问题,为什么会引入 Volume 这样一个概念?
“
答案很简单,为了实现数据持久化,数据的生命周期不随着容器的消亡而消亡。
”
在没有介绍 Kubernetes Volume 之前,先来回顾下 Docker Volume,Docker Volume 常用使用方式有两种,
volumes 通过这种方式,
Docker 管理宿主机文件系统的一部分,默认位于
/var/lib/docker/volumes 目录中,由于在创建时没有创建指定数据卷,
docker 自身会创建默认数据卷;bind mounts 通过这种方式, 可以把容器内文件挂载到宿主机任意目录。
既然有了 Docker Volume,为啥 Kubernetes 又搞出了自己的 Volume?谷歌的标新立异?
“
答案是否定的,Kubernetes Volume 和 Docker Volume 概念相似,但是又有不同的地方,Kubernetes Volume 与 Pod 的生命周期相同,但与容器的生命周期不相关。当容器终止或重启时,Volume 中的数据也不会丢失。当 Pod 被删除时,Volume 才会被清理。并且数据是否丢失取决于 Volume 的具体类型,比如 emptyDir 类型的 Volume 数据会丢失,而持久化类型的数据则不会丢失。另外 Kubernetes 提供了将近 20 种 Volume 类型。
”
现在有了 Kubernetes 的 Volume,我们就可以完全可以在 Yaml 编排文件中填写上 Volume 是字段,如下 nfs 所示:
....
volumes:
- name: static-nfs
nfs:
server: 12.18.17.240
path: /nfs/data/static
如果你使用 ceph 作为存储插件,你可以在编排文件中这样定义:
volumes:
- name: ceph-vol
cephfs:
monitors:
- 12.18.17.241:6789
- 12.18.17.242:6789
user: admin
secretRef:
name: ceph-secret
readOnly: true
当然只要是 Kubernetes 已经实现的数据卷类型,你都可以按照如上方式进行直接在 Yaml 编排文件中定义使用。
看到这里其实已经完成了 80% 的工作,那么为什么还要设计多此一举的 PV 呢?这个问题先搁置下,后面会有说明。
在没有说明为什么要设计多此一举的 PV PVC 之前,先来看看什么是 PV PVC?
“PV 是对持久化存储数据卷的一种描述。
”
PV 通常是由运维人员提前在集群里面创建等待使用的一种数据卷。如下所示:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
nfs:
server: 10.244.1.4
path: /nfs
“
PVC 描述的是持久化存储的属性,比如大小、读写权限等。
”
PVC 通常由开发人员创建,如下所示:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
而用户创建的 PV PVC 必须绑定完成之后才能被利用起来。而 PV PVC 绑定起来的前提是 PV 中 spec 中声明字段大小、权限等必须满足 PVC 的要求。
成功绑定之后,就可以在 Pod Yaml 编排文件中定义和使用。如下所示:
apiVersion: v1
kind: Pod
metadata:
labels:
role: web
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
volumeMounts:
- name: nfs
mountPath: /usr/share/nginx/html
volumes:
- name: nfs
persistentVolumeClaim:
claimName: nfs
看到这里,我们还会认为仅仅是 PV 对 Volume 多了一层抽象,并不见得比直接在 Yaml 中声明 Volume 高明多少。仔细思考下,我们为什么能够直接在 Yaml 中直接定义 Volume?因为 Kubernetes 已经帮助我们实现了这种 Volume 类型,如果我们有自己的存储类型,而 Kubernetes 中并没有实现,这种是没有办法直接在 Yaml 编排文件中直接定义 Volume 的。这个时候 PV PVC 面向对象的设计就体现出其价值了。这也是在软件开发领域经常碰到的问题, 开源软件无法满足要求, 但也没有提供出可扩展的接口, 没办法, 只能重新造轮子。
我们在开发过程中经常碰到这样一个问题,在 Pod 中声明一个 PVC 之后,发现 Pod 不能被调度成功,原因是因为 PVC 没有绑定到合适的 PV,这个时候要求运维人员创建一个 PV,紧接着 Pod 调度成功。刚才上在介绍 PV PVC,它们的创建过程都是手动,如果集群中需要成千上万的 PV,那么运维人员岂不累死?在实际操作中,这种方式根本行不通。所以 Kubernetes 给我们提供了一套自动创建 PV 的机制 Dynamic Provisioning. 在没有介绍这套自动创建机制之前,先看看 Static Provisioning,什么是 Static Provisioning?刚才人工创建 PV PVC 的方式就是 Static Provisioning。你可以在 PV PVC 编排文件中声明 StorageClass,如果没有声明,那么默认为 . 具体交互流程如下所示:
静态分配流程
首先由集群管理员事先去规划这个集群中的用户会怎样使用存储,它会先预分配一些存储,也就是预先创建一些 PV;然后用户在提交自己的存储需求(PVC)的时候,Kubernetes 内部相关组件会帮助它把 PVC PV 做绑定;最后 pod 使用存储的时候,就可以通过 PVC 找到相应的 PV,它就可以使用了。不足之处也非常清楚,首先繁琐,然后运维人员无法预知开发人员的真实存储需求,比如运维人员创建了多个 100Gi 的 PV 存储,但是在实际开发过程中,开发人员只能使用 10Gi,这就造成了资源的浪费。当然 Kubernetes 也为我们提供更好的使用方式,即 Dynamic Provisioning 它是什么呢?
“
Dynamic Provisioning 包含了创建某种 PV 所需要的参数信息,类似于一个创建 PV 的模板。具体交互流程如下所示:
”
Kubernetes 集群中的控制器,会结合 PVC 和 StorageClass 的信息动态生成用户所需要的 PV,将 PVC PV 进行绑定后,pod 就可以使用 PV 了。通过 StorageClass 配置生成存储所需要的存储模板,再结合用户的需求动态创建 PV 对象,做到按需分配,在没有增加用户使用难度的同时也解放了集群管理员的运维工作。
动态 PV 使用
Dynamic Provisioning 上面提到过,运维人员不再预分配 PV,而只是创建一个模板文件,这个模板文件正是 StorageClass。下面以 NFS 为例进行说明,动态 PV 的整个使用过程。
安装 NFS 服务
#安装 nfs
yum -y install nfs-utils rpcbind
#开机自启动
systemctl enable rpcbind nfs-server
# 配置 nfs 文件
echo /nfs/data *(rw,no_root_squash,sync) /etc/exports
部署置备程序 apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-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: [watch , create , update , patch]
- apiGroups: []
resources: [services , endpoints]
verbs: [get , create , list , watch , update]
- apiGroups: [extensions]
resources: [podsecuritypolicies]
resourceNames: [nfs-provisioner]
verbs: [use]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: logging
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-client-provisioner
spec:
selector:
matchLabels:
app: nfs-client-provisioner
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccount: nfs-provisioner
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 12.18.7.20
- name: NFS_PATH
value: /nfs/data
volumes:
- name: nfs-client
nfs:
server: 12.18.7.20
path: /nfs/data
创建 StorageClass 模板
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
provisioner: fuseim.pri/ifs
reclaimPolicy: Retain
这些参数是通过 Kubernetes 创建存储的时候,需要指定的一些细节参数。对于这些参数,用户是不需要关心的,像这里 provisioner 指的是使用 nfs 的置备程序。ReclaimPolicy 就是说动态创建出来的 PV, 当使用方使用结束、Pod 及 PVC 被删除后,这块 PV 应该怎么处理,我们这个地方写的是 Retain,意思就是说当使用方 pod PVC 被删除之后,这个 PV 会保留。
提交完成模板文件之后, 用户只需要在
Pod yaml 文件定义
PVC, 即可自动创建
PV 和
PVC。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: es
spec:
........
template:
metadata:
labels:
app: elasticsearch
spec:
.........
initContainers:
........
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
.......
volumeClaimTemplates:
- metadata:
name: data
labels:
app: elasticsearch
spec:
accessModes: [ ReadWriteOnce ]
storageClassName: nfs-storage
resources:
requests:
storage: 50Gi`
Capacity:存储对象的大小;
AccessModes:也是用户需要关心的,就是说使用这个 PV 的方式。它有三种使用方式:ReadWriteOnce 是单 node 读写访问;ReadOnlyMany 是多个 node 只读访问,常见的一种数据共享方式;ReadWriteMany 是多个 node 上读写访问;
StorageClassName:StorageClassName 这个我们刚才说了,动态 Provisioning 时必须指定的一个字段,就是说我们要指定到底用哪一个模板文件来生成 PV。
Kubernetes 存储架构
存储架构图 PV Controller: 负责
PV PVC 的绑定、生命周期管理,并根据需求进行数据卷的
Provision Delete 操作 AD Controller:负责存储设备的
Attach Detach 操作,将设备挂载到目标节点 Volume Manager:管理卷的
Mount Unmount 操作、卷设备的格式化以及挂载到一些公用目录上的操作 Volume Plugins:它主要是对上面所有挂载功能的实现。
PV Controller、AD Controller、Volume Manager 主要是进行操作的调用,而具体操作则是由
Volume Plugins 实现的。根据源码的位置可将
Volume Plugins 分为
In-Tree 和
Out-of-Tree 两类:
In-Tree 表示源码是放在
Kubernetes 内部的 (常见的
NFS、cephfs 等 ),和
Kubernetes 一起发布、管理与迭代,缺点是迭代速度慢、灵活性差;
Out-of-Tree 的
Volume Plugins 的代码独立于
Kubernetes,它是由存储
提供商实现的,目前主要有
Flexvolume CSI 两种实现机制,可以根据存储类型实现不同的存储插件 Scheduler:实现对
Pod 的调度能力,会根据一些存储相关的的定义去做存储相关的调度 动态 PV 交互流程
Kubernetes 挂载 Volume 过程用户创建一个包含
PVC 的
PodPV Controller 会观察
ApiServer,如果它发现一个
PVC 已经创建完毕但仍然是未绑定的状态,它就会试图把一个
PV 和
PVC 绑定 Provision 就是从远端上一个具体的存储介质创建一个
Volume,并且在集群中创建一个
PV 对象,然后将此
PV 和
PVC 进行绑定 Scheduler 进行多个维度考量完成后, 把
Pod 调度到一个合适的
NodeKubelet 不断
watch APIServer 是否有
Pod 要调度到当前所在节点 Pod 调度到某个节点之后,它所定义的
PV 还没有被挂载(
Attach),此时
AD Controller 就会调用
VolumePlugin,把远端的
Volume 挂载到目标节点中的设备上(
/dev/vdb);当
Volum Manager 发现一个
Pod 调度到自己的节点上并且
Volume 已经完成了挂载,它就会执行
mount 操作,将本地设备(也就是刚才得到的
/dev/vdb)挂载到
Pod 在节点上的一个子目录中启动容器, 并将已经挂载到本地的
Volume 映射到容器中
看完上述内容,你们对详解 Kubernetes 存储体系有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注丸趣 TV 行业资讯频道,感谢大家的支持。