OpenStack Cinder 创建卷

Cinder 的卷创建(create volume)是块存储服务的核心操作,涉及从请求接收、调度决策到存储后端实际创建的完整流程。

1、流程概览

创建卷的完整流程涉及 Cinder 多个组件的协同工作,整体流程如下:

plaintext 复制代码
客户端 → cinder-api → 消息队列 → cinder-scheduler → 消息队列 → cinder-volume → 存储后端
                               ↓                    ↓
                            数据库                数据库(更新状态)
  1. 客户端发起请求:通过 CLI、Dashboard 或 API 调用创建卷。
  2. API 服务处理:验证请求、解析参数、初始化卷元数据。
  3. 调度器选择后端:基于过滤和权重策略选择最优存储节点 / 后端。
  4. 卷服务执行操作:调用存储后端驱动创建实际卷,并更新状态。
  5. 返回结果:将卷状态(如 available)返回给客户端。

2、源码分析

2.1 客户端请求发起

客户端通过 OpenStack API 发起卷创建请求,示例 CLI 命令:

bash 复制代码
openstack volume create --size 10 --type lvm-type my-volume

该命令会向 cinder-api 发送 HTTP POST 请求(默认端点:http://<controller>:8776/v3/<project-id>/volumes)。

2.2 API 服务处理(cinder-api)

cinder-api 负责接收并验证请求cinder/api/v3/volumes.py关键逻辑

  1. 验证请求合法性(权限、配额、参数格式)。
  2. 生成卷元数据并保存到数据库(初始状态 creating)。
  3. 通过 volume_api.create_volume 向消息队列发送异步任务。
python 复制代码
# cinder/api/v3/volumes.py
class VolumeController(wsgi.Controller):
    @wsgi.response(202)
    @validation.schema(volumes_schema.create)
    def create(self, req, body):
        """处理卷创建请求"""
        context = req.environ['cinder.context']
        volume_data = body['volume']
        
        # 1. 参数解析与验证
        size = volume_data.get('size')  # 卷大小(GB)
        volume_type = volume_data.get('volume_type')  # 卷类型(关联存储后端)
        availability_zone = volume_data.get('availability_zone')  # 可用区
        
        # 验证权限、配额(如是否允许创建10GB卷)
        self._check_volume_quota(context, size)
        
        # 2. 初始化卷元数据
        volume = {
            'size': size,
            'status': 'creating',  # 初始状态为创建中
            'volume_type_id': self._get_volume_type_id(volume_type),
            'availability_zone': availability_zone,
            # 其他元数据:名称、描述、项目ID等
        }
        
        # 3. 保存初始状态到数据库
        volume_ref = objects.Volume(context=context, **volume)
        volume_ref.create()  # 调用ORM保存到数据库
        
        # 4. 发送创建任务到消息队列,由scheduler或volume服务处理
        self.volume_api.create_volume(
            context,
            volume_ref,
            filter_properties=self._get_filter_properties(req, volume_data)
        )
        
        # 5. 返回卷信息(状态为creating)
        return self._view_builder.detail(req, volume_ref)

2.3 调度器选择存储后端(cinder-scheduler)

cinder-scheduler 从消息队列接收任务,选择最优存储后端, cinder/scheduler/filter_scheduler.py

python 复制代码
# cinder/scheduler/filter_scheduler.py
class FilterScheduler(scheduler.Scheduler):
    def schedule_create_volume(self, context, request_spec, filter_properties):
        """选择存储后端创建卷"""
        # 1. 获取候选存储后端
        hosts = self.host_manager.get_all_host_states(context)
        
        # 2. 过滤后端(排除不符合条件的存储节点)
        filtered_hosts = self.host_manager.filter_hosts(
            hosts, request_spec, filter_properties)
        if not filtered_hosts:
            raise exception.NoValidHost(reason="No suitable storage backend found")
        
        # 3. 计算权重并排序(选择最优后端)
        weighed_hosts = self.host_manager.weigh_hosts(
            filtered_hosts, request_spec, filter_properties)
        best_host = weighed_hosts[0]  # 得分最高的后端
        
        # 4. 向目标存储节点的cinder-volume发送创建请求
        self._schedule_create_volume(context, best_host, request_spec)
2.3.1 过滤阶段(Filters)
  • CapacityFilter(cinder/scheduler/filters/capacity_filter.py):检查后端可用容量是否满足卷大小。
python 复制代码
class CapacityFilter(filters.BaseHostFilter):
    def host_passes(self, host_state, filter_properties):
        # 所需容量 = 卷大小 + 预留空间
        required = filter_properties['request_spec']['volume_size']
        # 可用容量 = 总容量 - 已用容量 - 预留容量
        available = host_state.free_capacity_gb - host_state.reserved_percentage
        return available >= required
  • AvailabilityZoneFilter(cinder/scheduler/filters/availability_zone_filter.py):过滤不在指定可用区的后端。
  • CapabilitiesFilter(cinder/scheduler/filters/capabilities_filter.py):检查后端是否支持卷类型要求的特性(如 thin_provisioning)
2.3.2 权重阶段(Weighters)

默认启用 CapacityWeigher(cinder/scheduler/weights/capacity.py),优先选择可用容量多的后端

python 复制代码
class CapacityWeigher(weights.BaseWeigher):
    def weigh(self, host_state, weight_properties):
        # 可用容量越高,权重越大
        return host_state.free_capacity_gb * self.weight_multiplier

2.4 卷服务执行创建操作(cinder-volume)

目标存储节点的 cinder-volume 服务从消息队列接收任务,调用存储后端驱动创建卷,cinder/volume/manager.py

python 复制代码
# cinder/volume/manager.py
class VolumeManager(manager.SchedulerDependentManager):
    def create_volume(self, context, volume, host):
        """执行卷创建操作"""
        # 1. 锁定卷(避免并发操作)
        volume = self._get_volume_and_lock(context, volume['id'])
        
        try:
            # 2. 调用存储后端驱动创建卷
            driver = self.driver  # 如LVM驱动、Ceph驱动
            # 驱动创建卷(返回卷在后端的路径/标识)
            provider_location = driver.create_volume(volume)
            
            # 3. 更新卷状态为可用(available)
            volume.update({
                'status': 'available',
                'provider_location': provider_location,  # 存储后端中的卷标识
            })
            volume.save()
            
        except Exception as e:
            # 出错时更新状态为error
            volume.update({'status': 'error'})
            volume.save()
            raise exception.VolumeCreationFailed(reason=str(e))

2.5 存储后端驱动实现(以 LVM 为例)

LVM 驱动代码位于 cinder/volume/drivers/lvm.pycreate_volume 方法实际调用 LVM 命令创建逻辑卷:

python 复制代码
# cinder/volume/drivers/lvm.py
class LVMVolumeDriver(volume_driver.VolumeDriver):
    def create_volume(self, volume):
        """通过LVM创建卷"""
        # 卷大小(转换为MB)
        size_in_mb = volume['size'] * 1024
        # 卷名(通常为volume-<uuid>)
        volume_name = self._get_volume_name(volume)
        # 卷组名(从配置读取,如cinder-volumes)
        vg_name = self.configuration.lvm_volume_group
        
        # 执行lvcreate命令:创建指定大小的逻辑卷
        self._execute(
            'lvcreate', '-L', f'{size_in_mb}M', '-n', volume_name, vg_name,
            run_as_root=True
        )
        
        # 返回卷路径(如/dev/cinder-volumes/volume-xxx)
        return f"/dev/{vg_name}/{volume_name}"

3、常用配置(cinder.conf)

3.1 核心配置([DEFAULT] 段)

ini 复制代码
[DEFAULT]
# 默认卷类型(未指定时使用)
default_volume_type = lvm-type

# 卷大小限制(最小/最大GB)
min_volume_size = 1
max_volume_size = 10240

# 并发创建卷的最大数量
volume_api_workers = 4  # 建议与CPU核心数一致

# 消息队列配置(与其他组件通信)
transport_url = rabbit://openstack:password@controller:5672/

3.2 调度器配置([scheduler] 段)

ini 复制代码
[scheduler]
# 启用的过滤器(按执行顺序)
enabled_filters = CapacityFilter,AvailabilityZoneFilter,CapabilitiesFilter

# 启用的权重器
enabled_weighters = CapacityWeigher

# 容量权重乘数(值越大,可用容量影响越强)
capacity_weight_multiplier = 1.0

# 调度器缓存过期时间(秒)
scheduler_cache_expiry = 60

3.3 存储后端配置(以 LVM 为例)

ini 复制代码
# 定义LVM存储后端
[lvm]
volume_driver = cinder.volume.drivers.lvm.LVMVolumeDriver
volume_group = cinder-volumes  # LVM卷组名
target_protocol = iscsi  # 共享协议(iscsi或fc)
target_helper = tgtadm  # iSCSI目标管理工具
volume_backend_name = lvm-backend  # 后端名称(用于卷类型关联)

# 可选:配置thin provisioning(瘦分配)
lvm_type = thin
thin_pool_name = cinder-thin-pool  # 瘦池名称

3.4 多后端部署

通过配置多个后端段(如 [lvm]、[ceph])支持混合存储,示例:

ini 复制代码
[DEFAULT]
enabled_backends = lvm,ceph

[lvm]
# LVM后端配置...

[ceph]
volume_driver = cinder.volume.drivers.rbd.RBDDriver
rbd_pool = volumes
rbd_ceph_conf = /etc/ceph/ceph.conf
volume_backend_name = ceph-backend

4、常见问题

日志位置

  • /var/log/cinder/api.log(API 层)
  • /var/log/cinder/scheduler.log(调度层)
  • /var/log/cinder/volume.log(卷服务层)。
    常见原因:
  • 存储后端容量不足(CapacityFilter 过滤)。
  • 卷类型与后端不匹配(CapabilitiesFilter 过滤)。
  • 驱动执行失败(如 LVM 卷组不存在)。
相关推荐
感哥4 小时前
OpenStack Cinder 架构
openstack
感哥5 小时前
OpenStack Nova Scheduler 计算节点选择机制
openstack
感哥3 天前
OpenStack Nova 创建虚拟机
openstack
感哥3 天前
OpenStack Glance(镜像)
openstack
感哥3 天前
OpenStack Keystone详解
openstack
安全菜鸟12 天前
传统方式部署OpenStack具体教程
openstack
哈里谢顿2 个月前
Ironic 中 Clean/deploy Step 延迟执行的原因分析
openstack
哈里谢顿2 个月前
ironic中为什么 IPMI Hardware Type 必须支持 IPMIManagement
openstack
哈里谢顿2 个月前
Ironic 中各个接口的作用详解
openstack