今天分享的主题是OPPO云存储的上云之路。
存储相比于其他组件,更底层,所以有必要做一个简单的科普。
主要是对整个文件进行操作,提供了对整个文件进行增删查改的能力。不支持对对象内容进行增量修改,如七牛的对象存储,AWS S3,阿里OSS,呈现给我们调用方式是http api。
文件存储实现了文件的POSIX接口,由于整个文件系统不依赖操作系统,常用于实现共享文件系统,常见的比如说ceph fs,gluster fs呈现给我们的使用方式是文件系统。
提供裸块的能力交由物理机使用,协议是SCSI,iSCSI,文件系统层由操作系统提供。呈现给我们的使用方式是裸盘,不带任何文件系统,需要格式化后使用,或者使用块API。
目前块存储主要是三个组件,gateway、storage、 cluster manager。
现在Kubernetes 的趋势愈演愈烈,Kubernetes 逐渐成为云原生时代的基础设施,为了给上云的程序提供服务,云原生也随之出现,目前世面上已经有OpenEBS Portworx 和Rook等产品。
云原生存储不仅要为上云的服务提供服务,自身也利用云的特性增强自身的功能,依赖Kubernetes的特性,我们可以轻运维,轻部署,利用容器隔离的能力,减少异常进程之前的相互影响,提高整体资源的利用率。
Kubernetes作为未来云上的操作系统,把存储整个生命周期和管理抽象成三种资源。
抽象了管理存储相关的配置,主要是provisioner、parameters、reclaimPolicy这三个配置。
通过声明不同stroageclass可以管理多种类型的存储比如说ceph,glusterfs等等。
表示一段已分配的存储,可以是文件系统,也可以是裸块,云存储的云盘或者文件系统映射到Kubernetes 就是一个PersistentVolume。
用户存储的请求,可以请求特定的容量大小和访问模式(例如,可以以读/写一次或指向多次模式挂载)。
抽象出PersistentVolumeClaim把存储和管理分离,通过PersistentVolumeClaim我们可以控制访问存储的权限,存储的容量和类型。
下图是Kubernetes使用存储的一个方式:
这里衍生下Kubernetes 的一些设计理念,Kubernetes 使用声明式的API,通过YAML声明请求,并保存到etcd,这样做的好处是把整个请求记录下来,对于问题的回溯也比较方便,不用自己去记录日志提炼请求。
另外Kubernetes 还提供了对于各种资源的watch Api,各种资源的crud都可以通过watch api实时的拿到对应的YAML,这样的设计的好处是让Kubernetes 拥有非常好的扩展性,通过实现controller 去watch各种资源的变化情况,定义该资源的crud行为。
提供一个将任意块或者文件存储系统对接到给容器编排系统(COs)上的接口标准,如Kubernetes。
把存储从创建到销毁整个生命周期抽象成一组标准接口,Kubernetes通过对接CSI,实现对存储整个生命周期的管理。
下图就是CSI定义的存储卷的生命周期:
上文说道Kubernetes 对存储的抽象是StorageClass,PersistentVolume ,PersistentVolumeClaim等资源CSI 则是提供一组标准接口。所以需要引入一层把Kubernetes 中的资源行为转为CSI接口的程序,Kubernetes 提供了多个sidecar屏蔽这个过程。
这里简单科普下sidecar,一般来说,引入sdk实现某些功能,在编译的时候把sdk代码编译进去,更新sdk要重新发布,和工程耦合的比较紧密,sidecar则是把sdk实现的功能通过在pod运行一个独立的容器实现,通过sidecar们提供rpc接口进行交互,可以作为被调用方,也可以是把服务包装起来增强服务功能,增加这样子的好处是解耦,让更新sidecar容器的版本更简单。
通过引入以下sidecar,我们可以只专注于实现CSI定义的接口。
从官网给的图我们就可以直白的看到粉红色框的sidecar们相当于一层胶水,把Kubernetes和csi链接起来。
1)PV与调度
至此我们已经讲完了Kubernetes和CSI与K8S怎么交互的,接下来讲下PV与调度的关系。
在调度阶段,PV的affinity 会影响Pod的调度,所以有调度需求的可以通过PV的affinity控制。
2)NodeStatgeVolume与NodePublishVolume
之前查阅资料的时候发现这两个接口的说明讲的比较少。
NodeStatgeVolume的接口是把远端的云盘挂到物理机上面。NodePublishVolume的接口是把NodeStatgeVolume之后的盘挂进容器里面。Kubernetes 在NodeStatgeVolume阶段会给每个PV生成一个全局挂载点,如下图:
通过判断这个挂载点是否挂载可以方式PV重复挂载导致出错。接下来NodePublishVolume把NodeStatgeVolume的的挂载点挂载的自己Pod文件夹下,最终这个Pod的挂载点会被挂载进容器里面。
存储作为基础组件,直接和本地盘打交道,所以我们一个要解决的事情就是如果Kubernetes 管理本地盘。
kubernetes管理本地盘
通过官方提供的local-static-provisioner自动生成LocalPersistentVolume管理磁盘。
LocalPersistentVolume是Kubernetes提供的一种管理本地盘的资源。
通过statefulset 管理有状态的存储服务, 为每个pod分配一个单独的磁盘可以使用volumeClaimTemplates给每个pod生成唯一的pvc,具体规则${claimNmae}-${podName},事先准备好PVC 和 PV,通过Statefulset 我们就可以把我们的存储托管到云上了。另外借助daemonset,可以把我们gateway模块部署到每一个node上面。处理云存储的请求。
1)降低运维成本
基于Kubernetes和statfulset获得了滚动更新,灰度更新,健康检查,快速扩容等功能,只需要一组yaml文件就可以快速搭建一个集群,相比于传统写ansible脚本部署的方式复杂度大大降低。
2)降低开发运维成本
由于Kubernetes把存储抽象成StorageClass PersistentVolume PersistentVolumeClaim。我们可以通过他们管理我们的存储资源,基于Kubernetes lable的过滤功能,可以实现简单的关系查询,通过PVC与PV管理存储资源,减少管理端的开发。定位问题也能通过POD信息快速定位到问题机器和问题云盘。而且接入Kubernetes生态上的prometheus后,监控告警也能快速开发。
3)隔离性增强
docker限制cpu memory使用,减少进程之间资源互相干扰,进一步提升资源利用率。
作者介绍:
蔡逸煌,OPPO云平台高级后端工程师
主要从事云平台开发工作,擅长K8S、容器网络、存储等领域
领取专属 10元无门槛券
私享最新 技术干货