本文主要讲述cinder scheduler调度服务的调度策略,以及常用到的Filter节点筛选器以及Weigher节点称重器

顺便提一嘴,这里所说的节点,其实就是各个后端,因为在cinder里host其实就对应着配置里enabled_backends的所有后端,所以也可以叫做后端筛选器、后端称重器。

[cinder@node-8 /]$ cinder-manage service list
Binary           Host                                 Zone             Status     State Updated At           RPC Version  Object Version  Cluster                             
cinder-scheduler cinder-volume-worker                 nova             enabled    :-)   2023-08-08 07:02:07  3.12         1.38                                                
cinder-volume    cinder-volume-worker@hdd             nova             enabled    :-)   2023-08-08 07:02:11  3.16         1.38                                                
cinder-volume    cinder-volume-worker@ssdpool         nova             enabled    :-)   2023-08-08 07:02:10  3.16         1.38                                                
cinder-volume    cinder-volume-worker@alcubierre      nova             enabled    :-)   2023-08-08 07:02:10  3.16         1.38

一、常用的调度器

cinder各服务之间大致流程关系如图:

openstack Cinder支持哪些卷类型 openstack的cinder组件_云计算

如图,cinder本身就支持多种调度器:

ChanceScheduler: 随机选取cinder-service 创建cinder volume
SimpleScheduler: 根据availability_zone 和 cinder-volume service的capacity进行选择
FilterScheduler: 可以选择具体的filter规则,满足filter规则的cinder-volume service将会通过筛选,创建cinder volume

目前90%都是使用FilterScheduler调度器,且若不手动修改,默认的scheduler_driver就是FilterScheduler

# Default scheduler driver to use (string value)
#scheduler_driver = cinder.scheduler.filter_scheduler.FilterScheduler

二、FilterScheduler调度器简单介绍

FilterScheduler调度器又分为Filter和Weigher两部分。简单来说就是,根据Filter选择出所有满足条件的可用节点,再根据weigher选择最优的节点

2.1 Filter

默认启用的Filter:

  • AvailabilityZoneFilter:按可用性区域过滤后端
  • CapacityFilter :基于卷后端的容量利用率的容量过滤器
  • CapabilitiesFilter:基于volume type中的extra specs(常用volume_backend_name)

本次文档暂时只梳理CapacityWeigher的原理,Filter都很容易理解

2.2 Weigher

默认启用的Weigher:

  • CapacityWeigher:可用存储空间最大的节点成为最优节点

CapacityWeigher类源码如下:

class CapacityWeigher(weights.BaseHostWeigher):  
    def weight_multiplier(self):  
        """Override the weight multiplier."""  
        return CONF.capacity_weight_multiplier  
  
    def weigh_objects(self, weighed_obj_list, weight_properties):  
        """Override the weigh objects. 
 
 
        This override calls the parent to do the weigh objects and then 
        replaces any infinite weights with a value that is a multiple of the 
        delta between the min and max values. 
 
        NOTE(jecarey): the infinite weight value is only used when the 
        smallest value is being favored (negative multiplier).  When the 
        largest weight value is being used a weight of -1 is used instead. 
        See _weigh_object method. 
        """  
        tmp_weights = super(weights.BaseHostWeigher, self).weigh_objects(  
            weighed_obj_list, weight_properties)  
  
        if math.isinf(self.maxval):  
            # NOTE(jecarey): if all weights were infinite then parent  
            # method returns 0 for all of the weights.  Thus self.minval  
            # cannot be infinite at this point  
            copy_weights = [w for w in tmp_weights if not math.isinf(w)]  
            self.maxval = max(copy_weights)  
            offset = (self.maxval - self.minval) * OFFSET_MULT  
            self.maxval += OFFSET_MIN if offset == 0.0 else offset  
            tmp_weights = [self.maxval if math.isinf(w) else w  
                           for w in tmp_weights]  
  
        return tmp_weights  
  
    def _weigh_object(self, host_state, weight_properties):  
        """Higher weights win.  We want spreading to be the default."""  
        free_space = host_state.free_capacity_gb  
        total_space = host_state.total_capacity_gb  
        if (free_space == 'infinite' or free_space == 'unknown' or  
                total_space == 'infinite' or total_space == 'unknown'):  
            # (zhiteng) 'infinite' and 'unknown' are treated the same  
            # here, for sorting purpose.  
  
            # As a partial fix for bug #1350638, 'infinite' and 'unknown' are  
            # given the lowest weight to discourage driver from report such  
            # capacity anymore.  
            free = -1 if CONF.capacity_weight_multiplier > 0 else float('inf')  
        else:  
            free = utils.calculate_virtual_free_capacity(  
                total_space,  
                free_space,  
                host_state.provisioned_capacity_gb,  
                host_state.thin_provisioning_support,  
                host_state.max_over_subscription_ratio,  
                host_state.reserved_percentage)  
  
        return free

当cinder.conf中capacity_weight_multiplier=1.0时,调用weigh_objects(),为-1.0时直接调用_weigh_object,若这个在cinder里添加后端配置时可以修改,如果不配置则默认1.0

weigh_objects()方法中line 19会调用会调用父类方法,父类源码如下:

def weigh_objects(self, weighed_obj_list, weight_properties):  
        """Weigh multiple objects. 

        Override in a subclass if you need access to all objects in order 
        to calculate weights. Do not modify the weight of an object here, 
        just return a list of weights. 
        """  
        # Calculate the weights  
        weights = []  
        for obj in weighed_obj_list:  
            weight = self._weigh_object(obj.obj, weight_properties)  
  
            # Record the min and max values if they are None. If they anything  
            # but none we assume that the weigher has set them  
            if self.minval is None:  
                self.minval = weight  
            if self.maxval is None:  
                self.maxval = weight  
  
            if weight < self.minval:  
                self.minval = weight  
            elif weight > self.maxval:  
                self.maxval = weight  
  
            weights.append(weight)  
  
        return weights

在此父类weigh_object()中会调用子类CapacityWeigher重写的_weigh_object(),计算出可用容量,然后计算出最大可用容量maxval和最小可用容量minval,并且子类CapacityWeigher中weigh_object()会根据maxval是否为无穷大对最终返回的tmp_weights()进行调整。

具体调整方案如下:

当存在maxval为无穷大时:

当maxval为无穷大时,minval不为无穷大时,将无穷大节点设置为(maxval-minval)*100

当maxval和minval都为无穷大时,将无穷大节点设置为10000

其中CapacityWeigher类中_weigh_object()方法line 49:调用到的计算可用容量的方法calculate_virtual_free_capacity()源代码如下:

def calculate_virtual_free_capacity(total_capacity,  
                                    free_capacity,  
                                    provisioned_capacity,  
                                    thin_provisioning_support,  
                                    max_over_subscription_ratio,  
                                    reserved_percentage):  
    """Calculate the virtual free capacity based on thin provisioning support. 

    :param total_capacity:  total_capacity_gb of a host_state or pool. 
    :param free_capacity:   free_capacity_gb of a host_state or pool. 
    :param provisioned_capacity:    provisioned_capacity_gb of a host_state 
                                    or pool. 
    :param thin_provisioning_support:   thin_provisioning_support of 
                                        a host_state or a pool. 
    :param max_over_subscription_ratio: max_over_subscription_ratio of 
                                        a host_state or a pool 
    :param reserved_percentage: reserved_percentage of a host_state or 
                                a pool. 
    :returns: the calculated virtual free capacity. 
    """  
  
    total = float(total_capacity)  
    reserved = float(reserved_percentage) / 100  
  
    if thin_provisioning_support:  
        free = (total * max_over_subscription_ratio  
                - provisioned_capacity  
                - math.floor(total * reserved))  
    else:  
        # Calculate how much free space is left after taking into  
        # account the reserved space.  
        free = free_capacity - math.floor(total * reserved)  
    return free

经过calculate_virtual_free_capacity()的line 25-33可以知道:

  如果后端存储是精简(thin)的, 按照可用容量free=总容量 - 已分配 - 预留,

  如果后端存储是厚置备(thick)的,按照可用容量free=总容量 -预留。

这个在对接商业存储时会进行配置,如果不配置则默认为精简

最终会从返回的tmp_weights()各节点的可用容量来选择最优节点