Hướng dẫn về Apache Mesos

1. Khái quát chung

Chúng tôi thường triển khai các ứng dụng khác nhau trên cùng một cụm máy. Ví dụ, ngày nay phổ biến là có một công cụ xử lý phân tán như Apache Spark hoặc Apache Flink với cơ sở dữ liệu phân tán như Apache Cassandra trong cùng một cụm.

Apache Mesos là một nền tảng cho phép chia sẻ tài nguyên hiệu quả giữa các ứng dụng như vậy.

Trong bài viết này, trước tiên chúng ta sẽ thảo luận một số vấn đề về phân bổ tài nguyên trong các ứng dụng được triển khai trên cùng một cụm. Sau đó, chúng ta sẽ xem cách Apache Mesos cung cấp việc sử dụng tài nguyên tốt hơn giữa các ứng dụng.

2. Chia sẻ cụm

Nhiều ứng dụng cần chia sẻ một cụm. Nhìn chung, có hai cách tiếp cận phổ biến:

  • Phân vùng cụm tĩnh và chạy ứng dụng trên mỗi phân vùng
  • Phân bổ một bộ máy cho một ứng dụng

Mặc dù các cách tiếp cận này cho phép các ứng dụng chạy độc lập với nhau, nhưng nó không đạt được hiệu quả sử dụng tài nguyên cao.

Ví dụ: hãy xem xét một ứng dụng chỉ chạy trong một khoảng thời gian ngắn, sau đó là một khoảng thời gian không hoạt động. Bây giờ, vì chúng tôi đã phân bổ các máy hoặc phân vùng tĩnh cho ứng dụng này, chúng tôi có tài nguyên chưa được sử dụng trong thời gian không hoạt động.

Chúng tôi có thể tối ưu hóa việc sử dụng tài nguyên bằng cách phân bổ lại các tài nguyên miễn phí trong thời gian không hoạt động cho các ứng dụng khác.

Apache Mesos giúp phân bổ tài nguyên động giữa các ứng dụng.

3. Apache Mesos

Với cả hai cách tiếp cận chia sẻ cụm mà chúng ta đã thảo luận ở trên, các ứng dụng chỉ nhận biết được tài nguyên của một phân vùng hoặc máy cụ thể mà chúng đang chạy. Tuy nhiên, Apache Mesos cung cấp một cái nhìn trừu tượng về tất cả các tài nguyên trong cụm cho các ứng dụng.

Như chúng ta sẽ thấy ngay sau đây, Mesos hoạt động như một giao diện giữa máy và ứng dụng. Nó cung cấp các ứng dụng với các tài nguyên có sẵn trên tất cả các máy trong cụm. thường xuyên cập nhật thông tin này để bao gồm các tài nguyên được giải phóng bởi các ứng dụng đã đạt đến trạng thái hoàn thành. Điều này cho phép các ứng dụng đưa ra quyết định tốt nhất về tác vụ sẽ thực thi trên máy nào.

Để hiểu cách hoạt động của Mesos, chúng ta hãy xem kiến ​​trúc của nó:

Hình ảnh này là một phần của tài liệu chính thức cho Mesos (nguồn). Ở đây, HadoopMPI là hai ứng dụng dùng chung cụm.

Chúng ta sẽ nói về từng thành phần được hiển thị ở đây trong một vài phần tiếp theo.

3.1. Mesos Master

Master là thành phần cốt lõi trong thiết lập này và lưu trữ trạng thái hiện tại của tài nguyên trong cụm. Ngoài ra, nó hoạt động như một người điều phối giữa các tác nhân và các ứng dụng bằng cách truyền thông tin về những thứ như tài nguyên và nhiệm vụ.

Vì bất kỳ lỗi nào trong tổng thể đều dẫn đến mất trạng thái về tài nguyên và tác vụ, chúng tôi triển khai nó ở cấu hình sẵn sàng cao. Như có thể thấy trong sơ đồ trên, Mesos triển khai các daemon chủ dự phòng cùng với một thủ lĩnh. Những con daemon này dựa vào Zookeeper để phục hồi trạng thái trong trường hợp bị lỗi.

3.2. Đại lý Mesos

Một cụm Mesos phải chạy một tác nhân trên mọi máy. Những tác nhân này báo cáo tài nguyên của họ cho chủ theo định kỳ và lần lượt, nhận các tác vụ mà ứng dụng đã lên lịch chạy . Chu kỳ này lặp lại sau khi nhiệm vụ đã lên lịch hoàn thành hoặc bị mất.

Chúng ta sẽ xem cách các ứng dụng lên lịch và thực thi các tác vụ trên các tác nhân này trong các phần sau.

3.3. Khung Mesos

Mesos cho phép các ứng dụng thực hiện một thành phần trừu tượng tương tác với Master để nhận các tài nguyên có sẵn trong cụm và hơn nữa đưa ra các quyết định lập lịch dựa trên chúng. Các thành phần này được gọi là khung.

Một khung công tác Mesos bao gồm hai thành phần phụ:

  • Bộ lập lịch - Cho phép ứng dụng lập lịch tác vụ dựa trên các tài nguyên có sẵn trên tất cả các tác nhân
  • Người thực thi - Chạy trên tất cả các tác nhân và chứa tất cả thông tin cần thiết để thực hiện bất kỳ tác vụ đã lên lịch nào trên tác nhân đó

Toàn bộ quá trình này được mô tả bằng luồng này:

Đầu tiên, các đại lý báo cáo tài nguyên của họ cho chủ. Ngay lập tức, chủ cung cấp các tài nguyên này cho tất cả những người lên lịch đã đăng ký. Quá trình này được gọi là một đề nghị tài nguyên và chúng ta sẽ thảo luận chi tiết về nó trong phần tiếp theo.

Sau đó, bộ lập lịch biểu chọn tác nhân tốt nhất và thực hiện các tác vụ khác nhau trên đó thông qua Master. Ngay sau khi người thực thi hoàn thành nhiệm vụ được giao, các tác nhân sẽ xuất bản lại tài nguyên của họ cho chủ. Master lặp lại quá trình chia sẻ tài nguyên này cho tất cả các khuôn khổ trong cụm.

Mesos cho phép các ứng dụng triển khai trình lập lịch và trình thực thi tùy chỉnh của chúng bằng các ngôn ngữ lập trình khác nhau. Thực hiện Một Java của lịch trình phải thực hiện các Scheduler giao diện :

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

Có thể thấy, nó chủ yếu bao gồm các phương thức gọi lại khác nhau để liên lạc với chủ nói riêng.

Tương tự, việc triển khai một trình thực thi phải triển khai giao diện Trình thực thi :

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

Chúng ta sẽ thấy phiên bản hoạt động của bộ lập lịch và trình thực thi trong phần sau.

4. Quản lý tài nguyên

4.1. Cung cấp tài nguyên

Như chúng ta đã thảo luận trước đó, các agent xuất bản thông tin tài nguyên của họ cho master. Đổi lại, tổng thể cung cấp các tài nguyên này cho các khuôn khổ đang chạy trong cụm. Quá trình này được gọi là cung cấp tài nguyên.

Một đề nghị tài nguyên bao gồm hai phần - tài nguyên và thuộc tính.

Tài nguyên được sử dụng để xuất bản thông tin phần cứng của máy tác nhân như bộ nhớ, CPU và đĩa.

Có năm tài nguyên được xác định trước cho mỗi Đại lý:

  • CPU
  • gpus
  • mem
  • đĩa
  • cổng

Các giá trị cho các tài nguyên này có thể được xác định theo một trong ba loại:

  • Vô hướng - Được sử dụng để biểu diễn thông tin số bằng cách sử dụng số dấu chấm động để cho phép các giá trị phân số như 1,5G bộ nhớ
  • Phạm vi - Được sử dụng để đại diện cho một phạm vi giá trị vô hướng - ví dụ: một phạm vi cổng
  • Set - Được sử dụng để đại diện cho nhiều giá trị văn bản

Theo mặc định, tác nhân Mesos cố gắng phát hiện các tài nguyên này từ máy.

Tuy nhiên, trong một số trường hợp, chúng tôi có thể định cấu hình tài nguyên tùy chỉnh trên một tác nhân. Các giá trị cho các tài nguyên tùy chỉnh đó lại phải thuộc bất kỳ loại nào trong các loại đã thảo luận ở trên.

Ví dụ: chúng tôi có thể bắt đầu đại lý của mình với các tài nguyên sau:

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

Có thể thấy, chúng tôi đã định cấu hình tác nhân với một vài tài nguyên được xác định trước và một tài nguyên tùy chỉnh có tên là lỗi thuộc loại tập hợp .

Ngoài tài nguyên, nhân viên có thể xuất bản các thuộc tính khóa-giá trị cho chủ. Các thuộc tính này hoạt động như một siêu dữ liệu bổ sung cho tác nhân và giúp khuôn khổ trong việc lập lịch quyết định.

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

Trong bài viết này, chúng tôi đã thảo luận ngắn gọn về chia sẻ tài nguyên giữa các ứng dụng chạy trong cùng một cụm. Chúng tôi cũng đã thảo luận về cách Apache Mesos giúp các ứng dụng đạt được mức sử dụng tối đa với một cái nhìn trừu tượng về các tài nguyên cụm như CPU ​​và bộ nhớ.

Sau đó, chúng tôi đã thảo luận về việc phân bổ tài nguyên động giữa các ứng dụng dựa trên các chính sách và vai trò công bằng khác nhau. Mesos cho phép các ứng dụng đưa ra quyết định lập lịch dựa trên các cung cấp tài nguyên từ các tác nhân Mesos trong cụm.

Cuối cùng, chúng tôi đã thấy việc triển khai khung công tác Mesos trong Java.

Như thường lệ, tất cả các ví dụ đều có sẵn trên GitHub.