Ceph OSD IO线程健康状态检查机制

目前看到H版本OSD IO线程健康状态有3种心跳机制(L版本对比看了下,基本没变化):
1. service线程:

  1. OSD tick:

  1. 其他OSD发过来的ping请求:

在L版本中其实还有一种机制,基于perfcounter实现,在service线程里(第一种机制代码位置相同)会更新健康IO线程数量和总IO线程数量到perfcounter中的l_cct_total_workers、l_cct_unhealthy_workers两个计数器上,可惜OSD启动时没有enable这两个counter(使用CephContext::enable_perf_counter),搜索代码可以看到rgw和rbd_mirror两个模块enable了,如果有需要我们可以自己在OSD启动过程中enable起来(g_ceph_context->enable_perf_counter()即可)。之后就可以通过ceph daemon osd.0 perf dump来查看相关counter信息了。

心跳超时默认15s打告警日志,超过150s会导致OSD自杀。

Ceph CPU&MEMORY profiling

环境信息

  • OS:debian 9 with kernel-4.9.65
  • Ceph:luminous-12.2.5

CPU profiling

有两个工具,Linux常用的是perf,这个工具比较通用,功能也非常强大,debian提供安装包,另一个是oprofile,debian没有安装包,需要自己编译,并且在虚拟机里面无法使用。

use perf

参考:
– http://docs.ceph.com/docs/master/dev/perf/
– https://www.ibm.com/developerworks/cn/linux/l-cn-perf1/index.html (推荐这篇,各种常用命令解释比较清楚)

安装非常简单,直接apt-get install linux-perf-4.9即可,其中4.9是内核大版本号。

主要用到的命令有:
– perf top/perf top -p 1234/perf top -e cpu-clock:u(用户态CPU时钟周期采样统计):实时观察进程CPU时钟周期采样计数信息
– perf stat/perf stat -p 1234:进程基础统计信息,用于高层次的分析进程情况,比如是IO密集还是CPU密集,或者先看下问题发生在哪个方面
– perf record -p 1234 -F 99 –call-graph dwarf — sleep 60:捕获进程CPU采样周期并且保存调用关系图
– perf report –call-graph caller/callee:报告展示调用关系,caller和callee是顺序相反的两个展示参数(调用者在上还是被调用者在上)
– perf list:查看所有perf支持的event列表,默认是cpu-cycles,这个是硬件事件,也可以用cpu-clock,这个是软件事件
– perf help xxx:查看命令帮助文档

配合FlameGraph脚本生成火焰图(需要先用perf record采集数据):
1. git clone https://github.com/brendangregg/FlameGraph
2. perf script | FlameGraph/stackcollapse-perf.pl > perf-fg
3. ./FlameGraph/flamegraph.pl perf-fg > perf.svg

perf-flamegraph

上图中条带越宽表示函数被采样到的次数占总采样次数比例越高(占用的CPU时间片越多),也就是越耗费CPU资源。

该工具的好处是不需要特殊的编译选项,实际测试加不加-fno-omit-frame-pointer这个CFLAGS看起来对结果没啥影响。

use oprofile

官方的文档已经太老了,新版本的oprofile已经没有opcontrol命令了:
– http://docs.ceph.com/docs/master/rados/troubleshooting/cpu-profiling/
– http://docs.ceph.com/docs/master/dev/cpu-profiler/

因此自己编译了一个新版本的,过程如下:
1. wget https://sourceforge.net/projects/oprofile/files/oprofile/oprofile-1.3.0/oprofile-1.3.0.tar.gz
2. tar xzf oprofile-1.3.0.tar.gz
3. cd oprofile-1.3.0
4. apt install libpopt-dev libiberty-dev
5. useradd oprofile
6. ./configure
7. make && make install

然后就可以使用operf命令了,但是我在虚拟机里面使用报错:

考虑到物理机上使用也要编译,因此也不再深究。

MEMORY profiling with google-perftools

参考:
– http://docs.ceph.com/docs/master/rados/troubleshooting/memory-profiling/
– http://goog-perftools.sourceforge.net/doc/heap_profiler.html (官方帮助文档)

我们L版本使用的是tcmalloc,因此可以直接使用google-perftools,该工具安装也是apt-get install google-perftools即可。

常用命令:
– ceph tell osd.0 heap start_profiler:开启内存使用统计
– ceph tell osd.0 heap dump:dump内存使用情况(需要先start_profiler),默认输出到日志目录
– google-pprof –text /usr/bin/ceph-osd /var/log/ceph/ceph-osd.0.profile.0001.heap:查看dump出来的内存使用情况
– google-pprof –text –base osd.1.profile.0002.heap /usr/bin/ceph-osd osd.1.profile.0003.heap:对比两次dump处理的内存堆使用情况,会把base里的内存减掉,方便查看内存增量
– ceph tell osd.0 heap stats:基础统计信息,不需要start_profiler就能使用
– ceph tell osd.2 heap release:释放tcmalloc的缓存,归还给OS,也不需要start_profiler
– ceph tell osd.0 heap stop_profiler:停止profiler

其中第一列是函数使用的内存量(MB),第二列是当前函数内存使用量占总内存使用量的百分比(也即第一列的占比),第三列是第二列的累加值,也即TopN函数使用内存占比,第四列是当前函数和所有他调用到的子函数的内存使用量(MB),第五列是第四列和内存使用总量的百分比,最后一列是函数名。

官方解释:
– The first column contains the direct memory use in MB.
– The fourth column contains memory use by the procedure and all of its callees.
– The second and fifth columns are just percentage representations of the numbers in the first and fifth columns.
– The third column is a cumulative sum of the second column (i.e., the kth entry in the third column is the sum of the first k entries in the second column.)

Ceph-blkin+lttng+zipkin性能追踪工具

部署

参考:
– http://docs.ceph.com/docs/master/dev/blkin/
– https://zipkin.io/pages/quickstart.html
– https://github.com/openzipkin/zipkin/blob/master/zipkin-server/README.md#cassandra-storage
– https://github.com/openzipkin/zipkin/tree/master/zipkin-storage/mysql-v1
– https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql
– http://cassandra.apache.org/download/
– https://blog.csdn.net/vbirdbest/article/details/77802031

环境要求

  1. ceph-luminous版本编译环境
  2. Debian9.1 + 4.9.65内核
  3. 已部署好ceph集群

部署ceph+blkin

步骤

  1. 修改do_cmake.sh,在cmake那行加上”-DWITH_BLKIN=ON”打开blkin编译选项
  2. ./do_cmake.sh; cd build; make; make install
  3. 修改ceph.conf,打开blkin相关配置项:

部署babeltrace-zipkin

这个项目的用途是把blkin配合lttng收集的trace数据转换并发送给zipkin的数据采集器,zipkin聚合后存储起来供后续web查询使用。

步骤

  1. git clone https://github.com/vears91/babeltrace-zipkin
  2. cd babeltrace-zipkin/setup; 找到 ubuntu.sh,注意这个脚本里面会安装依赖包,以及编译并安装blkin-lib,下载zipkin.jar,最后还会git pull更新下babeltrace-zipkin,由于github clone项目时快时慢,因此建议直接在PC上下载项目的压缩包,单独手工安装,这个很快
  3. 提取出ubuntu.sh里面的pip3 install和apt-get install相关命令,直接手工安装即可

部署lttng

直接apt安装即可:

安装完毕后有如下几个包:

具体哪几个有用我也不完全确定,lttng-tools这个肯定用到的,lttng-modules-dkms这个内核态的应该用不到,ceph都是用户态的。

部署zipkin

zipkin部署非常简单,只需要有jre环境,就可以通过下载可独立执行的zipkin.jar包即可运行。最新稳定版下载链接:
– https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec

zipkin的存储后端有多种:
– memory(默认):内存存储,非持久化,重启zipkin进程后数据丢失,需要重新导入
– MySQL:不解释
– Cassandra(官方推荐,最初支持的方案):个人理解是一种类似influxdb的时序数据库
– Elasticsearch:参考官网介绍,是一个分布式、RESTful 风格的搜索和数据分析引擎

我这边验证了前面3种方式,因此需要增加部署MySQL(mariadb)或者Cassandra数据库。

memory方式

直接执行java -jar zipkin.jar即可,web端口默认是9411,浏览器打开http://$IP:9411即可访问zipkin web页面。

注意内存方式下trace数据是非持久化的,重启后丢失。

mariadb方式

部署mariadb:
1. 使用apt安装包:apt-get install mariadb-common mariadb-server
2. 创建用户并添加权限(默认的root用户会连接失败):GRANT ALL PRIVILEGES ON *.* TO 'zipkin'@'%' IDENTIFIED BY 'admin123' WITH GRANT OPTION; FLUSH PRIVILEGES;
3. 创建数据库和schema:直接使用官方的sql脚本创建,https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql ,保存脚本到zipkin.sql并执行mysql -Dzipkin < zipkin.sql
4. 如果数据库和zipkin不在一个节点上,还需要修改mariadb的监听地址段并重启服务,/etc/mysql/mariadb.conf.d/50-server.cnf的bind-address配置项

启动zipkin+mariadb:STORAGE_TYPE=mysql MYSQL_USER=zipkin MYSQL_HOST=127.0.0.1 MYSQL_PASS=admin123 java -jar zipkin.jar

相关环境变量:https://github.com/openzipkin/zipkin/blob/master/zipkin-server/README.md#mysql-storage

导入数据后,可以用mysql命令查看数据:

Cassandra方式

部署Cassandra:

启动及相关默认路径:

时序数据库一般不需要特殊的schema配置,直接使用即可:STORAGE_TYPE=cassandra3 java -jar zipkin.jar

导入数据后可以用cqlsh命令查看数据库内容:

参考官网:http://cassandra.apache.org/download/

最终部署架构

ceph-blkin-zipkin

使用

  1. 确保lttng服务正常运行:systemctl status lttng-sessiond.service
  2. 停止ceph服务,包括所有服务端进程以及客户端进程(如果是客户端与服务端不再一个节点,也要单独部署lttng和使用打开了blkin的ceph)
  3. 创建lttng session,并添加trace event:

  1. 启动所有ceph进程,以及客户端进程,并进行相关测试
  2. lttng stop停止session,其他可用命令:lttng list/status/view/destroy
  3. 使用babeltrace-zipkin工具导入数据到zipkin:cd /mnt/babeltrace-zipkin-master/; python3 babeltrace_zipkin.py ~/lttng-traces/blkin-wp2-20181218-160516/ust/uid/0/64-bit/ -p 9411 -s 127.0.0.1,其中-p 9411是zipkin的数据接收端口(也是web端口),-s是zipkin服务监听地址。数据导入过程比较耗时,需要等待,开始导入和导入完成都会有日志提示:

  1. 导入过程中如果出错可以查看zipkin的日志(前台执行的话看控制台输出就可以了)。
  2. 使用web进行数据查询:浏览器打开http://$IP:9411即可访问zipkin web页面。需要注意的是,查询页面的时间参数是指lttng日志文件里的采集时间,而非数据导入到zipkin的时间,另外还要注意数量参数,经过实测仅搜索指定数量的trace event数据,然后对这些数据进行排序后展示,而不是对所有数据排序后进行截取展示,因此建议时间采用自定义时间段方式,缩小时间段,减少数据量之后再适当设置较大的数量进行查找。

zipkin

zipkin

自定义扩展

相关名词

  1. trace event:跟踪事件类型如zipkin:timestamp、zipkin:keyval_integer、zipkin:keyval_string
  2. service:跟踪服务类型如osd.1、pg 1.0、messenger、objecter、librbd-855346e482f5-test-blkin-vol1等,对应代码中的trace_endpoint,ZTracer::Endpoint类型
  3. span:service的子项,标记一个跨度,跨函数使用,例如librbd里面有多个跨度,从ImageRequestWQ的“wq: write”到“writeback”等,osd默认只有一个“osd op”,pg有“pg op”和“replicated op”,对应代码中的osd_trace、pg_trace、journal_trace等,ZTracer::Trace类型,可以用ZTracer::Trace::init函数来初始化,通过在初始化时指定parent span来构建调用链关系,parent可以跨service,顶级span的id跟traceId相同,可以理解为第一个span的ID就是traceId
  4. annotation:span的子项,基本上是在同一个函数里面使用,ZTracer::Trace::event即可创建

参考:https://blog.csdn.net/manzhizhen/article/details/52811600、https://blog.csdn.net/manzhizhen/article/details/53865368

ceph扩展相关trace类型

service类型一般不需要扩展,已经基本全部包含了,这里重点介绍span和annotation两种类型的扩展方法。
span:

src\common\TrackedOp.h::class TrackedOp类里有如下定义:

我们只需要模仿它们来定义我们自己的span名称即可。

src\osd\OSD.cc::void OSD::ms_fast_dispatch(Message *m)中会对osd_trace进行初始化,可以参考他的初始化过程来初始化我们自己的span:

实际上我们需要扩展span的场景也很少,因为这几个span已经差不多可以包含所有关键流程了。

扩展完span就可以通过往span中添加annotation来进行实际的时间戳记录了,添加annotation也非常简单:

keyval类型的annotation可以用来观察系统运行过程中某些关键变量的值,如关键运行参数等。

librbd库的使用

qemu、rbd-nbd等客户端都是使用librbd进行ceph rbd卷的IO访问,如果要深入理解librbd,那么尝试自己写一个client来访问rbd卷(控制操作、IO操作),肯定是一个不错的途径。

写了个C的(异步IO模式),C++的可以参考:https://blog.csdn.net/JDPlus/article/details/76522298

测试ceph peering对客户端IO影响的场景下正好用到了异步IO工具,于是稍微改造了一下这个基于librbd的client工具:https://github.com/aspirer/study/blob/master/rbdclient.c

依赖包:apt-get install librbd-dev librados-dev
编译:gcc -g3 -O0 librbdtest.c -o librbdtest -lrados -lrbd

执行(需要先创建”rbd” pool和”sotest”卷):

 

官方example:https://github.com/ceph/ceph/blob/master/examples/librbd/hello_world.cc

Ceph monmap消息编解码过程

本文源码基于luminous-12.2.5版本分析

最近在分析H版本librbd client无法兼容L版本Ceph集群的问题,提前说明下最终结论是H版本被之前的同时修改过才导致的不兼容,官方版本应该是兼容的。这篇文章正是问题分析过程的一次整理总结。

需求描述

需求是想要在同一个OpenStack环境中同时使用H、L版本两个Ceph集群做云主机系统盘、云硬盘存储系统,因此计算节点(也即librbd client节点)就可能分为H、L两个版本,众所周知,升级librbd动态库,需要重启云主机才能生效(除非对librbd做过较大的改动),重启所有云主机这个操作用户显然是不能接受的(当然也可以执行热迁移操作,但大批量的热迁移也存在耗时长、风险大的问题,况且还有部分云主机不支持热迁移),因此如果要上线L版本集群就必须做到兼容H版本client,这样才能保证L版本集群的卷能被当前正使用H版本client的云主机挂载、读写。H版本是之前遗留的,不能升级到L版本(主要考虑2个问题,1是风险高,二是代价大。实际上还有一个小问题就是H版本client不兼容L版本server,这个正是我本次需要解决的问题)。

问题描述

有两个问题:
1. 不修改server的crush参数情况下,客户端执行ceph -s、rbd ls等命令报错:
2. 通过ceph osd crush tunables hammer命令修改crush参数之后,执行命令直接抛异常,原因是decode monmap的created字段时发生越界异常,位置是:

本次重点分析第二个问题,bt查看调用栈(调试的是rbd ls命令):

是在接收到服务端monmap消息后decode过程中抛的异常,具体是在decode created字段出错的。

单步调试发现,在此之前last_changed字段decode出来就是空的,时间都是0,因此怀疑是更早之前就发生错误了,调试后发现最初的错误是在decode mon_addr时就发生了。

通过调试服务端ceph-mon encode过程,发现mon_addr和last_changed、created字段都是被添加到monmap消息体内的,并且通过调试L版本client,这几个字段也都能正常decode出来,因此确认是客户端问题或者服务端发送的monmap消息客户端不兼容。但看了下L版本服务端encode代码,当前版本是v5,并且最低兼容v3版本,而H版本decode是v3版本,也就是说社区代码在设计上兼容性应该是没问题的。因此需要继续分析问题出在哪里。

仔细对比了mon_addr的decode和encode流程(主要是H版本和L版本的差异),发现L版本encode过程检查连接的features,并走了不同的encode过程。因此问题聚焦在了客户端和服务端协商features过程上。

也就是features的第59位是怎么设置上的(1<<59),看了下H版本的代码,发现了这个feature:

按道理在CEPH_FEATURE_HAMMER_0_94_4(1<<55)之后应该不会再有其他feature了才对,但这个CEPH_OSD_PARTIAL_RECOVERY为啥被加入了?去看了下官方代码,发现没有这个feature,然后git log看了下我们的代码仓库,果然是我们自己人加上的。至此问题原因已经清晰。

L版本中用CEPH_FEATURE_MSG_ADDR2(1<<59)来标记是否为新版本消息,这一位是0表示是老版本(未设置该bit表示老版本),但是H版本里面正好用到了这一位CEPH_OSD_PARTIAL_RECOVERY(1<<59),导致L版本服务端检测失败,误认为是新版本客户端,使用了新的编码方式对monmap中的monitor地址信息进行编码,导致客户端无法解码。

改动方案:

由于client端升级困难,因此对L版本服务端进行修改,使其兼容H版本client,增加对其他bit位的检查,当发现client是H版本后,就走老的encode流程,修改之后客户端可以正常使用(包括基本命令行和qemu启动云主机)。

 

此次问题分析的难点主要是:

  1. 不熟悉ceph源码,尤其是消息编解码过程
  2. 编译的第一个环境没有去掉编译优化(-O2),增加debug等级(-g),改成(-O0 -g3 -gdwarf-4)修改之后调试起来就很方便了,https://www-zeuthen.desy.de/unix/unixguide/infohtml/gdb/Inline-Functions.html
  3. 确定服务端是否把monitor地址信息编码到消息体过程被wireshark误导了一段时间

使用tcpdump+wireshark解析Ceph网络包

记得之前在通读docs.ceph.com上的文档时(http://docs.ceph.com/docs/luminous/dev/wireshark/),有提到过wireshark支持对Ceph网络数据包进行解析,于是试着用tcpdump抓了monitor和client的数据包,然后导入到wireshark进行解析,对H版本来说确实好用,但对L版本来说,monmap解析貌似支持不太好,monitor address地址解析不出来,估计也是只支持低版本的编码协议。参考:https://www.wireshark.org/docs/dfref/c/ceph.html

tcpdump命令(在client节点执行数据包比较少,服务端对接的客户端比较多因此数据包也比较多,当然也可以加入更多的过滤条件来精确抓包):tcpdump -i eth0 host 192.168.0.2 and port 6789 -w ceph.cap

之后把抓到的ceph.cap数据导入到windows系统的wireshark软件中,我下载的是2.6.2版本(官网有2.6.4版本,但是下载困难,就在国内找了软件站下载了2.6.2的)。导入之后就可以自动分析出结果了。

L版本抓包结果(monmap包解析不太好,认为包有问题“malformed packet”,monitor地址信息解析失败,跟我遇到的问题一样,因此误导了我2天,我一直认为服务端就是没有把地址编码进来,客户端才解析不出来,最后在服务端和客户端分别单步调试才发现是编码进去了的):

 

H版本数据包解析就比较完美了:

Mon Map:

OSD Map:

 

Mon Map编解码过程分析

关键数据结构都定义在src\include\buffer.h中,主要包括:

  • buffer::ptr
    •  _raw:保存实际的编码消息数据
    • buffer::ptr::iterator :遍历ptr的迭代器类,提供各种函数用来寻址ptr中的数据
  • buffer::list
    • _buffers:保存ptr的list,也即保存多条编码数据
    • append_buffer:ptr类型,4K对齐,保存编码数据,,其append操作实际是append到ptr._raw,之后会把它push_back到_buffers中,push_back之前会通过ptr构造函数填充ptr的_raw、_off、_len等数据,也即把_raw中保存的数据的偏移量和长度也保存起来,解码时使用
    • buffer::list::iterator:遍历_buffers的迭代器,继承自buffer::list::iterator_impl,主要是对外提供接口,对内封装了iterator_impl的相关接口
    • buffer::list::iterator_impl:遍历_buffers的迭代器实际实现类,主要实现有advance、seek、copy等函数,用来从_buffers里取出数据,advance函数用来在copy函数执行过程中进行ptr内或_buffers的多个ptr前后跳转,有两种场景,一中是跳转还在当前ptr内(ptr内取数据),另外一种是_buffers list中一个ptr已经copy完(跨ptr取数据),需要跳转到下一个ptr对象继续copy。seek函数也是利用advance函数完成数据寻址操作。

以L版本代码为例,编解码代码如下:

encode调用栈:

decode调用栈:

 

接下来要说的是具体的encode和decode流程,以我调试的mon_addr为例:

encode

其他几个字段也是类似过程,就不做分析,差别就是编码的数据类型不一样,比如string、struct等,string属于基础数据类型,encode.h有对应的encode函数,struct数据类型则需要在对应的struct结构体定义其自己的encode函数,如entity_addr_t::encode (src\msg\msg_types.h)。

decode过程也是类似,根据decode的数据类型,找到对应的decode函数,按段解码即可。数据类型是预先定义好的,如decode(mon_addr, p),mon_addr的类型是已知的,因此其decode函数也可以找到(一般都是跟encode放在一起),只不过由于encode.h中有很多的宏,不好找到源码而已,配合gdb单步调试应该容易很多(记得在do_cmake.sh里的cmake命令那行加上-O0 -g3 -gdwarf-4这几个CXX_FLAGS编译选项: -DCMAKE_CXX_FLAGS="-O0 -g3 -gdwarf-4" -DCMAKE_BUILD_TYPE=Debug)。一般是先解码出消息体长度,然后再逐条解码。总之一切过程都是预先定义好的,一旦收到的消息内容与预设的解码方案不匹配,就会导致各种错误。这就是编解码协议存在的原因。(话说这也是我第一次接触编解码协议,看完这些流程还有点小激动)。

encode是把数据存入消息体,decode是从消息体取出数据,最底层的编码解码都是按字节完成的,编码时会强转为char *类型,底层解码也不区分数据类型,由上层使用方负责进行转换,对应的结构体都定义在src\include\buffer.h中,相关的函数实现在src\common\buffer.cc中。

decode的核心是ceph::buffer::ptr::copy_out () (/mnt/ceph/src/common/buffer.cc:1035),最终都是由它把数据从消息体里取出来的。相关的调用栈可以参考上面贴出来的decode调用栈。

下面附上我在调试过程中手绘的协议字段表格:

L版本:

H版本:

 

任意整数以内的加减法口算练习题生成web服务源码及搭建过程

提示:写这个web服务是为了练手玩,实际上已经有很多web(基于html或其他)、桌面应用、手机APP等支持题目生成功能了,并且手机APP还支持OCR识别判断答案是否正确,比如我试了一个小猿口算APP就挺好:http://kousuan.yuanfudao.com/

先上服务链接:http://aspirer.wang:3389/kousuan/7

链接的最后一个数字是可以修改的,改成几就是生成几以内的加减法练习题(比如上面的链接就是生成7以内的加减法口算题目,每次刷新都是新的题目不会重复)。

儿子上一年级经常有口算练习题,老师发的是一张习题纸,一共100道题,需要家长复印,但是存在三个小问题:一是复印出来的题目完全一样(有一次我发现儿子做题居然在参考前面一张。。。);二是打印不方便,必须得复印,有些家长是没有复印机的(复印还要带上原件,老师发下来的时候家长不一定能及时拿到原件);三是想自己提前给孩子出题练习其他更大数字的加减法不方便。

有了这个web站,后面还可以稍微修改下,支持生成乘法、除法的口算题。

整体部署架构:nginx+uwsgi+bottle,python编写的web后台服务。

部署过程参考资料:

  1. 使用bottle.py体验WSGI服务
  2. Nginx 部署Bottle + uwsgi

使用nginx的原因是我的博客就是用的它,跟博客部署在一起了,只是端口不同。

源码:

共3个文件:bottle.py是bottle wsgi框架,可以pip install安装,也可以直接拷贝源文件过来,非常方便。gen.py是为了方便后台测试用的,python执行它可以直接打印出题目。kousuan.py是给uwsgi用的,算是wsgi配置文件,当然里面也有一些其他代码,主要是生成html模板文件,以及配置wsgi router。

文件都很短,这里直接贴出来,不放github了:

 

html格式很简单,就没用专门的模板渲染框架如jinjia2等。

uwsgi配置文件:

nginx配置文件:

上面两个文件需要在对应的enabled目录下建立软链接,具体参考上面的部署过程参考资料(第二个链接)。

uwsgi和nginx的安装就不说了,apt就行。

部署好之后重启uwsgi和nginx服务就可以了。

 

监控:

为了及时发现web故障,用监控宝给3389 tcp端口和网站都加了监控,出现不可用会发短信和邮件通知。(我的博客用他们免费版用了这么久,也给人家打个广告)。

 

10.22更新:

  1.  修改了题目生成方法,大幅减少了包含0的题目的数量

 

其他:

生成题目时是暴力穷举符合条件的题目,其实可以根据每个题目的类型(加法或减法)以及生成的第一个数字,来限定第二个数字的随机范围,保证一次就可以生成符合条件的题目,可以很大程度减少计算量,不过对于这么小的程序和用户量的场景来说,这一点点计算量也就无所谓了。

Ceph iscsi方案及环境搭建

方案1:Ceph iscsi gateway及tcmu-runner部署流程

本部署流程文档基于Centos 7.5云主机验证。

TCMU原理介绍:Linux LIO 与 TCMU 用户空间透传 – Lixiubo_Liuyuan.pdf

环境准备

  1. Ceph L版本可用集群
  2. 至少两台Centos7.5版本主机(云主机或物理机)作为iscsi gateway节点,可以与Ceph集群public网络互通,或者其他发行版,但内核版本需要4.16以上

 

RHEL/CentOS 7.5; Linux kernel v4.16 or newer; or the Ceph iSCSI client test kernel

If not using a distro kernel that contains the required Ceph iSCSI patches, then Linux kernel v4.16 or newer or the ceph-client ceph-iscsi-test branch must be used.

Warning: ceph-iscsi-test is not for production use. It should only be used for proof of concept setups and testing. The kernel is only updated with Ceph iSCSI patches. General security and bug fixes from upstream are not applied.

 

部署过程

小提示:两台iscsi gateway节点,如果是使用的云主机,可以先只部署一台,部署ok之后做个自定义镜像,再用自定义镜像创建一台,修改主机名和/etc/hosts文件及iscsi gateway配置即可复制出新的gateway节点。

整体步骤:

  1. 配置两台Centos7.5云主机的主机名,修改/etc/hosts,保证两边可通过主机名互通,本次部署主机名分别为tcmu、tcmu2,ip分别为192.168.0.6、192.168.0.7
  2. 在gateway节点安装ceph包(使用ceph-deploy或手工安装),参考:http://docs.ceph.com/docs/master/start/quick-rbd/#install-ceph
  3. 下载tcmu-runner源码并编译安装
  4. 下载并安装ceph-iscsi-config(rbdtargetgw)、cephiscsicli(rbdtargetapi及gwcli命令),以及相关依赖包

安装ceph包

# 在gateway节点添加Ceph公司内部源,或者添加官方源:yum install centos-release-ceph-luminous.noarch -y
$ ceph-deploy install --release luminous tcmu  # 及tcmu2,ceph-deploy节点需要修改/etc/hosts文件
$ ceph-deploy admin tcmu   # 及tcmu2,或者手工copy /etc/ceph目录到gateway节点
# 验证ceph命令是否正常,如ceph -s

安装tcmu-runner

$ git clone https://github.com/open-iscsi/tcmu-runner.git
$ yum -y install epel-release python-pip python-rbd python-devel python-crypto # 安装依赖包
cd tcmu-runner
$ cmake -Dwith-glfs=false -Dwith-qcow=false -DSUPPORT_SYSTEMD=ON -DCMAKE_INSTALL_PREFIX=/usr
make make install
$ systemctl daemon-reload
$ systemctl enable tcmu-runner   # 如报错,则手工copy service:cp tcmu-runner.service /lib/systemd/system,参考:https://github.com/open-iscsi/tcmu-runner#running-tcmu-runner
$ systemctl start tcmu-runner

安装ceph-iscsi-config

$ git clone https://github.com/open-iscsi/targetcli-fb; git clone https://github.com/open-iscsi/configshell-fb; git clone https://github.com/open-iscsi/rtslib-fb  ## 下载依赖包,targetcli可不装,用ceph-iscsi-cli代替
$ python setup.py install  # 在上述3个依赖包目录下执行安装命令,如有提示缺少相关包则手工pip install安装
$ git clone https://github.com/ceph/ceph-iscsi-config.git
$ python setup.py install # 在ceph-iscsi-config目录执行安装命令,如有提示缺少相关包则手工pip install安装
$ systemctl daemon-reload
$ systemctl enable rbd-target-gw  # 如提示错误,则手工copy service文件到/lib/systemd/system
$ systemctl start rbd-target-gw  # 注意需要先创建配置文件,否则配置文件创建完需要重启该服务

在所有gateway节点创建配置文件:

[root@tcmu ~]# cat /etc/ceph/iscsi-gateway.cfg
[config]
# name of the *.conf file. A suitable conf file allowing access to the ceph
# cluster from the gateway node is required.
cluster_name = ceph
# Place a copy of the ceph cluster's admin keyring in the gateway's /etc/ceph
# drectory and reference the filename here
gateway_keyring = ceph.client.admin.keyring
# API settings.
# The api supports a number of options that allow you to tailor it to your
# local environment. If you want to run the api under https, you will need to
# create crt/key files that are compatible for each gateway node (i.e. not
# locked to a specific node). SSL crt and key files *must* be called
# iscsi-gateway.crt and iscsi-gateway.key and placed in /etc/ceph on *each*
# gateway node. With the SSL files in place, you can use api_secure = true
# to switch to https mode.
# To support the api, the bear minimum settings are;
api_secure = false
# Additional API configuration options are as follows (defaults shown);
api_user = admin
api_password = admin
api_port = 5001
trusted_ip_list = 192.168.0.6, 192.168.0.7

安装ceph-iscsi-cli

$ git clone https://github.com/ceph/ceph-iscsi-cli.git
cd ceph-iscsi-cli
$ python setup.py install --install-scripts=/usr/bin # 在ceph-iscsi-cli目录执行安装命令,如有提示缺少相关包则手工pip install安装
cp usr/lib/systemd/system/rbd-target-api.service /lib/systemd/system
$ systemctl daemon-reload
$ systemctl enable rbd-target-api
$ systemctl start rbd-target-api

iscsi target配置

通过gwcli命令即可配置,过程请参考:http://www.zphj1987.com/2018/04/11/ceph-ISCSI-GATEWAY/

iscsi initiator配置

为了简化部署,可使用gateway节点作为initiator节点来测试功能,相关软件的安装及配置,iscsiadm操作过程请参考:http://www.zphj1987.com/2018/04/11/ceph-ISCSI-GATEWAY/

参考:

  1. http://docs.ceph.com/docs/master/rbd/iscsi-requirements/
  2. http://docs.ceph.com/docs/master/rbd/iscsi-target-cli-manual-install/
  3. http://docs.ceph.com/docs/master/rbd/iscsi-target-cli/
  4. http://www.zphj1987.com/2018/04/11/ceph-ISCSI-GATEWAY/

 

方案2:TGT+rbd backing store部署流程

本次测试在两台Centos7.5云主机上完成,分别为tcmu(192.168.0.6)、tcmu2(192.168.0.7)。

前提:一个可以正常创建卷的ceph L版本集群,TGT节点可以访问该集群。

安装TGT服务(target端)

Centos 7.5添加epel源,可以直接安装scsi-target-utils包(yum –enablerepo=epel -y install scsi-target-utils),但是这个包里的TGT不支持rbd backing store,所以还是要手工编译。

Debian 9发行版可能有tgt-rbd包可以用,https://packages.debian.org/stretch/tgt, https://packages.debian.org/stretch/tgt-rbd, 安装这两个包应该就可以了。

 

编译TGT

  1. 下载源码:wget https://github.com/fujita/tgt/archive/v1.0.73.tar.gz
  2. tar xzf v1.0.73.tar.gz; cd tgt-1.0.73; make; make install (如果make提示xsltproc command not found,需要先执行yum install libxslt -y安装依赖包)
  3. 启动tgtd服务,可以手工启动:/usr/sbin/tgtd -f,或者用systemctl start tgtd(实际测试过程中发现tgt源码目录tgt-1.0.73/scripts下的tgtd.service配置文件并不好用,启动会卡住,我这里是先安装了epel源的scsi-target-utils,然后make install替换掉二进制文件,就可以正常启动带rbd backing store的tgtd服务了)
  4. 检查是否支持rbd backing store:tgtadm –lld iscsi –op show –mode system | grep rbd,输出rbd (bsoflags sync:direct)表示支持。

 

部署iscsi initiator(initiator端)

这里为了方便,直接在节点tcmu2上部署initiator软件(与target端共用一个节点)。

参考http://www.zphj1987.com/2018/04/11/ceph-ISCSI-GATEWAY/ Linux的客户端连接部分即可,主要是安装iscsi-initiator-utils客户端软件,以及多路径软件device-mapper-multipath。

 

创建target及lun(target端)

先创建一个rbd卷:

之后在tcmu、tcmu2上执行相同命令:

$ tgtadm --lld iscsi --mode target --op new --tid 1 --targetname iqn.2018-10.com.netease:cephtgt.target0  # 创建target
$ tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 1 --backing-store disk2 --bstype rbd  # 将rbd卷作为lun添加到target,注意lun id要从1开始,0被tgt使用
$ tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL  # 配置ACL授权,ALL表示所有节点均可访问该target,也可以用CIDR限制某个网段访问

 

initiator连接到target(initiator端)

在tcmu2节点上执行:

$ iscsiadm -m discovery -t st -p tcmu  # 发现target
$ iscsiadm -m discovery -t st -p tcmu2
$ iscsiadm -m node -T iqn.2018-10.com.netease:cephtgt.target0 -l -p tcmu # 登录target
$ iscsiadm -m node -T iqn.2018-10.com.netease:cephtgt.target0 -l -p tcmu2

即可连接到target,查看映射到本机的iscsi卷:

[root@tcmu2 ~]# lsblk
NAME     MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINT
sda        8:0    0    1G  0 disk 
└─mpathc 252:0    0    1G  0 mpath
sdb        8:16   0    1G  0 disk 
└─mpathc 252:0    0    1G  0 mpath
[root@tcmu2 ~]# multipath -ll   # 查看多路径设备信息
mpathc (360000000000000000e00000000020001) dm-0 IET     ,VIRTUAL-DISK   
size=1.0G features='0' hwhandler='0' wp=rw
|-+- policy='service-time 0' prio=0 status=enabled
| - 10:0:0:1 sda 8:0  failed faulty running
-+- policy='service-time 0' prio=1 status=active
  `- 11:0:0:1 sdb 8:16 active ready running

使用/dev/mapper/mpathc设备即可访问rbd卷disk2,并且是多路径方式,tcmu节点上的tgtd服务异常或者网络异常、节点宕机,均可自动切换到tcmu2的tgtd进行正常的IO访问。

 

添加一个新的lun到target(target端)

先创建一个rbd卷:

在tcmu、tcmu2上分别执行:

$ tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 2 --backing-store disk3 --bstype rbd  # 将rbd卷作为lun添加到target

 

initiator端发现新的lun(initiator端)

 

$ iscsiadm -m session -R  # 重新扫描所有已建立的target连接,发现新的lun及lun大小变动等信息更新

查看映射的iscsi卷方法同上。

 

initiator端登出

$ iscsiadm -m node -T iqn.2018-10.com.netease:cephtgt.target0 --logout # 先确保卷未使用
$ iscsiadm -m node -T iqn.2018-10.com.netease:cephtgt.target0 -o delete
$ iscsiadm -m node  # 查看所有保存的target记录(可能未login)

 

target端清理

$ tgtadm --lld iscsi --mode logicalunit --op delete --tid 1 --lun 2  # 删除target id为1中的id为2的lun,先确保initiator端先logout
$ tgtadm --lld iscsi --mode target --op delete --tid 1  # 删除id为1的target
$ tgt-admin --show   # 查看所有target信息

配置持久化

通过配置文件实现target和initiator端重启后自动恢复相关配置和连接。

待补充

参考:

  1. http://www.zphj1987.com/2018/04/11/ceph-ISCSI-GATEWAY/
  2. https://jerry.red/300/%E5%88%9B%E5%BB%BA-iscsi-target-%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%92%8C-iscsi-initiator-%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%BF%9E%E6%8E%A5
  3. http://linux.vbird.org/linux_server/0460iscsi.php#initiator_exam

 

方案3:LIO+krbd/rbd-nbd实现iscsi target方案

LIO是内核态的iscsi target实现,支持多种backing store,但还不支持rbd,只能用krbd或者rbd-nbd方式先把rbd卷map成block device,之后再将映射的设备如/dev/rbdX或/dev/nbdX给LIO作为block backing store使用,并最终作为target导出给initiator使用。

TCMU是LIO的用户态实现,可直接支持rbd后端:Ceph iscsi gateway及tcmu-runner部署流程

LIO原理介绍:Linux LIO 与 TCMU 用户空间透传 – Lixiubo_Liuyuan.pdf

本次测试在两台Centos7.5云主机上完成,分别为tcmu(192.168.0.6)作为target节点、tcmu2(192.168.0.7)作为initiator节点。

前提:一个可以正常创建卷的ceph L版本集群,target节点可以访问该集群。

相关操作流程如下:

内核模块检查

一般内核都是默认加载的,如果没有加载可以手工modprobe加载上:

# lsmod | grep target_core_mod               
target_core_mod       340809  13 target_core_iblock,target_core_pscsi,iscsi_target_mod,target_core_file,target_core_user
crc_t10dif             12912  2 target_core_mod,sd_mod

 

安装targetcli客户端

# 在target节点(tcmu)上执行
$ git clone https://github.com/open-iscsi/targetcli-fb; git clone https://github.com/open-iscsi/configshell-fb; git clone https://github.com/open-iscsi/rtslib-fb  ## 下载包及依赖
$ python setup.py install  # 在上述3个目录下执行安装命令,targetcli-fb依赖后面两个包,如有提示缺少其他依赖包则手工pip install安装,或者从github上(pip命令需要安装python-pip rpm包)

 

进行target配置

首先要有一个rbd卷,这里已经create过一个vol3,1G大小,属于rbd pool。

然后在target(tcmu)节点上map这个rbd卷:

# 在target节点(tcmu)上执行
$ rbd map vol3  # 或者用rbd-nbd方式map也可以
$ rbd showmapped
id pool image snap device   
0  rbd  vol3  -    /dev/rbd0

之后将/dev/rbd0作为block设备给LIO使用。

在target(tcmu)节点执行targetcli命令:

$ targetcli
targetcli shell version 2.1.fb49
Copyright 2011-2013 by Datera, Inc and others.
For help on commands, type 'help'.
 /> cd /backstores/block
/backstores/blockls
o- block ...................................................................................................... [Storage Objects: 0]
/backstores/block> create name=rbd0 dev=/dev/rbd0 # 添加block后端
Created block storage object rbd0 using /dev/rbd0.
/backstores/blockls
o- block ...................................................................................................... [Storage Objects: 1]
 o- rbd0 .............................................................................. [/dev/rbd0 (1.0GiB) write-thru deactivated]
 o- alua ....................................................................................................... [ALUA Groups: 1]
 o- default_tg_pt_gp ........................................................................... [ALUA state: Active/optimized]/>
cd /iscsi/iscsi> create
Created target iqn.2003-01.org.linux-iscsi.tcmu.x8664:sn.546f452bcfe2.
Created TPG 1.
Global pref auto_add_default_portal=true
Created default portal listening on all IPs (0.0.0.0), port 3260.
/iscsils
o- iscsi .............................................................................................................. [Targets: 1]
 o- iqn.2003-01.org.linux-iscsi.tcmu.x8664:sn.546f452bcfe2 .............................................................. [TPGs: 1]
 o- tpg1 ................................................................................................. [no-gen-acls, no-auth]
 o- acls ............................................................................................................ [ACLs: 0]
 o- luns ............................................................................................................ [LUNs: 0]
 o- portals ...................................................................................................... [Portals: 1]
 o- 0.0.0.0:3260 ....................................................................................................... [OK]
/iscsicd iqn.2003-01.org.linux-iscsi.tcmu.x8664:sn.546f452bcfe2/tpg1/luns
/iscsi/iqn.20...fe2/tpg1/luns> create /backstores/block/rbd0
Created LUN 0.
/iscsi/iqn.20...fe2/tpg1/lunsls
o- luns .................................................................................................................. [LUNs: 1]
 o- lun0 .............................................................................. [block/rbd0 (/dev/rbd0) (default_tg_pt_gp)]
/iscsi/iqn.20...fe2/tpg1/lunscd ..
/iscsi/iqn.20...452bcfe2/tpg1set attribute authentication=0 demo_mode_write_protect=0 generate_node_acls=1 cache_dynamic_acls=1
Parameter demo_mode_write_protect is now '0'.
Parameter authentication is now '0'.
Parameter generate_node_acls is now '1'.
Parameter cache_dynamic_acls is now '1'./iscsi/iqn.20...452bcfe2/tpg1cd /
/> saveconfig
Last 10 configs saved in /etc/target/backup/.
Configuration saved to /etc/target/saveconfig.json/> exit

 

initiator操作

与其他target方式相同,都是用iscsiadm工具连接到target,具体参考TGT+rbd backing store部署流程这篇文档中的操作流程。

# 在tcmu2节点执行
$ iscsiadm -m discovery -t st -p tcmu
$ iscsiadm -m node -T iqn.2018-10.com.netease:cephtgt.target0 -l -p tcmu
$ lsblk
NAME   MAJ:MIN RM SIZE RO TYPE  MOUNTPOINT
sda      8:0    0   1G  0 disk

 

多路径

RBD exclusive lock feature对多路径有影响,参考:https://www.sebastien-han.fr/blog/2017/01/05/Ceph-RBD-and-iSCSI/(第5节),只能是主备模式?这部分有待补充。

 

 

 

QEMU中librbd相关线程和回调及IO写流程简要介绍

概念介绍

Image

对应于LVM的Logical Volume,是能被attach/detach到VM的载体。在RBD中,Image的数据有多个Object组成。

Snapshot

Image的某一个特定时刻的状态,只能读不能写但是可以将Image回滚到某一个Snapshot状态。Snapshot必定属于某一个Image。

Clone

为Image的某一个Snapshot的状态复制变成一个Image。如ImageA有一个Snapshot-1,clone是根据ImageA的Snapshot-1克隆得到ImageB。ImageB此时的状态与Snapshot-1完全一致,区别在于ImageB此时可写,并且拥有Image的相应能力。

元数据

striping

  • order:22,The size of objects we stripe over is a power of two, specifically 2^[order] bytes. The default is 22, or 4 MB.
  • stripe_unit:4M,Each [stripe_unit] contiguous bytes are stored adjacently in the same object, before we move on to the next object.
  • stripe_count:1,After we write [stripe_unit] bytes to [stripe_count] objects, we loop back to the initial object and write another stripe, until the object reaches its maximum size (as specified by [order]. At that point, we move on to the next [stripe_count] objects.

root@ceph1 ~ $ rados -p rbd ls

  • rbd_header.1bdfd6b8b4567:保存image元数据(rbd info的信息)
  • rbd_directory:保存所有image的id和名称列表
  • rbd_info:“overwrite validated”,EC pool使用?
  • rbd_id.vol1:保存image的id
  • rbd_data.233546b8b4567.0000000000000025:保存image数据的对象,按需分配,233546b8b4567为image id,0000000000000025为stripe_unit id,从0开始增长

参考:

  1. http://hustcat.github.io/rbd-image-internal-in-ceph/
  2. http://tracker.ceph.com/issues/19081

回调

回调类

3个特征:

  1. 类名称以C_开头
  2. 实现了finish成员函数
  3. Context子类

举例:

还有一种回调适配器类,通过模板类实现通用的回调类,可以把各种类转换成回调类:

之后通过回调生成函数create_xxx_callback(create_context_callback、create_async_context_callback)函数创建出回调类,供后续注册使用。

回调适配函数

通过模板函数将任意函数转换为回调函数。

为啥不直接用原始函数作为回调函数注册进去?

回调生成函数

create_context_callback、create_async_context_callback上面已经介绍过,这里主要介绍create_rados_callback:

这个函数只做了一件事,就是创建一个rados操作需要的AioCompletion回调类(与上面),而回调类里的回调函数,则是用上面提到的回调适配函数转换的,把普通函数转换为回调函数。

回调注册

有如下几种方式:

  1. 直接注册:通常在最外层,对外接口中使用,一般需要在librbd内部二次封装
  2. 通过回调生成函数:librbd内部使用较多
  3. 通过回调适配函数:librbd内部使用较多

回调与Finisher线程的关系

回调类为啥必须继承Context?

这是因为所有的回调都由finisher线程处理(执行体为Finisher::finisher_thread_entry),而该线程会调用回调类的complete成员函数,Context类实现了这个函数,专门用来作为回调公共类。只是为了方便、统一,并不是必须的,你可以可以自己实现回调类的complete成员函数,而不继承Context。

参考下面finisher thread的关联队列finisher_queue、finisher_queue_rval的入队过程,可了解回调入队过程。

回调流

在rbd image打开过程中,需要执行很多流程来获取image的各种元数据信息(流程描述参考OpenRequest的注释,主要包括V2_DETECT_HEADER、V2_GET_ID|NAME、V2_GET_IMMUTABLE_METADATA、V2_GET_STRIPE_UNIT_COUNT、V2_GET_CREATE_TIMESTAMP、V2_GET_DATA_POOL等),当然你也可以在一个方法中一次获取全部元数据,但会导致单次操作耗时太长,各元数据的获取函数耦合也比较重,这是我个人的猜测,也可能其他方面的考虑,目前还没有理解。

librbd中用回调流的方式,来依次调用各个元数据请求函数和响应处理函数,入口是rbd_open,第一个执行的元数据请求函数是send_v2_detect_header(发送检查是否为v2版本image header的请求),qemu的具体调用栈如下:

通过直接调用+设置回调再调用形成回调流,最后进入send_v2_apply_metadata,它会注册最后一个回调handle_v2_apply_metadata。

控制流

  • 请求:由RadosClient、MgrClient及其成员函数处理,一般是普通dispatch流程,最终都交给AsyncMessenger发送出去
  • 响应:AsyncMessenger相关方法

数据流

由Objecter类及其成员函数处理,一般是fast dispatch流程,最终都交给AsyncMessenger发送出去

数据结构及IO数据流转

控制流

Context

所有回调的基类

CephContext

所有操作都需要用到,存储了各种全局信息,每个client一个(librbd算一个client)

ImageCtx

存储image的全局信息,每个image一个

ContextWQ

IO控制流的工作队列类(包含队列和处理方法),op_work_queue对象

librados::IoCtx、IoCtxImpl

与rados交互所需的全局信息,一个对外一个内部使用,一个pool一个

Finisher、Finisher::FinisherThread

回调执行类,专门管理回调队列并在线程中调用各种回调

数据流

AsyncConnection

与ceph服务端连接信息,由AsyncMessenger维护,所有请求都由其发送,AsyncConnection::process

librbdioAioCompletion

用户层发起的异步IO完成后的librbd内部回调,主要用来记录perf counter信息,以及IO请求发起用户传入的外部回调函数

librbd::ThreadPoolSingleton

封装ThreadPool,实现tp_librbd单例线程

ThreadPool

所有线程池的基类

ThreadPool::PointerWQ

IO数据流、控制流工作队列的共同基类

librbdioImageRequestWQ

IO数据流的工作队列类(包含队列和处理方法),io_work_queue对象

librbdioImageRequest

IO请求的基类,image级别,对应用户IO请求

librbdioAbstractImageWriteRequest

IO写请求的抽象类,继承自ImageRequest

librbdioImageWriteRequest

IO写请求类,继承自AbstractImageWriteRequest

Thread

所有线程、线程池的基类,子类通过start函数启动各自的entry函数进入thread执行体完成实际工作。

Objecter

上层单次IO操作对象,对应用户IO请求

Objecter::Op

上层IO操作对象可能包含多个object,需要拆分成多个Op,对应到rados对象

Dispatcher

与服务端交互的分发方法基类,MgrClient、Objecter、RadosClient都继承自Dispatcher类

Striper

IO封装、解封,读写操作过程中从IO到object互相转换

librbdioObjectRequest、librbdioObjectReadRequest、librbdioAbstractObjectWriteRequest、librbdioObjectWriteRequest

用户IO请求拆分后的object级别的IO请求

线程池与队列

tp_librbd(librbd::thread_pool)

tp_thread启动(处理io_work_queue及op_work_queue):ThreadPoolstart–ThreadPoolstart_threads–new WorkThread(this)–Threadcreate–Threadtry_create–pthread_create–Thread::_entry_func–Threadentry_wrapper–ThreadPoolWorkThread::entry–线程启动完毕,worker开始工作

关联队列1:io_work_queue

入队过程:见下面主要代码流程部分,从ImageRequestWQ::aio_write()到入队io_work_queue。

关联队列2:op_work_queue

入队过程:搜索op_work_queue->queue()即可找到,主要是执行各种rbd image控制操作时会用到。

两个队列的关系及出队过程

由tp_librbd(ThreadPool)的work_queues成员保存,work_queues[0] == op_work_queue,work_queues[1] == io_work_queue。在ThreadPool::worker里会死循环处理这两个队列,交替处理。

io_work_queue出队过程:ThreadPoolworker–ThreadPoolPointerWQ_void_dequeue/_void_process/_void_process_finish–ThreadPoolPointerWQ<librbdioImageRequestlibrbd::ImageCtx >_void_process–librbdio::ImageRequestWQlibrbd::ImageCtx::process

op_work_queue出队过程类似,只是最终调用的是ContextWQ::process。

finisher thread

执行体

Finisher::finisher_thread_entry

thread1:fn-radosclient

  • 启动及用途:libradosRadosClientconnect里启动的finisher thread,为rados client服务,用来执行相关回调

thread2:fn_anonymous

  • 启动及用途:MonClient::init里启动的finisher thread,为monitor client服务,用来执行相关回调
  • 与fn-radosclient的区别:anonymous不会通过perfcounter记录队列长度(queue_len),处理延时(complete_latency),而fn-radosclient会记录

thread3:taskfin_librbd

  • 启动及用途:主要用来给ImageWatcher对象执行各种任务(基于SafeTimer定时的或者基于finisher_queue的),ImageWatcher主要是在镜像属性变动的发送通知给关注方。
  • 入队过程与其他两个类似,看queue方法调用位置即可。

关联队列:Finisher::finisher_queue、finisher_queue_rval

二者区别见注释:

  • 入队过程:所有调用Finisher::queue函数的地方(一般都是finisher.queue,如c->io->client->finisher.queue),
  • 出队过程:线程执行体Finisher::finisher_thread_entry里面出队

入队过程示例(fn-radosclient线程):

handle_write_object是write_object函数注册的回调,属于tp_librbd线程,也即处理io的线程。

rados_completion回调最终传递给了ObjecterOponfinish(经过一次封装:C_aio_Complete(c)),实现了从tp_librbd线程转到msgr-worker-*线程,再到fn-radosclient线程(也即Finisher线程)的流转,这也是(几乎)所有回调都由Finisher线程调用的缘由。

msgr-worker-*

  • 暂未深入分析
  • 启动及用途:异步消息收发线程,主要与ms_dispatch、ms_local线程交互
  • 关联的队列:用于处理各种事件
  • 执行体:NetworkStackadd_thread里面return的lambda函数,由PosixNetworkStackspawn_worker启动
  • 数量:由配置项cct->_conf->ms_async_op_threads决定,默认值3,代码里写死上限值24个,配置项超出这个会被强制改为24,看代码逻辑应该不能在线修改

admin_socket

  • 用途:用来创建ceph-client.admin.2840389.94310395876384.asok,socket文件位置由ceph.conf配置文件中的[client]admin_socket = /var/run/ceph/qemu/$cluster-$type.$id.$pid.$cctid.asok决定。创建完之后作为UNIX domain socket的server端接收客户端请求,并给出响应,客户端可以用ceph –admin-daemon ceph-client.admin.2840389.94310395876384.asok命令发送请求,支持配置修改、perf dump等命令,具体命令列表可以用help子命令查看。
  • 初始化及启动:在CephContext构造函数中初始化,在CephContext::start_service_thread中启动。

ms_dispatch、ms_local

ms_dispatch

  • 用途:暂未深入分析,接收ms_local线程转发的普通dispatch消息,然后转发给Messager注册的普通dispatcher处理(dispatcher有MgrClient、Objecter、RadosClient,他们都继承自Dispatcher类)
  • 关联队列:优先级队列PrioritizedQueue<QueueItem, uint64_t> mqueue
  • 入队:通过DispatchQueue::enqueue入队
  • 出队:线程执行体DispatchQueue::entry

ms_local

  • 用途:初步理解是接收librbd client端请求,转发给ms_dispatch线程处理(普通dispatch,入队mqueue),或者fast dispatch(直接通过Messenger的fast dispatcher发送,messenger目前为AsyncMessenger,dispatcher有MgrClient、Objecter、RadosClient,他们都继承自Dispatcher类)
  • 关联队列:list<pair<Message *, int> > local_messages
  • 入队:通过DispatchQueue::local_delivery入队
  • 出队:线程执行体DispatchQueue::run_local_delivery

启动

safe_timer

  • 用途:管理及触发定时任务事件,librbd中主要用来跟monitor保持心跳(MonClient::schedule_tick),以及ImageWatcher的定时事件。
  • 初始化及启动:qemu中一共启动了3个线程,其中一处是在libradosRadosClientRadosClient构造函数中初始化,在libradosRadosClientconnect中调用SafeTimerinit启动。通过SafeTimer类进行管理和对外提供接口,SafeTimer类包含一个SafeTimerThread类型的成员thread,SafeTimerThread继承Thread类,safe_timer线程通过SafeTimerinit函数使用thread成员进行创建及启动,线程执行的实体函数是SafeTimertimer_thread(SafeTimerThreadentry里面调用),用来轮询检查是否有新的定时任务事件需要触发。另一处是在ImageWatcher对象初始化时启动,第三处未分析,在构造函数处加断点调试即可知晓。
  • 与cephtimer_detailtimer的关系:二者都有定时器功能,但cephtimer_detailtimer更轻量(参考该类的注释),IO卡顿预警功能使用的是cephtimer_detailtimer。

关联的队列:SafeTimer::schedule

  • 入队过程:SafeTimeradd_event_after、SafeTimeradd_event_at
  • 出队过程:SafeTimercancel_event、SafeTimercancel_all_events,以及SafeTimer::timer_thread中正常的事件触发。

service

  • 用途:CephContextServiceThread::entry是线程执行体,有3个工作,1是检查是否需要重新打开log文件,2是检查心跳,3是更新perfcounter中的记录值,但如果是默认配置情况下,这个线程2、3两个任务是不做的。
  • 初始化及启动:过程与admin_socket的启动过程相同,都在CephContext::start_service_thread中完成

log

  • 初始化及启动:在CephContext构造函数中初始化和启动。
  • 用途:负责文件日志打印和内存日志的存储和dump(通过admin socket)。

主要代码流程分析

块设备IO到rados对象映射过程(Striper)

object到osd的crush计算过程

遗留问题

  • 整体IO流程图
  • IO到object到op的拆分过程,以及op执行完毕后如何判断用户层单次IO全部执行完毕
  • object到osd的crush计算过程
  • IO请求发送过程及响应处理过程

perf counter机制

每个image一个perf counter,初始化过程:

使用过程:

cephtimer_detailtimer机制

类似SafeTimer,一个线程专门检查定时任务是否需要触发,可以取消定时任务,取消时如果发现任务已经触发了就忽略,没触发就取消任务。

线程未命名,仍然叫qemu-system-x86,在Objecter对象构造的时候启动:

参考:Ceph动态更新参数机制浅析 http://t.cn/EPQE1tt

 

使用vstart搭建ceph开发环境

准备工作

  1. 准备代码使用的目录,注意目录要足够大,保证有100G以上可用空间(编译过程占用很多磁盘)
  2. clone源码,切换分支
$ git clone https://github.com/ceph/ceph.git
$ git checkout v12.2.5 -b v12.2.5
$ git submodule update --init --recursive

 

编译vstart所需二进制文件

$ ./run-make-check.sh    ## ceph源码根目录执行,默认如果有2个以上的CPU,只使用一半数量的CPU进行编译,可以编辑下这个脚本文件,把get_processors里面的“expr $(nproc) / 2”改成“expr $(nproc) / 1”,使用全部CPU进行编译
cd ceph/build
make vstart

等待编译结束后,即可执行vstart命令启动ceph集群。

启动集群

$ ../src/vstart.sh -d -n -X --bluestore --mon_num 1 --osd_num 3 --mgr_num 1 --mds_num 1   ### build目录下执行
## 各参数意义
$ ../src/vstart.sh -h
usage: ../src/vstart.sh [option]...
ex: ../src/vstart.sh -n -d --mon_num 3 --osd_num 3 --mds_num 1 --rgw_num 1
options:
        -d, --debug
        -s, --standby_mds: Generate standby-replay MDS for each active
        -l, --localhost: use localhost instead of hostname
        -i <ip>: bind to specific ip
        -n, --new                                                           ####### 注意这个参数,首次启动新集群要加,二次启动不要加
        -N, --not-new: reuse existing cluster config (default)
        --valgrind[_{osd,mds,mon,rgw}] 'toolname args...'
        --nodaemon: use ceph-run as wrapper for mon/osd/mds
        --smallmds: limit mds cache size
        -m ip:port              specify monitor address
        -k keep old configuration files
        -x enable cephx (on by default)
        -X disable cephx
        --hitset <pool> <hit_set_type>: enable hitset tracking
        -e : create an erasure pool
        -o config                add extra config parameters to all sections
        --mon_num specify ceph monitor count
        --osd_num specify ceph osd count
        --mds_num specify ceph mds count
        --rgw_num specify ceph rgw count
        --mgr_num specify ceph mgr count
        --rgw_port specify ceph rgw http listen port
        --rgw_frontend specify the rgw frontend configuration
        --rgw_compression specify the rgw compression plugin
        -b, --bluestore use bluestore as the osd objectstore backend         ######## 使用bluestore后端
        --memstore use memstore as the osd objectstore backend
        --cache <pool>: enable cache tiering on pool
        --short: short object names only; necessary for ext4 dev
        --nolockdep disable lockdep
        --multimds <count> allow multimds with maximum active count

 

查看集群状态

### build目录下执行
$ bin/ceph -s
*** DEVELOPER MODE: setting PATH, PYTHONPATH and LD_LIBRARY_PATH ***
2018-08-21 14:25:48.651800 7f35d4418700 -1 WARNING: all dangerous and experimental features are enabled.
2018-08-21 14:25:48.659410 7f35d4418700 -1 WARNING: all dangerous and experimental features are enabled.
  cluster:
    id:     376aef1c-90a7-4dc1-ba3f-d0ce00490988
    health: HEALTH_OK
 
  services:
    mon: 1 daemons, quorum a
    mgr: x(active)
    mds: cephfs_a-1/1/1 up  {0=a=up:active}
    osd: 3 osds: 3 up, 3 in
 
  data:
    pools:   3 pools, 24 pgs
    objects: 21 objects, 2246 bytes
    usage:   3081 MB used, 27638 MB / 30720 MB avail
    pgs:     24 active+clean

 

停止集群

$ ../src/stop.sh         ### build目录下执行

 

清理集群

$ ../src/stop.sh           ### build目录下执行
rm -rf out dev           ### build目录下执行

注意清理后,如果要再次启动集群,vstart.sh参数里要加上-n。

编译rbd命令

默认情况下,vstart并不编译rbd相关命令和库,需要手工编译,编译方法和普通编译过程没有区别,编译好的二进制文件都在build/bin目录下,跟vstart编译的其他二进制文件一样

make rbd -j4       ##### build目录下执行

之后就可以进行rbd卷相关操作了,

### build目录下执行
$ bin/ceph osd pool create rbd 8 replicated      #### 创建rbd pool
$ bin/rbd ls
2018-08-21 14:26:49.086272 7f887ac2c0c0 -1 WARNING: all dangerous and experimental features are enabled.
2018-08-21 14:26:49.086515 7f887ac2c0c0 -1 WARNING: all dangerous and experimental features are enabled.
2018-08-21 14:26:49.089905 7f887ac2c0c0 -1 WARNING: all dangerous and experimental features are enabled.
$ bin/rbd create vol1 --size 10M
$ bin/rbd ls
vol1
$ bin/rbd info vol1
rbd image 'vol1':
        size 10240 kB in 3 objects
        order 22 (4096 kB objects)
        block_name_prefix: rbd_data.10346b8b4567
        format: 2
        features: layering, exclusive-lock, object-map, fast-diff, deep-flatten
        flags:
        create_timestamp: Tue Aug 21 14:27:13 2018
$ bin/rbd create vol2 --size 10M --image-feature layering
$ bin/rbd info vol2
rbd image 'vol2':
        size 10240 kB in 3 objects
        order 22 (4096 kB objects)
        block_name_prefix: rbd_data.10386b8b4567
        format: 2
        features: layering
        flags:
        create_timestamp: Tue Aug 21 14:28:24 2018
$ bin/rbd map vol2
/dev/rbd0
 

 

参考资料:

http://docs.ceph.com/docs/luminous/dev/quick_guide/