[ English | Indonesia | Deutsch | 日本語 ]

Anpassen des OpenStack Compute (nova) Schedulers

Viele OpenStack-Projekte ermöglichen die Anpassung spezifischer Funktionen über eine Treiberarchitektur. Sie können einen Treiber schreiben, der einer bestimmten Schnittstelle entspricht, und ihn über die Konfiguration anschließen. So können Sie beispielsweise ganz einfach einen neuen Scheduler für Compute einbinden. Die vorhandenen Scheduler für Compute sind vollständig ausgestattet und gut dokumentiert unter Scheduling. Abhängig von den Anwendungsfällen Ihres Benutzers entsprechen die vorhandenen Planer jedoch möglicherweise nicht Ihren Anforderungen. Möglicherweise müssen Sie einen neuen Scheduler erstellen.

Um einen Scheduler zu erstellen, müssen Sie von der Klasse nova.scheduler.driver.driver.scheduleuler erben. Von den fünf Methoden, die Sie überschreiben können, müssen Sie die beiden Methoden, die mit einem Sternchen (*) gekennzeichnet sind, überschreiben:

  • update_service_capabilities

  • hosts_up

  • group_hosts

  • * schedule_run_instance

  • * select_destinations

Um die Anpassung von OpenStack zu demonstrieren, erstellen wir ein Beispiel für einen Compute-Scheduler, der eine Instanz zufällig auf einer Teilmenge von Hosts platziert, abhängig von der ursprünglichen IP-Adresse der Anfrage und dem Präfix des Hostnamens. Ein solches Beispiel könnte nützlich sein, wenn Sie eine Gruppe von Benutzern in einem Subnetz haben und möchten, dass alle ihre Instanzen innerhalb einer Teilmenge Ihrer Hosts starten.

Warnung

Dieses Beispiel dient nur zur Veranschaulichung. Es sollte nicht als Scheduler für Compute ohne weitere Entwicklung und Tests verwendet werden.

Wenn Sie an der Bildschirmsitzung teilnehmen, die stack.sh mit screen -r stack beginnt, werden Sie mit vielen Bildschirmfenstern begrüßt:

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

Eine Shell, in der Sie etwas Arbeit erledigen können

key

Der Keystone-Service

horizon

Die Webanwendung des Horizon Dashboards

n-{name}

Die nova-Dienste

n-sch

Der nova Scheduler-Dienst

Erstellen Sie den Scheduler und schließen Sie ihn über die Konfiguration an

  1. Der Code für OpenStack befindet sich in /opt/stack, also gehen Sie in das Verzeichnis nova und bearbeiten Sie Ihr Scheduler-Modul. Wechseln Sie in das Verzeichnis, in dem nova installiert ist:

    $ cd /opt/stack/nova
    
  2. Erstellen Sie die ip_scheduler.py Python-Quellcode-Datei:

    $ vim nova/scheduler/ip_scheduler.py
    
  3. Der untenstehende Code ist ein Treiber, der Server auf Hosts basierend auf der IP-Adresse plant, wie am Anfang des Abschnitts beschrieben. Kopieren Sie den Code in die ip_scheduler.py. Wenn Sie fertig sind, speichern und schließen Sie die Datei.

    # 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)
    

    Es gibt viele nützliche Informationen in context, `request_spec und filter_properties, die Sie verwenden können, um zu entscheiden, wo Sie die Instanz planen möchten. Um mehr darüber zu erfahren, welche Eigenschaften verfügbar sind, können Sie die folgenden Protokollanweisungen in die Methode schedule_run_instance des obigen Schedulers einfügen:

    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. Um diesen Scheduler in nova einzubinden, bearbeiten Sie eine Konfigurationsdatei, /etc/nova/nova/nova.conf:

    $ vim /etc/nova/nova.conf
    
  5. Suchen Sie die Konfiguration scheduler_driver und ändern Sie sie so:

    scheduler_driver=nova.scheduler.ip_scheduler.IPScheduler
    
  6. Starten Sie den Dienst nova scheduler neu, damit nova Ihren Scheduler verwenden kann. Wechseln Sie zunächst zum Bildschirm „n-sch“:

    1. Drücken Sie Strg+A gefolgt von 9.

    2. Drücken Sie Strg+A gefolgt von N, bis Sie den Bildschirm n-sch erreichen.

    3. Drücken Sie Strg+C, um den Dienst zu beenden.

    4. Drücken Sie Pfeil nach oben, um den letzten Befehl aufzurufen.

    5. Drücken Sie Enter, um es auszuführen.

  7. Testen Sie Ihren Scheduler mit der nova CLI. Beginnen Sie mit dem Umschalten auf den Bildschirm Shell und gehen Sie anschließend zurück zum Bildschirm n-sch, um die Protokollausgabe zu überprüfen:

    1. Drücken Sie Strg+A gefolgt von 0.

    2. Stellen Sie sicher, dass Sie sich im Verzeichnis devstack befinden:

      $ cd /root/devstack
      
    3. Quelle openrc, um Ihre Umgebungsvariablen für die CLI einzurichten:

      $ . openrc
      
    4. Fügen Sie die Image-ID für das einzige installierte Image in eine Umgebungsvariable ein:

      $ IMAGE_ID=`openstack image list | egrep cirros | egrep -v "kernel|ramdisk" | awk '{print $2}'`
      
    5. Starten Sie einen Testserver:

      $ openstack server create --flavor 1 --image $IMAGE_ID scheduler-test
      
  8. Wechseln Sie zurück zum Bildschirm n-sch. Unter den Protokollanweisungen sehen Sie die Zeile:

    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
    

Warnung

Funktionstests wie dieser sind kein Ersatz für richtige Geräte- und Integrationstests, aber sie dienen dazu, Ihnen den Einstieg zu erleichtern.

Ein ähnliches Muster kann in anderen Projekten, die die Treiberarchitektur verwenden, verfolgt werden. Erstellen Sie einfach ein Modul und eine Klasse, die der Treiberoberfläche entsprechen, und schließen Sie es über die Konfiguration an. Ihr Code läuft bei Verwendung dieser Funktion und kann bei Bedarf andere Dienste aufrufen. Es wird kein Projektkern-Code berührt. Suchen Sie nach einem „Treiber“-Wert in den Konfigurationsdateien des Projekts unter /etc/<project>, um Projekte zu identifizieren, die eine Treiberarchitektur verwenden.

Wenn Ihr Scheduler fertig ist, empfehlen wir Ihnen, ihn Open Source zu verwenden und die Community auf der OpenStack-Mailingliste darüber zu informieren. Vielleicht benötigen andere die gleiche Funktionalität. Sie können Ihren Code verwenden, Feedback geben und möglicherweise einen Beitrag leisten. Wenn es genügend Unterstützung dafür gibt, können Sie vielleicht vorschlagen, dass sie dem offiziellen Compute schedulers hinzugefügt wird.