观海听涛
Waiting for you

实例resize跨可用域问题

27 Jan 2016

问题:

在实际使用openstack的过程中发现实例resize操作的时候存在跨可用域的问题。

问题分析:

resize的时候找符合要求的计算结点这个工作是由nova-schduler来完成的。scheduler会对所有的计算结点做一遍filter,然后对通过filter的主机做weight。问题就出在filter这里,默认的filter是包括AvailabilityZoneFilter的,也就是说,如果计算结点不满足实例所属的az的话,那么它不会通过filter,实例当然也就不会resize到这台计算结点上,表现出来的功能就是az之间的schduler是隔离的。 AvailabilityZoneFilter的源码:

class AvailabilityZoneFilter(filters.BaseHostFilter):
    """Filters Hosts by availability zone.

    Works with aggregate metadata availability zones, using the key
    'availability_zone'
    Note: in theory a compute node can be part of multiple availability_zones
    """

    # Availability zones do not change within a request
    run_filter_once_per_request = True

    def host_passes(self, host_state, filter_properties):
        spec = filter_properties.get('request_spec', {})
        props = spec.get('instance_properties', {})
        availability_zone = props.get('availability_zone')

        if not availability_zone:
            return True

        context = filter_properties['context']
        metadata = db.aggregate_metadata_get_by_host(
                context, host_state.host, key='availability_zone')

        if 'availability_zone' in metadata:
            hosts_passes = availability_zone in metadata['availability_zone']
            host_az = metadata['availability_zone']
        else:
            hosts_passes = availability_zone == CONF.default_availability_zone
            host_az = CONF.default_availability_zone

        if not hosts_passes:
            LOG.debug("Availability Zone '%(az)s' requested. "
                      "%(host_state)s has AZs: %(host_az)s",
                      {'host_state': host_state,
                       'az': availability_zone,
                       'host_az': host_az})

        return hosts_passes

里面这个host_passes方法就是一个过滤器,每一个计算结点都要经过这个过滤器的筛选,如果host_passes返回真,则表示该计算结点通过了这个过滤器,可以进入下一个过滤器或者进行weight了,若返回假,表示该计算结点未通过过滤器,实例不会被调度到该结点上。

再详细分析一下上述代码。 下面的代码是取到实例所属的az(可用域),如果实例的az为空,即不属于任何az,那么直接返回真(所有的计算结点都可以通过这个filter):

spec = filter_properties.get('request_spec', {})
props = spec.get('instance_properties', {})
availability_zone = props.get('availability_zone')

if not availability_zone:
            return True

下面这部分是取到计算结点的az:

context = filter_properties['context']
metadata = db.aggregate_metadata_get_by_host(
        context, host_state.host, key='availability_zone')

下面这部分是比较实例的az和当前被筛选的计算结点的az是否一致,一致返回真,否则返回假(注意,有些计算结点属于默认的nova可用域)。

if 'availability_zone' in metadata:
            hosts_passes = availability_zone in metadata['availability_zone']
            host_az = metadata['availability_zone']
        else:
            hosts_passes = availability_zone == CONF.default_availability_zone
            host_az = CONF.default_availability_zone

经过分析上面的代码,以及对数据库的检查,出现问题的原因很明显是因为实例的az信息为空,导致了所有计算结点都通过了这个filter,所以resize的时候存在跨可用域的现象。

那么为什么实例的az信息会为空呢?

通过查看nova数据库中的instances表发现,有些实例az为空,有些不为空。仔细回忆想起来,这些为空的实例当时都是通过nova boot以命令行的方式来创建的,在参数中并没有指定可用域,会不会是不明确指定可用域,实例的可用域就是空的呢?后面经过实验验证了这一猜测。

为什么实例可用域为空这件事情居然没有被我发现呢?

因为通过命令行创建实例后,我在dashboard里确认了一下信息,看到实例的可用域显示正常。

难道dashboard显示实例的可用域信息不是直接从instances这个表中取到的吗?

确实不是直接从instances表中取到的。dashboard获得实例信息是请求的nova-api,经过查看dashboard与nova-api相关部分的代码,发现nova-api有扩展api这一说(具体实现还是比较复杂的,这里不深入讨论),实际上nova-api返回给dashboard实例所属的az信息是通过下面这一段代码来做的:

def get_instance_availability_zone(context, instance):
    """Return availability zone of specified instance."""
    host = str(instance.get('host'))
    if not host:
        return None

    cache_key = _make_cache_key(host)
    cache = _get_cache()
    az = cache.get(cache_key)
    if not az:
        elevated = context.elevated()
        az = get_host_availability_zone(elevated, host)
        cache.set(cache_key, az, AZ_CACHE_SECONDS)
    return az

这段代码的逻辑是先得到实例在哪台计算结点上,然后再取得这个计算结点属于哪个可用域,然后做为实例的可用域信息返回给dashboard。

原来是这个扩展api返回的数据欺骗了我的双眼。。。

解决方法:

以后用命令行创建实例的时候一定要明确指定可用域信息。对于已经存在的实例,可以去instances表中直接改实例的可用域信息(该方法未经过测试)。