[ 英语 | 日本語 | Deutsch | Indonesia ]

定制 OpenStack 计算 (nova) 调度器

许多 OpenStack 项目允许使用驱动程序架构定制特定功能。您可以编写符合特定接口的驱动程序,并通过配置将其插入。例如,您可以轻松地为计算插入新的调度器。计算的现有调度器功能齐全,并在 调度 中有充分的文档记录。但是,根据您的用户用例,现有的调度器可能无法满足您的要求。您可能需要创建一个新的调度器。

要创建调度器,您必须从类 nova.scheduler.driver.Scheduler 继承。在您可以重写五个方法中,您必须重写以下标有星号 (*) 的两个方法

  • update_service_capabilities

  • hosts_up

  • group_hosts

  • * schedule_run_instance

  • * select_destinations

为了演示 OpenStack 的定制,我们将创建一个计算调度器的示例,该调度器根据请求的源 IP 地址和主机名的前缀,将实例随机放置在主机的一个子集上。当您有一组位于子网上的用户,并且希望他们的所有实例都启动在您主机的一个子集内时,这样的示例可能很有用。

警告

此示例仅用于说明目的。在进一步开发和测试之前,不应将其用作计算的调度器。

当您加入 stack.sh 使用 screen -r stack 启动的 screen 会话时,您会看到许多 screen 窗口

0$ shell*  1$ key  2$ horizon  ...  9$ n-api  ...  14$ n-sch ...
shell

一个您可以完成一些工作的 shell

key

keystone 服务

horizon

horizon 仪表板 Web 应用程序

n-{name}

nova 服务

n-sch

nova 调度器服务

要创建调度器并通过配置将其插入

  1. OpenStack 的代码位于 /opt/stack 中,因此转到 nova 目录并编辑您的调度器模块。更改到 nova 安装的目录

    $ cd /opt/stack/nova
    
  2. 创建 ip_scheduler.py Python 源代码文件

    $ vim nova/scheduler/ip_scheduler.py
    
  3. 下面显示的代码是一个驱动程序,它将根据 IP 地址调度服务器到主机,如本节开头所述。将代码复制到 ip_scheduler.py 中。完成后,保存并关闭文件。

    # vim: tabstop=4 shiftwidth=4 softtabstop=4
    # Copyright (c) 2014 OpenStack Foundation
    # All Rights Reserved.
    #
    #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    #    not use this file except in compliance with the License. You may obtain
    #    a copy of the License at
    #
    #         https://apache.org/licenses/LICENSE-2.0
    #
    #    Unless required by applicable law or agreed to in writing, software
    #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    #    License for the specific language governing permissions and limitations
    #    under the License.
    
    """
    IP Scheduler implementation
    """
    
    import random
    
    from oslo_config import cfg
    
    from nova.compute import rpcapi as compute_rpcapi
    from nova import exception
    from nova.openstack.common import log as logging
    from nova.openstack.common.gettextutils import _
    from nova.scheduler import driver
    
    CONF = cfg.CONF
    CONF.import_opt('compute_topic', 'nova.compute.rpcapi')
    LOG = logging.getLogger(__name__)
    
    class IPScheduler(driver.Scheduler):
        """
        Implements Scheduler as a random node selector based on
        IP address and hostname prefix.
        """
    
        def __init__(self, *args, **kwargs):
            super(IPScheduler, self).__init__(*args, **kwargs)
            self.compute_rpcapi = compute_rpcapi.ComputeAPI()
    
        def _filter_hosts(self, request_spec, hosts, filter_properties,
            hostname_prefix):
            """Filter a list of hosts based on hostname prefix."""
    
            hosts = [host for host in hosts if host.startswith(hostname_prefix)]
            return hosts
    
        def _schedule(self, context, topic, request_spec, filter_properties):
            """Picks a host that is up at random."""
    
            elevated = context.elevated()
            hosts = self.hosts_up(elevated, topic)
            if not hosts:
                msg = _("Is the appropriate service running?")
                raise exception.NoValidHost(reason=msg)
    
            remote_ip = context.remote_address
    
            if remote_ip.startswith('10.1'):
                hostname_prefix = 'doc'
            elif remote_ip.startswith('10.2'):
                hostname_prefix = 'ops'
            else:
                hostname_prefix = 'dev'
    
            hosts = self._filter_hosts(request_spec, hosts, filter_properties,
                hostname_prefix)
            if not hosts:
                msg = _("Could not find another compute")
                raise exception.NoValidHost(reason=msg)
    
            host = random.choice(hosts)
            LOG.debug("Request from %(remote_ip)s scheduled to %(host)s" % locals())
    
            return host
    
        def select_destinations(self, context, request_spec, filter_properties):
            """Selects random destinations."""
            num_instances = request_spec['num_instances']
            # NOTE(timello): Returns a list of dicts with 'host', 'nodename' and
            # 'limits' as keys for compatibility with filter_scheduler.
            dests = []
            for i in range(num_instances):
                host = self._schedule(context, CONF.compute_topic,
                        request_spec, filter_properties)
                host_state = dict(host=host, nodename=None, limits=None)
                dests.append(host_state)
    
            if len(dests) < num_instances:
                raise exception.NoValidHost(reason='')
            return dests
    
        def schedule_run_instance(self, context, request_spec,
                                  admin_password, injected_files,
                                  requested_networks, is_first_time,
                                  filter_properties, legacy_bdm_in_spec):
            """Create and run an instance or instances."""
            instance_uuids = request_spec.get('instance_uuids')
            for num, instance_uuid in enumerate(instance_uuids):
                request_spec['instance_properties']['launch_index'] = num
                try:
                    host = self._schedule(context, CONF.compute_topic,
                                          request_spec, filter_properties)
                    updated_instance = driver.instance_update_db(context,
                            instance_uuid)
                    self.compute_rpcapi.run_instance(context,
                            instance=updated_instance, host=host,
                            requested_networks=requested_networks,
                            injected_files=injected_files,
                            admin_password=admin_password,
                            is_first_time=is_first_time,
                            request_spec=request_spec,
                            filter_properties=filter_properties,
                            legacy_bdm_in_spec=legacy_bdm_in_spec)
                except Exception as ex:
                    # NOTE(vish): we don't reraise the exception here to make sure
                    #             that all instances in the request get set to
                    #             error properly
                    driver.handle_schedule_error(context, ex, instance_uuid,
                                                 request_spec)
    

    contextrequest_specfilter_properties 中有很多有用的信息,您可以使用它们来决定在哪里调度实例。要了解更多有关可用属性的信息,您可以将以下日志语句插入调度器的 schedule_run_instance 方法中

    LOG.debug("context = %(context)s" % {'context': context.__dict__})
    LOG.debug("request_spec = %(request_spec)s" % locals())
    LOG.debug("filter_properties = %(filter_properties)s" % locals())
    
  4. 要将此调度器插入 nova,请编辑一个配置文件,/etc/nova/nova.conf

    $ vim /etc/nova/nova.conf
    
  5. 找到 scheduler_driver 配置并像这样更改它

    scheduler_driver=nova.scheduler.ip_scheduler.IPScheduler
    
  6. 重新启动 nova 调度器服务,以使 nova 使用您的调度器。首先切换到 n-sch screen

    1. 按下 Ctrl+A,然后按下 9

    2. 按下 Ctrl+A,然后按下 N,直到到达 n-sch screen。

    3. 按下 Ctrl+C 以终止该服务。

    4. 按下 Up Arrow 以调出上一个命令。

    5. 按下 Enter 以运行它。

  7. 使用 nova CLI 测试您的调度器。首先切换到 shell screen,最后切换回 n-sch screen 以检查日志输出

    1. 按下 Ctrl+A,然后按下 0

    2. 确保您位于 devstack 目录中

      $ cd /root/devstack
      
    3. source openrc 以设置 CLI 的环境变量

      $ . openrc
      
    4. 将唯一已安装的图像的图像 ID 放入环境变量中

      $ IMAGE_ID=`openstack image list | egrep cirros | egrep -v "kernel|ramdisk" | awk '{print $2}'`
      
    5. 启动测试服务器

      $ openstack server create --flavor 1 --image $IMAGE_ID scheduler-test
      
  8. 切换回 n-sch screen。在日志语句中,您将看到以下行

    2014-01-23 19:57:47.262 DEBUG nova.scheduler.ip_scheduler
    [req-... demo demo] Request from xx.xx.xx.xx scheduled to devstack-havana
    _schedule /opt/stack/nova/nova/scheduler/ip_scheduler.py:76
    

警告

像这样的功能测试不能替代适当的单元和集成测试,但它可以帮助您入门。

可以使用相同的模式在其他使用驱动程序架构的项目中进行操作。只需创建一个符合驱动程序接口的模块和类,并通过配置将其插入。当使用该功能时,您的代码会运行并可以根据需要调用其他服务。不会触碰项目核心代码。在项目的 .conf 配置文件中查找“driver”值,位于 /etc/<project> 中,以识别使用驱动程序架构的项目。

当您的调度器完成后,我们鼓励您将其开源并在 OpenStack 邮件列表中告知社区。也许其他人需要相同的功能。他们可以使用您的代码、提供反馈并可能做出贡献。如果存在足够的支持,也许您可以建议将其添加到官方计算 调度器 中。