LabHub
学习 学习路径 课程

Apache Hadoop — HDFS 와 YARN 을 한 파드에 세우고 운영한다 · YARN 컨테이너와 대기열 · 讲解

YARN 은 메모리를 컨테이너 크기로 잘라 나눠 준다

在 LabHub 中继续学习

한 줄 요약

YARN 은 클러스터의 메모리와 CPU 를 컨테이너라는 조각으로 잘라 앱에 나눠 준다. 조각의 크기에는 하한과 상한이 있고, 하한보다 작은 요청은 하한으로 올라가며 상한보다 큰 요청은 거절된다. 누구에게 먼저 줄지는 대기열이 정하고, 끝난 컨테이너의 로그는 로그 모으기를 켜야 한곳에 남는다.

概念图: 컨테이너라는 조각 · 대기열 · 로그 모으기 · ResourceManager

왜 이게 필요했나

하둡 1 의 JobTracker 는 자원 관리와 잡 감시를 혼자 다 했다. 클러스터가 커질수록 이 한 프로세스가 병목이 되었고, 슬롯이 맵용과 리듀스용으로 미리 갈라져 있어 맵만 도는 시간에는 리듀스 슬롯이 놀았다. 게다가 그 클러스터에서는 MapReduce 말고 다른 것을 돌릴 수 없었다.

[YARN 구조 문서](https://hadoop.apache.org/docs/r3.5.0/hadoop-yarn/hadoop-yarn-site/YARN.html)는 이것을 둘로 쪼갠 설계라고 설명한다. 자원 관리는 클러스터에 하나뿐인 ResourceManager 가, 잡의 계획과 감시는 앱마다 하나씩 뜨는 ApplicationMaster 가 맡는다. 기계마다 도는 NodeManager 는 컨테이너를 띄우고 자원 사용을 감시해 보고한다. ResourceManager 안의 Scheduler 는 문서의 표현으로 "순수한" 스케줄러다 — 자원을 나눠 줄 뿐 앱의 상태를 추적하거나 실패한 태스크를 다시 띄우지 않는다. 그 일은 AM 의 몫이다. 그래서 MapReduce 도, Spark 도, 다른 프레임워크도 각자의 AM 만 가져오면 같은 클러스터를 나눠 쓴다.

어떻게 동작하나

잡을 내면 ResourceManager 가 첫 컨테이너를 잡아 AM 을 띄운다. AM 은 "메모리 X MB, vcore Y 개짜리 컨테이너를 N 개" 식으로 요청하고, 스케줄러가 NodeManager 의 빈자리에 맞춰 내준다. 여기서 크기 규칙이 끼어든다. [yarn-default.xml](https://hadoop.apache.org/docs/r3.5.0/hadoop-yarn/hadoop-yarn-common/yarn-default.xml)에 따르면 이렇다.

[자원 모델 문서](https://hadoop.apache.org/docs/r3.5.0/hadoop-yarn/hadoop-yarn-site/ResourceModel.html)는 요청이 최소·최대에 맞춰지거나 설정된 증분의 배수로 바뀔 수 있다고 덧붙인다. 요청한 크기와 받은 크기가 다를 수 있다는 뜻이다. MapReduce 의 AM 은 태스크 요청이 최대 할당을 넘는 것을 미리 알아채고, 컨테이너를 요청하기도 전에 잡을 KILLED 로 끝낸 뒤 그 이유를 애플리케이션의 진단(Diagnostics) 줄에 남긴다.

NodeManager 가 컨테이너에 내줄 총량은 yarn.nodemanager.resource.memory-mb 다. 기본값 -1 은 하드웨어 자동 감지가 켜져 있을 때만 계산되고, 그 밖의 경우 8192MB 로 잡힌다. 기계에 실제로 그만한 메모리가 있는지는 확인하지 않는다.

NodeManager 가 컨테이너에 내주는 1024MB 를 두 가지 최소 할당으로 나눠 쓰는 그림. 최소 할당이 기본값 1024MB 이면 512MB 를 요청한 AM 이 1024MB 로 올라가 노드를 혼자 차지하고 맵 컨테이너는 자리가 없어 잡이 멈춘다. 최소 할당을 256MB 로 낮추면 AM 512MB 와 맵 256MB 두 개가 함께 들어간다. 최대 할당보다 큰 요청은 거절된다

이 실습 파드를 숫자로 보면 규칙이 왜 중요한지 보인다. 파드 메모리는 2Gi 이고, NameNode·DataNode·ResourceManager·NodeManager 네 데몬의 힙 상한만 합쳐도 864MB(256·160·256·192)다. NodeManager 가 컨테이너에 내주는 몫은 1024MB 다. 최소 할당을 기본값 1024MB 로 두면 컨테이너 하나가 노드 전체다. AM 이 그 하나를 가져가면 맵이 들어갈 자리가 없고, 잡은 AM 만 띄운 채 영영 기다린다. 게다가 MapReduce AM 의 기본 요청 [yarn.app.mapreduce.am.resource.mb](https://hadoop.apache.org/docs/r3.5.0/hadoop-mapreduce-client/hadoop-mapreduce-client-core/mapred-default.xml)는 1536MB 라서 1024MB 노드에는 아예 들어가지 않는다. 작은 노드에서는 최소 할당을 낮추고 AM 과 태스크의 요청도 함께 낮춰야 한다. 실습 이미지가 최소 할당 128MB·최대 할당 1024MB, AM 384MB, 맵·리듀스 256MB 로 잡아 둔 이유다.

요청과 JVM 힙은 따로 논다는 것도 기억할 것. mapreduce.map.memory.mb 가 컨테이너 크기이고, 힙은 기본 비율 0.8 로 그 안에서 정해진다. 나머지 20% 는 JVM 자체와 네이티브 메모리의 몫이다. NodeManager 는 기본으로 물리 메모리와 가상 메모리 검사를 모두 켜 두고(vmem-pmem-ratio 2.1), 넘친 컨테이너를 죽인다(실습 이미지는 JVM 의 예약 주소 공간을 사용량으로 잘못 세는 가상 메모리 검사를 꺼 두었다).

대기열은 누가 먼저 받는지 정한다

기본 스케줄러는 CapacityScheduler 다. [Capacity Scheduler 문서](https://hadoop.apache.org/docs/r3.5.0/hadoop-yarn/hadoop-yarn-site/CapacityScheduler.html)에 따르면 모든 대기열은 root 의 자식이고, yarn.scheduler.capacity.root.queues 에 쉼표로 자식을 적는다. 각 대기열의 capacity 는 백분율이며 한 층의 합이 100 이어야 한다. 이 몫은 보장이지 울타리가 아니다. 다른 대기열이 비어 있으면 자기 몫보다 더 쓸 수 있고, 그 탄력을 maximum-capacity 로 제한한다.

<property>  <name>yarn.scheduler.capacity.root.queues</name>  <value>default,etl,adhoc</value></property><property>  <name>yarn.scheduler.capacity.root.etl.capacity</name>  <value>60</value></property>

설정 파일을 고친 뒤에는 ResourceManager 를 재시작하지 않고 yarn rmadmin -refreshQueues 로 반영한다. 잡은 mapreduce.job.queuename(기본 default)으로 대기열을 고르고, 없는 대기열로 낸 잡은 제출 단계에서 거절된다.

작은 클러스터에서 자주 걸리는 값이 하나 더 있다. maximum-am-resource-percent 는 클러스터 자원 중 AM 에 쓸 수 있는 비율로, 기본 10% 다. 동시에 활성화되는 앱 수를 이것이 정한다. 1024MB 짜리 노드의 10% 는 AM 하나보다 작으니, 잡 두 개를 동시에 내면 두 번째가 ACCEPTED 에 머물기 쉽다.

로그는 켜야 모인다

컨테이너의 로그는 그 컨테이너를 띄운 NodeManager 의 로컬 디스크에 남는다. yarn.log-aggregation-enable 의 기본은 false 이고, 이때 로그는 yarn.nodemanager.log.retain-seconds 기본 10800초(3시간) 뒤 지워진다. 켜면 앱이 끝난 뒤 로그가 HDFS 의 /tmp/logs 아래로 옮겨지고, yarn logs -applicationId <앱 ID> 한 줄로 모든 컨테이너의 로그를 한꺼번에 읽는다. 실습 이미지는 이 값을 켜 두었다. 노드가 수백 대면 이것 없이는 실패한 태스크 하나의 로그를 찾는 데 반나절이 간다.

클러스터가 지금 얼마나 비어 있는지는 [ResourceManager REST](https://hadoop.apache.org/docs/r3.5.0/hadoop-yarn/hadoop-yarn-site/ResourceManagerRest.html)의 /ws/v1/cluster/metrics 로 본다. totalMB·allocatedMB·availableMBappsPending 이 한 번에 나온다.

현장에서 만나는 모습

첫째, 잡이 ACCEPTED 에서 움직이지 않는다. 원인은 대개 셋 중 하나다. AM 이 들어갈 만큼 큰 노드가 없거나, 대기열의 AM 몫이 찼거나, AM 은 떴는데 태스크 컨테이너가 들어갈 자리가 없다. availableMB 와 요청 크기를 나란히 놓으면 대부분 풀린다.

둘째, 요청을 줄였는데 쓰는 메모리가 그대로다. 최소 할당보다 작게 요청하면 최소 할당으로 올라간다. 요청 256MB, 최소 1024MB 면 받는 것은 1024MB 다.

셋째, 컨테이너가 메모리 초과로 죽는다. 힙은 요청 안에 들어가도 스레드 스택과 네이티브 버퍼가 나머지를 넘긴 경우다. 힙 비율을 낮추거나 요청을 키운다.

넷째, 대기열을 지웠더니 잡이 전부 거절된다. 제출 스크립트가 기본값 default 대기열을 쓰는데 그 대기열을 목록에서 뺐다.

실무에서 진짜 중요한 것

다음 실습에서 할 것

ResourceManager 의 REST 로 클러스터 전체 메모리와 빈자리를 읽고, 맵 하나에 최대 할당보다 큰 2048MB 를 요청한 잡이 컨테이너를 받지 못하고 KILLED 로 끝나는 것을 애플리케이션 상태의 진단 줄로 확인한다. capacity-scheduler.xml 에 etl 과 adhoc 대기열을 더해 보장 몫의 합을 100 으로 맞추고 refreshQueues 로 반영한 뒤, etl 대기열로 낸 잡이 그 대기열에서 도는 것과 없는 대기열로 낸 잡이 제출 단계에서 거절되는 것을 본다. 스케줄러 REST 로 대기열 상태를 읽고, 끝난 잡의 모인 로그를 yarn logs 로 받아 보고서로 정리한다.