
第一次被 cinder-scheduler 坑到是在一个多存储后端的生产环境里。创建一块 2TB 的块存储卷状态卡在creating半天不动cinder-volume 那边却完全没动静。旁边同事瞟了一眼丢过来一句“去翻 scheduler 日志。”翻到No valid host found那一行的瞬间我才意识到自己一直没真正搞懂 OpenStack 的存储调度是怎么运转的。这篇文章就当是那段时间的复盘笔记。先说清楚 cinder-scheduler 到底管哪一段事再拆它的 Filter Weigher 两阶段逻辑然后聊怎么用 Volume Type 和 Extra Specs 控制卷的落盘位置接着给出一套可以直接抄的生产配置最后手写一个自定义过滤器和权重器把排障经验也一并整理出来。这个系列走到第 48 篇前面的文章已经把 Cinder 的架构、后端驱动、卷生命周期都过了这篇正好卡在“卷建出来之后如何选择后端”这个关键点上。1. 先弄明白 cinder-scheduler 管什么再谈怎么调试它1.1 没有调度器会怎样多存储后端的现实场景想象你面对的不是一台测试机而是一套承载了多种业务的 OpenStack 云平台。后端挂着好几类存储SAS 盘组成的本地 LVM 池、SSD 组成的高性能池、还有通过 FC 接进来的集中式存储阵列。业务方在申请卷的时候可能明确要求“这块卷要放在高性能存储上”也可能完全没概念只求“能建出来”。如果 Cinder 没有调度器卷会创建到哪里答案是取决于enabled_backends里后端的顺序或者干脆随机落在一个可用后端上。这在单后端环境里没问题多后端的生产环境就乱了套——高性能卷可能落到慢速盘上容量大户可能把某个池塞爆而另一个池还在闲置。cinder-scheduler 存在的意义就是解决这件事在收到创建卷的请求后从所有启用后端中选出最合适的那一个再让 cinder-volume 在那个后端上真正把卷建出来。这里要说清楚一个容易被忽略的点OpenStack 里有三个核心服务同时参与卷的创建。cinder-api负责接请求、校验参数cinder-scheduler负责做选择也就是“调度”cinder-volume负责在选定的后端上执行实际创建动作。以前很多教程把目光都放在 cinder-volume 上导致排障时只盯卷的驱动和存储池状态。实际上有相当大比例的“卷建不起来”问题根源都在调度这一层。1.2 调度器不是只在“创建卷”时工作另一个常见误解是cinder-scheduler 只在创建卷时才被调用。实际不止。卷迁移、卷跨后端复制、创建卷备份、从镜像创建卷、甚至在某些版本里创建一致性组时都会触发调度流程。理解这一点对运维有帮助——你遇到“迁移走了半天没完成”的故障时也要去查 scheduler 日志而不只是看 volume 服务日志。调度器本身是一个独立进程通过消息队列消费请求。你在控制台上看到卷状态从creating变成available中间经历了“API 接收请求 - 调度器做过滤和权重计算 - 选定 host - 通知对应 cinder-volume - 后端驱动执行创建”这条链路。链路里任何一环出问题卷状态都会卡住。2. Filter Weigher 两阶段调度逻辑的核心骨架2.1 过滤器是“海选”先把不合格的后端踢出局cinder-scheduler 的调度逻辑核心是两阶段先过滤Filter再加权Weigher。为什么这样设计因为过滤和加权的目标不同。过滤阶段是“一票否决制”。每个过滤器都会对每个候选后端做出通过或淘汰的判断所有过滤器都通过了这个后端才进入下一轮任何一个过滤器不通过直接出局。这个设计保证了一些硬性规则不会被权重计算覆盖比如容量不足的后端即使性能再好、得分再高也不能选它否则卷肯定建失败。常用的内置过滤器有这几个我按日常使用频率排个序过滤器作用判断依据CapacityFilter检查后端剩余容量是否足够后端上报的free_capacity_gb、total_capacity_gbCapabilitiesFilter匹配 Volume Type 的 Extra Specs 与后端能力后端的 capability 字典AvailabilityZoneFilter检查后端所属可用域是否与请求匹配cinder-volume 服务的 availability_zoneDriverFilter围绕后端驱动自身的运行状态做基础检查驱动上报能力、是否处于只读/异常状态JSONFilter支持自定义 JSON 形式的过滤规则写在 Extra Specs 里的过滤表达式每个过滤器都是一个 Python 类核心方法接收host_state后端的状态信息和filter_properties请求参数返回 True 或 False。这个设计非常方便扩展我后面会写一个自定义过滤器给大家参考。2.2 权重器是“评分”在海选结果里挑最优后端经过过滤之后剩下的后端理论上都能满足创建卷的条件但它们之间仍然有优劣之分。权重阶段就是要给这些“合格选手”打分排序得分最高的后端被选中。和过滤器最大的区别是权重器是“非一票否决”的它产出的是数值通过对比打分让调度器选出一个综合最优解。最常见的默认权重器是 AllocatedCapacityWeigher老版本叫 CapacityWeigher它的逻辑很直观同样是能放下这块卷的两个后端一个剩余 100GB另一个剩余 500GB默认情况下调度器会把新卷放到剩余空间更多的那个上面。目的是什么是让卷尽量均匀地分布在各存储池之间避免出现“一个池满了另一个池闲着”的尴尬。不同版本里权重器的名字会有差异但核心逻辑都一样。如果在多可用域的复杂环境里还可以通过调整权重器的 multiplier 和 offset 来改变排序策略。比如给某个后端设置更高的权重偏移就能让调度器“偏心”它一点。需要知道的是权重计算不是简单的“剩余容量大就赢”它背后有归一化公式但运维层面通常不需要深究公式重点关注配置项里scheduler_default_weighers写谁就够。2.3 为什么 OpenStack 坚持两阶段而不是一口气搞定我最早看这块代码的时候也有个疑问过滤和权重明明可以在一个阶段里用条件判断实现为什么非拆成两层拆开的优势在于解耦。过滤器的职责是维护“硬性边界”比如容量够不够、可用域对不对权重器的职责是优化“软性偏好”比如容量均衡、指定后端优先。硬性边界不能因为分数高就被突破软性偏好不该承担“否决”的功能。这种设计让运维可以根据业务需求自由组合过滤器比如我可以在默认过滤器后面追加一个自定义的“只允许 SSD 后端参加评选”的过滤器而不影响原本的容量均衡逻辑。另外两阶段也方便做审计。日志里会分别记录哪个过滤器淘汰了哪个后端以及权重计算的结果排障的时候信息量比“一步到位”的方案大得多。3. 让卷落到指定后端Volume Type 和 Extra Specs 的绑定玩法3.1 为什么官方一直强调用 Volume Type 管理后端在只有一两个后端的测试环境里你可能觉得调度器无所谓所有后端都能建卷就好。但在生产环境里用 Volume Type 区分后端不是建议而是基本要求。Volume Type 的作用有点像“套餐规格”。它可以定义性能等级比如“高性能 SSD”、可用性等级比如“双副本”、或者直接指定后端名称比如“这个类型只允许建在后端 A 上”。业务方在申请卷的时候带上 Volume Type 参数cinder-scheduler 的 CapabilitiesFilter 就会把“这个类型允许的 Extra Specs”和“后端上报的能力”做比对把不符合的后端全部剔除。这样做的最大好处是业务方完全不需要知道平台上到底挂了哪些存储也不需要关心哪个后端叫什么名字只要告诉 OpenStack“我要一块高性能卷”平台就自动帮你匹配到 SSD 后端上。对运维来说也省心新挂载一个存储后端时只要它上报的 capability 能匹配已有 Volume Type新后端就自动进入候选池。3.2 Extra Specs 是如何变成调度条件的理解 Extra Specs 之前先要明白后端是如何“上报”自己的。每个后端驱动在启动时或定期会上报一组 capabilities包括容量、池名、驱动支持的特性等等。这些数据保存在 scheduler 侧通过cinder get-pools --detail或openstack volume service list可以看到。Extra Specs 就是 Volume Type 上挂的一组键值对。常见的调度相关键有两个方向一个是volume_backend_name这直接对应后端配置里的volume_backend_name属于“点名式”匹配另一个是capabilities:xxx前缀比如capabilities:disk_typessd这会被转换为对后端自定义 capability 的匹配条件。还有个细节如果你在 Volume Type 上挂了volume_backend_namessd调度器就会只选择volume_backend_name恰好等于ssd的后端。注意这里是“恰好”大小写也要一致。我见过一个案例类型里写的是SSD而后端配置里写的是ssd结果所有卷全部调度失败。这种问题在 get-pools 的输出里一眼就能看出来。3.3 实操定义 Volume Type 并验证调度效果下面给出一套可落地的操作流程。先在控制节点或者装有 cinder 客户端的环境里执行。假设我的平台上有两个后端一个叫sata-pool一个叫ssd-pool。第一步创建两个 Volume Typeopenstack volume type create sata openstack volume type create ssd第二步给 Volume Type 绑定后端名称这是让调度器“认路”的关键openstack volume type set --property volume_backend_namesata-pool sata openstack volume type set --property volume_backend_namessd-pool ssd第三步验证绑定结果openstack volume type show ssd这时能看到 ssd 这个类型下面有volume_backend_name属性。接着用两个类型分别创建卷创建完成后用openstack volume show查看卷的os-vol-host-attr:host字段它标注了卷实际落在哪个后端上。如果你发现指定了类型后卷仍然调度失败第一件事就是看后端的可用性openstack volume service list cinder get-pools --detail正常情况下输出里能看到每个后端上报的volume_backend_name、total_capacity_gb、free_capacity_gb、还有各种 capability。拿这个输出和 Volume Type 的 Extra Specs 一比对问题基本就浮出水面了。4. 改配置就能调优scheduler 参数和生产配置示例4.1 三个最重要的配置项调度器相关的配置集中在/etc/cinder/cinder.conf的[DEFAULT]段下。核心项有四个scheduler_driver指定使用哪个调度驱动。默认值是cinder.scheduler.filter_scheduler.FilterScheduler绝大多数场景不需要换。scheduler_default_filters设置启用哪些过滤器按顺序执行逗号分隔。scheduler_default_weighers设置启用哪些权重器。scheduler_available_filters设置可用的过滤器类路径配合自定义过滤器使用。这里面最容易踩坑的是过滤器列表的顺序。比如你既做了容量过滤又做了自定义磁盘类型过滤顺序会影响最终结果吗多数情况下不影响因为过滤是“AND”关系不管先执行哪个最终都必须全部通过。但也有例外像 CityCache 这类依赖缓存数据的过滤器如果放在了缓存尚未初始化的过滤器之前可能导致误判。我个人的习惯是把成本最低、最容易判断的过滤器放前面比如可用域过滤和容量过滤这样可以在早期就快速淘汰一批不相关的后端减轻后续过滤器和权重器的计算压力。4.2 一个可复制的多后端配置模板下面这个是实际多后端环境里我会用的配置模板做了脱敏处理。重点不是参数有多炫而是结构合理、易于排障。[DEFAULT] enabled_backends sata-pool:ssd-pool,xfs-pool scheduler_driver cinder.scheduler.filter_scheduler.FilterScheduler scheduler_default_filters AvailabilityZoneFilter, CapacityFilter, CapabilitiesFilter scheduler_default_weighers AllocatedCapacityWeigher [sata-pool] volume_backend_name sata-pool volume_driver cinder.volume.drivers.lvm.LVMVolumeDriver volume_group cinder-volumes-sata volume_type sata [ssd-pool] volume_backend_name ssd-pool volume_driver cinder.volume.drivers.lvm.LVMVolumeDriver volume_group cinder-volumes-ssd volume_type ssd [xfs-pool] volume_backend_name xfs-pool volume_driver cinder.volume.drivers.lvm.LVMVolumeDriver volume_group cinder-volumes-xfs volume_type xfs在这里我用了三个后端每个后端都指定了对应的volume_type。这样一来当用户创建卷时若指定了类型调度器会严格按类型选择池如果用户没指定类型调度器就会在所有启用后端中进行过滤和权重排序由 AllocatedCapacityWeigher 决定落盘位置。顺带提醒enabled_backends的写法不同版本略有差异冒号和逗号混用可能导致后端无法正常加载。如果不确定先只配一个后端测试再逐步增加。改完配置后记得重启 cinder-scheduler 和 cinder-volume 服务sudo systemctl restart cinder-scheduler cinder-volume重启后第一时间用openstack volume service list确认后端全部处于 enabled 状态。4.3 权重参数调整的实用思路权重器的行为可以通过weight_multiplier和weight_offset微调。举个例子默认的 AllocatedCapacityWeigher 会尽量把卷散到容量较多的池里。如果业务上希望优先使用某个高性能池可以把该池对应的后端权重提高而不是在过滤器里硬性指定。硬性指定会让低优先级池彻底失去使用机会一旦高优先级池容量不够卷就建不出来软性偏好则保留了一定的容错空间。在实际操作中先跑一阵子默认配置观察各池的容量使用曲线再根据“哪个池消耗过快”来微调权重比一开始就拍脑袋定参数靠谱得多。权重调优属于渐进式优化别指望一步到位。5. 扩展调度器手写过滤器和权重器的实战代码5.1 写一个只匹配指定磁盘类型的自定义过滤器内置过滤器不能覆盖所有需求比如我想根据后端上报的自定义能力disk_type来过滤内置的 CapabilitiesFilter 只能匹配 Extra Specs 里的条件不能直接表达“后端磁盘必须是 SSD”这种语义。这时候就需要自定义过滤器。下面这段代码非常简单但结构完整。在控制节点上新建一个 Python 文件比如/etc/cinder/my_filters.pyfrom cinder.scheduler import filters class DiskTypeFilter(filters.BaseFilter): 只调度到磁盘类型为 SSD 的后端 def host_passes(self, host_state, filter_properties): capabilities getattr(host_state, capabilities, None) if not capabilities: return False disk_type capabilities.get(disk_type) return disk_type SSD这段代码的意图很直白读取后端上报的 capabilities 字典检查disk_type是否为SSD。不是就返回 False直接淘汰。注意后端上报的自定义能力需要在驱动里自行定义比如 LVM 驱动会将配置里自定义的volume_capacity_disk_type之类键值上报上来。如果你的后端驱动没有上报disk_type这个过滤器会永远返回 False这也是排障时要注意的点。配置里启用这个过滤器有两个办法。第一种直接在配置里指定可用的过滤器类路径[DEFAULT] scheduler_available_filters my_filters.DiskTypeFilter scheduler_default_filters AvailabilityZoneFilter, CapacityFilter, CapabilitiesFilter, DiskTypeFilter第二种通过 entry_points 注册这种方式适合把过滤器打包成独立 Python 包时使用。在包名对应的 setup.cfg 里加上[entry_points] cinder.scheduler.filters disk_type my_filters:DiskTypeFilter5.2 让权重器优先选择指定后端过滤负责“能不能选”权重负责“更倾向于谁”。如果想要的效果是“SSD 优先但不是唯一”可以写一个权重器而不是过滤器。先看代码from cinder.scheduler import weights class PreferSSDWeigher(weights.BaseWeigher): def _weigh_object(self, host_state, weight_properties): capabilities getattr(host_state, capabilities, None) if capabilities and capabilities.get(disk_type) SSD: return 1 return 0这个权重器对所有后端打分SSD 后端得 1 分其他后端得 0 分。配合原有的容量权重器共同使用排序时会优先选择 SSD 后端但假如 SSD 后端被 CapacityFilter 淘汰了其他后端依然能顶上不会出现“一个后端都选不出来”的情况。配置方式也类似[DEFAULT] scheduler_default_weighers AllocatedCapacityWeigher, PreferSSDWeigher这里有个实践细节自定义权重器和默认权重器同时使用时各个权重器产生的分数会按一定规则合并。不同权重器的分数量级不同可能导致其中一个权重器彻底主导排序。实际配置时最好先只保留一个自定义权重器跑一段时间观察卷的分布是否符合预期再加第二个进去。别嫌慢调度策略的调整就是靠实测说话。5.3 开发自定制功能时绕不开的坑自定义过滤器看起来简单但有几个坑躲不掉。第一个是类路径加载问题。配置里写了scheduler_available_filters之后重启 cinder-scheduler 时会立即加载你的类任何语法错误或导入错误都会让调度器卡死。建议先在 Python 交互式环境里手动导入测试一遍确认无误后再改配置重启。第二个是后端 capability 的可见性。过滤器读的host_state.capabilities来自后端起上报的数据但如果某个后端从未成功上报过数据这个属性可能是空的。很多自定义过滤器在这个场景下直接 False导致全部后端被淘汰。调试时可以临时加一行日志输出 capabilities 的内容确认数据长什么样。第三个是版本兼容。不同 OpenStack 版本里BaseFilter.host_passes的签名基本稳定但权重器内部类名和方法位置可能有变化。跨版本升级时务必对照官方 Release Notes 检查自定义代码别拿旧代码直接凑合。6. 卷一直 creatingcinder-scheduler 排查实录6.1 日志追查路径和关键日志行卷卡在creating不动这是 OpenStack 运维里出现频率极高的故障。很多人第一反应是去查 cinder-volume 日志但更高效的做法是两边日志同时翻/var/log/cinder/cinder-scheduler.log和/var/log/cinder/cinder-volume.log。调度器日志里我最关注的是这几个关键行Filtering out ...某个过滤器淘汰了某个后端。No valid host found所有后端都被淘汰没有可用后端这是卷无法创建的直接原因。Weighed Host ...权重计算后选出的后端列表排第一的就是最终选中的 host。举个例子一个比较典型的日志片段是Filtering out host xxxx due to Filters: [CapabilitiesFilter] No valid host found看到CapabilitiesFilter淘汰了全部后端就知道问题出在 Volume Type 的 Extra Specs 和后端上报的 capability 不匹配后面的排查方向就明确了。6.2 常见调度失败原因速查表我把这些年遇到过的调度失败原因整理成一个速查表排障时可以直接对照现象可能原因排查方法No valid host found日志里所有后端被 CapabilitiesFilter 淘汰Extra Specs 与后端 capability 不匹配对比 get-pools 输出与 Volume Type 属性No valid host foundCapacityFilter 淘汰所有后端后端容量上报为 unknown或超额配置比设置不合理检查 get-pools 里的容量字段调整 max_over_subscription_ratio卷一直 creatingscheduler 日志无异常cinder-volume 后端驱动创建失败翻 cinder-volume 日志检查存储连接指定可用域创建失败可用域内没有可用的后端服务对比 service list 里的 availability_zone类型创建卷失败换默认类型却能成功类型绑定的后端名称写错或后端未启用检查 Volume Type 属性拼写检查 enabled_backends这份表不是教科书是我在实操里一条条填出来的经验汇总。遇到新问题就往里补一行时间久了就是自己的故障手册。6.3 一次“容量充足却调度失败”的完整复盘说一个我印象深刻的案例。那是给业务方创建一块 500GB 的卷指定了ssd类型后端 SSD 池的剩余空间有 3TB怎么看都够用但卷就是建不出来。翻开 scheduler 日志发现淘汰行指向 CapabilitiesFilter但后端明明正常工作。我用cinder get-pools --detail一看SSD 池上报的volume_backend_name是ssd-pool而 Volume Type 里配的volume_backend_namessd两边差了一个-pool。原因是之前另一个同事配置类型时图省事写成了ssd跟后端配置里的实际名称对不上。造成的后果是所有指定这个类型的卷全部调度失败。这种问题不好查因为容量充足、后端在线、日志也不会报致命错误只有逐行对比时才能发现。从那以后我的习惯是Volume Type 里的volume_backend_name永远从后端配置里复制粘贴绝不手打。另外还想提醒一件事改完 Volume Type 属性后不需要重启任何服务但后端上报的 capabilities 可能不是实时的存在一个上报周期。刚改完配置立刻查 get-pools看到的可能还是旧数据。别急着一遍遍重启 cinder-volume稍微等几十秒再查询你会看到数据刷新。我在这个细节上浪费过不少时间。6.4 一个最实用的排障习惯最后分享一个我踩过几次坑之后的习惯任何卷建不起来不要只盯 cinder-volume先看 scheduler 日志。日志会直接告诉你哪个后端被哪个过滤器淘汰了以及最终有没有选到宿主。很多人上来就去看存储池状态、看网络连通性折腾半天其实问题就出在类型属性写错这种小地方。拿到No valid host found的时候先别慌着调配置先回答三个问题后端服务是不是都正常后端的容量和 capability 是什么要求的 Volume Type 属性是什么答出这三件事九成以上的调度问题都能定位。我个人在实际操作中体会最深的一点是cinder-scheduler 的调度逻辑本身不难理解它就是一个“先淘汰再打分”的选人流程。真正难的是让你环境里所有的后端、类型、上报数据保持一致。OpenStack 的设计给了你很强的灵活性你能写过滤器、改权重、调参数但这意味着你维护的信息更多、更容易出错。所以生产环境的准则应该是能少配就少配能用类型约束就用类型约束日志里每一条过滤记录都值得认真读一遍。