Ein Leitfaden für Apache Mesos

1. Übersicht

Normalerweise stellen wir verschiedene Anwendungen auf demselben Maschinencluster bereit. Beispielsweise ist es heutzutage üblich, eine verteilte Verarbeitungs-Engine wie Apache Spark oder Apache Flink mit verteilten Datenbanken wie Apache Cassandra im selben Cluster zu haben.

Apache Mesos ist eine Plattform, die eine effektive gemeinsame Nutzung von Ressourcen zwischen solchen Anwendungen ermöglicht.

In diesem Artikel werden zunächst einige Probleme bei der Ressourcenzuweisung in Anwendungen erläutert, die auf demselben Cluster bereitgestellt werden. Später werden wir sehen, wie Apache Mesos eine bessere Ressourcennutzung zwischen Anwendungen bietet.

2. Freigeben des Clusters

Viele Anwendungen müssen einen Cluster gemeinsam nutzen. Im Großen und Ganzen gibt es zwei gängige Ansätze:

  • Partitionieren Sie den Cluster statisch und führen Sie auf jeder Partition eine Anwendung aus
  • Ordnen Sie einer Anwendung eine Reihe von Computern zu

Mit diesen Ansätzen können Anwendungen zwar unabhängig voneinander ausgeführt werden, es wird jedoch keine hohe Ressourcennutzung erzielt.

Stellen Sie sich beispielsweise eine Anwendung vor, die nur für einen kurzen Zeitraum ausgeführt wird, gefolgt von einem Zeitraum der Inaktivität. Da wir dieser Anwendung statische Maschinen oder Partitionen zugewiesen haben, haben wir während des inaktiven Zeitraums nicht genutzte Ressourcen.

Wir können die Ressourcennutzung optimieren, indem wir freie Ressourcen während des inaktiven Zeitraums anderen Anwendungen zuweisen.

Apache Mesos hilft bei der dynamischen Ressourcenzuweisung zwischen Anwendungen.

3. Apache Mesos

Bei beiden oben beschriebenen Ansätzen zur gemeinsamen Nutzung von Clustern kennen Anwendungen nur die Ressourcen einer bestimmten Partition oder eines bestimmten Computers, auf dem sie ausgeführt werden. Apache Mesos bietet Anwendungen jedoch eine abstrakte Ansicht aller Ressourcen im Cluster.

Wie wir gleich sehen werden, fungiert Mesos als Schnittstelle zwischen Maschinen und Anwendungen. Es stellt Anwendungen die verfügbaren Ressourcen auf allen Computern im Cluster zur Verfügung. Es aktualisiert häufig diese Informationen enthalten Ressourcen , die von Anwendungen freigegeben werden , den Beendigungsstatus erreicht hat. Auf diese Weise können Anwendungen die beste Entscheidung darüber treffen, welche Aufgabe auf welchem ​​Computer ausgeführt werden soll.

Um zu verstehen, wie Mesos funktioniert, werfen wir einen Blick auf seine Architektur:

Dieses Bild ist Teil der offiziellen Dokumentation für Mesos (Quelle). Hier sind Hadoop und MPI zwei Anwendungen, die sich den Cluster teilen.

Wir werden in den nächsten Abschnitten über jede hier gezeigte Komponente sprechen.

3.1. Mesos Meister

Der Master ist die Kernkomponente in diesem Setup und speichert den aktuellen Status der Ressourcen im Cluster. Darüber hinaus fungiert es als Orchestrator zwischen den Agenten und den Anwendungen, indem Informationen zu Ressourcen und Aufgaben übergeben werden.

Da ein Fehler im Master zum Verlust des Status von Ressourcen und Aufgaben führt, stellen wir ihn in einer Hochverfügbarkeitskonfiguration bereit. Wie im obigen Diagramm zu sehen ist, stellt Mesos zusammen mit einem Anführer Standby-Master-Daemons bereit. Diese Dämonen verlassen sich auf Zookeeper, um den Status im Falle eines Fehlers wiederherzustellen.

3.2. Mesos-Agenten

Ein Mesos-Cluster muss auf jedem Computer einen Agenten ausführen. Diese Agenten melden ihre Ressourcen regelmäßig an den Master und empfangen wiederum Aufgaben, deren Ausführung für eine Anwendung geplant ist . Dieser Zyklus wird wiederholt, nachdem die geplante Aufgabe entweder abgeschlossen ist oder verloren gegangen ist.

In den folgenden Abschnitten erfahren Sie, wie Anwendungen Aufgaben auf diesen Agenten planen und ausführen.

3.3. Mesos Frameworks

Mit Mesos können Anwendungen eine abstrakte Komponente implementieren, die mit dem Master interagiert, um die verfügbaren Ressourcen im Cluster zu empfangen und darüber hinaus Planungsentscheidungen zu treffen . Diese Komponenten werden als Frameworks bezeichnet.

Ein Mesos-Framework besteht aus zwei Unterkomponenten:

  • Planer - Ermöglicht Anwendungen das Planen von Aufgaben basierend auf den verfügbaren Ressourcen aller Agenten
  • Executor - Wird auf allen Agenten ausgeführt und enthält alle Informationen, die zum Ausführen einer geplanten Aufgabe auf diesem Agenten erforderlich sind

Dieser gesamte Prozess wird mit diesem Ablauf dargestellt:

Zunächst melden die Agenten ihre Ressourcen dem Master. Zu diesem Zeitpunkt bietet der Master diese Ressourcen allen registrierten Schedulern an. Dieser Prozess wird als Ressourcenangebot bezeichnet und wird im nächsten Abschnitt ausführlich erläutert.

Der Scheduler wählt dann den besten Agenten aus und führt verschiedene Aufgaben über den Master aus. Sobald der Executor die zugewiesene Aufgabe abgeschlossen hat, veröffentlichen die Agenten ihre Ressourcen erneut im Master. Der Master wiederholt diesen Prozess der gemeinsamen Nutzung von Ressourcen für alle Frameworks im Cluster.

Mit Mesos können Anwendungen ihren benutzerdefinierten Scheduler und Executor in verschiedenen Programmiersprachen implementieren. Eine Java-Implementierung von Scheduler muss die Scheduler- Schnittstelle implementieren :

public class HelloWorldScheduler implements Scheduler { @Override public void registered(SchedulerDriver schedulerDriver, Protos.FrameworkID frameworkID, Protos.MasterInfo masterInfo) { } @Override public void reregistered(SchedulerDriver schedulerDriver, Protos.MasterInfo masterInfo) { } @Override public void resourceOffers(SchedulerDriver schedulerDriver, List list) { } @Override public void offerRescinded(SchedulerDriver schedulerDriver, OfferID offerID) { } @Override public void statusUpdate(SchedulerDriver schedulerDriver, Protos.TaskStatus taskStatus) { } @Override public void frameworkMessage(SchedulerDriver schedulerDriver, Protos.ExecutorID executorID, Protos.SlaveID slaveID, byte[] bytes) { } @Override public void disconnected(SchedulerDriver schedulerDriver) { } @Override public void slaveLost(SchedulerDriver schedulerDriver, Protos.SlaveID slaveID) { } @Override public void executorLost(SchedulerDriver schedulerDriver, Protos.ExecutorID executorID, Protos.SlaveID slaveID, int i) { } @Override public void error(SchedulerDriver schedulerDriver, String s) { } }

Wie zu sehen ist, besteht es hauptsächlich aus verschiedenen Rückrufmethoden für die Kommunikation mit dem Master .

Ebenso muss die Implementierung eines Executors die Executor- Schnittstelle implementieren :

public class HelloWorldExecutor implements Executor { @Override public void registered(ExecutorDriver driver, Protos.ExecutorInfo executorInfo, Protos.FrameworkInfo frameworkInfo, Protos.SlaveInfo slaveInfo) { } @Override public void reregistered(ExecutorDriver driver, Protos.SlaveInfo slaveInfo) { } @Override public void disconnected(ExecutorDriver driver) { } @Override public void launchTask(ExecutorDriver driver, Protos.TaskInfo task) { } @Override public void killTask(ExecutorDriver driver, Protos.TaskID taskId) { } @Override public void frameworkMessage(ExecutorDriver driver, byte[] data) { } @Override public void shutdown(ExecutorDriver driver) { } }

Wir werden in einem späteren Abschnitt eine betriebsbereite Version des Schedulers und Executors sehen.

4. Ressourcenverwaltung

4.1. Ressourcenangebote

Wie bereits erwähnt, veröffentlichen Agenten ihre Ressourceninformationen im Master. Der Master bietet diese Ressourcen wiederum den im Cluster ausgeführten Frameworks an. Dieser Prozess wird als Ressourcenangebot bezeichnet.

Ein Ressourcenangebot besteht aus zwei Teilen - Ressourcen und Attributen.

Ressourcen werden verwendet, um Hardwareinformationen des Agentencomputers wie Speicher, CPU und Festplatte zu veröffentlichen.

Für jeden Agenten gibt es fünf vordefinierte Ressourcen:

  • Zentralprozessor
  • gpus
  • mem
  • Scheibe
  • Häfen

Die Werte für diese Ressourcen können in einem der drei Typen definiert werden:

  • Skalar - Wird verwendet, um numerische Informationen mithilfe von Gleitkommazahlen darzustellen, um Bruchwerte wie 1,5 G Speicher zuzulassen
  • Bereich - Wird verwendet, um einen Bereich von Skalarwerten darzustellen, z. B. einen Portbereich
  • Set - Wird verwendet, um mehrere Textwerte darzustellen

Standardmäßig versucht der Mesos-Agent, diese Ressourcen vom Computer aus zu erkennen.

In einigen Situationen können wir jedoch benutzerdefinierte Ressourcen auf einem Agenten konfigurieren. Die Werte für solche benutzerdefinierten Ressourcen sollten wieder in einem der oben beschriebenen Typen liegen.

Zum Beispiel können wir unseren Agenten mit folgenden Ressourcen starten:

--resources='cpus:24;gpus:2;mem:24576;disk:409600;ports:[21000-24000,30000-34000];bugs(debug_role):{a,b,c}'

Wie zu sehen ist, haben wir den Agenten mit wenigen vordefinierten Ressourcen und einer benutzerdefinierten Ressource namens bugs konfiguriert, die vom festgelegten Typ ist.

Zusätzlich zu den Ressourcen können Agenten Schlüsselwertattribute im Master veröffentlichen. Diese Attribute dienen als zusätzliche Metadaten für den Agenten und helfen Frameworks bei der Planung von Entscheidungen.

A useful example can be to add agents into different racks or zones and then schedule various tasks on the same rack or zone to achieve data locality:

--attributes='rack:abc;zone:west;os:centos5;level:10;keys:[1000-1500]'

Similar to resources, values for attributes can be either a scalar, a range, or a text type.

4.2. Resource Roles

Many modern-day operating systems support multiple users. Similarly, Mesos also supports multiple users in the same cluster. These users are known as roles. We can consider each role as a resource consumer within a cluster.

Due to this, Mesos agents can partition the resources under different roles based on different allocation strategies. Furthermore, frameworks can subscribe to these roles within the cluster and have fine-grained control over resources under different roles.

For example, consider a cluster hosting applications which are serving different users in an organization. So by dividing the resources into roles, every application can work in isolation from one another.

Additionally, frameworks can use these roles to achieve data locality.

For instance, suppose we have two applications in the cluster named producer and consumer. Here, producer writes data to a persistent volume which consumer can read afterward. We can optimize the consumer application by sharing the volume with the producer.

Since Mesos allows multiple applications to subscribe to the same role, we can associate the persistent volume with a resource role. Furthermore, the frameworks for both producer and consumer will both subscribe to the same resource role. Therefore, the consumer application can now launch the data reading task on the same volume as the producer application.

4.3. Resource Reservation

Now the question may arise as to how Mesos allocates cluster resources into different roles. Mesos allocates the resources through reservations.

There are two types of reservations:

  • Static Reservation
  • Dynamic Reservation

Static reservation is similar to the resource allocation on agent startup we discussed in the earlier sections:

 --resources="cpus:4;mem:2048;cpus(baeldung):8;mem(baeldung):4096"

The only difference here is that now the Mesos agent reserves eight CPUs and 4096m of memory for the role named baeldung.

Dynamic reservation allows us to reshuffle the resources within roles, unlike the static reservation. Mesos allows frameworks and cluster operators to dynamically change the allocation of resources via framework messages as a response to resource offer or via HTTP endpoints.

Mesos allocates all resources without any role into a default role named (*). Master offers such resources to all frameworks whether or not they have subscribed to it.

4.4. Resource Weights and Quotas

Generally, the Mesos master offers resources using a fairness strategy. It uses the weighted Dominant Resource Fairness (wDRF) to identify the roles that lack resources. The master then offers more resources to the frameworks that have subscribed to these roles.

Event though fair sharing of resources between applications is an important characteristic of Mesos, its not always necessary. Suppose a cluster hosting applications that have a low resource footprint along with those having a high resource demand. In such deployments, we would want to allocate resources based on the nature of the application.

Mesos allows frameworks to demand more resources by subscribing to roles and adding a higher value of weight for that role. Therefore, if there are two roles, one of weight 1 and another of weight 2, Mesos will allocate twice the fair share of resources to the second role.

Similar to resources, we can configure weights via HTTP endpoints.

Besides ensuring a fair share of resources to a role with weights, Mesos also ensures that the minimum resources for a role are allocated.

Mesos allows us to add quotas to the resource roles. A quota specifies the minimum amount of resources that a role is guaranteed to receive.

5. Implementing Framework

As we discussed in an earlier section, Mesos allows applications to provide framework implementations in a language of their choice. In Java, a framework is implemented using the main class – which acts as an entry point for the framework process – and the implementation of Scheduler and Executor discussed earlier.

5.1. Framework Main Class

Before we implement a scheduler and an executor, we'll first implement the entry point for our framework that:

  • Registers itself with the master
  • Provides executor runtime information to agents
  • Starts the scheduler

We'll first add a Maven dependency for Mesos:

 org.apache.mesos mesos 0.28.3 

Next, we'll implement the HelloWorldMain for our framework. One of the first things we'll do is to start the executor process on the Mesos agent:

public static void main(String[] args) { String path = System.getProperty("user.dir") + "/target/libraries2-1.0.0-SNAPSHOT.jar"; CommandInfo.URI uri = CommandInfo.URI.newBuilder().setValue(path).setExtract(false).build(); String helloWorldCommand = "java -cp libraries2-1.0.0-SNAPSHOT.jar com.baeldung.mesos.executors.HelloWorldExecutor"; CommandInfo commandInfoHelloWorld = CommandInfo.newBuilder() .setValue(helloWorldCommand) .addUris(uri) .build(); ExecutorInfo executorHelloWorld = ExecutorInfo.newBuilder() .setExecutorId(Protos.ExecutorID.newBuilder() .setValue("HelloWorldExecutor")) .setCommand(commandInfoHelloWorld) .setName("Hello World (Java)") .setSource("java") .build(); }

Here, we first configured the executor binary location. Mesos agent would download this binary upon framework registration. Next, the agent would run the given command to start the executor process.

Next, we'll initialize our framework and start the scheduler:

FrameworkInfo.Builder frameworkBuilder = FrameworkInfo.newBuilder() .setFailoverTimeout(120000) .setUser("") .setName("Hello World Framework (Java)"); frameworkBuilder.setPrincipal("test-framework-java"); MesosSchedulerDriver driver = new MesosSchedulerDriver(new HelloWorldScheduler(), frameworkBuilder.build(), args[0]);

Finally, we'll start the MesosSchedulerDriver that registers itself with the Master. For successful registration, we must pass the IP of the Master as a program argument args[0] to this main class:

int status = driver.run() == Protos.Status.DRIVER_STOPPED ? 0 : 1; driver.stop(); System.exit(status);

In the class shown above, CommandInfo, ExecutorInfo, and FrameworkInfo are all Java representations of protobuf messages between master and frameworks.

5.2. Implementing Scheduler

Since Mesos 1.0, we can invoke the HTTP endpoint from any Java application to send and receive messages to the Mesos master. Some of these messages include, for example, framework registration, resource offers, and offer rejections.

For Mesos 0.28 or earlier, we need to implement the Scheduler interface:

For the most part, we'll only focus on the resourceOffers method of the Scheduler. Let's see how a scheduler receives resources and initializes tasks based on them.

First, we'll see how the scheduler allocates resources for a task:

@Override public void resourceOffers(SchedulerDriver schedulerDriver, List list) { for (Offer offer : list) { List tasks = new ArrayList(); Protos.TaskID taskId = Protos.TaskID.newBuilder() .setValue(Integer.toString(launchedTasks++)).build(); System.out.println("Launching printHelloWorld " + taskId.getValue() + " Hello World Java"); Protos.Resource.Builder cpus = Protos.Resource.newBuilder() .setName("cpus") .setType(Protos.Value.Type.SCALAR) .setScalar(Protos.Value.Scalar.newBuilder() .setValue(1)); Protos.Resource.Builder mem = Protos.Resource.newBuilder() .setName("mem") .setType(Protos.Value.Type.SCALAR) .setScalar(Protos.Value.Scalar.newBuilder() .setValue(128));

Here, we allocated 1 CPU and 128M of memory for our task. Next, we'll use the SchedulerDriver to launch the task on an agent:

 TaskInfo printHelloWorld = TaskInfo.newBuilder() .setName("printHelloWorld " + taskId.getValue()) .setTaskId(taskId) .setSlaveId(offer.getSlaveId()) .addResources(cpus) .addResources(mem) .setExecutor(ExecutorInfo.newBuilder(helloWorldExecutor)) .build(); List offerIDS = new ArrayList(); offerIDS.add(offer.getId()); tasks.add(printHelloWorld); schedulerDriver.launchTasks(offerIDS, tasks); } }

Alternatively, Scheduler often finds the need to reject resource offers. For example, if the Scheduler cannot launch a task on an agent due to lack of resources, it must immediately decline that offer:

schedulerDriver.declineOffer(offer.getId());

5.3. Implementing Executor

As we discussed earlier, the executor component of the framework is responsible for executing application tasks on the Mesos agent.

We used the HTTP endpoints for implementing Scheduler in Mesos 1.0. Likewise, we can use the HTTP endpoint for the executor.

In an earlier section, we discussed how a framework configures an agent to start the executor process:

java -cp libraries2-1.0.0-SNAPSHOT.jar com.baeldung.mesos.executors.HelloWorldExecutor

Notably, this command considers HelloWorldExecutor as the main class. We'll implement this main method to initialize the MesosExecutorDriver that connects with Mesos agents to receive tasks and share other information like task status:

public class HelloWorldExecutor implements Executor { public static void main(String[] args) { MesosExecutorDriver driver = new MesosExecutorDriver(new HelloWorldExecutor()); System.exit(driver.run() == Protos.Status.DRIVER_STOPPED ? 0 : 1); } }

The last thing to do now is to accept tasks from the framework and launch them on the agent. The information to launch any task is self-contained within the HelloWorldExecutor:

public void launchTask(ExecutorDriver driver, TaskInfo task) { Protos.TaskStatus status = Protos.TaskStatus.newBuilder() .setTaskId(task.getTaskId()) .setState(Protos.TaskState.TASK_RUNNING) .build(); driver.sendStatusUpdate(status); System.out.println("Execute Task!!!"); status = Protos.TaskStatus.newBuilder() .setTaskId(task.getTaskId()) .setState(Protos.TaskState.TASK_FINISHED) .build(); driver.sendStatusUpdate(status); }

Of course, this is just a simple implementation, but it explains how an executor shares task status with the master at every stage and then executes the task before sending a completion status.

In some cases, executors can also send data back to the scheduler:

String myStatus = "Hello Framework"; driver.sendFrameworkMessage(myStatus.getBytes());

6. Conclusion

In diesem Artikel haben wir kurz die gemeinsame Nutzung von Ressourcen zwischen Anwendungen erläutert, die im selben Cluster ausgeführt werden. Wir haben auch diskutiert, wie Apache Mesos Anwendungen dabei hilft, eine maximale Auslastung mit einer abstrakten Ansicht der Clusterressourcen wie CPU und Speicher zu erreichen.

Später diskutierten wir die dynamische Verteilung von Ressourcen zwischen Anwendungen basierend auf verschiedenen Fairness-Richtlinien und Rollen. Mit Mesos können Anwendungen Planungsentscheidungen basierend auf Ressourcenangeboten von Mesos-Agenten im Cluster treffen.

Schließlich sahen wir eine Implementierung des Mesos-Frameworks in Java.

Wie üblich sind alle Beispiele auf GitHub verfügbar.