클라이언트의 애플리케이션 제출 시 RM, AM, NM 의 역할

하둡 1.0 에서 2.0 으로 넘어가며 맵리듀스에서 잡을 관리해주는 기존의 잡트래커 + 태스크트래커의 역할을 YARN 이 가져가게 된다.

클러스터에 있는 cpu, 메모리 등의 다양한 리소스들을 맵리듀스 뿐만 아니라 다른 에코시스템 프로그램들이 사용하다 보면 문제가 발생하기 때문에, yarn 이 중간에서 리소스들을 중재해주기 시작한다.

따라서 맵리듀스를 돌리기 위해 프로그램을 제출하면, yarn 쪽에서 자원 상황을 확인하여 알맞게 할당해주는데, 이 과정에 대한 이해가 필요하다.


YARN 은 크게 리소스매니저와 노드매니저로 이루어져있다.

클러스터의 리소스와 스케줄링 등이 YARN 에 의해 관리되어야 했기 때문에, 얀은 리소스들에 대한 모니터링과 작업에 대한 상황을 파악할 필요가 있어 이를 리소스매니저와 노드매니저로 해결한다.

리소스매니저는 마스터 역할을 하는 대몬으로, 클러스터의 리소스를 요청에 맞게 할당해주고 스케줄링해준다(사실 스케줄러도 따로 있긴 함).

RM은 다른 노드매니저들로부터 각 노드의 상태를 지속적으로 받아옴으로써 클러스터 전체의 상황을 확인할 수 있고, 클라이언트의 요청에 맞게 애플리케이션마스터(리소스매니저와 다른 워커노드들 사이의 중재자)를 실행시키기도 한다.

노드매니저는 워커 역할을 하는 대몬으로, 워커 노드들에서 실행되어 리소스매니저(마스터)에게 자신의 상황을 지속적으로 보내준다.

맵리듀스나 스파크 같은 분산 컴퓨팅 프로그램이 제출되거나, 다른 분산 서비스들이 자신이 일하기 위해 얀에 요청을 보내면 리소스매니저가 현 상황에 맞게 할당해주고, 워커들에 컨테이너를 생성한 뒤 각 노드매니저들이 자신의 컨테이너를 확인하여 리소스매니저에게 알려줌으로써 지속적으로 모니터링이 가능해지는 것이다.

예를 들어 맵리듀스 잡이 제출된 경우, 수행되는 과정은 아래와 같다.

  1. 클라이언트가 리소스매니저에게 잡을 제출

  2. 스케줄러가 워커 노드들 중에 임의로 하나를 선택하여 해당 잡의 애플리케이션마스터(AM) 역할을 지정하여 컨테이너를 실행시킨다.

  3. 리소스매니저는 자원을 어떻게 할당할지 결정한 뒤, AM에게 전달함으로써 AM이 다른 노드매니저들에게 그에 맞게 컨테이너 실행 요청을 보낸다.

  4. 각 요청에 맞게 컨테이너가 생성되어 작업을 수행한다.

  5. 이번 잡을 위해 실행된 컨테이너들은 AM을 통해 관리되며, 실행이 완료되면 AM은 RM에게 이를 보고하고 종료된다.

  6. 리스소매니저는 상황에 맞게 잡을 완료할 수 있도록 추가적으로 컨테이너를 실행시키기도 하는데, 이러한 모든 일은 AM을 거친다.

컨테이너의 생성과 실행, 상태 등에 대해서는 각 애플리케이션별로 AM이 생성되어 관리되지만, 이외에도 노드매니저는 리소스매니저에게 지속적으로 자신의 자원 상황을 보고함으로써 리소스매니저가 다른 애플리케이션을 위한 스케줄링을 원활히 할 수 있게 한다.


간단한 wordcount 맵리듀스 코드와 실행 도중 jps 명령어를 통해 어떻게 호출되는지 확인해보자.

@Override
public class WordCount extends Configured implements Tool {

    public static void main(String[] args) throws Exception {
        ToolRunner.run(new WordCount(), args);
    }

    @Override
    public int run(String[] strings) throws Exception {
        Job job = Job.getInstance(getConf(), "word counting");
        job.setJarByClass(WordCount.class);

        job.setMapperClass(wcMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        job.setReducerClass(wcReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        job.setInputFormatClass(TextInputFormat.class);
        job.setOutputFormatClass(TextOutputFormat.class);

        FileInputFormat.addInputPath(job, new Path(strings[0]));
        FileOutputFormat.setOutputPath(job, new Path(strings[1]));

        job.waitForCompletion(false);

        return 0;
    }
}

우선 메인함수를 호출하는 프로세스는 jar 파일을 실행시킨 노드 위에서 실행되고, 잡을 생성하고 호출하는 드라이버 부분 역시 동일한 프로세스 위에서 동작한다.

해당 클래스는 Configured 를 상속받았기 때문에, 드라이버 부분에서 getConf() 로 Configuration 객체를 가져올 수 있고, 그 설정값들을 기반으로 잡을 생성한다.

잡을 생성한 단계에서는 아직 클러스터에 제출되지 않는다.

Job 레퍼런스를 확인해보면, 스태틱 함수인 getInstance 로는 Job 을 생성하여 반환하지만, 클러스터에 연결되지는 않는다고 나와있다.

따라서 이렇게 생성된 Job에 매퍼와 리듀서 관련된 클래스들을 지정해주고, 인풋/아웃풋포맷, 입력 경로, 출력할 경로들을 지정해줄 때 까지도 클러스터에는 요청이 들어오지 않으며, 마지막으로 waitForCompletion을 호출할 때 잡을 제출하는 것이다.

현 시점에서 jps 로 어떤 자바 프로세스들이 띄워져 있는지 확인해보면,

bconfiden2@h01:~$ jps
7248 SecondaryNameNode
7824 NodeManager
7026 DataNode
7479 ResourceManager
7994 Jps
6826 NameNode
8094 RunJar

bconfiden2@h02:~$ jps
34854 Jps
34522 DataNode
34733 NodeManager

마스터 및 워커 역할을 하는 h01 에는 네임노드, 데이터노드, 리소스매니저, 노드매니저등의 대몬들과, 메인함수와 드라이버를 실행시키는 RunJar 프로세스만이 떠져 있음을 확인할 수 있다.

이 이후부터 앞서 언급한 YARN의 과정에 맞게, 일단 리소스매니저에게 할당 요청을 보내서 애플리케이션마스터를 생성하고 컨테이너들을 할당하여 작업을 수행하는데, 중간에 다시 jps 로 확인해보면,

bconfiden2@h01:~$ jps
8229 MRAppMaster
8487 YarnChild
6826 NameNode
8523 YarnChild
8428 YarnChild
8525 YarnChild
8431 YarnChild
7248 SecondaryNameNode
7824 NodeManager
8722 Jps
7026 DataNode
7479 ResourceManager
8472 YarnChild
8094 RunJar

bconfiden2@h02:~$ jps
35024 YarnChild
35058 YarnChild
35046 YarnChild
35065 YarnChild
35034 YarnChild
35002 YarnChild
34522 DataNode
35036 YarnChild
34733 NodeManager
35038 YarnChild
35183 Jps

아까와는 다르게 각 노드들에 YarnChild 프로세스들이 많이 생성되어있음을 확인할 수 있는데, 이들이 컨테이너로써 실질적은 매퍼와 리듀서 역할을 수행중인 것이다.

또한 h01 노드에는 MRAppMaster이 추가적으로 생성되었는데, 이것이 애플리케이션마스터 컨테이너이며 두 노드 중 임의로 생성되기 때문에 다른 잡을 수행할 때는 h02 노드에서 실행될 수도 있다.

이러한 컨테이너 상황은 리소스매니저 웹UI로도 제공되기 때문에, 8088 포트에서 확인할 수도 있다.