1. 概要

通常、さまざまなアプリケーションを同じマシンクラスターにデプロイします。 たとえば、最近では、 ApacheSparkApacheFlink のような分散処理エンジンと、 ApacheCassandraのような分散データベースが同じクラスター内にあるのが一般的です。

Apache Mesosは、そのようなアプリケーション間で効果的なリソース共有を可能にするプラットフォームです。

この記事では、最初に、同じクラスターにデプロイされたアプリケーション内のリソース割り当てのいくつかの問題について説明します。 後で、ApacheMesosがアプリケーション間のリソース使用率を向上させる方法を説明します。

2. クラスターの共有

多くのアプリケーションはクラスターを共有する必要があります。 概して、2つの一般的なアプローチがあります。

  • クラスタを静的にパーティション化し、各パーティションでアプリケーションを実行します
  • 一連のマシンをアプリケーションに割り当てます

これらのアプローチでは、アプリケーションを互いに独立して実行できますが、高いリソース使用率は達成されません。

たとえば、次のようなアプリケーションについて考えてみます。 短時間だけ実行され、その後に非アクティブな期間が続きます。 これで、静的マシンまたはパーティションをこのアプリケーションに割り当てたので、次のようになります。 非アクティブ期間中に未使用のリソース。

非アクティブ期間中に空きリソースを他のアプリケーションに再割り当てすることで、リソース使用率を最適化できます。

Apache Mesosは、アプリケーション間の動的なリソース割り当てを支援します。

3. Apache Mesos

上で説明した両方のクラスター共有アプローチでは、アプリケーションは、実行している特定のパーティションまたはマシンのリソースのみを認識します。 ただし、Apache Mesosは、クラスター内のすべてのリソースの抽象的なビューをアプリケーションに提供します。

後で説明するように、Mesosはマシンとアプリケーション間のインターフェースとして機能します。 アプリケーションにクラスタ内のすべてのマシンで利用可能なリソース。 これこの情報を頻繁に更新して、アプリケーションによって解放されたリソースを含めます完了ステータスに達したもの。 これにより、アプリケーションは、どのマシンでどのタスクを実行するかについて最良の決定を下すことができます。

Mesosがどのように機能するかを理解するために、そのアーキテクチャを見てみましょう。

この画像は、Mesos( source )の公式ドキュメントの一部です。 ここで、HadoopMPIは、クラスターを共有する2つのアプリケーションです。

次のいくつかのセクションでは、ここに示されている各コンポーネントについて説明します。

3.1. メソスマスター

マスターはこのセットアップのコアコンポーネントであり、リソースの現在の状態をクラスターに格納します。 さらに、リソースやタスクなどに関する情報を渡すことにより、エージェントとアプリケーション間のオーケストレーターとして機能します。

マスターに障害が発生すると、リソースとタスクに関する状態が失われるため、高可用性構成で展開します。 上の図からわかるように、Mesosはスタンバイマスターデーモンを1つのリーダーとともに展開します。 これらのデーモンは、障害が発生した場合に状態を回復するためにZookeeperに依存しています。

3.2. Mesosエージェント

Mesosクラスターは、すべてのマシンでエージェントを実行する必要があります。 これらのエージェントは、リソースを定期的にマスターに報告し、次にアプリケーションが実行するようにスケジュールしたタスクを受信します。 このサイクルは、スケジュールされたタスクが完了または失われた後に繰り返されます。

次のセクションでは、アプリケーションがこれらのエージェントでタスクをスケジュールおよび実行する方法を説明します。

3.3. Mesosフレームワーク

Mesosを使用すると、アプリケーションはマスターと対話する抽象コンポーネントを実装して、クラスターで使用可能なリソースを受信し、さらにそれらに基づいてスケジューリングを決定することができます。 これらのコンポーネントはフレームワークと呼ばれます。

Mesosフレームワークは、次の2つのサブコンポーネントで構成されています。

  • Scheduler –アプリケーションがすべてのエージェントで利用可能なリソースに基づいてタスクをスケジュールできるようにします
  • Executor –すべてのエージェントで実行され、そのエージェントでスケジュールされたタスクを実行するために必要なすべての情報が含まれています

このプロセス全体は、次のフローで表されます。

 

まず、エージェントはリソースをマスターに報告します。 この時点で、マスターはこれらのリソースを登録済みのすべてのスケジューラーに提供します。 このプロセスはリソースオファーと呼ばれ、次のセクションで詳しく説明します。

次に、スケジューラーは最適なエージェントを選択し、マスターを介してそのエージェントに対してさまざまなタスクを実行します。 エグゼキュータが割り当てられたタスクを完了するとすぐに、エージェントはリソースをマスターに再公開します。 マスターは、クラスター内のすべてのフレームワークに対して、このリソース共有プロセスを繰り返します。

Mesosを使用すると、アプリケーションはさまざまなプログラミング言語でカスタムスケジューラとエグゼキュータ実装できます。 スケジューラーのJava実装は、実装スケジューラーインターフェースでなければなりません。

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<Offer> 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) {
    }
}

ご覧のとおり、特にマスターと通信するためのさまざまなコールバックメソッドで構成されています。

同様に、エグゼキュータの実装は、エグゼキュータインターフェイスを実装する必要があります。

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) {
    }
}

スケジューラーとエグゼキューターの操作バージョンについては、後のセクションで説明します。

4. 資源管理

4.1. リソースオファー

前に説明したように、エージェントはリソース情報をマスターに公開します。 次に、マスターはこれらのリソースをクラスターで実行されているフレームワークに提供します。 このプロセスは、リソースオファーとして知られています。

リソースオファーは、リソースと属性の2つの部分で構成されます。

リソースは、メモリ、CPU、ディスクなどのエージェントマシンのハードウェア情報を公開するために使用されます。

エージェントごとに5つの事前定義されたリソースがあります。

  • CPU
  • GPU
  • mem
  • ディスク
  • ポート

これらのリソースの値は、次の3つのタイプのいずれかで定義できます。

  • スカラー–浮動小数点数を使用して数値情報を表し、1.5Gのメモリなどの小数値を許可するために使用されます
  • Range –スカラー値の範囲を表すために使用されます–たとえば、ポート範囲
  • Set –複数のテキスト値を表すために使用されます

デフォルトでは、Mesosエージェントはマシンからこれらのリソースを検出しようとします。

ただし、状況によっては、エージェントにカスタムリソースを構成できます。 このようなカスタムリソースの値は、上記のいずれかのタイプである必要があります。

たとえば、次のリソースを使用してエージェントを開始できます。

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

ご覧のとおり、事前定義されたリソースのいくつかと、セットタイプのバグという名前の1つのカスタムリソースを使用してエージェントを構成しました。

リソースに加えて、エージェントはキー値属性をマスターに公開できます。 これらの属性は、エージェントの追加のメタデータとして機能し、スケジューリングの決定におけるフレームワークを支援します。

有用な例としては、エージェントを異なるラックまたはゾーンに追加してから、同じラックまたはゾーンでさまざまなタスクをスケジュールしてデータの局所性を実現することができます。

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

リソースと同様に、属性の値は、スカラー、範囲、またはテキストタイプのいずれかになります。

4.2. リソースの役割

現代の多くのオペレーティングシステムは、複数のユーザーをサポートしています。 同様に、Mesosも同じクラスター内の複数のユーザーをサポートします。 これらのユーザーはロールとして知られています。 各役割は、クラスター内のリソースコンシューマーと見なすことができます。

このため、Mesosエージェントは、さまざまな割り当て戦略に基づいて、さまざまな役割の下でリソースを分割できます。 さらに、フレームワークはクラスター内でこれらの役割をサブスクライブし、さまざまな役割の下でリソースをきめ細かく制御できます。

たとえば、組織内のさまざまなユーザーにサービスを提供しているクラスターホスティングアプリケーションについて考えてみます。 したがって、リソースを役割に分割することにより、すべてのアプリケーションを互いに分離して動作させることができます。

さらに、フレームワークはこれらの役割を使用してデータの局所性を実現できます。

たとえば、クラスター内に2つのアプリケーションがあるとします。 プロデューサー消費者。 ここ、 プロデューサー永続ボリュームにデータを書き込みます。 消費者後で読むことができます。 ボリュームをプロデューサーと共有することで、コンシューマーアプリケーションを最適化できます。

Mesosでは複数のアプリケーションが同じロールにサブスクライブできるため、永続ボリュームをリソースロールに関連付けることができます。 さらに、プロデューサーコンシューマーの両方のフレームワークは、両方とも同じリソースロールにサブスクライブします。 したがって、コンシューマーアプリケーションは、プロデューサーアプリケーションと同じボリュームでデータ読み取りタスクを起動できるようになりました。

4.3. リソース予約

ここで、Mesosがクラスターリソースをさまざまな役割にどのように割り当てるかについて疑問が生じる可能性があります。 Mesosは、予約を通じてリソースを割り当てます。

予約には2つのタイプがあります。

  • 静的予約
  • 動的予約

静的予約は、前のセクションで説明したエージェント起動時のリソース割り当てに似ています。

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

ここでの唯一の違いは、Mesosエージェントがbaeldungという名前のロール用に8つのCPUと4096mのメモリを予約していることです。

動的予約では、静的予約とは異なり、ロール内のリソースを再シャッフルできます。 Mesosを使用すると、フレームワークとクラスターオペレーターは、リソース提供への応答としてフレームワークメッセージを介して、またはHTTPエンドポイントを介してリソースの割り当てを動的に変更できます。

Mesosは、役割のないすべてのリソースを(*)という名前のデフォルトの役割に割り当てます。 マスターは、サブスクライブしているかどうかに関係なく、すべてのフレームワークにそのようなリソースを提供します。

4.4. リソースの重みと割り当て

一般的に、Mesosマスターは公平性戦略を使用してリソースを提供します。 加重ドミナントリソースフェアネス(wDRF)を使用して、リソースが不足している役割を識別します。 次に、マスターは、これらの役割にサブスクライブしているフレームワークにより多くのリソースを提供します。

イベントは、アプリケーション間でのリソースの公平な共有がMesosの重要な特性ですが、必ずしも必要ではありません。 リソースのフットプリントが低いアプリケーションと、リソースの需要が高いアプリケーションをホストしているクラスターを想定します。 このような展開では、アプリケーションの性質に基づいてリソースを割り当てたいと思います。

Mesosを使用すると、フレームワークは、ロールにサブスクライブし、そのロールに高いウェイト値を追加することで、より多くのリソースを要求できます。したがって、 2つのロール(ウェイト1とウェイト2の1つ)がある場合、Mesosリソースの公平なシェアの2倍を2番目の役割に割り当てます。

リソースと同様に、HTTPエンドポイントを介して重みを構成できます。

Mesosは、ウェイトのある役割にリソースを公平に配分するだけでなく、次のことも保証します。 ロールの最小リソースが割り当てられます。

Mesosは私たちがすることを可能にしますリソースロールにクォータを追加します。 クォータは指定しますロールが受け取ることが保証されているリソースの最小量

5. フレームワークの実装

前のセクションで説明したように、Mesosを使用すると、アプリケーションは選択した言語でフレームワークの実装を提供できます。 Javaでは、フレームワークは、フレームワークプロセスのエントリポイントとして機能するメインクラスと、前述のSchedulerおよびExecutorの実装を使用して実装されます。

5.1. フレームワークメインクラス

スケジューラとエグゼキュータを実装する前に、まずフレームワークのエントリポイントを実装します。

  • マスターに登録します
  • エグゼキュータのランタイム情報をエージェントに提供します
  • スケジューラーを開始します

まず、MesosのMaven依存関係を追加します。

<dependency>
    <groupId>org.apache.mesos</groupId>
    <artifactId>mesos</artifactId>
    <version>0.28.3</version>
</dependency>

次に、フレームワークにHelloWorldMainを実装します。 最初に行うことの1つは、Mesosエージェントでエグゼキュータプロセスを開始することです。

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();
}

ここでは、最初にエグゼキュータのバイナリロケーションを設定しました。 Mesosエージェントは、フレームワークの登録時にこのバイナリをダウンロードします。 次に、エージェントは指定されたコマンドを実行してエグゼキュータプロセスを開始します。

次に、フレームワークを初期化し、スケジューラーを開始します。

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]);

ついに、 マスターに登録するMesosSchedulerDriverを起動します。 登録を成功させるには、マスターのIPをプログラム引数args[0]としてこのメインクラスに渡す必要があります。

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

driver.stop();

System.exit(status);

上記のクラスでは、 CommandInfo、ExecutorInfo、、および FrameworkInfo はすべて、マスターとフレームワーク間のprotobufメッセージのJava表現です。

5.2. スケジューラの実装

Mesos 1.0以降、任意のJavaアプリケーションからHTTPエンドポイントを呼び出して、Mesosマスターとメッセージを送受信できます。 これらのメッセージには、フレームワークの登録、リソースの提供、提供の拒否などが含まれます。

Mesos 0.28以前の場合、スケジューラインターフェイスを実装する必要があります。

ほとんどの場合、SchedulerのresourceOffers メソッドにのみ焦点を当てます。スケジューラがリソースを受け取り、それに基づいてタスクを初期化する方法を見てみましょう。

まず、スケジューラーがタスクにリソースを割り当てる方法を確認します。

@Override
public void resourceOffers(SchedulerDriver schedulerDriver, List<Offer> list) {

    for (Offer offer : list) {
        List<TaskInfo> tasks = new ArrayList<TaskInfo>();
        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));

ここでは、タスクに1つのCPUと128Mのメモリを割り当てました。 次に、 SchedulerDriver を使用して、エージェントでタスクを起動します。

        TaskInfo printHelloWorld = TaskInfo.newBuilder()
          .setName("printHelloWorld " + taskId.getValue())
          .setTaskId(taskId)
          .setSlaveId(offer.getSlaveId())
          .addResources(cpus)
          .addResources(mem)
          .setExecutor(ExecutorInfo.newBuilder(helloWorldExecutor))
          .build();

        List<OfferID> offerIDS = new ArrayList<>();
        offerIDS.add(offer.getId());

        tasks.add(printHelloWorld);

        schedulerDriver.launchTasks(offerIDS, tasks);
    }
}

あるいは、スケジューラーは、リソースの提供を拒否する必要があると感じることがよくあります。 たとえば、スケジューラーがリソース不足のためにエージェントでタスクを起動できない場合、そのオファーを直ちに拒否する必要があります。

schedulerDriver.declineOffer(offer.getId());

5.3. Executorの実装

前に説明したように、フレームワークのエグゼキュータコンポーネントは、Mesosエージェントでアプリケーションタスクを実行する役割を果たします。

Mesos1.0でSchedulerを実装するためにHTTPエンドポイントを使用しました。 同様に、エグゼキュータにはHTTPエンドポイントを使用できます。

前のセクションでは、フレームワークがエグゼキュータプロセスを開始するようにエージェントを構成する方法について説明しました。

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

特に、 このコマンドは、HelloWorldExecutorをメインクラスと見なします。 これを実装します主要方法 MesosExecutorDriverを初期化します Mesosエージェントと接続して、タスクを受信し、タスクステータスなどの他の情報を共有します。

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);
    }
}

ここで最後に行うことは、フレームワークからタスクを受け入れ、エージェントでそれらを起動することです。 タスクを起動するための情報は、 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);
}

もちろん、これは単純な実装ですが、エグゼキュータがすべての段階でマスターとタスクステータスを共有し、完了ステータスを送信する前にタスクを実行する方法を説明しています。

場合によっては、エグゼキュータはデータをスケジューラに送り返すこともできます。

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

6. 結論

この記事では、同じクラスターで実行されているアプリケーション間のリソース共有について簡単に説明しました。 また、Apache Mesosが、CPUやメモリなどのクラスターリソースの抽象的なビューを使用して、アプリケーションが最大の使用率を達成するのにどのように役立つかについても説明しました。

後で、私たちは議論しましたアプリケーション間のリソースの動的割り当てに基づくさまざまな公平性の方針と役割。 Mesosを使用すると、アプリケーションで作成できますリソースオファーに基づくスケジューリングの決定クラスタ内のMesosエージェントから。

最後に、JavaでのMesosフレームワークの実装を見ました。

いつものように、すべての例はGitHubから入手できます。