1. 개요

우리는 일반적으로 동일한 머신 클러스터에 다양한 애플리케이션을 배포합니다. 예를 들어 오늘날에는 Apache Spark 또는 Apache Flink같은 분산 처리 엔진을  동일한 클러스터에 Apache Cassandra 와 같은 분산 데이터베이스와 함께 사용 하는 것이 일반적입니다.

Apache Mesos는 이러한 애플리케이션 간에 효과적인 리소스 공유를 허용하는 플랫폼입니다.

이 기사에서는 먼저 동일한 클러스터에 배포된 애플리케이션 내 리소스 할당의 몇 가지 문제에 대해 논의합니다. 나중에 Apache Mesos가 애플리케이션 간에 더 나은 리소스 활용도를 제공하는 방법을 살펴보겠습니다.

2. 클러스터 공유

많은 애플리케이션이 클러스터를 공유해야 합니다. 일반적으로 두 가지 일반적인 접근 방식이 있습니다.

  • 클러스터를 정적으로 분할하고 각 파티션에서 애플리케이션 실행
  • 애플리케이션에 머신 세트 할당

이러한 접근 방식을 사용하면 응용 프로그램을 서로 독립적으로 실행할 수 있지만 리소스 사용률이 높지 않습니다.

예를 들어, 짧은 기간 동안만 실행된 후 일정 기간 비활성 상태로 실행 되는 응용 프로그램을 생각해 보십시오 . 이제 이 애플리케이션에 정적 시스템이나 파티션을 할당했기 때문에 비활성 기간 동안 사용하지 않은 리소스 가 있습니다 .

비활성 기간 동안 사용 가능한 리소스를 다른 애플리케이션에 재할당하여 리소스 활용을 최적화할 수 있습니다.

Apache Mesos는 애플리케이션 간의 동적 리소스 할당을 지원합니다.

3. 아파치 메소스

위에서 논의한 두 클러스터 공유 접근 방식을 통해 응용 프로그램은 실행 중인 특정 파티션 또는 시스템의 리소스만 인식합니다. 그러나 Apache Mesos는 클러스터의 모든 리소스에 대한 추상 보기를 애플리케이션에 제공합니다.

곧 보게 되겠지만, Mesos는 기계와 애플리케이션 사이의 인터페이스 역할을 합니다. 클러스터의 모든 시스템에서 사용 가능한 리소스를 애플리케이션에 제공 합니다. 그것은 자주 응용 프로그램에 의해 해제되는 자원을 포함하려면이 정보 업데이트 완료 상태에 도달했습니다. 이를 통해 응용 프로그램은 어떤 작업을 어떤 컴퓨터에서 실행할 것인지에 대해 최선의 결정을 내릴 수 있습니다.

Mesos의 작동 방식을 이해하기 위해 아키텍처를 살펴보겠습니다 .

이 이미지는 Mesos( 출처 )에 대한 공식 문서의 일부입니다 . 여기서 HadoopMPI 는 클러스터를 공유하는 두 개의 애플리케이션입니다.

다음 몇 섹션에서 여기에 표시된 각 구성 요소에 대해 설명합니다.

3.1. 메소스 마스터

마스터는 이 설정의 핵심 구성 요소이며 클러스터에 리소스의 현재 상태를 저장합니다. 또한 리소스 및 작업과 같은 정보를 전달  하여 에이전트 와 애플리케이션 간의 조정자 역할을 합니다.

마스터에 장애가 발생하면 리소스 및 작업에 대한 상태가 손실되므로 고가용성 구성으로 배포합니다. 위의 다이어그램에서 볼 수 있듯이 Mesos는 하나의 리더와 함께 대기 마스터 데몬을 배포합니다. 이러한 데몬은 오류 발생 시 상태를 복구하기 위해 Zookeeper에 의존합니다.

3.2. 메소스 에이전트

Mesos 클러스터는 모든 머신에서 에이전트를 실행해야 합니다. 이러한 에이전트는 주기적으로 마스터에 자원을보고 차례로, 응용 프로그램이 실행되도록 예약했다고 작업을받을 . 이 주기는 예약된 작업이 완료되거나 손실된 후 반복됩니다.

다음 섹션에서 애플리케이션이 이러한 에이전트에서 작업을 예약하고 실행하는 방법을 살펴보겠습니다.

3.3. 메소스 프레임워크

메소는 응용 프로그램이 마스터와 상호 작용을 할 수 있다는 추상적 인 구성 요소를 구현할 수 있습니다 사용할 수있는 클러스터의 자원을 수신 하고 또한 메이크업 스케줄링 결정 들을 기반으로합니다. 이러한 구성 요소를 프레임워크라고 합니다.

Mesos 프레임워크는 두 개의 하위 구성요소로 구성됩니다.

  • 스케줄러 – 애플리케이션이 모든 에이전트에서 사용 가능한 리소스를 기반으로 작업을 예약할 수 있도록 합니다.
  • 실행자 – 모든 에이전트에서 실행되며 해당 에이전트에서 예약된 작업을 실행하는 데 필요한 모든 정보를 포함합니다.

이 전체 프로세스는 다음 흐름으로 표시됩니다.

 

먼저 에이전트는 자신의 리소스를 마스터에게 보고합니다. 이때 마스터는 등록된 모든 스케줄러에게 이러한 리소스를 제공합니다. 이 프로세스를 리소스 제안이라고 하며 다음 섹션에서 자세히 설명합니다.

그런 다음 스케줄러는 최상의 에이전트를 선택하고 마스터를 통해 다양한 작업을 수행합니다. 실행자가 할당된 작업을 완료하자마자 에이전트는 리소스를 마스터에 다시 게시합니다. 마스터는 클러스터의 모든 프레임워크에 대해 이 리소스 공유 프로세스를 반복합니다.

Mesos를 사용하면 애플리케이션이 다양한 프로그래밍 언어로 맞춤형 스케줄러 및 실행기구현할 수 있습니다. 스케줄러의 자바 구현해야한다 구현 스케줄러  인터페이스를 :

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

특히 마스터와의 통신을 위한 다양한 콜백 메소드 로 구성되어 있음을 알 수 있다 .

유사하게, Executor의 구현은 Executor  인터페이스를 구현해야 합니다 :

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. 리소스 제안

앞서 논의한 바와 같이 에이전트는 자신의 리소스 정보를 마스터에 게시합니다. 차례로 마스터는 클러스터에서 실행되는 프레임워크에 이러한 리소스를 제공합니다. 이 프로세스를 리소스 제안 이라고 합니다.

리소스 제안은 리소스와 속성의 두 부분으로 구성됩니다.

리소스는 메모리, CPU 및 디스크와 같은 에이전트 시스템의 하드웨어 정보게시 하는 데 사용됩니다 .

모든 에이전트에 대해 5개의 사전 정의된 리소스가 있습니다.

  • CPU
  • GPU
  • 디스크
  • 항구

이러한 리소스의 값은 다음 세 가지 유형 중 하나로 정의할 수 있습니다.

  • 스칼라 – 1.5G 메모리와 같은 소수 값을 허용하기 위해 부동 소수점 숫자를 사용하여 숫자 정보를 나타내는 데 사용됩니다.
  • 범위 – 스칼라 값 범위를 나타내는 데 사용됩니다(예: 포트 범위).
  • Set – 여러 텍스트 값을 나타내는 데 사용

기본적으로 Mesos 에이전트는 머신에서 이러한 리소스를 감지하려고 시도합니다.

그러나 경우에 따라 에이전트에서 사용자 지정 리소스를 구성할 수 있습니다. 이러한 사용자 지정 리소스의 값은 위에서 설명한 유형 중 하나여야 합니다.

예를 들어 다음 리소스로 에이전트를 시작할 수 있습니다.

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

알 수 있는 바와 같이 사전 정의된 리소스 중 일부와 유형 이  설정된  bug  라는 사용자 지정 리소스 하나를 사용하여 에이전트를 구성했습니다 .

리소스 외에도 에이전트는 키-값 속성을 마스터에 게시할 수 있습니다. 이러한 속성은 에이전트에 대한 추가 메타데이터 역할을 하고 프레임워크를 스케줄링하는 데 도움이 됩니다.

유용한 예는 에이전트를 다른 랙 또는 영역추가 한 다음 동일한 랙 또는 영역 에서 다양한 작업예약 하여 데이터 지역성을 달성하는 것입니다.

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

자원과 유사하게 속성 값은 스칼라, 범위 또는 텍스트 유형이 될 수 있습니다.

4.2. 자원 역할

많은 현대 운영 체제가 여러 사용자를 지원합니다. 마찬가지로 Mesos는 동일한 클러스터에서 여러 사용자를 지원합니다. 이러한 사용자를 역할 이라고 합니다 . 클러스터 내에서 각 역할을 리소스 소비자로 간주할 수 있습니다.

이로 인해 Mesos 에이전트는 다른 할당 전략에 따라 다른 역할로 리소스를 분할할 수 있습니다. 또한 프레임워크는 클러스터 내에서 이러한 역할을 구독할 수 있으며 다양한 역할의 리소스를 세밀하게 제어할 수 있습니다.

예를 들어, 조직의 다른 사용자에게 서비스를 제공 하는 클러스터 호스팅 응용 프로그램을 생각해 보십시오 . 따라서 리소스를 역할로 나누면 모든 애플리케이션이 서로 분리 되어 작동할 수 있습니다 .

또한 프레임워크는 이러한 역할을 사용하여 데이터 지역성을 달성할 수 있습니다.

예를 들어 클러스터에 생산자  와  소비자 라는 두 개의 애플리케이션이 있다고 가정합니다 여기서  생산자 소비자  가 나중에 읽을 수 있는 영구 볼륨에 데이터를 씁니다  . 생산자 와 볼륨을 공유 하여 소비자  애플리케이션을  최적화할 수 있습니다  .

Mesos는 여러 애플리케이션이 동일한 역할을 구독할 수 있도록 하므로 영구 볼륨을 리소스 역할과 연결할 수 있습니다. 또한 생산자  와  소비자  모두에 대한 프레임워크 는 동일한 리소스 역할을 구독합니다. 따라서 소비자  응용 프로그램은 이제 데이터 읽기 작업을 시작할 수 있습니다 동일한 볼륨에 는 AS 생산자  응용 프로그램입니다.

4.3. 리소스 예약

이제 Mesos가 클러스터 리소스를 다른 역할에 할당하는 방법에 대한 질문이 제기될 수 있습니다. Mesos는 예약을 통해 리소스를 할당합니다.

두 가지 유형의 예약이 있습니다.

  • 정적 예약
  • 동적 예약

정적 예약은 이전 섹션에서 논의한 에이전트 시작 시 리소스 할당과 유사합니다.

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

여기서 유일한 차이점은 이제 Mesos 에이전트가 baeldung 이라는 역할을 위해 8개의 CPU와 4096m의 메모리를 예약 한다는 것 입니다.

동적 예약을 사용하면 정적 예약과 달리 역할 내 리소스를 다시 섞을 수 있습니다. Mesos를 사용하면 프레임워크 및 클러스터 운영자가 리소스 제공에 대한 응답으로 또는 HTTP 끝점 을 통해 프레임워크 메시지를 통해 리소스 할당을 동적으로 변경할 수 있습니다 .

Mesos는 역할이 없는 모든 자원을 (*)이라는 기본 역할에 할당합니다. Master는 구독 여부와 상관없이 모든 프레임워크에 이러한 리소스를 제공합니다.

4.4. 리소스 가중치 및 할당량

일반적으로 Mesos 마스터는 공정성 전략을 사용하여 리소스를 제공합니다. 가중된 Dominant Resource Fairness(wDRF)를 사용하여 리소스가 부족한 역할을 식별합니다. 그런 다음 마스터는 이러한 역할을 구독한 프레임워크에 더 많은 리소스를 제공합니다.

이벤트 애플리케이션 간의 공정한 리소스 공유는 Mesos의 중요한 특성이지만 항상 필요한 것은 아닙니다. 리소스 요구량이 높은 애플리케이션과 함께 리소스 풋프린트가 낮은 애플리케이션을 호스팅하는 클러스터를 가정해 보겠습니다. 이러한 배포에서는 응용 프로그램의 특성에 따라 리소스를 할당하려고 합니다.

Mesos를 사용하면 프레임워크가 역할을 구독하고 해당 역할에 더 높은 가중치를 추가하여 더 많은 리소스요구할 수 있습니다 . 따라서 가중치 1과 가중치 2의 두 역할이 있는 경우 Mesos는 두 번째 역할에 두 배의 공정한 리소스를 할당합니다.

리소스와 마찬가지로 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 구현합니다  . 우리가 할 첫 번째 작업 중 하나는 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 마스터에 메시지를 보내고 받을 수 있습니다. 이러한 메시지 중 일부에는 예를 들어 프레임워크 등록, 리소스 제안 및 제안 거부가 포함됩니다.

들어 메소 0.28 또는 이전, 우리가 구현해야 스케줄러 인터페이스를 :

대부분 의 경우 스케줄러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 에이전트에서 애플리케이션 작업을 실행하는 역할을 합니다.

Mesos 1.0에서 스케줄러 를 구현하기 위해 HTTP 엔드포인트를 사용했습니다 . 마찬가지로 실행 프로그램에 대한 HTTP 끝점사용할 수 있습니다 .

이전 섹션에서 프레임워크가 실행자 프로세스를 시작하도록 에이전트를 구성하는 방법에 대해 논의했습니다.

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

특히 이 명령은 HelloWorldExecutor  를 기본 클래스로 간주  합니다. 작업을 수신하고 작업 상태와 같은 기타 정보를 공유하기 위해 Mesos 에이전트와 연결 하는 MesosExecutorDriver 초기화 하기 위해 주요 메서드를 구현 합니다.

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

물론 이것은 단순한 구현이지만 Executor가 모든 단계에서 마스터와 작업 상태를 공유한 다음 완료 상태를 보내기 전에 작업을 실행하는 방법을 설명합니다.

경우에 따라 실행자는 데이터를 다시 스케줄러로 보낼 수도 있습니다.

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

6. 결론

이 기사에서는 동일한 클러스터에서 실행되는 애플리케이션 간의 리소스 공유에 대해 간략하게 설명했습니다. 또한 Apache Mesos가 CPU 및 메모리와 같은 클러스터 리소스에 대한 추상적 보기를 통해 애플리케이션이 최대 활용도를 달성하도록 돕는 방법에 대해서도 논의했습니다.

나중에 우리는 다양한 공정성 정책과 역할을 기반으로 애플리케이션 간의 동적 리소스 할당에 대해 논의 했습니다. Mesos를 사용하면 애플리케이션 이 클러스터의 Mesos 에이전트가 제공하는 리소스를 기반으로 일정 결정을 내릴있습니다.

마지막으로 Java에서 Mesos 프레임워크의 구현을 보았습니다.

평소와 같이 모든 예제는 GitHub에서 사용할 수 있습니다 .

Generic footer banner