本文由 CNCF + Alibaba 云原生技术公开课 整理而来
Volumes 介绍
Pod
存在的问题:
Kubernetes 集群通过 Deployment
解决了 Pod
的更新、回滚及期望数量维持等问题。但 Pod
是一个逻辑概念,它对应的真正运行的还是容器,容器运行就会存在数据持久化的问题,而 Pod
在 Kubernetes 集群中是允许在各个 Node
之间漂移的,如何解决 Pod
的这两个问题:
1. Pod 中的某一个容器在运行时异常退出,被 Kubelet 重新拉起之后,如何保证之前容器产生的数据没有丢失?
2. 同一 Pod 中的多个容器如何共享数据?
对于 Pod
数据持久化的这两个问题,Kubernetes 借助 Volume
来解决,Pod Volumes
的常用类型有这些:
1. 本地存储,常用的有 emptyDir 和 hostPath
2. 网络存储,网络存储当前的实现方式有两种:in-tree,这种方式的代码是放在 Kubernetes 代码仓库中的,随着 Kubernetes 对存储类型支持的增多,这种方式会对 Kubernetes 本身的维护和发展带来很大负担;
out-of-tree,它可以给 Kubernetes 本身解耦,通过抽象接口将不同存储驱动的实现代码剥离于 Kubernetes 代码仓库,各云厂商关于 Kubernetes 的网络存储插件都是基于 out-of-tree 方式实现的,Kubernetes 社区主推这种方式来实现网络存储插件
3. Projected Volumes,它其实是将一些配置信息,如前面提到的 Secret、ConfigMap 用 Volumes 形式挂载在容器中,让容器中的程序可以通过 POSIX 接口来访问配置数据
Persistent Volume
:
Pod
中声明的 Volume
的生命周期与 Pod
相同,这样会无法满足这些场景:
1. Pod 销毁重建(如 Deployment 管理的 Pod 升级)
2. 宿主机故障迁移(如 StatefulSet 管理的 Pod 带 远程 Volume 迁移)
3. 多 Pod 共享同一个数据 Volume
4. 数据 Volume snapshot、resize 等功能的扩展实现
使用 Pod Volumes
无法准确实现数据 Volume
的复用和共享,snapshot、resize 等功能的扩展很难实现。如果能将存储和计算分离,使用不同的 Controller
管理存储与计算资源,解耦 Pod
和 Volume
的生命周期关联,就可以很好地满足以上场景。
因此,Kubernetes 引入了新的资源对象 Persistent Volume(PV)
,它可以将存储和计算分离。当把 Pod
删除之后,Pod
使用的 Persistent Volume
仍然存在,还可以被新建的 Pod
复用。
Persistent Volume Claim
:
有了 Persistent Volume
之后,Kubernetes 又引入了与之对应使用的资源对象 Persistent Volume Claim(PVC)
,这样做的目的在于:
1. 职责分离,PVC 中只需要声明自己需要存储的 size、access mode等业务真正关心的存储需求,PV 和其对应的后端存储信息则由集群管理员同一运维和管控,安全访问策略更容易控制
2. PVC 简化了用户对存储的需求,PVC 是用户对 PV 资源的请求,PV 才是存储的实际信息的承载体。通过 Controller Manager 中的 PersistentVolumeController 将 PVC 与 合适的 PV 绑定到一起,从而满足用户对存储的实际需求
3. PVC 可以理解为面向对象编程中抽象出来的接口,PV 是接口对应的实现
用户在使用 PV
时其实是通过 PVC
来完成的,通常用户在使用存储的时候,只需要声明所需的存储大小及访问模式,用户并不需要关心存储相关的实现细节。访问模式有 3 种:
ReadWriteOnce – 单节点读写
ReadOnlyMany – 多节点只读
ReadWriteMany – 多节点读写
通过 PV
和 PVC
,将用户需求和实现细节解耦,用户只需要通过 PVC
声明自己的存储需求,PV
由集群管理员统一管控,PV
这个资源对象的产生有 Static Volume Provisioning
和 Dynamic Volume Provisioning
两种方式。
Static Volume Provisioning
:
静态供给:由集群管理员事先去规划这个集群中的用户会怎样使用存储,他会预先创建一些 PV
,然后用户再提交自己的 PVC
,Kubernetes 内部相关组件会把 PVC
和 PV
绑定,之后用户在通过 Pod
使用存储时就可以通过 PVC
找到对应的 PV
。
可以看到,静态方式需要集群管理员预分配,但预分配很难预测用户真实需求。例如,用户只申请了 20G 的存储需求,但实际上分配了 50G,这就会造成存储资源的浪费;还有可能预先分配的 PV
无法满足用户的存储需求,这就是更无法接受的结果。另外,如果集群规模很大,对存储资源的需求就会非常频繁,此时如果依靠集群管理员预分配,难免会导致需求无法满足的情况,这显然并不可取。
Dynamic Volume Provisioning
:
动态供给:集群管理员并不会预先创建 PV
,他会写一个模板文件,这个模板文件用来表示创建某一类型存储(块存储、文件存储等)所需的一些参数,这些参数与存储实现有关。用户只需要提交自己的存储需求,并在 PVC
文件中指定要使用的存储模板(StorageClass
)即可。
Kubernetes 集群中的管控组件,会结合 PVC
和 StorageClass(SC)
的信息动态,生成用户所需要的存储 PV
,将 PVC
和 PV
进行绑定后,Pod
就可以使用 PV
了。通过 StorageClass
配置生成存储所需要的存储模板,再结合用户的需求动态创建 PV
对象,做到按需分配,在没有增加用户使用难度的同时也解放了集群管理员的运维工作。
Volumes 用法
下面简单介绍 Pod Volumes
的用法。
Pod Volumes
语法:
test-pod Pod
yaml 文件示例:
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: container-1
image: ubuntu:18.04
volumeMounts:
- name: cache-volume
mountPath: /cache
subPath: cache1
- name: hostpath-volume
mountPath: /data
readOnly: true
- name: container-2
image: ubuntu:18.04
volumeMounts:
- name: cache-volume
mountPath: /cache
subPath: cache2
volumes:
- name: cache-volume
emptyDir: {
}
- name: hostpath-volume
hostPath:
path: /tmp/data
文件解析:
.spec.volumes 声明 Pod 的 Volumes 信息
.spec.containers.volumeMounts 声明 Containers 如何使用 Pod 的 Volumes
.spec.containers.volumeMounts.mountPath 声明 Pod 的 Volumes 在 Containers 内部的挂载路径
.spec.containers.volumeMounts.subPath 声明多个 Container 在共享同一个 Volume 时数据存储的子路径
.spec.containers.volumeMounts.readOnly 声明该 Volume 为只读挂载
emptyDir
、hostPath
都是本地存储。emptyDir
是在 Pod
创建的过程中临时创建的一个目录,这个目录随着 Pod
删除而被删除,里面的数据会被清空掉;hostPath
顾名思义,就是宿主机上的一个路径,在 Pod
删除之后,这个目录还是存在的,它的数据也不会被丢失。
PV
语法:
nas-csi-pv PersistentVolume
yaml 文件示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nas-csi-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPloicy: Retain
csi:
driver: nasplugin.csi.alibabacloud.com
volumeHandle: data-id
volumeAttributes:
host: "xxx.cn-hangzhou.nas.aliyuncs.com"
path: "/k8s"
vers: "4.0"
文件解析:
apiVersion: v1 表示 PersistentVolume 的 API 版本是 v1
kind 表示 Kubernetes 资源类型是 PersistentVolume
metadata 表示 PersistentVolume 的元数据,元数据通常包含 name、labels、annotations 等
spec 表示 PersistentVolume 的期望状态,.spec.capacity.storage 表示期望的 PersistentVolume 容量大小;.spec.accessModes 表示 PersistentVolume 的访问模式;
.spec.csi 表示 Container Storage Interface,云厂商的 CSI 各不相同,.spec.csi.driver 需要提前在 Node 上部署
nas-pvc PersistentVolumeClaim
yaml 文件示例:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nas-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
文件解析:
apiVersion: v1 表示 PersistentVolumeClaim 的 API 版本是 v1
kind 表示 Kubernetes 资源类型是 PersistentVolumeClaim
metadata 表示 PersistentVolumeClaim 的元数据,元数据通常包含 name、namespace、labels、annotations 等
spec 表示 PersistentVolumeClaim 的期望状态,.spec.accessModes 表示需要的 PersistentVolume 的访问模式;.spec.resources.requests.storage 表示需要的 PersistentVolume 容量大小
PVC
期望的状态如果有 PV
能够满足,则 Kubernetes 内部相关组件会把 PVC
和 PV
绑定。 Pod
yaml 文件中使用上面的 PVC
:
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
volumeMounts:
- name: nginx-pvc
mountPath: /data
volumes:
- name: nginx-pvc
persistentVolumeClaim:
claimName: nas-pvc #指定 PVC 名称
SC
语法:
SC
就是动态创建 PV
的模板,为创建 PV
对象提供必要的参数。
alicloud-nas-subpath-public StorageClass
yaml 文件示例:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-nas-subpath-public
provisioner: nasplugin.csi.alibabacloud.com
mountOptions:
- nolock,tcp,noresvport
- vers=4
parameters:
volumeAs: subpath
server: "xxxxxx.cn-hangzhou.nas.aliyuncs.com:/"
reclaimPolicy: Retain
文件解析:
apiVersion: v1 表示 StorageClass 当前所属的组是 storage.k8s.io,版本是 v1
kind 表示 Kubernetes 资源类型是 StorageClass
metadata 表示 StorageClass 的元数据,元数据通常包含 name、labels、annotations 等
provisioner 表示 StorageClass 的插件名称
mountOptions 表示插件的挂载选项
parameters 表示插件的参数
reclaimPolicy 表示 StorageClass 动态分配的 PV 的回收策略
每个StorageClass
都包含 provisioner
、parameters
、reclaimPolicy
字段,这些字段会在 StorageClass
动态分配 PersistentVolume
时会使用到。
nas-pvc PersistentVolumeClaim
yaml 文件示例:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nas-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
sotrageClassName: alicloud-nas-subpath-public
文件解析:
apiVersion: v1 表示 PersistentVolumeClaim 的 API 版本是 v1
kind 表示 Kubernetes 资源类型是 PersistentVolumeClaim
metadata 表示 PersistentVolumeClaim 的元数据,元数据通常包含 name、namespace、labels、annotations 等
spec 表示 PersistentVolumeClaim 的期望状态,.spec.accessModes 表示需要的 PersistentVolume 的访问模式;.spec.resources.requests.storage 表示需要的 PersistentVolume 容量大小;.sepc.sotrageClassName 表示 StorageClass 的名称
PVC
期望的状态如果有 PV
能够满足,则 Kubernetes 内部相关组件会把 PVC
和 PV
绑定。 Pod
yaml 文件中使用上面的 PVC
:
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
volumeMounts:
- name: nginx-pvc
mountPath: /data
volumes:
- name: nginx-pvc
persistentVolumeClaim:
claimName: nas-pvc #指定 PVC 名称
在动态创建 PV
时,与静态创建不同之处在于,PVC
需要增加 .sepc.sotrageClassName
字段来指定 StorageClass
的名称,而 Pod
在使用 PVC
时并不需要改变。
注意:
PV
和SC
不区分 namespace;PVC
区分 namespace。
PV Spec
重要字段解析:
Capacity:存储容量大小
AccessModes:访问模式
ReadWriteOnce:只允许单个 Node 读写访问
ReadOnlyMany:允许多个 Node 只读访问
ReadWriteMany:允许多个 Node 读写访问
ReclaimPolicy:回收策略
Recycle:回收(已废弃,不推荐使用)
Delete:删除,PVC 被删除之后,PV 也会被删除
Retain:保留,由管理员手动处理
StorageClassName:StorageClassName 名称
NodeAffinity:Node 亲和度,声明对 Node 的限制
架构设计
PV
和PVC
的处理流程:
CSI
的全称是 Container Storage Interface
,它是 Kubernetes 社区对存储插件实现(out of tree
)的官方推荐方式。CSI
的实现大体可以分为两部分:
一部分是由 Kubernetes 社区驱动实现的通用的部分,例如 CSI-Provisioner Controller
和 CSI-Attacher Controller
。另外一种是由云厂商实现的,对接云存储厂商的 OpenApi,主要是实现存储真正的 create/delete/mount/unmount
相关操作,对应到 CSI-Controller-Server
和 CSI-Node-Server
。
当提交 yaml 文件之后,Kubernetes 内部首先会在集群中生成一个 PVC
对象,然后 PVC
对象会被 CSI-Provisioner Controller
watch 到,CSI-Provisioner Controller
会结合 PVC
对象以及 PVC
对象中声明的 StorageClass
,通过 GRPC 调用 CSI-Controller-Server
,之后到云存储服务这边创建真正的存储,并创建出来 PV
对象。最后,由集群中的 PV Controller
将 PVC
和 PV
对象做绑定操作, 之后这个 PV
就可以被使用了。
用户在提交 Pod
之后,首先会被调度器调度选中某一个合适的 Node
,然后该 Node
上的 Kubelet
在创建 Pod
流程中会先通过 CSI-Node-Server
将前面创建的 PV
挂载到 Pod
可以使用的路径,之后 Kubelet
才开始创建并启动 Pod
中的所有 Container
。
- PV、PVC 以及通过 csi 使用存储流程:
主要分为三个阶段:
-
Create 阶段:用户提交完
PVC
,由CSI-Provisioner
创建存储,并生成PV
对象,之后PV Controller
将PVC
及生成的PV
对象做绑定; -
Attach 阶段:之后用户在提交
Pod
yaml 时,首先会被调度到某一个合适的Node
,等Node
被选出来之后,会被AD Controller
watch 到,它会去查找Pod
中使用了哪些PV
。然后它会生成一个内部的对象叫VolumeAttachment
对象,从而去触发CSI-Attacher
去调用CSI-Controller-Server
去做真正的 Attach 操作,Attach 操作会调到云存储厂商的 OpenAPI; -
Mount 阶段:发生在
Kubelet
创建Pod
的过程中,它会先去做一个 Mount 操作,是为了将已经 Attach 到Node
上面的那块盘,进一步 Mount 到Pod
可以使用的一个具体路径,之后Kubelet
才开始创建并启动容器。