- OpenStack 存储调度管理基础设施 Cinder 组件介绍
- Cinder 核心流程和实现方式
- Cinder 高可用架构实现方式
Cinder
- Cinder在OpenStack中为虚拟机实例提供volume的块存储服务,可将卷挂载到实例上,作为虚拟机实例的本地磁盘使用。
- 组件化:便于添加新功能
- 高可用:能承受高负载
- 容错:隔离机制避免级联故障
- 可恢复:简便及时的故障检测与恢复
- 开放标准:提供标准实现,为社区提供参考
主要模块
/- ( LDAP )
[ Auth Manager ] ---
| \- ( DB )
|
|
cinderclient |
/ \ | /- [ scheduler ] -- [ volume ] -- ( iSCSI )
[ Web Dashboard ]- -[ api ] -- < AMQP > --
\ / | \- [ backup ]
novaclient |
|
|
|
< REST >
cinder-api
- 对外提供稳定而统一的北向 RESTful API,cinder-api service 服务进程通常运行在控制节点,支持多 Workers 进程(通过配置项 osapivolumeworkers 设定)。接收到的合法请求会经由 MQ 传递到 cinder-volume 执行。Cinder API 现存 v2(DEPRECATED)、v3(CURRENT) 两个版本,可以通过配置文件来启用。
- 官方API介绍:https://developer.openstack.org/api-ref/block-storage/
核心思想
- TaskFlow : TaskFlow 能够控制应用程序中的长流程业务逻辑任务的暂停、重启、恢复以及回滚, 主要用于保证长流程任务执行的可靠性和一致性。应用场景主要为 面向任务 的工作场景。
- Atom: Atom 是 TaskFlow 的最小单位, 其他的所有类, 包括 Task 类都是 Atom 类的子类.
- Task: 拥有执行和回滚功能额最小工作单元. 在 Task 类中开发者能够自定义 execute(执行) 和 revert(回滚) method。
- Flow:在 TaskFlow 中使用 flow(流) 来关联各个 Task, 并且规定这些 Task 之间的执行和回滚顺序. flow 中不仅能够包含 task 还能够嵌套 flow. 常见类型有以下几种:
- Linear(linear_flow.Flow): 线性流, 该类型 flow 中的 task/flow 按照加入的顺序来依次执行, 按照加入的倒序依次回滚.
- Unordered(unordered_flow.Flow): 无序流, 该类型 flow 中的 task/flow 可能按照任意的顺序来执行和回滚.
- Graph(graph_flow.Flow): 图流, 该类型 flow 中的 task/flow 按照显式指定的依赖关系或通过其间的 provides/requires 属性的隐含依赖关系来执行和回滚.
- Retry:一个控制当错误发生时, 如何进行重试的特殊工作单元, 而且当你需要的时候还能够以其他参数来重试执行别的 Atom 子类. 常见类型:
- AlwaysRevert: 错误发生时, 回滚子流
- AlwaysRevertAll: 错误发生时, 回滚所有流
- Times: 错误发生时, 重试子流
- ForEach: 错误发生时, 为子流中的 Atom 提供一个新的值, 然后重试, 直到成功或 retry 中定义的值用完为止.
- ParameterizedForEach: 错误发生时, 从后台存储(由 store 参数提供)中获取重试的值, 然后重试, 直到成功或后台存储中的值用完为止.
- Engine: Engines 才是真正运行 Atoms 的对象, 用于 load(载入) 一个 flow, 然后驱动这个 flow 中的 task/flow 开始运行. 我们可以通过 engine_conf 参数来指定不同的 engine 类型. 常见的 engine 类型如下:
- serial: 所有的 task 都在调用了 engine.run 的线程中运行.
- parallel: task 可以被调度到不同的线程中运行.
- worker-based: task 可以被调度到不同的 woker 中运行.
核心流程
- 创建 Volume 的流程:
- cinder-api 创建 volume_create_api 工作流(flow)
- ExtractVolumeRequestTask:获取(Extract)、验证(Validates)create volume 在 cinder-api 阶段相关的信息
- QuotaReserverTask:预留配额
- EntryCreateTask:在数据库中创建 Volumn 条目
- QuotaCommitTask:确认配额
- VolumeCastTask:发出一个 Cast 异步请求,将创建请求丢到 MQ,最终被 cinder-scheduler service 接收
- cinder-scheduler 启动一个 volume_create_scheduler flow
- ExtractSchedulerSpecTask:将 request body 转换成为 Scheduler Filter 中通用的 RequestSpec 数据结构(实例对象)。
- SchedulerCreateVolumeTask:完成 Filter 和 Weight 的调度算法。
- RPC 请求发送到 cinder-volume service 接收
- cinder-volume 启动工作流 volume_create_manager:
- ExtractVolumeRefTask : 解析请求体
- OnFailureRescheduleTask:
- ExtractVolumeSpecTask:将 request body 转换成为 Volume 模块中通用的 RequestSpec 数据结构(实例对象)
- NotifyVolumeActionTask:当对已有的卷采取措施的时候发送相应的通知
- CreateVolumeFromSpecTask:创建对应的卷
- CreateVolumeFromSpecTask:执行创建成功之后的后续操作
- cinder-api 创建 volume_create_api 工作流(flow)
- 创建 Volume 的方式
- 建立raw格式的新卷
- 从快照建立新卷
- 从已有卷建立新卷
- 从副本建立新卷
- 从镜像建立新卷
细节
根据现在最新的 V3 版本的 API总结如下:
- Github: Volume 资源模型
- Volumn Type 主要是针对多后端存储进行卷类型的管理
- Volumes 针对 卷 来进行管理。其中一个卷(Volume)类似于一个如USB硬盘的可插拔的块存储设备,每一次可以将卷对应的挂载到实例上。
- 对 Volumes 设备进行 CRUD 时,对应的会有很多种状态,可以参考对应官方文档中的状态表。
- CRUD of Cinder Volumn
CRUD of Cinder Volumn
- POST
/v3/{project_id}/volumes
Request Body
{
"volume": {
"size": 10, //int The size of the volume, in gibibytes (GiB).
"availability_zone": null, //The name of the availability zone.
"source_volid": null, //The UUID of the source volume.
"description": null, //The volume description.
"multiattach": false, //To enable this volume to attach to more than one server, set this value to true. Default is false.
"snapshot_id": null, //The UUID of the volume snapshot
"backup_id": null, //The UUID of the backup.
"name": null, //The volume name.
"imageRef": null, //The UUID of the image from which you want to create the volume.
<!--"volume_type": null, //The volume type (either name or ID).
"metadata": {}, //One or more metadata key and value pairs to be associated with the new volume.
"consistencygroup_id": null //The UUID of the consistency group.
},
"OS-SCH-HNT:scheduler_hints": { //The dictionary of data to send to the scheduler.
"same_host": [
"a0cf03a5-d921-4877-bb5c-86d26cf818e1",
"8c19174f-4220-44f0-824a-cd1eeef10287"
]
}
}
- POST
/v3/{project_id}/volumes
Response Body
{
"volume": {
"attachments": [], //Instance attachment information.(Server/Host/Device/Volume)
"availability_zone": "nova",//The name of the availability zone.
"bootable": "false",//Enables or disables the bootable attribute. You can boot an instance from a bootable volume.
"consistencygroup_id": null,//The UUID of the consistency group.
"created_at": "2018-11-28T06:21:12.715987",//The date and time when the resource was created.CCYY-MM-DDThh:mm:ss±hh:mm
"description": null, //The volume description.
"encrypted": false, //If true, this volume is encrypted.
"id": "2b955850-f177-45f7-9f49-ecb2c256d161",//The UUID of the volume.
"links": [ //The volume links.
{
"href": "http://127.0.0.1:33951/v3/89afd400-b646-4bbc-b12b-c0a4d63e5bd3/volumes/2b955850-f177-45f7-9f49-ecb2c256d161",
"rel": "self"
},
{
"href": "http://127.0.0.1:33951/89afd400-b646-4bbc-b12b-c0a4d63e5bd3/volumes/2b955850-f177-45f7-9f49-ecb2c256d161",
"rel": "bookmark"
}
],
"metadata": {},//A metadata object. Contains one or more metadata key and value pairs that are associated with the volume.
"migration_status": null,
"multiattach": false,//If true, this volume can attach to more than one instance.
"name": null,//The volume name.
"replication_status": null,//The volume replication status.
"size": 10,//The size of the volume, in gibibytes (GiB).
"snapshot_id": null,// The UUID of the volume snapshot
"source_volid": null,//The UUID of the source volume.
"status": "creating",//The volume status.
"updated_at": null, //The date and time when the resource was updated. CCYY-MM-DDThh:mm:ss±hh:mm
"user_id": "c853ca26-e8ea-4797-8a52-ee124a013d0e",//The UUID of the user.
"volume_type": null //The associated volume type for the volume.
}
- GET
/v3/{project_id}/volumes
(List accessible volumes) Response Body
{
"volumes": [
{
"id": "efa54464-8fab-47cd-a05a-be3e6b396188",
"links": [
{
"href": "http://127.0.0.1:37097/v3/89afd400-b646-4bbc-b12b-c0a4d63e5bd3/volumes/efa54464-8fab-47cd-a05a-be3e6b396188",
"rel": "self"
},
{
"href": "http://127.0.0.1:37097/89afd400-b646-4bbc-b12b-c0a4d63e5bd3/volumes/efa54464-8fab-47cd-a05a-be3e6b396188",
"rel": "bookmark"
}
],
"name": null
}
]
}
- GET
/v3/{project_id}/volumes/{volume_id}
Response Body
{
"volume": {
"attachments": [],
"availability_zone": "nova",
"bootable": "false",
"consistencygroup_id": null,
"created_at": "2018-11-29T06:50:07.770785",
"description": null,
"encrypted": false,
"id": "f7223234-1afc-4d19-bfa3-d19deb6235ef",
"links": [
{
"href": "http://127.0.0.1:45839/v3/89afd400-b646-4bbc-b12b-c0a4d63e5bd3/volumes/f7223234-1afc-4d19-bfa3-d19deb6235ef",
"rel": "self"
},
{
"href": "http://127.0.0.1:45839/89afd400-b646-4bbc-b12b-c0a4d63e5bd3/volumes/f7223234-1afc-4d19-bfa3-d19deb6235ef",
"rel": "bookmark"
}
],
"metadata": {},
"migration_status": null,
"multiattach": false,
"name": null,
"os-vol-host-attr:host": null,//Current back-end of the volume. Host format is host@backend#pool.
"os-vol-mig-status-attr:migstat": null,//The status of this volume migration (None means that a migration is not currently in progress).
"os-vol-mig-status-attr:name_id": null,//The volume ID that this volume name on the back- end is based on.
"os-vol-tenant-attr:tenant_id": "89afd400-b646-4bbc-b12b-c0a4d63e5bd3",//The project ID which the volume belongs to.
"replication_status": null,
"size": 10,
"snapshot_id": null,
"source_volid": null,
"status": "creating",
"updated_at": null,
"user_id": "c853ca26-e8ea-4797-8a52-ee124a013d0e",
"volume_type": null
}
}
- PUT
/v3/{project_id}/volumes/{volume_id}
Request Body
{
"volume": {
"name": "vol-003",
"description": "This is yet, another volume.",
"metadata": {
"name": "metadata0"
}
}
}
- DELETE
/v3/{project_id}/volumes/{volume_id}
- Preconditions: status available, in-use, error, error_restoring, error_extending, error_managing.
- Async Postconditions: Delete index and volumn on storage node.
CRUD of Cinder Volumn metadata
- POST
/v3/{project_id}/volumes/{volume_id}/metadata
Request Body (Same with response)
{
"metadata": {
"name": "metadata0"
}
}
- GET
/v3/{project_id}/volumes/{volume_id}/metadata
- PUT
/v3/{project_id}/volumes/{volume_id}/metadata
- GET
/v3/{project_id}/volumes/{volume_id}/metadata/{key}
//Show a volume’s metadata for a specific key - DELETE
/v3/{project_id}/volumes/{volume_id}/metadata/{key}
- PUT
/v3/{project_id}/volumes/{volume_id}/metadata/{key}
- GET
/v3/{project_id}/volumes/summary
Response Body //Get volumes summary
{
"volume-summary": {
"total_size": 4,
"total_count": 4,
"metadata": {
"key1": ["value1", "value2"],
"key2": ["value2"]
}
}
}
Volume manage extension (manageable_volumes)¶
- POST
/v3/{project_id}/manageable_volumes
- Creates a Block Storage volume by using existing storage rather than allocating new storage.
Request
{
"volume": {
"host": "geraint-VirtualBox",
"ref": {
"source-name": "existingLV",
"source-id": "1234"
},
"name": "New Volume",
"availability_zone": "az2",
"description": "Volume imported from existingLV",
"volume_type": null,
"bootable": true,
"metadata": {
"key1": "value1",
"key2": "value2"
}
}
}
- GET
/v3/{project_id}/manageable_volumes
List summary of volumes available to manage
Response
{
"manageable-volumes": [
{
"safe_to_manage": false,
"reference": {
"source-name": "volume-3a81fdac-e8ae-4e61-b6a2-2e14ff316f19"
},
"size": 1
},
{
"safe_to_manage": true,
"reference": {
"source-name": "lvol0"
},
"size": 1
}
]
}
- GET
/v3/{project_id}/manageable_volumes/detail
List detail of volumes available to manage
Response
{
"manageable-volumes": [
{
"cinder_id": "9ba5bb53-4a18-4b38-be06-992999da338d",
"reason_not_safe": "already managed",
"reference": {
"source-name": "volume-9ba5bb53-4a18-4b38-be06-992999da338d"
},
"safe_to_manage": false,
"size": 1,
"extra_info": null
},
{
"cinder_id": null,
"reason_not_safe": null,
"reference": {
"source-name": "lvol0"
},
"safe_to_manage": true,
"size": 1,
"extra_info": null
}
]
}
Volume transfer
- Transfers a volume from one user to another user.
Services (os-services)¶
- Administrator only. Lists all Cinder services, enables or disables a Cinder service, freeze or thaw the specified cinder-volume host, failover a replicating cinder-volume host.
Volume actions (volumes, action)¶
- POST
/v3/{project_id}/volumes/{volume_id}/action
{
// Extend the size
"os-extend": {
"new_size": 3
}
// Reset a volume’s statuses
"os-reset_status": {
"status": "available",
"attach_status": "detached",
"migration_status": "migrating"
}
// Revert volume to snapshot
"revert": {
"snapshot_id": "5aa119a8-d25b-45a7-8d1b-88e127885635"
}
// Set image metadata for a volume
"os-set_image_metadata": {
"metadata": {
"image_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
"image_name": "image",
"kernel_id": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
"ramdisk_id": "somedisk"
}
}
// Remove image metadata from a volume
"os-unset_image_metadata": {
"key": "ramdisk_id"
}
// Show image metadata for a volume
"os-show_image_metadata": {}
// Attach volume to a server
"os-attach": {
"instance_uuid": "95D9EF50-507D-11E5-B970-0800200C9A66", //The UUID of the attaching instance.
"mountpoint": "/dev/vdc" //The attaching mount point.
}
// Detach volume from server
"os-detach": {
"attachment_id": "d8777f54-84cf-4809-a679-468ffed56cf1"
}
// Unmanage a volume
"os-unmanage": {}
// Force detach a volume
"os-force_detach": {
"attachment_id": "d8777f54-84cf-4809-a679-468ffed56cf1",
"connector": {
"initiator": "iqn.2012-07.org.fake:01"
}
}
//Retype a volume
//Migrate a volume
//Complete migration of a volume
//Force delete a volume
//Update a volume’s bootable status
//Upload volume to image
//Reserve volume
//Unmark volume as reserved.
//Update volume status to detaching
//Roll back volume status to in-use
//Terminate volume attachment
//Initialize volume attachment
//Updates volume read-only access-mode flag
...
}
Generic volume groups
- Generic volume groups enable you to create a group of volumes and manage them together.
cinder-scheduler
-
如果说 cinder-api 接收的是关于 “创建” 的请求(e.g. Create Volume),那么该请求就会通过 MQ 转发到 cinder-scheduler service 服务进程,cinder-scheduler 与 nova-scheduler 一般,顾名思义是调度的层面。通过 Filters 选择最 “合适” 的 Storage Provider Node 来对请求资源(e.g. Volume)进行创建。不同的 Filters 具有不同的过滤(调度)算法,所谓的 “合适” 就是达到客户预期的结果,用户还可以自定义 Filter Class 来实现符合自身需求的过滤器,让调度更加灵活。与 nova-scheduler 一般,cinder-scheduler 同样需要维护调度对象(存储节点)“实时” 状态,cinder-volume service 会定期的向 cinder-scheduler service 上报存储节点状态(注:这实际上是通过后端存储设备的驱动程序上报了该设备的状态)。
- 首先判断存储节点状态,只有状态为 up 的存储节点才会被考虑。
- 创建 Volume 时,根据 Filter 和 Weight 算法选出最优存储节点。
- 迁移 Volume 时,根据 Filter 和 Weight 算法来判断目的存储节点是否符合要求。
cinder-volume
- cinder-volume service 是 Cinder 的核心服务进程,运行该服务进程的节点都可以被称之为存储节点。cinder-volume 通过抽象出统一的 Back-end Storage Driver 层,让不同存储厂商得以通过提供各自的驱动程序来对接自己的后端存储设备,实现即插即用(通过配置文件指定),多个这样的存储节点共同构成了一个庞大而复杂多样的存储资源池系统。
cinder-volumn Driver
- OpenStack 作为开放的 Infrastracture as a Service 云操作系统,支持业界各种优秀的技术,这些技术可能是开源免费的,也可能是商业收费的。 这种开放的架构使得 OpenStack 保持技术上的先进性,具有很强的竞争力,同时又不会造成厂商锁定(Lock-in)。
- 以 Cinder 为例,存储节点支持多种 Volume Provider,包括 LVM、NFS、Ceph、GlusterFS 以及 EMC、IBM 等商业存储系统。 cinder-volume 为这些 Volume Provider 抽象了统一的 Driver 接口,Volume Provider 只需要实现这些接口,就可以以 Driver 的形式即插即(volume_driver 配置项)用到 OpenStack 中。下面是 Cinder Driver 的架构示意图:
cinder-volumn Plugin
- Driver 和 Plugin 通常不会分家,Driver 是由各存储厂商提供的,那么 Plugins(插槽)就应该有 Cinder 的提供。 根据 FileSystem Storage 和 Block Storage 两个不同类型的外部存储系统,Cinder Plugins 也提供了 FileSystem based 和 Block based 两种不同类型 Plugin。除此之外,Cinder Plugins 还提供了 iSCSC、FC、NFS 等常用的数据传输协议 Plugin 框架,上传逻辑得以根据实际情况来使用(Attached/Dettached)存储资源。
cinder-backup
- 提供 Volume 的备份功能,支持将 Volume 备份到对象存储中(e.g. Swift、Ceph、IBM TSM、NFS),也支持从备份 Restore 成为 Volume。
Cinder HA (Cinder 高可用)
- 无状态服务(cinder-api、cinder-scheduler、cinder-volume)使用多活(无状态服务利于横向扩展,高并发):Active/Active(A/A)
- 有状态服务使用主备:Active/Passive(A/P)
- cinder-api + cinder-scheduler 都部署在 Controller,多个 Controller 同时共享同一个 VIP 实现多活
一个存储设备可以对应多个 cinder-volume,结合 cinder-volume 分布式锁实现多活,分布式锁可以避免不同的 cinder-scheduler 同时调用到同一个 cinder-volume,从而避免多活部署的 cinder-volume 同时操作后端存储设备,简而言之就是避免并发操作带来(cinder-volume 与后端存储设备之间的)数据不一致性。锁是为了通过互斥访问来保证共享资源在并发环境中的数据一致性。分布式锁主要解决的是分布式资源访问冲突的问题,保证数据的一致性(Etcd、Zookeeper)。
Cinder的分布式锁
本地锁
- Volume 资源本身也是一种共享资源,也需要处理并发访问冲突的问题,比如:删除一个 Volume 时,另一个线程正在基于该 Volume 创建快照;又或者同时有两个线程都在执行 Volume 挂载操作。cinder-volume 也是使用锁机制来实现 Volume 资源的并发访问的,Volume 的删除、挂载、卸载等操作都会对 Volume 上锁。在 Newton 版本以前,cinder-volume 的锁实现是基于本地文件实现的,使用了 Linux 的 flock 工具进行锁的管理。Cinder 执行加锁操作默认会从配置指定的 lockpath 目录下创建一个命名为 cinder-volume_uuid-{action} 的空文件,并对该文件使用 flock 加锁。flock 只能作用于同一个操作系统的文件锁。即使使用共享存储,另一个操作系统也不能通过 flock 工具判断该空文件是否有锁。
- 本地锁的局限,只能够保证同一个 cinder-volume 下的共享资源(e.g. Volume)的数据一致性,也就只能使用 A/P 主备的模式进行高可用,不能管理分布式 cinder-volume 下共享资源,导致了 cinder-volume 不支持多实例高可用的问题。所以为了避免 cinder-volume 服务宕机,就需要引入自动恢复机制来进行管理。比如: Pacemaker,Pacemaker 轮询判断 cinder-volume 的存活状态,一旦发现挂了,Pacemaker 会尝试重启服务。如果 Pacemaker 依然重启失败,则尝试在另一台主机启动该服务,实现故障的自动恢复。
Tooz
- 用户不需要分布式锁时,只需要指定后端为本地文件即可,此时不需要部署任何 DLM,和引入分布式锁之前的方式保持一致,基本不需要执行大的变更。当用户需要 cinder-volume 支持 AA 时,可以选择部署一种 DLM,比如 Zookeeper 服务。Cinder 对 Tooz 又封装了一个单独的 coordination 模块,其源码位于 cinder/coordination.py,当代码需要使用同步锁时,只需要在函数名前面加上 @coordination.synchronized 装饰器即可,方便易用,并且非常统一。
- 社区为了解决项目中的分布式问题,开发了一个非常灵活的通用框架,项目名为 Tooz,它是一个 Python 库,提供了标准的 coordination API,其主要目标是解决分布式系统的通用问题,比如节点管理、主节点选举以及分布式锁等。简而言之,Tooz 实现了非常易用的分布式锁接口。
- Tooz 封装了一套锁管理的库或者框架,只需要简单调用 lock、trylock、unlock 即可完成实现,不用关心底层细节,也不用了解后端到底使用的是 Zookeeper、Redis 还是 Etcd。使用 Tooz 非常方便,只需要三步:
- 与后端 DLM 建立连接,获取 coordination 实例。
- 声明锁名称,创建锁实例。
- 使用锁。