Hadoop 的用户日志有很多的用途, 首先最重要的是, 它们能用来调试 MapReduce 应用(application)的问题, 可能是应用本身的问题, 或者在极少数的情况下, 当在集群中执行应用时, 日志可以用来调试硬件或者平台的问题所引起的 task/job 的失败. 其次, 人们可以通过分析历史日志来了解单个任务(task)在 job/workflow 中的历史表现, 甚至可以用 Hadoop 的 MapReduce 来分析 MapReduce 的用户日志(user-logs)来确定性能问题.
在以前, 处理应用(application)生成的用户日志已经成了安装 Hadoop 过程中的一个最大痛点. 在 Hadoop 1.x, 用户日志被 TaskTracker 留在每个节点上, 这样长期来看在各个节点上的用户日志的是难于管理并且也很难被获取和使用到的. YARN 处理日志管理问题的办法是让 NodeManagers(NMs) 提供一个选项, 可以在应用结束后将日志文件安全地移到一个分布式文件系统中, 比如 HDFS.
动机
在 Hadoop 1.x 的各发行版中, MapReduce 是用户唯一可用的编程模型. 每个 MapReduce job 会执行一堆 mapper 和 reducer, 每个 map/reduce task 在本地磁盘上直接生成日志, 比如 syslog/stderr/stdout 或者 profile.out/debug.out 等等. 这些 task 日志可以通过网页读取到, 这种方式也是最方便的方式, 其次就是直接登录到节点服务器上去查看日志文件.
Hadoop 1.x 中日志管理的状态
为了处理用户的 task-logs, TaskTrackers(TTs) 要使用 UserLogManager, 它是由下面几个组件构成:
UserLogCleaner: 它在 job 结束后的一段时间后就把这个 job 的日志清理. 这个时间的长度通过 mapred.userlog.retain.hours 配置来调整, 默认值是一天. 它把每个 job 的用户日志文件夹加入线程中等待保留时间后就可以将其删除.
在那之上, 每个 TaskTracker 重启后, 所有在 ${mapred.local.dir}/userlogs 中的日志都会被加到 UserLogCleaner 上.
TaskLogsTruncater: 这个组件是在 task 结束后截断超过一定长度的日志文件. 配置 mapreduce.cluster.map.userlog.retain-size 和 mapreduce.cluster.reduce.userlog.retain-size 就是控制日志文件可以保留多长. 这里的做的假设是, 如果一个 task 出错, 它的错误信息通常会出现在日志文件的尾部, 这样当日志文件超出一定大小后我们可以截断丢弃日志的头部内容.
mapred.userlog.limit.kb 是早于上面这个的一个解决方案: 当 task 在运行时, 它的 stdout 和 stderr 流数据会导向 unix 的 tail 程序, 并且设置 tail 程序只保留一定大小的日志, 然后写入到 stdout/stderr 文件中.
下面还有一些最终没有被并入生成环境的工作:
Log collection: 用户可以启动执行在 client/gateway 机器上的 LogCollector 程序来将每个 job 的日志存入一个紧格式.
杀掉日志文件超过 N GB 的 tasks, 因为一个失控的 task 的日志会填满磁盘导致宕机.
现存日志管理的问题
截断(Truncation): 用户比预期的更频繁抱怨截断日志的问题, 又很少有用户需要所有的日志内容. 不限制日志大小能满足所有人, 100KB 大小能满足一部分人, 但是不是所有.
失控的任务(Run-away tasks): TaskTrackers/DataNodes 还是有可能因为超大的日志文件把磁盘空间给占满, 因为截断只会发生在 task 结束之后.
保留时间不足(Insufficient retention): 用户会抱怨很长的保留时间, 没法满足所有人的需求, 默认的一天保留时间对很多人都是够用的, 但是还是有人抱怨没法做后续的历史分析.
获取(Access): 通过 HTTP 来供日志查看完全依赖于 job 的完成时间和保留时间, 这不是很可靠.
收集状态(Collection status): 同样地, 日志收集工具也没有很好的可靠性, 这种办法需要建立更多的解决方案去检测日志收集器的启动和结束, 以及何时需要从 TTs 管理的日志文件切换到日志收集器.
mapred.userlog.limit.kb 增加每个 task 的内存使用, 并且也不支持 YARN 的更一般的 application container.
历史日志分析: 所有通过 HTTP 可以访问的日志仅当这些日志还存在节点服务器上, 如果用户想要做历史日志的分析就需要使用日志收集器以及相关的工具.
Load-balancing & SPOF: 所有的日志都写入到一个日志文件夹中, 没有跨磁盘的加载平衡, 如果一个磁盘出了问题, 那么所有 job 的日志都会丢失.
现存的解决方案是解决了最基础的问题, 我们现在有机会去做得更好, 启用平台内的日志聚合.
YARN 中日志聚合的详细描写
NodeManager 就没有像之前 TaskTracker 那样去截断用户日志, 把日志留在每个节点的本地, 它通过提供一个选项可以在应用结束后将日志安全地移动到分布式文件系统中, 比如 HDFS.
对属于同一个应用(Application), 跑在一个 NM 上的所有容器(containers)的日志都会被聚合并写入一个配置好地址的(很可能是压缩过的)日志文件中.
在当前的实现中, 一个应用一结束就会得到如下:
应用级别的日志日志目录和
此节点上该应用的所有的容器的日志文件
用户能通过 YARN 的命令行工具, 网页端或者直接从文件系统(FS)中来查看这些日志, 没必要只局限于网页端一种形式.
通过将日志文件存储在分布式文件系统中, 比起第一代的实现, 现在可以让日志文件存储更长的时间.
我们不再需要去截断日志文件, 只要日志文件的长度是合理的, 我们就会保存整个日志文件.
除此之外, 在容器运行中时, 日志会被写入多个节点的目录中, 这样可以实现有效的负载均衡, 增加容错.
AggregatedLogDeletionService 是一个定期删除聚合后的日志的服务. 现在它只运行在 MapReduce JobHistoryServer 中.
用法
网页端访问(Web UI)
通过网页端访问, 需要知道的事情是, 日志的聚合操作用户是感觉不到的.
当 MapReduce 应用还在执行中时, 用户查看日志是通过 ApplicationMaster UI 来访问的, 后者会自动跳转到 NodeManager UI 上.
一旦应用结束, 所有的日志都会归属到 MapReduce 的 JobHistoryServer 中, 此时将由后者来提供日志访问.
对于 非-MapReduce 应用, 由更一般性质的 ApplicationHistoryServer 来提供一样的日志访问服务.
命令行访问
除了可以通过网页端访问, 我们同样可以命令行工具来和日志做交互.
> $HADOOP_YARN_HOME/bin/yarn logs
Retrieve logs for completed YARN applications.
usage: yarn logs -applicationId <application ID> [OPTIONS]
general options are:
-appOwner <Application Owner> AppOwner (assumed to be current user if
not specified)
-containerId <Container ID> ContainerId (must be specified if node
address is specified)
-nodeAddress <Node Address> NodeAddress in the format nodename:port
(must be specified if container id is
specified)
所以, 要打印出一个给定应用的所有日志, 命令如下
> yarn logs -applicationId <application ID>
如果只要一个给定容器的所有日志, 可以如下:
> yarn logs -applicationId <application ID> -containerId <Container ID> -nodeAddress <Node Address>
使用命令行工具的一个显著优势是可以使用常规的 shell 工具, 如 grep, sort 等等, 来过滤出我们想要的信息.
管理
日志相关的一般配置项
yarn.nodemanager.log-dirs: 确定当容器还在执行中时, 容器日志在节点上的存储位置, 默认值是 ${yarn.log.dir}/userlogs.
一个应用的本地化日志目录是这样的格式 ${yarn.nodemanager.log-dirs}/application_${appid}
一个独立的容器的日志文件夹会在上面的文件夹下, 文件夹命名格式是这样 container_${containerid}
对于 MapReduce 的应用, 每个容器目录下会包含容器生成的三个文件, stderr, stdin 和 syslog
其他的框架可以自行选择放置多少个日志文件, YARN 对文件名的文件数量都没有限制
yarn.log-aggregation-enable: 选择是否启用日志聚合. 如果不启用聚合, NMs 会把日志存储在节点本地(就像第一代所做的那样).
日志聚合启用后的相关配置
yarn.nodemanager.remote-app-log-dir: 这是 NMs 将日志聚合后存放在默认的文件系统(一般就是 HDFS 上的)上的地址. 这个地址不应是本地的文件系统, 否则日志服务器会无法提供聚合后日志的能力. 默认值是 /tmp/logs.
yarn.nodemanager.remote-app-log-dir-suffix: 日志目录会这样创建 {yarn.nodemanager.remote-app-log-dir}/${user}/{thisParam}, 默认值是 logs.
yarn.log-aggregation.retain-seconds: 配置多久后聚合后的日志文件被删除, 配置成 -1 或者一个负值就不会删除聚合日志.
yarn.log-aggregation.retain-check-interval-seconds: 确定多长时间去检查一次聚合日志的留存情况以执行日志的删除. 如果设置为 0 或者负值, 那这个值就会用聚合日志的留存时间的十分之一来自动配置, 默认值是 -1.
yarn.log.server.url: 一旦一个应用结束, NMs 会将网页访问自动跳转到聚合日志的地址, 现在它指向的是 MapReduce 的 JobHistory.
日志聚合未启用的相关配置
yarn.nodemanager.log.retain-seconds: 保存在本地节点的日志的留存时间, 默认值是 10800.
yarn.nodemanager.log.deletion-threads-count: 确定 NMs 启动的进程数去删除本地的日志文件.
其他一些配置指导
远端的聚合日志的地址的文件夹权限应该是 1777, ${NMUser} 和 ${NMGroup} 是所有者和所有组.
每个应用层次的日志的权限是 770, 但是文件夹所有人是应用的提交者, 文件夹的所有群组是 ${NMGroup}, 这样安排可以让应用的提交者能访问到聚合后的日志, 并且 ${NMGroup} 可以访问和修改日志.
${NMGroup} 应该是一个有限访问的群组, 这样才不会造成访问泄露.
结论
在这篇文章中, 我们描叙了实现日志聚合的动机以及如何面向用户和管理者. 日志聚合到目前为止证明是一个非常有用的特性.