nova、cinder的snapshot与ceph相关交互

其实这两块代码已经看过很多遍了,但是经常忘记,而且经常有同事问起来,每次都要翻一遍代码,因此决定整理下相关交互流程,后续备查。(注:仍然是基于Mitaka版本分析源码,Queens版本粗略看了下改动不大)

先说nova部分,nova的snapshot主要是针对系统盘的(root disk),相关命令行是:

从nova源码来看,backup和image(snapshot)的区别只是多了个rotation的概念,多出的这个概念主要是用来限制备份数量的(超过这个数量的backup会被滚动删除,肯定是先删除最老的),你如果把rotation设置的很大,那它就跟image没什么区别了,nova后端的代码也是一套(api入口不一样,但是到了nova compute manager那层就没什么区别了)

从上面源码可见二者调用的具体实现没有区别。

_snapshot_instance调用了libvirt driver的snapshot方法,这里面区分了live和cold的snapshot类型,并且还区分了direct snapshot和外部快照,ceph后端是用的direct snapshot,也即通过ceph的rbd image相关api来做快照。

可以看出经过了创建临时snapshot(还在nova系统盘的pool)、在glance pool中clone snapshot出新rbd卷(跨pool clone卷)、flatten(clone的新卷与snapshot解除关联)、删除临时快照(清理临时资源)、glance pool中的rbd image创建snapshot,此snapshot就是生成的云主机(虚拟机)系统盘的快照(新的镜像,或者叫自定义镜像、捕获镜像、镜像模板等,总之就是nova image-create生成的东西,可以用glance image-list看到),也就是说glance中的image(不管是管理员上传的image还是nova image-create制作的image,都是snap)对应的是rbd里面的一个snap而不是实际的卷,这样创建新的云主机(虚拟机)的时候,系统盘直接从snap clone一个rbd卷就好了,由于支持COW,因此实际clone过程中数据copy量极少、创建系统盘卷速度特别快(这也是glance镜像在有云主机使用的情况下不能删除的原因)。

rbd snapshot的原理可以参考前同事的一篇文章:http://www.sysnote.org/2016/02/28/ceph-rbd-snap/

direct+live snapshot场景下,创建临时snapshot过程中,由于云主机一直运行中,因此可能有部分数据还在内存的磁盘缓存中,没有刷新到磁盘,所以还是有一定概率导致制作的系统盘快照是损坏的。

上面是ceph后端的流程,本地存储后端的snapshot流程可参考之前的文章:Mitaka Nova在线快照数据丢失问题及解决方法

nova这边其实还有一种需要跟cinder(ceph)交互的功能,boot-from-volume,从卷启动云主机(虚拟机),这种情况下cinder list里面看到的volume是bootable的,不过这种功能在ceph后端场景下不常用,就不介绍了。

接下来是cinder部分,涉及的命令行应该有create、backup-create、snapshot-create这几个(还有没有其它的不确定,估计应该没了):

先看create,创建卷,支持多种参数,比如创建裸卷、从snapshot创建卷、从已有的volume创建卷等。

上面忽略了很多taskflow,直接到了cinder.volume.flows.manager.create_volume.CreateVolumeFromSpecTask#execute,cinder里面用到的taskflow一般都是linear类型的,顺序执行,只要一个一个看过去就行了,一般都包含一个参数解析的task,如cinder.volume.flows.manager.create_volume.ExtractVolumeSpecTask,解析出来的参数传递给下一个task使用,最后run起来,正常执行execute,有异常的话就执行revert方法。关于OpenStack的taskflow介绍:https://docs.openstack.org/taskflow/latest/

创建卷的api文档(v2版本,v3也类似):https://developer.openstack.org/api-ref/block-storage/v2/index.html#create-volume

跟snapshot相关的主要是_create_from_snapshot和_create_from_source_volume,先看第一个:

共3步,从snapshot clone新卷、flatten、resize,后面两步不是必须步骤。配置项rbd_flatten_volume_from_snapshot,

从snapshot创建卷的时候是否flatten,默认是False,不flatten。

再看_create_from_source_volume,它调用的是create_cloned_volume,

这个流程比较多,毕竟要先做一个snapshot,然后再clone新卷,相当于包含了从snapshot创建卷的流程。配置项rbd_max_clone_depth,

默认最大clone深度是5层,达到5层就flatten。

再看下backup操作(其实这个操作跟rbd snapshot没啥大关系),cinder.backup.drivers.ceph.CephBackupDriver#_backup_rbd这里是最终执行的方法,就不具体分析了,主要是有个增量备份过程:

先用’rbd export-diff’导出增量部分,再用’rbd import-diff’导入。参考:https://ceph.com/geen-categorie/incremental-snapshots-with-rbd/

第一次备份的话是走全量备份过程:

 

最后看下snapshot create,这个流程跟create创建卷流程类似,直接看最终rbd调用就行了:

非常简单,就创建snap,然后protect。

 

 

写给儿子们<18>

今天是一个特别的日子,哥哥幼儿园毕业典礼,弟弟一周岁生日

今天是一个不特别的日子,妈妈不在家,弟弟也不在

本应该一家人开心幸福的参加哥哥的毕业典礼,然后再给弟弟过生日,可惜并没有。。。

 

从某种程度上说,是爸爸自己亲手造成这一切后果的,爸爸内心充满愧疚和自责

曾经是爸爸最不愿发生的事情却实实在在的发生了,弟弟成了留守儿童。。。

妈妈说了很多次,哭着说爸爸太狠心,怎么舍得让弟弟一个人留在老家,即如此为何当初还要生下他?爸爸当时还嘴硬,还狡辩,还强词夺理,说弟弟还小,还不懂事,在家呆一段时间没啥问题,过段时间还会回来的

其实爸爸也很难受,但爸爸还能说什么?让弟弟回来谁来带?妈妈不在家,爷爷连哥哥的饭都做不好(他真的已经尽力了),而爸爸又经常加班。。。

一年来,工作上的困难爸爸都预料到了,实际上虽说很多困难、问题,也都一一化解或规避了,虽说进展不很大,但也比预估的不差太多

一年前决定创业的时候,爸爸想法很简单,只要一家人在一起,没什么好担心的

可是现在,一家人不在一起了,爸爸就不能不担心了

但生活上或者家庭方面的问题,已经完全偏离了爸爸的预期,爸爸感觉生活已经有点失控了

可以说,爸爸与奋斗的目标已经南辕北辙,妈妈、哥哥、弟弟过上好日子是爸爸奋斗的目标和动力来源

最近爸爸的耳边时不时的会想起妈妈哭着对爸爸说的那个词:“妻离子散”

妈妈去外地工作,弟弟回了老家,只有哥哥和爸爸还在家里

是的,爸爸目前的生活状态用这个词来形容真的是很贴切,多么可悲。。。

每当想到这个词,爸爸都有一种生无可恋的挫败感,这么努力的工作,却导致这种下场,所有的努力不但化作泡影,反而还结出了恶果,可悲可叹可笑。。。

爸爸最近也在思考,为啥会出现这种状况?爸爸究竟错在哪里?

想到的一个原因,可能是妈妈和爸爸的心态发生了变化,当爸爸决定出来创业的时候,已经想好了要过苦日子,而妈妈则感觉到咱们家的经济基础已经不稳固了,需要她也努力工作,以防家里没有收入来源,所以她去了外地工作,因为那是她看起来最好、最有前途的选择了

如果爸爸工作稳定,爸爸想妈妈可能还不会下这么大的决心把弟弟放老家,离开哥哥和爸爸去外地工作

因此这一切的问题根源,都可能在爸爸去年的那个出来创业决定

而现在,爸爸后悔了

爸爸不是后悔在工作不稳定、公司发展不如意、收入下降等等问题上

而是在家庭问题上,爸爸真的感觉后悔了。。。

爸爸想恢复到一年前的状态,也决定要这么做

钱,爸爸需要,一家人在一起快乐生活,爸爸更需要

而这一年来,你们妈妈流的泪,比之前10年都要多

而这一切的根源,都在爸爸

爸爸还有什么好说的呢?无话可说。。。

所有人的眼光爸爸都不在乎,而妈妈的眼泪,弟弟的哭声,哥哥想妈妈想弟弟的梦呓,爸爸在乎、爸爸不能不在乎、爸爸必须在乎

爸爸一直希望,替你们走的路,不会变成给你们挖的坑

钱,我们赚不来,也不赚了,只希望我们一家人永远不分开,这比什么都重要

希望一切都还来得及,一切都还回的来

 

kubernetes源码调试体验

源码是k8s的release-1.10分支,为啥没用master?因为我的虚拟机里面安装的golang版本是1.9.4的,不满足最新版的要求,也懒得更新了。

编译环境是CentOS 7.2 x86_64。

总的来说,Go的调试与C比较接近,调试工具和调试命令都很像,尤其是都可以用gdb调试,但我这次没有用gdb,而是用了网上说的更适合Go的delve

要调试k8s,首先得编译出来debug版本的二进制程序,正常编译肯定是make all就好了,debug版本的要去掉编译优化选项并打开调试选项,看了下k8s的Makefile:

可以看到 81 # make all GOGCFLAGS="-N -l" 这行和下面的几行注释,提到了-N -l这两个编译选项,分别是指禁用编译优化和禁用内联优化,最终目的是可以在调试代码的时候单步执行可以看到实际对应的每行源码,具体支持的选项列表可以通过如下命令查看:

 

要编译首先得下载源码, git clone https://github.com/kubernetes/kubernetes.git ,之后切到你想要编译的分支,这里以release-1.10为例, git checkout origin/release-1.10 -b release-1.10 ,根据官方文档准备编译环境:https://github.com/kubernetes/community/blob/master/contributors/devel/development.md#building-kubernetes-on-a-local-osshell-environment

我这里选的是Linux本地编译,不是Docker编译。etcd和go的安装就不多说了。接下来就是编译了,命令上面已经说过了 make all GOGCFLAGS="-N -l" ,在kubernetes目录(源码根目录)下执行就好了。然后就是等待编译结束,第一次会比较慢,编译好之后会把编译好的二进制文件放到kubernetes目录下的 _output/bin/ ,对应源码则是在 _output/local/go/src/ ,进入目录可以看出, _output/local/go/src/k8s.io/ 下面的kubernetes目录其实是一个软链接,链接到kubernetes源码根目录。

接下来是调试过程,首先要安装delve,安装也比较简单,我是用的 go get github.com/derekparker/delve/cmd/dlv 直接下载编译的,自动编译好的二进制文件放在 ~/go/bin/ 目录下(也就是是$GOPATH/bin目录),接下来要么你把这个dlv工具copy到系统变量$PATH里任何目录下(比如/usr/local/bin),要么也可以建个软链接过去,或者把它所在的目录也加到系统变量$PATH下,比如在dlv工具所在目录执行: export PATH=$PATH:`pwd` (注意这种方式没有持久化,退出shell窗口之后就失效了,需要再次执行),之后就可以在任何地方运行dlv命令了。

首先看下dlv用法,目前看起来比较常用的是attach、exec、debug三个子命令:

简单调试下kube-apiserver:

这里我没有给kube-apiserver传参数,执行c之后就异常退出了,我们这次调试的目标就是分析为啥退出?首先在入口的main函数处设置断点(可以不设置):

之后根据错误日志”–etcd-servers must be specified”找到报错的代码位置处继续设置断点(注意源文件路径,是相对路径,相对于/root/k8s/kubernetes/_output/local/go/src/目录的,其中kubernetes是git clone的源码根目录):

bt命令可以看到整个调用栈:

这里简单调试下,主要是为后续深入分析k8s代码做准备,有了调试工具,所有代码流程都可以分析清楚,只需要在你关心的代码或者函数哪里加上断点,之后等代码执行过来执行bt命令就能看到整个调用栈了,非常省事省力快速有效,拿到调用栈之后就是一步一步的分析源码了,这样可以保证分析流程不出错(尤其是在大型项目里,很多同名函数、回调函数,很容易绕晕,调用栈基本就相当于是分析代码流程的指南针了)。

dlv的attach和debug,也试了下,也比较简单,只是使用场景不太一样而已,attach是挂载到运行中的进程进行debug,debug是直接针对go源码调试的,命令执行起来之后跟exec调试过程就没有区别了。这里就不贴执行流程了。

 

超级账本fabric项目试玩

最近跟着这本书学习区块链相关入门知识,

区块链技术指南pdf下载:https://legacy.gitbook.com/download/pdf/book/yeasy/blockchain_guide

该pdf已出版纸质书,《区块链原理、设计与应用》:https://item.jd.com/12159265.html

总体来说这本书对想了解区块链技术的初学者非常有价值,可以了解到区块链相关技术的来龙去脉、基本原理、知识体系、相关开源项目等等。看完超级账本项目fabric之后,感觉其整体框架跟同为分布式系统的OpenStack、k8s也比较类似(当然只是从框架来看,从底层实现来讲还是有很大差异的),主要差异在于其数据存储方式上,共识机制方面,OpenStack、k8s都是以传统数据库或etcd作为基础的,类似区块链中共识算法的多个提案者+一个确认者的场景。都是由多个服务构成,比如nova服务有nova-api、nova-compute、nova-scheduler、nova-conductor等服务,k8s有kublet、kube-apiserver、kube-proxy、kube-scheduler等服务,而fabric则有peer、ca、order这几个服务,他们也都有对应的客户端(命令行、sdk)用来发送请求给服务端。

fabric具体能干啥就不提了,还是看书吧,我还是从已知的IaaS、PaaS等云计算技术来对比,总体来说它可以用高级编程语言编写链码,然后上传到fabric区块链网络的某个节点里(具体来说是某个peer节点的docker容器里,我这里还有个疑问没有找到答案,一个链码只能跑在一个peer节点的一个容器里面吗?我理解应该是可以跑在多个peer节点的,从而实现分布式应用,不然就没有意义了),链码可以实现各种各样的功能(实现智能合约、账本管理等),链码用到的持久化数据都在区块链的块里存储,区块相当于是一个分布式数据库(或者专业名词叫分布式账本),从而实现应用的分布式、高可用、高可靠,以及数据的不可篡改,相比较而言,利用IaaS、PaaS等云计算技术也可以实现类似的功能或架构,尤其是k8s的微服务架构,也能支持分布式应用,但其数据却通常还是集中式存储的,并且容易篡改(当然其适用场景也不是为了解决这个问题,我这里只是随意对比下),也可以用分布式数据库来存数据,但仍然不像区块链那样,数据是用链表+区块来存储的。

要说fabric和k8s唯一的关联,我理解就是他们都是把链码或者说应用跑在docker里面的。fabric通过gRPC协议调用链码接口(接口比较固定),而k8s的服务则一般是通过HTTP RestFul API来调用服务(当然也有其他服务使用其他协议,如tcp或者专有协议)。

代码跑在哪里、数据存在哪里其实都不是关键,关键是怎么样让代码跑的愉快,跑的稳定,跑的没压力,还有就是让数据存的可靠,存的准确,存的安全。去中心化的问题,也是相对的,如果你的应用跑在全世界各地,数据也做到分布式存储到世界各地,我理解这也算是去中心化的。

不胡扯了,下面讲下怎么搭fabric测试环境,我这也是现学现卖,跟着官网文档学的,跑了一个example。我这里用的是fabric-1.1版本,操作系统是CentOS-7.2 x86_64。

环境准备:https://hyperledger-fabric.readthedocs.io/en/release-1.1/prereqs.html

主要是两个部分,一个是golang环境,一个是docker环境,docker包含docker daemon和docker-compose两部分。

安装比较简单,yum install golang docker docker-compose,完事儿,其他依赖如curl一般操作系统都是自带有的,当然下面还要用到git clone代码,也可以提前装上。

docker最好配置下国内的镜像源,否则下镜像要等死。修改docker daemon的配置,之后重启docker服务,systemctl restart docker。

上面的几个registry-mirrors不保证可用,最好自己找好用的替换。

之后就是下载代码了,需要下载两个项目,一个是fabric本身,一个是fabric-example,当然我是为了跑example示例才下载的第二个,如果你想手工搭fabric环境,可以不用下载第二个。

再之后就是准备fabric可执行文件了,脚本在fabric项目目录下的scripts目录下:

fabric/scripts/bootstrap.sh,直接cd到这个目录下,./bootstrap.sh执行就好了,因为我们已经checkout到1.1版本的release分支了,默认就是跑的1.1版本。

相关可执行文件会下载到scripts目录下的bin目录(get-docker-images.sh是代码库自带的,其他几个二进制文件是新下载的):

看下bootstrap脚本,可以看到是用curl命令下载的文件,每个文件几十M,国外网站比较慢,建议加上代理或者找国内的镜像(我是挂的代理)。

之后还要把这几个可执行文件所在目录加到PATH环境变量里(后面部署环境跑example要用到,具体来说是byfn.sh里面要用),让它们在任何目录下都可以被调用,当然也可以加上软链接,或者直接copy到/usr/local/bin之类的默认已加入环境变量的路径下,我这里是用的添加环境变量方式:export PATH=$PATH:/root/fabric/scripts/bin/

之后就是参考官方上手文档跑example了:https://hyperledger-fabric.readthedocs.io/en/release-1.1/build_network.html

文档里说是执行3步(文档里面有-m参数,看了下byfn.sh,里面已经没用这个参数了,不过貌似不影响执行,被忽略了):

每一步都有一堆输出,参考官网文档就行了,这里不贴了。

主要就是启动了一坨docker容器(在执行./byfn.sh up命令过程中通过docker ps -a命令查看):

通过镜像名就可以看出来容器跑的什么服务。

仔细看下byfn.sh就可以分析出fabric是怎么部署的(至少开发测试环境可以这么部署):

部署环境主要就是跑了这句: IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE up -d 2>&1

然后就是在cli那个容器里跑example: docker exec cli scripts/script.sh $CHANNEL_NAME $CLI_DELAY $LANGUAGE $CLI_TIMEOUT

跑example过程中会有一坨日志在屏幕上输出。

因此部署环境就是通过docker-compose -f $COMPOSE_FILE up -d来搞定的,IMAGETAG可以通过byfn.sh的-i命令指定,默认是latest。COMPOSE_FILE可以通过-f命令指定,默认是docker-compose-cli.yaml,我们看下这个yaml文件:

可以看出里面定义了一个容器网络,networks: byfn,几个volumes与容器名称相同,在base/docker-compose-base.yaml文件里面有用到,6个service包含1个order节点、4个peer节点、一个cli节点(依赖其他5个service,这个容易理解,客户端必须要等服务端启动才能执行命令),每个service各包含一个容器实例,容器名称与service名称一样(就是上面贴出来的docker ps -a命令的看到的容器列表)。

除了cli这个service之外,其它几个都继续使用了base/docker-compose-base.yaml这个compose配置,这里就不贴了,而这个yaml里的peer服务又依赖了base/peer-base.yaml。总之就是定义了一坨docker容器,然后利用docker-compose编排功能把容器跑起来。

关于docker-compose,可以参考官方文档:

networks、volumes等配置文件中关键字的意义可以参考上面的文档,简单来说部分是给docker实例准备的各种参数,与k8s的service配置文件比较类似,都是为编排服务里各种容器的。这里networks默认就是一个Linux bridge,volumes就是一个临时目录mount到docker容器里给容器用来保存临时数据。

配置文件里端口映射、命令行、环境变量啥的就不说了。

可以看出,整个集群或者说区块链网络就是通过docker compose的编排功能实现的。比手工部署跑起来简单方便多了。

 

使用kubeasz快速搭建k8s集群all-in-one开发测试环境

最近在为VisionStack产品开发容器服务,提起容器服务就不得不考虑kubernetes项目(简称k8s),当今主流开源的容器编排框架,因此就想搭建一个k8s环境调研下它的功能、使用、相关概念等内容,在搭建环境过程中,首先是参考官网文档提到的minikube工具,用它来进行测试环境的搭建,结果折腾了一下午,也没有成功,遇到的最大问题是国内的防火墙屏蔽了google相关网站的访问,而k8s是google主推的,大部分资源都在google服务器上(如环境部署过程中用到的各种docker镜像都需要从gcr.io上下载,这个是google云提供的docker镜像库网站),如果镜像都无法下载,那肯定环境也就无法搭建成功了。而另外一个工具kubeadm,由于比较复杂,另外估计很可能也会遇到类似问题,也就没有尝试。

正准备放弃的时候,发了个朋友圈吐槽这个事情,正好有朋友也遇到过这个问题,他推荐了另外一个工具:kubeasz,专门针对国内网络环境开发的k8s安装部署工具。下面的安装部署过程就是基于它来完成的,并且是一次成功,对搭建开发测试环境来说非常简单易用,因此这里强烈推荐下(这个故事告诉我们,朋友圈真的是万能的)。下面的安装过程也主要是参考它给的官方文档,略有删减。

项目地址:https://github.com/gjmzj/kubeasz

项目用途:使用Ansible脚本安装K8S集群,介绍组件交互原理,方便直接,不受国内网络环境影响。

快速指南

以下为快速体验k8s集群的测试、开发环境all-in-one部署,国内环境下觉得比官方的minikube方便、简单很多。

1.基础系统配置

  • 推荐内存2G/硬盘20G以上
  • 最小化安装Ubuntu 16.04 server或者CentOS 7 Minimal
  • 配置基础网络、更新源、SSH登陆等

2.安装依赖工具

我实验过程中使用的是CentOS 7.2系统。

Ubuntu 16.04 请执行以下脚本:

CentOS 7 请执行以下脚本:

3.ansible安装及准备

4.安装kubernetes集群

如果执行成功,k8s集群就安装好了。详细分步讲解请查看项目目录 /docs 下相关文档

5.验证安装

6.安装主要组件

7.dashboard安装及登录

  • 登陆 dashboard可以查看和管理集群,更多内容请查阅dashboard文档
  • 本文档基于 dashboard 1.8.3版本,k8s版本v1.10.0。

7.1部署dashboard

如果之前已按照本项目部署dashboard1.6.3,先删除旧版本:kubectl delete -f /etc/ansible/manifests/dashboard/1.6.3/

1.8.3配置文件参考官方文档

  • 增加了通过api-server方式访问dashboard
  • 增加了NodePort方式暴露服务,这样集群外部可以使用 https://NodeIP:NodePort (注意是https不是http,区别于1.6.3版本) 直接访问 dashboard,生产环境建议关闭该访问途径。

安装部署

7.2验证部署结果

7.3登录dashboard

访问 https://x.x.x.x:8443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy (该URL具体使用kubectl cluster-info|grep dashboard查看) ,先使用admin、test1234账号密码通过http认证,之后选择令牌方式登录,令牌通过命令行kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')获取(token:那行),复制冒号后的全部字符并粘贴到dashboard‘输入令牌’处即可。admin登陆拥有所有权限,比如删除某个部署;使用 readonly登陆只有查看权限,尝试删除某个部署会提示错误 forbidden: User \"readonly\" cannot delete services/proxy in the namespace \"kube-system\"

8.清理集群

以上步骤创建的K8S开发测试环境请尽情折腾,碰到错误尽量通过查看日志、上网搜索、提交issues等方式解决;当然如果是彻底奔溃了,可以清理集群后重新创建。

一步清理:ansible-playbook 99.clean.yml

总结

上述流程大部分都是由工具自动完成的,并且所需要的资源也已经全部下载到本地,因此安装过程其实很快,如果你也有类似需求,可以试试这个工具。

参考资料

Apache+WSGI中文文件名导致HTTP请求返回500问题解决方法

问题症状

Centos7服务器上搭建了Apache服务,版本httpd-2.4.6-67.el7.centos.6.x86_64,配合mod_wsgi+python+flask搭建web网站。

用flask的render_template渲染html页面,并且用配置文件里的文件名和路径来生成文件下载链接,如果文件路径包含中文,会在请求页面时返回500响应码,而python main.py前台启动没有这个问题,改成英文文件名后Apache+WSGI启动方式也恢复正常。

相关代码如下所示:

解决方法

网上搜到很多方法都是说修改/etc/apache2/envvars文件,但新版本Apache已经没这个文件了,折腾了好久都没进展,偶然看了下Apache的systemd的启动脚本/usr/lib/systemd/system/httpd.service,发现里面有个EnvironmentFile配置项:

原来是在这里配置的,尝试把原来的LANG=C改成上面两行,之后重启Apache服务,再访问出问题的页面,一切正常了,问题解决。

基于云主机+docker engine的容器服务

在基于kubernetes实现容器编排产品之前,我们基于现有的VisionStack云平台,利用云主机+docker engine+自研编排服务+私有镜像仓库的方式,实现了一套自己的容器服务,方便用户基于docker镜像来发布和管理业务。

后续我们也会考虑基于kubernetes研发一套真正意义上的容器编排产品,这个产品应该是独立于现有的VisionStack产品之外的一套新系统。

名词说明

服务:对应业务产品,通常一个产品可以创建一个服务(如果有需要的话,一个产品的不同版本也可对应多个服务)

节点:服务的基础,业务容器化首先需要创建云主机(容器跑在云主机上),一个服务可以创建多个云主机便于高可用及业务升级

副本:第一期的版本,每个节点只启一个容器,后续考虑完善,每个节点启多个容器来部署服务,一个容器即为一个副本

容器镜像:用户根据自己的业务环境制作的容器镜像,可以满足业务产品的运行,用户需要自己上传到镜像仓库

负载均衡权重:每个服务对应一个负载均衡,来实现对于各个节点服务的访问,权重可以控制访问频率。权重是相对来说的,权重越高的节点被访问的频率越高

本次功能添加如图所示:

导航栏添加容器服务,并为其添加两个子项——服务列表、镜像仓库

镜像仓库

镜像仓库是容器服务功能的基础,我们搭建了自己的私有仓库并提供筛选和删除功能。用户使用该功能之前,首先需要自作自己的业务docker镜像,并将其push到私有仓库,以便后续的使用。上传的镜像主要支持名称及版本,满足升级和管理。

服务列表

服务列表是该项功能的主要操作页面,大部分的功能点集中在这个页面。

一、服务列表

服务列表包括信息如图:名称、对外提供的ip及端口、节点数(每个节点为一台云主机,目前采用的实现方式是,一个节点启一个容器;后续会进一步完善为一个节点多个容器)、规格(为所有节点的规格,现在所有节点规格相同,后续会考虑支持不同的节点对应不同的规格)、状态(展示该项服务的健康状况,便于监控)

二、创建服务

我们可以把某项业务部署创建为一项服务,以便于管理和部署。创建之前,需要用户将自己的业务制作为容器镜像,并上传到私有镜像仓库,创建页面如下:

如图所示,创建过程中我们首先需要填写自己的服务名称,并设置服务端口号和节点数(目前支持1-10个节点);可用域和物理机需要多选,单选的话无法支持高可用(高可用需要多节点多物理机);节点(云主机)创建需要用户名密码、主机规格、网络、安全组等参数(参考创建云主机);创建服务最重要的就是选择自己的容器镜像,目前提供镜像及镜像版本的选择(如果愿意的话,你可以将某项业务部署多个版本);负载均衡用于对外提供可以访问的ip及端口。

三、修改服务

当前修改功能比较简单,当前仅支持修改服务名称和节点数。修改弹框如图:

四、删除服务

提供删除服务的功能,当前删除时彻底删除,后续考虑提供回收站及 删除防误

五、服务升级

服务升级是我们的重点,当前提供3种升级方式:滚动发布、停服发布、手动发布。

1、滚动发布

滚动发布可向后滚动(产品升级),也可以向前滚动(产品回滚),该项发布为自动发布,只需选择镜像及版本,就可以完成升级工作。页面如下:

2、停服发布

停服服务可以理解为滚动发布的简化,也支持向后滚动(产品升级)、向前滚动(产品回滚)。其与滚动发布的唯一区别就是滚动发布不需停服(会出现极短时间的新老版本共存的情况),停服发布需要停服。页面如下:

3、手动发布

手动发布适用于测试不充分或者需要在生产环境补充测试的业务发布(可在升级过程中进行测试),页面如图:

该升级功能可选镜像和节点,每个节点后标注镜像、版本和负载均衡权重,手动升级可以分N次,每次可选M(M<=8, M=8时为手动停服发布)个节点进行升级。勾选服务,打开弹框,选择手动发布,弹框中显示节点列表(节点包括镜像版本和负载均衡权重,权重为0时表示该节点在负载均衡服务中处于禁用状态)。下拉选择镜像及版本,勾选想要升级或回滚的镜像,若不需要测试,可配置负载均衡权重为非0,直接将选中的节点升级。若需要测试则勾选选镜像、节点并将勾选节点的权重置为0,点击确定,此时服务列表中该服务及该服务的节点列表状态显示升级中。发布人员可以在此时进行测试,若测试成功,可以再次勾选该服务,选择手动发布,将新升级的节点负载均衡权重置为非0(因为当前每个节点的规格相同,所以建议完成升级之后把每个节点的权重设为相同的值)。当全部节点中只升级了一部分,此时该服务包括新老两个发布版本的产品,若为了谨慎发布可以将新升级的节点负载权重配小,待产品稳定后可以再将权重全部保持一致。当然用户也可以自己随意设置自己的各个节点的权重。

六、导出excel及自定义列表功能

这两个功能和产品的其他页面一致

七、详情

详情用于展示某项服务的详细信息,目前展示信息如下图所示,后面可以考虑继续完善。

八、节点列表

勾选某项服务,点击节点列表按钮可进入节点列表子页面:

列表中展示的内容如图所示,功能包括添加/删除节点、控制台、导出excel、自定义列表。

控制台提供登录Docker所在节点(云主机)的入口。

九、副本列表(当前未做)

当前每个节点只包含一个容器,后面考虑完善功能,在一个节点启多个容器(副本),这时就需要副本列表页面。

 

写给儿子们<17>

这两天小区里的家长们在讨论民办小学报名的事情,有些还去报了名,录取的也有(虽然最终也没交钱确认入学)

关于这件事情,你们的妈妈想法跟爸爸的又有一些差别

你们的妈妈想让你们报名,试试看,能行就去上

而爸爸则不打算去

原因有如下几个:

  1. 学费比较贵(便宜的一万多每学期,贵的5万,这还是明面上的费用,其他的算下来每年估计也得再翻倍),即使我们家能拿得出来这么多钱,我也更愿意花钱在别的投资上(比如买房、做生意)
  2. 离家太远,接送太折磨人(包括大人小孩)
  3. 爸爸从内心深处还是觉得小学阶段不是那么重要,初高中尤其是高中才是最重要的,这也是爸爸的切身体会(当然有些家长认为小学是基础,基础都不好以后肯定上不了好初中、高中)
  4. 你们奶奶可以给你们辅导,小学阶段应该不用发愁

上述原因我第一条和第三条我得深入解释下,比如第一条,宁愿花钱在投资上也不愿意让你们读一年20万(5万的太远,20万的太贵)的私立学校,为啥这么想?不重视孩子的教育吗?

我觉得我是没有重视到着魔的程度,但也没有轻视到忽略的地步。只能说是在我们家现有条件下,尽力做到最好。

为啥要投资别的方面而不是给你们读私立?因为从爸爸目前具有的人生经验来说,学历不是万能的,即使你们都考上北大清华,毕业后最多还是进一家顶尖企业(当然出国留学甚至移民之类的这里没考虑,这条路我没走过,也不敢乱说,但我估计也没那么轻松),然后当一个高级白领(当然金领也有可能,但希望不大),起早贪黑,加班严重,生活压力巨大,相比一个有N套房的拆迁户来说,还是屌丝一个,还是不敢吃不敢穿不敢消费,生活质量也不会高到哪里去。曾经有一篇文章让我感触很深,说的是上海税前一个月拿3W房贷车贷1W多的高级白领和公司里有N套房的拆迁户清洁大妈,那一个生活质量更高?哪一个更幸福?我觉得大部分都会毫不犹豫的选后者。当然拆迁户不常有,拿做生意或者投资房产来比较会更合适。如果爸爸赚了大钱,每人给你们在一线城市留下5套房子(爸爸的10套房目标的来源),哪怕是小户型,只要你们不乱搞不败家,保证你们一辈子都可以衣食无忧,不用担心生计问题,上班嘛也就成了体验生活的一项爱好或者兴趣了,奋斗或者创业起来也更有底气(有保底生活来源,不用惧怕失败),心情不好可以随时炒老板鱿鱼,可以做到世界那么大、随时去看看。

你们的妈妈则老说,不用给孩子们留那么多钱、房子,让他们自己赚去,什么都给他们准备好了,就没有奋斗的动力了。其实爸爸也不是完全没有这种想法,爸爸和妈妈也是这么过来的(当然你们的爷爷奶奶和姥姥姥爷也帮衬了我们一些,但也只是有限的一部分),但如果我们能多准备点,你们的人生起点不是更高一些吗?

关于第三条原因,爸爸是基于自己的人生经历来考虑的,爸爸在小学、初中、甚至高中的成绩来说都是中等,偶尔偏上一些,但爸爸从来没有放弃过,一直在努力往前进,也就是人们常说的上进心比较强。有些同学原本成绩很优秀,但到了初中、高中阶段就不学了,去打游戏、上网、看小说、谈恋爱等等,转移了方向,不再以学习为主了,自然就落后了(当然并不是说学习不好就没有前途,恰恰相反,有些成绩不怎么好的同学反而赚钱更多,活的更潇洒),爸爸这里想表达的意思是,在人生的每个阶段都有该做的事情,初中高中阶段肯定是以学习知识为主题,其他的事情都是附带的边角料,适当的了解下也没问题,算是拓展视野,丰富人生阅历,但千万不能转移了大主题。还有些同学千辛万苦考上了大学,在学校里醉生梦死、无所事事,连考试都科科必挂,最终毕业证、学位证一个都没拿到,而爸爸则由于担心找不到工作,只得硬着头皮继续考研,虽然也没考上什么好学校,至少可以让爸爸毕业后能有一份工作,能养家糊口了。任何时候都不能放弃奋斗,除非你的人生追求不在于此,享受人生也得等到正确的时间,没有物质基础的享受是不可能长久的。

一直以来爸爸对你们的希望都只有两点最重要:平安,健康。其他的方面都是补充或者说点缀,属于有了更好的那种性质。

有些人常说:条条大路通罗马,但有些孩子就出生在罗马。爸爸妈妈没能让你们生在罗马,但至少我们在努力的带着你们往罗马的方向慢慢前进。

 

写给儿子们<16>

上周末过完清明假期,你们的妈妈开始上班了

为啥要提这事儿?

1是因为她去上班的地方很远,歙县,200多公里,开车要2个半小时

2是因为她不想去那么远的地方上班,但还是去了,为啥呢?

妈妈生弟弟之前就在这家工厂上班,生完弟弟工厂搬走了,搬到了现在的地方

妈妈带着弟弟和姥姥去了那边,舅舅也在那边打工,姥姥照看弟弟,妈妈上班

爷爷带着哥哥在家,负责接送上学、做饭

妈妈估计要一两周甚至个把月才能回家一次,弟弟去了吃东西也不方便

 

你们妈妈说去那边上班是爸爸伤了她的心,是爸爸想让她去,不然她都不想去

至于为啥她会这么想,我也没搞太清楚,简单跟她沟通了下,可能是有下面几个原因:

  1. 妈妈说爸爸曾在各个场合跟多个亲人表示她要去那边上班,需要人过来帮忙照顾弟弟,这样姥姥才愿意从老家过来的
  2. 妈妈认为爸爸不想让她在家,认为爸爸想一个人乐得清净,没人管更自在
  3. 妈妈认为爸爸觉得她不挣钱,所以想让她抓紧挣钱
  4. 妈妈觉得跟爸爸没啥共同语言,在家不在家没啥区别

总体来说,我感觉是妈妈觉得爸爸对她不够好、不够关心、不顾家,只想着赚钱、赚钱、多赚钱,钱都不舍得给她花,她多次说过都不敢跟爸爸要钱花(没有取钱给她花过)。

实际来说,妈妈想的也不完全错,爸爸是有一些地方做的不好,举例来说:

  1. 只想着赚钱、赚钱、多赚钱,忘记了初心(当年爸爸跟妈妈刚订婚的那段时间,经常跟妈妈说咱赚到10万就回郑州付个首付买房结婚,后来目标改成了30万,再后来到现在目标改成了买10套房),让她压力越来越大
  2. 不顾家,对妈妈和家人不够关心(上班经常加班,下班就抱着手机,周末还大小周,再加上周日哥哥兴趣班,基本都没怎么陪妈妈)
  3. 舍不得花钱,太抠门(没给妈妈买过什么东西,也没主动给她过钱)
  4. 沟通交流太少(除了带娃就没啥共同语言了)

实际来说,有些事情我得解释下,主要是缺乏沟通,俩人带娃带的都忘了对方也需要关心、交流、倾诉了。上面这些有些是玩笑话,比如买10套房,估计有生之年都不一定实现得了,只能算是梦想。不顾家这个也是一定程度上存在的,不顾家也是为了多赚钱,或者为了以后多赚钱,或者能赚更长远的钱,提升自己的实力,花了一部分时间在看书学习工作上。爱看手机,这部分也是存在的,上班憋的,下班想放松下,看看新闻、段子啥的,这东西还有点上瘾(游戏啥的早就不玩了),就忽略了妈妈,其实也不是特别多,妈妈自己也有点手机控。抠门这个不得不说,生活习惯就这样,不光爸爸,妈妈也一样,导致生活质量太低,这个真的是没办法,从小养成的习惯或者说毛病,一时半会儿改不了,也不知道买啥搞啥浪漫,在这方面爸爸妈妈俩人谁也别说谁。沟通交流少这个是这样,妈妈有啥事情都不愿意跟爸爸说,可能是爸爸让她觉得说与不说都一样,不会被重视,这点我一定得改掉,多主动跟妈妈沟通交流,少看手机。不过我估计等弟弟再大一些,不用这么费时间照顾,应该就会好一些。

至于说爸爸觉得妈妈不挣钱,爸爸真没这么想。只是跟妈妈说,现在的工厂好歹也有一丁点股份在里面,并且目前工厂发展前景看起来还不错,如果这点股份能有些回报,也是很不错的,并没有勉强的意思,妈妈要是不想去我肯定不会非得让她去。主要还是想着爸爸后面很有可能干不了多久现在的工作,虽然不知道这么想对不对,所以还是想给咱们家留条后路,毕竟还有俩儿子要养,养大你俩还不知道得花多少钱呢,为啥要生你俩呢,哎,看在你俩这么可爱的份上,继续努力挣钱吧。希望你俩长大了可以自己赚钱,赚大钱,让爸爸妈妈也跟着享享福。

妈妈上班的事情先这样吧,跟妈妈说了不要勉强,不想干就回来,赚钱够吃饭的就行,能赚多少算多少,尽力而为。就是妈妈在那边特别想哥哥,还有点愧疚,觉得抛弃了哥哥。

【翻译】RADOS: A Scalable, Reliable Storage Service for Petabyte-scale Storage Clusters

RADOS:Reliable Autonomic Distributed Object Store

论文原文地址:https://ceph.com/wp-content/uploads/2016/08/weil-rados-pdsw07.pdf

翻译动机:看一遍没感觉,看两遍有点理解,翻译出来加上自己的思考才是真的有意义。

摘要

原文翻译略,简单写下自己对这篇论文的理解。

这篇论文主要介绍了RADOS存储系统的原理和设计思路,以及各个组件的用途和一批专用名词的概念,最后大概介绍了后续工作方向。这是一篇学术论文,所以比较格式化。

阅读这篇文章可以学到RADOS存储系统的基本原理和相关概念,配合其他Ceph相关文章或书籍(比如learning ceph)来学习,效果更佳。

1. 简介

对存储系统设计师来说,研发一套高可靠、高性能的存储系统一直都是一个巨大的挑战。文件系统、数据库及其他相关系统依赖的高吞吐、低时延的存储系统是保证各类应用性能的关键基础设施。构建于块、对象存储设备的新兴存储集群架构通过将底层块的分配决策、安全增强能力分散到智能的存储设备上,并让客户端直接访问数据,简化了数据布局并消除了IO瓶颈。对象存储设备通常都是由常见的CPU、网卡、带缓存的硬盘(或RAID)等组件构成,它提供的是用来存储可命名的变长对象接口,而非传统的块存储层接口(注:也即对象存储替换了传统的硬盘或SAN设备)。

但是,采用这种架构的系统大部分都不能很好的发挥底层存储设备的潜力(注:存储节点的计算能力)。传统存储系统(本地或SAN)甚至是符合T10 OSD标准的存储节点只用来响应读写命令,并读取或写入数据到存储器,尽管存储节点有很多的计算潜力。当一个存储集群增长到上千台甚至更多设备时,数据放置的一致性管理、故障检测、故障恢复等问题会给客户端、控制节点、元数据目录节点带来巨大的负担,从而限制住了集群的可伸缩性。

我们设计实现了RADOS–一个可靠的自动化分布式对象存储系统,它旨在利用设备智能,来分散围绕在由数以千计的设备组成的存储集群周围的一致性数据访问、冗余存储、错误检测、错误恢复等的复杂性。RADOS是Ceph分布式文件系统的一部分(注:RADOS是Ceph的核心),它使一个动态的异构存储集群中不断增长的数据和工作负载二者之间的均衡变得容易,同时它能给应用提供一个单一的对象存储视角(注:节点增加、退出对应用无感),并且还能提供定义良好的安全语义(注:IO接口比较规范的意思)和强一致性保证(注:数据一致性保证,任何情况下不会读取错误脏数据,但可能无法响应IO,根据CAP理论牺牲了可用性)。

PB级的存储系统必须是动态的,系统容量是逐步扩容上去的,或者各种原因导致的缩容,也会因为旧设备的故障、退役进行设备替换,因此会有大量数据的创建和销毁。RADOS通过带版本的集群地图(注:cluster map,我这里简单的翻译成集群地图,它是用来指引各方包括client、monitor、osd等找到数据对象存放位置的)来确保数据分布和对象读写的一致性(注:可理解为通过cluster map可确定对象的分布,找到并进行读写)。cluster map被各方复制(存储系统组件、客户端等),通过惰性传播来进行小步增量更新。

通过为存储节点提供完整的数据在系统中的分布信息,设备可以半自主地使用点对点协议来自我管理数据复制,持续安全地处理更新,参与故障检测,并对设备故障和由此产生的数据分布变化做出响应,并重新复制或迁移数据对象。这简化了管理主cluster map副本的monitor集群的负担,并通过它(注:monitor?)使系统无缝地从几十扩展到上千个设备。

我们的原型实现公开了一个对象接口,使用其可以以类似文件读写的方式读取或写入字节内容,这是Ceph的最基本需求。 数据对象在多个OSD中以n个副本保存以防止节点故障。 但是,RADOS的可扩展性决不依赖于特定的对象接口或冗余策略; 存储键/值对的对象和基于奇偶校验(RAID)的冗余方案都在考虑之中(注:当前Ceph中使用RAID貌似不推荐,应该没实现)。

2. 集群伸缩管理

RADOS系统由大量的OSD和少量的monitor节点组成,monitor节点是负责管理OSD集群成员资格的(该哪些OSD加进来、踢出去等)(图1)。每个OSD通常包含CPU、内存、网卡、磁盘(或RAID),monitor则是单独的进程,只需要少量的存储空间(注:实际上还需要占用少量CPU和内存资源)。

图1
图1

2.1  Cluster Map

管理存储集群的唯一方式是通过monitor集群修改cluster map,cluster map指明了集群中包含哪些OSD,并详细记录了数据在存储设备上的分布信息,每个存储节点(OSD)以及客户端(clients)都会复制一份cluster map(注:还要保持更新)。由于cluster map完整的指明了数据分布,客户端暴露的接口就可以对调用方屏蔽集群内部包含的成千上万的存储节点的复杂性和分布式特征,使调用方可以认为在跟一个对象存储系统打交道。

每次cluster map发生变化都会导致epoch增加,map发生变化的原因可能是由于OSD状态变化(设备故障)或者其他影响数据分布的事件(注:手工修改CRUSH rule、扩容OSD等)。map的epoch可以让互相通信的各方对系统当前的数据分布达成一致,还能确定谁的信息是过期的(相对其他节点来说)。cluster map变更会比较频繁,因为大型系统中OSD故障和恢复是非常常见的,更新也是与增量map类似的分布式进行的,两次成功的epoch的差异只需要很少量的信息就可以描述。尽管通常更新中会包含许多OSD,但大多数情况下,更新简单的记录OSD错误或恢复信息,并且相距较远的两次epoch的差异会进行更新信息的打包聚合,以减少更新消息的数量。

表1
表1

2.2  Data Placement

数据存放位置。

RADOS采用了一种伪随机分配对象到存储设备的数据分布策略(注:类似hash映射,但不是完全随机的),每当添加新的存储设备时,会随机选择现有的部分数据迁移到新设备以保存均衡。这种策略是健壮(鲁棒)的,因为它保持了数据在存储设备上的概率均衡分布,通常它可以保持所有设备的负载比较接近,因而可以使得系统在任何潜在的工作负载下运行良好[22]。更重要的是,对象数据存放位置的计算是分两步进行的,不需要大量复杂的中心化位置分配表(注:不需要专门的元数据节点来管理数据存放位置,使用CRUSH算法即可解决)。

系统中存储的每个对象都首先被映射到一个PG(placement group),PG是一个逻辑上的对象集合,PG中的对象会被复制多份保存到不同的存储设备上(注:以实现副本功能,保证数据可靠性,PG是管理副本的最小单元)。每个对象所在的PG由公式pgid = (r, hash(o)&m)确定,其中o为对象名称,r为副本数量,m可以控制系统中PG总数(m = 2^k – 1),&为按位与操作,将系统中PG总数限制为2的倍数。随着系统的容量伸缩,需要定期修改m的值以调整PG总数,这个过程是逐步完成的,这样就能保证每次伸缩后需要在不同存储设备间迁移的数据量可控。

根据cluster map设置,PG会分配到OSD,每个PG对应r个排序的OSD(r为副本数量)用来实现对象的副本存储。我们利用CRUSH(一种稳定的副本分布算法)计算出一个稳定的伪随机映射(注:从PG到OSD的映射),其他数据存放策略也是可行的,即使是用明确的表来管理每个PG到OSD的映射关系,在一个相当大的集群情况下这个表也是很小的(MB级别)。从高层次抽象来看,CRUSH与hash函数的行为非常类似:PG映射关系是确定的,但是符合伪随机分布。与hash函数不同的是,CRUSH的计算结果是稳定的:即使有一个或多个设备加入或离开集群,大部分PG到OSD的映射关系是不变的,CRUSH仅移动足够的数据来维持数据分布的均衡性。作为对比,hash方法则通常需要强制重组所有之前的映射关系。CRUSH还支持设置权重来控制每个存储设备上分配的数据量,权重的设置依据可以是设备的容量或性能等方面。

注:CRUSH是另一篇论文《CRUSH: Controlled, scalable, decentralized placement of replicated data》,后面有时间会翻译。

2.3 设备状态

OSD状态

cluster map包含设备的当前状态以及设备上的数据分布的描述,描述信息包含所有在线可连通的OSD的网络地址(up状态的OSD),也包含当前离线的OSD信息。RADOS还将另外一个OSD是否存活的维度考虑在内:in状态的OSD会映射到PG,out的不会。

CRUSH会给每个PG生成一个包含r(副本数量)个OSD的列表的映射关系,RADOS之后会把down状态的OSD过滤掉,只保留活跃的OSD给PG使用。如果活跃的OSD列表为空,那么PG里面包含的数据就处于临时不可用状态,所有访问这些数据的IO操作都被阻塞(注:处于IO hang状态)。

OSD通常都处于up/in状态,可以正常提供IO服务,或者出于down/out状态,从而生成包含r个OSD的精确列表。

OSD也可能处于down/in状态,这种状态意味着OSD处于不可连通状态,但是PG里面的数据还没来得及重新映射到其他OSD(与RAID的降级模式类似)。同样的,OSD也可能处于up/out状态,意味着OSD在线但是空闲(注:未真正加入集群,没有PG分配过来)。OSD之所以有这些状态值,是为了让集群应对网络波动、OSD重启等场景时,可以不执行数据迁移操作,还可以在新存储节点上线时不必立即启用他们(比如为了提前测试网络连通性等),也可以帮助实现旧设备下线过程中安全的数据转移。

2.4 map传播

由于RADOS集群可能包含上千个OSD甚至更多,所以简单的广播cluster map的更新到各个OSD是不现实的。幸运的是,只有同一个PG内的OSD或者OSD和client之间才需要互相就cluster map的epochs达成一致,这是因为同一PG内的r个OSD需要选出一个主OSD来处理IO请求。从这方面来说RADOS可以利用OSD之间通信的机制来进行cluster map的惰性更新,从而将cluster map更新相关的负载下放到各个OSD上。

每个OSD都维护一份cluster map增量更新的历史记录,所有消息都带有最新的epoch标记,当OSD与其他OSD通信时都会对最近的epoch进行跟踪。如果一个OSD接收到另外的OSD消息时发现其map的epoch版本比较老,则当前OSD会把增量更新分享给它以便让他同步到新版map。类似地,当主动发消息给其他OSD时,如果发现对端map的epoch版本比较老,也会触发cluster map的增量更新流程(注:不管是主动还是被动通信,如果发现对端版本较老都会同步cluster map)。同时,用于错误检测(见3.3节)的OSD定时心跳消息也会携带map信息,以确保map更新以O(log n)的收敛速度快速扩散到整个集群的n个OSD。

举例来说,当一个OSD第一次启动时,它会发送一个OSD启动的通知给一个monitor(见第4节)表示它上线了,这个启动消息包含了OSD本地最新的map epoch版本号。之后monitor集群会把这个OSD置为up状态,并把cluster map的增量更新信息发送给OSD,以便确保它获取的是最新的版本。当这个新的OSD开始与和他共享数据的OSD建立联系时(见3.4.1节,注:新的OSD分配了PG,其他OSD是一起保存对象的多个副本),受这个新OSD的状态变更影响的精确OSD集合会学习到恰当的cluster map更新(注:同一个PG的多个OSD副本之间互相建立关系时保持一致的map版本)。由于OSD启动时不清楚对端的OSD保存的map epoch,所以它会分享至少最近30秒的增量更新历史版本给对端。

这种抢占式的map共享策略是保守的:一个OSD会一直跟对端(注:对端表示同一个PG的多个副本所在的OSD)共享map更新,除非它确信对端已经获取到这个更新,这就导致OSD会接收到重复的map更新信息(注:相同的map更新信息一个OSD收到多次)。但是,一个OSD接收到的重复信息的数量会受到对端数量的限制(注:也即副本数量),因此也受限于一个OSD上包含的PG数量(注:副本数量一定的情况下,一个OSD上PG数量越多,接收到的重复消息越多)。在具体实践中我们发现OSD真正接收的重复更新消息数量是比上述理论值要少很多的(见5.1节)。

3. INTELLIGENT STORAGE DEVICES

智能存储设备(注:意思赋予了OSD计算能力,可以用来处理数据副本、错误检测、错误恢复)

cluster map包含的数据分布信息可以让RADOS将数据冗余备份、错误检测、错误恢复等方面的能力分散到组成存储集群的各个OSD上。在一个高性能的集群环境中,通过点对点类似的协议可以很好的利用OSD的计算能力。

RADOS当前为每个PG实现了n路复制(注:多副本功能)、对象版本和短期日志(注:应该是指journal功能)功能。复制是有OSD自身负责执行的:客户端提交一个单独的写操作到主OSD,之后主OSD会负责一致、安全地更新到所有副本(注:同一个PG内的其他OSD)。这个设计将副本相关的带宽转移到了存储集群内部网络,并且简化了客户端的设计(注:不需要考虑副本问题)。对象版本和短期日志功能则使存储节点间歇性故障场景(如网络断开或节点宕机/重启)下的恢复变得更快。

我们在这一大节内容中会主要描述RADOS的架构,特别是cluster map相关的架构,因为它是实现分布式副本和恢复操作,以及这些能力可以适用于其他副本机制(如基于奇偶校验的RAID)的关键。

3.1 副本

RADOS实现了3种副本方案:primarycopy [3],chain [26],以及一个我们称之为splay的混合方案。图2展示了一次更新操作(注:写IO)的消息交换流程,在所有方案场景下,客户端发送IO操作到一个OSD(每次IO操作对应的OSD可能是不同的),存储集群会确保副本被安全的更新并且读写语义的一致性(如经过序列化)会被保留。当所有副本都被更新之后,会返回一个通知消息给客户端。

图2
图2

Primary-copy副本方案会并行更新所有副本,并且读和写请求都在主OSD上处理。Chain副本方案则是顺序更新所有副本:写操作发送到主OSD处理,读操作则在最后的副本上进行(注:更新顺序是从主到从,从头到尾),以此来确保读操作总是获取完整更新过的副本内容(注:所有副本都更新过了才放行读操作)。splay副本方案则简单的把primary-copy方案的并行更新机制以及chain方案的读写角色分离结合在一起,其主要优点是2路镜像场景(注:应该是说2副本情况)下的消息交换数量比较少。

3.2 Strong Consistency

强一致性

包括client以及其他OSD生成的所有的RADOS消息都会被加上map的epoch标记,以此来确保所有更新操作都能被完整一致的实施(注:这句话没太理解,感觉应该是说有了版本号才知道最新的数据分布,不会导致数据更新不一致)。如果一个客户端由于其拥有的map是过时的而发送了一个IO请求到错误的OSD上,OSD会把map的增量更新返回给客户端,之后客户端就可以重新发送请求到正确的OSD。这就避免了主动推送cluster map更新到客户端的需求,因为客户端可以在与存储集群交互过程中获取到map的更新。大部分情况下客户端都可以在不影响当前IO操作的同时把之后的IO重定向到正确的OSD。

如果cluster map的主副本已经更新并影响到一个PG的成员关系,PG的成员还没有得知map的更新,对象更新可能被PG的旧成员处理。如果map的变更首先被PG中的某个非主副本得知,则当主副本所在的OSD转发更新IO操作到次副本或其他副本时,次副本会把新的map更新返回给主副本。这个流程是安全的,因为map更新后PG对应的新OSD集合需要首先与PG之前对应的旧OSD集合建立联系,以便确定PG正确的数据内容,这就确保了之前的OSD可以得知map更新,并停止执行IO操作直到新的OSD启动。

让读操作达到与更新操作类似的一致性会更加复杂,在类似网络断开导致一个OSD只能被部分节点连通的场景下,这个OSD不应该为可以连通它的使用旧的map的客户端提供PG读操作。同时,更新后的cluster map会指定一个新的OSD来替换这个异常的OSD,为了在更新操作被新OSD接管后阻止任何读操作被旧的OSD处理,我们需要在PG的OSD之间保持周期性的心跳消息,以便确认OSD是否存活从而保证PG是可读的,具体策略是,当一个提供读操作的OSD超过H秒都没有收到PG内其他副本的心跳消息,则它会暂停提供读操作(注:因为写操作需要从主OSD转发到各个从OSD,已经保证了各个OSD都要在线才能执行,而读操作则不然,可以在PG的任何OSD上执行,因此需要叠加OSD心跳机制来保证PG的读操作可执行)。在一个OSD接管PG的主OSD角色之前,它必须要么获取到旧OSD主动发送的确认消息(确保旧OSD知道他们的角色变化),要么继续等待H秒。在当前的实现中,我们设置了一个比较短的心跳间隔(2秒),这保证了错误检测周期很短,也保证了PG的主OSD异常时PG里数据不可访问的持续时间较短。

3.3 Failure Detection

错误检测

RADOS实现了一个异步的、顺序的点对点消息传递库用来传递消息,TCP错误导致的消息传递失败会进行有限次数的重连尝试,重试无效会将错误信息上报到monitor集群(注:消息传递的一端网络异常、节点异常等场景会进行消息重发)。同一个PG内的存储节点(OSD)之间周期性的交换心跳消息以确保OSD异常可以被检测到,当OSD发现它被标记为down状态之后,会在执行数据落盘操作之后自杀,以确保数据一致性。

3.4 Data Migration and Failure Recovery

数据迁移和错误恢复

RADOS数据迁移和错误恢复是完全由cluster map的更新以及由此引发的PG到OSD映射变化驱动的,这种变化可能是由于设备(注:此处设备指代OSD)故障、恢复、集群扩缩容,甚至是由于CRUSH副本分布策略更新导致的整体数据重新分布引发,有众多可能原因会导致存储集群建立新的数据分布,设备错误是其中最简单的一个。

RADOS不对两个map之间的数据分布做任何连续性假设(注:map更新前后可能导致数据分布变更),在所有情况下,RADOS采用健壮的对等算法来建立PG数据一致性视图,也是用该算法来恢复数据的恰当分布和副本。这种策略依赖一个基本的设计前提:即使本地对象副本丢失,OSD可以主动复制PG的日志,这个日志记录了PG的当前应该保存的内容(例如保存的对象版本为何)。因此即使恢复比较慢、对象的安全性在一段时间内降级,PG的元数据是会小心保管的,这就简化了恢复算法,并且使得系统可以可靠地检测出数据丢失。

3.4.1 Peering

结对(注:同属一个PG的各OSD之间进行结对)

当一个OSD接收到cluster map的更新后,它从远及近地遍历所有新的增量更新,检查并可能会调整PG状态值,所有本地保存的PG的在线OSD列表发生变化后都会被标记为需要重新结对。顾及到所有map epoch而不是最近一个是为了确保所有中间数据分布的变化也被考虑在内:如一个OSD被从PG中移除后又再次加入该PG,了解PG内容中间更新过程可能已经发生是很重要的(注:这部分没太理解,应该是说cluster map多次更新可能导致数据分布发生了变化,但cluster map最终并没有发生变化,所以为了保证数据一致性,需要确保每次map的更新都被正确处理)。类似副本过程,结对过程以及随后的数据恢复过程也是在每个PG中各自处理的。

结对过程是由PG的主OSD发起的,对于OSD保存的每个PG来说(注:一个OSD上有多个PG,OSD在一个PG里可能是一个副本OSD,也可能是一个已经离线的OSD),如果OSD不是PG的主OSD,那么它就会发送一个通知消息给PG当前的主OSD,消息内容包括OSD本地保存的PG的基本状态信息,最新的map更新,PG log的边界,以及PG成功结对后最近获知的epoch。通知消息确保PG里一个新的主OSD可以发现它的新角色,而不必为每次map变化考虑所有可能的PG(可能是数百万个)(注:这段应该是说map变化只影响涉及到的PG,而与其他PG无关)。一旦意识到角色变化,主OSD会生成一个优先集合,集合包含了参与PG的所有已成功结对的OSD,通过显式查询优先集合之后会给集合中的每个OSD请求一次通知消息,以避免无限等待一个实际没保存这个PG的OSD(例如对于中间状态的PG映射结对永远无法完成)(注:这部分没太看懂,是说主OSD会主动发消息给PG内其他OSD并让他们回复,以便完成结对?结对过程是要两方都确认才算真正结对成功?)。

有了整个优先OSD集合的PG元数据,主OSD可以确定已应用到每个副本的最近更新,并且向优先OSD请求任何PG日志的片段以便使得所有在线副本OSD的PG日志同步到最新。如果可用的PG日志不足(例如一个或多个OSD没有任何PG的数据),则会生成一个完整PG内容的列表(注:这句没看明白,生成这个列表有啥用?是说数据不完整的也会记录起来?之后怎么处理呢?)。但是对于节点重启或者其他短时间的中断情况来说,这个流程是不需要的,最近的PG日志就足以快速地重新同步所有副本。

最后,主OSD会将丢失的日志片段共享给副本OSD,之后所有副本就都知道了PG应该包含的对象(即使这些对象可能在OSD上不存在),并且在后台进行数据恢复的同时开始处理IO请求。

3.4.2 Recovery

分组副本(declustered replication)一个关键优势是可以进行并行故障恢复,一个故障OSD的相关副本是分散到多个其他OSD的,故障OSD加入的每个PG会独立的选择一个替代品,让副本重新复制到替代OSD。一般来说,在一个大型系统中,介入一次故障恢复过程的OSD只会在一个PG上获取或推送数据内容,这使得恢复过程非常快速。(注:这段是说一个OSD挂了,则PG会找一个新的OSD替代它,这个OSD会把挂了的OSD的数据副本同步过来,同步过程是从挂了的OSD加入的所有PG包含的其他OSD上获取数据,而其他OSD则是推送数据给替代OSD,因此每个介入恢复过程的OSD–非替代OSD–都只负责一个PG的数据恢复,大型系统的PG比较分散,参与恢复过程的两个OSD包含同一PG的概率比较低)

RADOS的故障恢复是在观察到IO操作经常性被读吞吐量限制后激发的(注:上面讲过,OSD心跳超时后读操作会被block,但为啥不是观察写入被限制呢?)。尽管每个独立的OSD配合PG元数据都可以单独的获取丢失的对象,这个策略仍然有两个局限性,首先是同一PG的多个OSD独立恢复对象过程中,它们不一定会同时在同一批OSD拉取同样的对象,这就引出了恢复过程代价最大的问题:寻址和读数据;其次是当副本OSD丢失了被修改的对象时,副本更新协议(见3.1节)就变的更加复杂(注:IO写入过程要在每个副本OSD完成后才返回给客户端,如果一个副本OSD没有这个写IO操作的对象,则需要进行特殊处理才行,比如要先把副本同步过来才能继续执行写操作,本次写IO返回执行失败结果给客户端?)。

基于上述原因,RADOS的PG数据恢复是由主OSD协调的,如前所述,操作主OSD丢失的对象时会被阻塞直到主OSD同步对象数据到本地,由于主OSD通过结对过程已经知道所有副本OSD丢失的对象信息,所以它就可以主动推送丢失任何即将被修改对象到副本OSD,这就简化了副本同步逻辑,同时也保证了存活的副本只需要被读取一次(注:被主OSD读取一次之后主动推送到副本OSD)。在主OSD推送对象(注:客户端通过主OSD获取对象)或者为自己拉取对象过程中,它会在对象还在内存中时将对象推送给所有需要这个对象的副本OSD,因而总体上每个被恢复副本的对象仅会被读取一次。

4. MONITORS

一个小的monitor集群通过保存cluster map的主副本,并定期更新以响应配置更改或OSD状态更改(例如,设备故障或恢复),来共同负责管理存储系统。该集群部分基于Paxos part-time parliament[14],旨在支持一致性和持久性,而不是可用性和更新延迟。 值得注意的是,必须在多数monitor可用时才能读取或更新cluster map,并保证做出的更改的持久性。

4.1 Paxos Service

monitor集群是基于Paxos算法实现的分布式状态机服务,其中cluster map是当前机器状态,map每次成功更新都会产生新的epoch。我们的实现通过一次只允许一个并发map变更来简化标准Paxos(与[17]类似),同时将基本算法与租约机制相结合,允许将IO请求发给任何monitor节点并且仍能确保IO读取和更新操作的一致性。(这个功能已经被实现为通用服务,它还被用来管理Ceph的全局数据结构,其中包括用来协调客户端访问存储系统的MDS的cluster map和状态)

monitor集群会初始选举出一个leader并由它来负责序列化cluster map的更新和管理一致性。leader一旦被选举出来就会向每个monitor节点请求map的epoch,monitor节点需要在固定时间T内(目前T为2秒)做出回应并加入选举的法定人数。如果存活的monitor占多数,Paxos算法的第一阶段保证每个monitor拥有最近提交的cluster map epoch(可以向其他monitor按需请求cluster map的增量更新),之后开始给每个monitor发布短期租约(注:意思是每个monitor都是最新的map了,就可以开始对外服务了,但是为了保证map的时效性、对象数据的一致性,需要增加租约有效期限制,租约过期了就需要重新获取map和续租)。

在每个租约有效期内的存活monitor被允许分发cluster map的副本给请求cluster map的OSDs或者客户端,如果超出租约有效时间T都没完成续约,则认为leader monitor已经死亡,然后发起新的选举。每个monitor的租约都需要通过发送收据通知给leader monitor来确认,如果一个租约发布后leader没有及时接收到某个monitor节点的确认消息,leader节点会假设这个monitor节点已经死亡,并发起一次新的选举(建立新的选举法定人数)。当一个monitor第一次启动时,或者发现之前发起的选举在合理时间段内没有结束,就会发起新的选举。

当一个正常的monitor节点接收到一个更新请求(如有错误发生)(注:更新请求是指请求获取map更新),它会先检查这个请求是不是新的,举例,如果发送更新请求的OSD已经被标记为down,那么monitor会简单的恢复必要的增量map更新给OSD让它保存的map保持最新版本,而如果是有新的错误发生则会转发给leader monitor节点,由它负责聚合多次更新。leader monitor会通过增加map epoch来定期发起map更新,并利用Paxos更新协议将更新提案分发给其他monitor节点,与此同时还会撤销之前的租约。如果更新被多数monitor节点确认,leader节点最终提交map信息并生成一个新的租约(注:Paxos协议的第二个阶段)。

同步两阶段提交和探测间隔T的组合使用可以确保如果monitor的存活集合发生变化,所有之前的租约(还在有效期T内)都会在任何后续的map更新发生之前过期。因而,只要多数monitor节点可用,任何顺序的map查询和更新会导致map版本的一致性前进(持续增加版本号),无论消息发给哪个monitor、不管monitor发生什么故障,版本号都不会倒退。

4.2 Workload and Scalability

工作负载和可扩展性

一般情况下,monitor做的事情非常少:大部分map分发都是OSD节点处理的(见2.4节,注:通过PG的OSD结对机制来搞定),而OSD状态变化(如由于设备故障)通常都不频繁。

monitor集群内部使用的租约机制可以允许任何monitor节点处理来自OSD的读IO请求或者来自客户端的获取最新的cluster map请求,这些请求很少由OSD发起,因为它们有主动map共享机制,而客户端请求map更新仅会在检测到OSD发生操作超时错误时才会执行,对于大型存储集群,可以通过扩容monitor集群来分散它们的工作负载。

获取新map的请求(如OSD启动通知或首次上报故障)会被转发到leader monitor,leader会聚合多次map变更到一个map更新,因此map更新的频率是可调的,并且与存储集群的大小无关。尽管如此,最严重的负载会发生在短时间内大量OSD出错场景下,如果每个OSD上面有µ个PG,同时有f个OSD出错,则在一个大型OSD集群经历一次部分网络中断故障时,最多会有µ*f个错误报告会产生(注:每个OSD上的PG都完全不同的场景),这个数字可能非常庞大。为了防止这种消息泛滥问题,OSD在半随机间隔内发送心跳用来将检测到OSD故障的时间错开,之后对错误上报消息进行限流和批量化处理,这样就可以让monitor集群的负载与存储集群大小成比例关系。非leader monitor节点之后仅转发一次错误报告消息(注:转发前会检查是否已经转发过,只转发那些没转过的消息),因此leader节点的负载与f*m成比例关系(m为monitor节点数量)。

5. PARTIAL EVALUATION

这是一个专有名词,不知道怎么翻译,参考资料:

利用起来每个OSD的对象存储层的性能在Ceph那篇文章[27]中已经被测量过,类似地,CRUSH的数据分配性能以及他们对集群吞吐量的影响也被评估过[27,28]。在本篇文章中我们仅聚焦在map分发性能方面,因为这个问题对集群伸缩能力有直接影响,尽管我们在架构上对系统的可扩展性有信心,但我们还没有通过实验评估monitor集群的性能表现。

5.1 Map Propagation

RADOS map分发算法(见2.4节)确保map的更新可以在logn跳内到达所有OSD,然而随着存储集群扩容,设备故障以及与此相关的集群更新的频率也会增加,由于map更新仅在同一PG内的OSD之间交换,一个OSD能接受到的同一个map更新的次数上限与µ成比例关系(µ是OSD上PG数量)。

在定期进行map更新情况下的接近最差map传播环境的模拟场景下,我们发现即使在指数级集群扩容场景下map重复更新(注:传播的更新数与实际更新数的比值)也是接近稳态的,在模拟实验中,monitor会分享map到一个随机的OSD,之后OSD会把map分享给PG里的其它OSD。图3展示了我们通过调整集群大小x以及每个OSD上的PG数量(对应每个OSD有几个对端),得出的一个OSD接收的重复map更新消息数量y。map更新的重复消息量接近一个常量,少于µ的20%,即使集群大小成指数级扩展,也只会产生固定的map分发开销。我们还考虑了一种最差场景,让一个OSD频繁的上下线,导致其获取map更新非常慢(更新已经被它的对端获取),这种情况下map分发的开销只依赖于map更新频率限流情况,而这种限流monitor集群已经做了。

图3

图3

6. FUTURE WORK

我们当前已经实现了一个工作良好的对象存储系统,这个系统是Ceph分布式文件系统的基础,但这个对象存储系统提供的可伸缩集群管理服务不仅能满足Ceph的需求,而且还更加通用,我们还有很多额外的方向需要进一步研究。

注:第6节内容与RADOS关系不太大,就简单的翻译下。作者提到了一些考虑到的后续研究方向,实际上应该很多都没做,这也是写论文的常见套路,主要是为了说明论文内容的普适性和可持续性。

6.1 Key-value Storage

这里应该是说RADOS当前只用来存储对象,实际上它还可以用来保存key-value键值对,实现类似mongodb、memcache、redis等NoSQL数据库的功能。

6.2 Scalable FIFO Queues

基于RADOS实现一个可伸缩的先进先出队列服务。

6.3 Object-granularity Snapshot

对象粒度的快照功能。这个需求是Ceph需要的。

6.4 Load Balancing

热点对象的负载均衡,场景是大量客户端同时访问同一个对象,会导致这个保存这个对象的OSD节点负载很高。这确实是一个值得研究的方向,问题很有现实意义。

6.5 Quality of Service

QoS,保证故障恢复期间客户端的IO操作性能不受大的影响或者受影响的程度可控制,这也是一个值得研究的方向,也是Ceph社区当前的重点改进方向。

6.6 Parity-based Redundancy

基于奇偶校验的冗余,我理解应该是研究除了副本冗余策略之外的其他策略,Ceph当前已经实现了EC纠删码冗余策略

第7节是相关工作,第8节是小结,都是科研论文的一般讨论,就不再翻译了。另外还有最后的参考文献也不列出来了,有需要可以参考论文原文。

后面有时间我会把剩下的两篇论文也翻译下:

  • CRUSH: Controlled, scalable, decentralized placement of replicated data
  • Ceph: A scalable, high-performance distributed file system(2018-04-24更新:这篇看了下,只讲了MDS相关的,感兴趣的rbd相关的没看到,就不翻译了)