OpenStack Compute (nova) スケジューラーのカスタマイズ

OpenStack Compute (nova) スケジューラーのカスタマイズ

Many OpenStack projects allow for customization of specific features using a driver architecture. You can write a driver that conforms to a particular interface and plug it in through configuration. For example, you can easily plug in a new scheduler for Compute. The existing schedulers for Compute are feature full and well documented at Scheduling. However, depending on your user’s use cases, the existing schedulers might not meet your requirements. You might need to create a new scheduler.

スケジューラーを作成するには、 nova.scheduler.driver.Scheduler クラスを継承しなければなりません。オーバーライド可能な 5 つのメソッドのうち、以下のアスタリスク (*) で示される 2 つのメソッドはオーバーライド しなければなりません

  • update_service_capabilities
  • hosts_up
  • group_hosts
  • * schedule_run_instance
  • * select_destinations

OpenStack のカスタマイズをデモするために、リクエストの送信元IPアドレスとホスト名のプレフィックスに基づいてインスタンスを一部のホストにランダムに配置するような Compute のスケジューラーの例を作成します。この例は、1つのユーザのグループが1つのサブネットにおり、インスタンスをホスト群の中の一部のサブネットで起動したい場合に有用です。

警告

この例は実証目的のみのためにあります。さらなる作りこみとテストなしで、Compute のスケジューラーとして使用するべきではありません。

stack.shscreen -r stack で作成したセッションに join すると、多数の screen ウィンドウが見えます。

0$ shell*  1$ key  2$ horizon  ...  9$ n-api  ...  14$ n-sch ...
shell
作業を行うためのシェル
key
keystone サービス
horizon
horizon dashboard 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
    #
    #         http://www.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. n-sch 画面が表示されるまで Ctrl+A に続けて N を押します。
    3. Ctrl+C を押し、サービスを強制停止します。
    4. 上矢印キー を押し、最後のコマンドを表示させます。
    5. Enter キーを押し、実行します。
  7. nova の CLI でスケジューラーのテストをしてください。 shell の screen セッションに切り替えてテストを開始し、 n-sch screen セッションにもどってログ出力をチェックして終了します。

    1. Ctrl+A に続けて 0 を押します。

    2. devstack ディレクトリーにいることを確認します。

      $ cd /root/devstack
      
    3. 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 画面に切り替えます。ログ出力の中に、以下の行を見つけられます。

    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
    

警告

このような機能試験は、正しいユニットテストと結合テストの代わりになるものではありませんが、作業を開始することはできます。

ドライバ・アーキテクチャーを使う他のプロジェクトで、類似のパターンに従うことができます。単純に、そのドライバーインタフェースに従うモジュールとクラスを作成し、環境定義によって組み込んでください。あなたのコードはその機能が使われた時に実行され、必要に応じて他のサービスを呼び出します。プロジェクトのコアコードは一切修正しません。ドライバーアーキテクチャーを使っているプロジェクトを確認するには、/etc/<project> に格納されている、プロジェクトの .conf 設定ファイルの中で driver 変数を探してください。

あなたのスケジューラーが完成したら、オープンソースにし、OpenStack メーリングリストでコミュニティに知らせることをお薦めします。もしかしたら他の人も同じ機能を必要としているかもしれません。彼らはあなたのコードを使い、フィードバックし、おそらくコントリビュートするでしょう。もし十分な支持があれば、もしかしたら公式な Compute スケジューラー への追加を提案してもよいでしょう。

Creative Commons Attribution 3.0 License

Except where otherwise noted, this document is licensed under Creative Commons Attribution 3.0 License. See all OpenStack Legal Documents.