平台概览
2014 年下半年左右,去哪儿完成了有关构建私有云服务的技术调研,并最终拍定了 Docker/Mesos 这一方案。下图 1 展示了去哪儿数据平台的整体架构:
图 1:去哪儿数据平台的整体架构
该平台目前已实现了如下多项功能:
每天处理约 340 亿/25TB 的数据;
90%的数据在 100ms 内完成处理;
最长 3h/24h 的数据回放;
私有的 Elasticsearch Cloud;
自动化监控与报警。
为什么选择 Docker/Mesos
目前为止,这个数据平台可以说是公司整个流数据的主要出入口,包括私有的 Elasticsearch Cloud 和监控报警之类的数据。那么为什么选择 Docker/Mesos?
选择 Docker 有两大原因。第一个是打包:对于运维来讲,业务打完包之后,每天面对的是用脚本分发到机器上时所出现的各种问题。业务包是一个比较上层的话题,这里不做深入的讨论,这里讲的“打包”指软件的 Runtime 层。如果用 Docker 的打包机制,把最容易出现问题的 Runtime 包装成镜像并放在 registry 里,需要的时候拿出来,那么整个平台最多只执行一个远程脚本就可以了,这是团队最看好的一个特性。第二个是运维:Docker 取消了依赖限制,只要构建一个虚拟环境或一个 Runtime 的镜像,就可以直接拉取到服务器上并启动相应的程序。此外 Docker 在清理上也较为简单,不需要考虑环境卸载不干净等问题。
以常见的计算框架来说,它们本质上仍然属于运行在其上的 Job 的 Runtime。综合上述情况,团队选择针对 Runtime 去打包。
选择 Mesos 是因为它足够简单和稳定,而且拥有较成熟的调度框架。Mesos 的简单体现在,与 Kubernetes 相比其所有功能都处于劣势,甚至会发现它本身都是不支持服务的,用户需要进行二次开发来满足实际要求,包括网络层。不过,这也恰好是它的强项。Mesos 本身提供了很多 SDN 接口,或者是有模块加载机制,可以做自定义修改,平台定制功能比较强。所以用 Mesos 的方案,需要考虑团队是否可以 Hold 住整个开发过程。
从框架层面来看,Marathon 可以支撑一部分长期运行的服务,Chronos 则侧重于定时任务/批处理。
以下图 2 是 Mesos 的一个简单结构图:
图 2:Mesos 结构
数据平台的最终目标架构如下图 3 所示:
图 3:平台目标
组件容器化与部署
组件的容器化分为 JVM 容器化和 Mesos 容器化。JVM 容器化需要注意以下几方面:
潜在创建文件的配置都要注意
java.io.tmpdir
-XX:HeapDumpPath
-Xloggc
-Xloggc 会记录 GC 的信息到制定的文件中。现在很少有直接用 XLoggc 配置的了(已经用 MXBean 方式替代了)。如果有比较老的程序是通过-Xloggc 打印 GC 日志的话,那么要额外挂载 volume 到容器内。
时区与编码
–env TZ=Asia/Shanghai
–volume /etc/localtime:/etc/localtime:ro
–env JAVA_TOOL_OPTIONS=”-Dfile.encoding=UTF-8 -Duser.timezone=PRC
时区是另一个注意点。上面所列的三种不同的方法都可以达到目的,其中第一/三个可以写在 Dockerfile 里,也可以在docker run 时通过–env 传入。第二种只在docker run 时通过 volume 方式挂载。另外,第三种额外设置了字符集编码,推荐使用此方式。
主动设置 heap
防止 ergonomics 乱算内存
这是 Docker 内部实现的问题。即使给 Docker 设置内存,容器内通过 free 命令看到的内存和宿主机的内存是一样的。而 JVM 为了使用方便,会默认设置一个人机功能会根据当前机器的内存计算一个堆大小,如果我们不主动设置 JVM 堆内存的话,很有可能计算出一个超过 Memory Cgroup 限制的内存,启动就宕掉,所以需要注意在启动时就把内存设置好。
CMS 收集器要调整并行度
-XX:ParallelGCThreads=cpus
-XX:ConcGCThreads=cpus/2
CMS 是常见的收集器,它设置并行度的时候是取机器的核数来计算的。如果给容器分配 2 个 CPU,JVM 仍然按照宿主机的核数初始化这些线程数量,GC 的回收效率会降低。想规避这个问题有两点,第一点是挂载假的 Proc 文件系统,比如 Lxcfs。第二种是使用类似 Hyper 的基于 Hypervisor 的容器。
Mesos 容器化要求关注两类参数:配置参数和 run 参数。
需要关注的配置参数
MESOS_systemd_enable_support
MESOS_docker_mesos_image
MESOS_docker_socket
GLOG_max_log_size
GLOG_stop_logging_if_full_disk
Mesos 是配置参数最多的。在物理机上,Mesos 默认使用系统的 Systemd 管理任务,如果把 Mesos 通过 Docker run 的方式启动起来,用户就要关 systemd_Enable_support,防止 Mesos Slave 拉取容器运行时数据造成混乱。
第二个是 Docker_Mesos_Image,这个配置告诉 Mesos Slave,当前是运行在容器内的。在物理机环境下,Mesos Slave 进程宕掉重启,、就会根据 executor 进程/容器的名字做 recovery 动作。但是在容器内,宕机后 executor 全部回收了,重启容器,Slave 认为是一个新环境,跳过覆盖动作并自动下发任务,所以任务有可能会发重。
Docker_Socket 会告诉 Mesos,Docker 指定的远端地址或本地文件,是默认挂到 Mesos 容器里的。用户如果直接执行文件,会导致文件错误,消息调取失败。这个时候推荐一个简单的办法:把当前物理机的目录挂到容器中并单独命名,相当于在容器内直接访问整个物理机的路径,再重新指定它的地址,这样每次一有变动 Mesos 就能够发现,做自己的指令。
后面两个是 Mesos Logging 配置,调整生成 logging 文件的一些行为。
需要关注的 run 参数
–pid=host
–privileged
–net=host (optional)
root user
启动 Slave 容器的时候最好不加 Pid Namespace,因为容器内 Pid=1 的进程一般都是你的应用程序,易导致子进程都无法回收,或者采用 tini 一类的进程启动应用达到相同的目的。–privileged 和 root user 主要是针对 Mesos 的持久化卷功能,否则无法 mount 到容器内,–net=host 是出于网络效率的考虑,毕竟源生的 bridge 模式效率比较低。
图 4:去哪儿数据平台部署流程图
上图 4 就是去哪儿数据平台部署的流程图。
基于 Marathon 的 Streaming 调度
拿 Spark on Mesos 记录子,即使是基于 Spark 的 Marathon 调度,也需要用户开发一个 Frameworks。上生产需要很多代码,团队之前代码加到将近一千,用来专门解决 Spark 运行在 Master 中的问题,但是其中一个软件经常跑到 Master,对每一个框架写重复性代码,而且内部逻辑很难复用,所以团队考虑把上层的东西全都跑在一个统一框架里,例如后面的运维和扩容,都针对这一个框架做就可以了。团队最终选择了 Marathon,把 Spark 作为 Marathon 的一个任务发下去,让 Spark 在 Marathon 里做分发。
除去提供维标准化和自动化外,基于 Spark 的 Marathon 还可以解决 Mesos-Dispatcher 的一些问题:
配置不能正确同步;这一块更新频率特别慢,默认速度也很慢,所以需要自己来维护一个版本。第一个配置不能正确同步,需要设置一些参数信息、Spark 内核核数及内损之类,这里它只会选择性地抽取部分配置发下去。
基于 attributes 的过滤功能缺失;对于现在的环境,所设置的 Attributes 过滤功能明显缺失,不管机器是否专用或有没有特殊配置,上来就发,很容易占满 ES 的机器。
按 role/principal 接入 Mesos;针对不同的业务线做资源配比时,无法对应不同的角色去接入 Mesos。
不能 re-registery;框架本身不能重注册,如果框架跑到一半挂掉了,重启之后之前的任务就直接忽略不管,需要手工 Kill 掉这个框架。
不能动态扩容 executor。最后是不能扩容、动态调整,临时改动的话只能重发任务。
整个过程比较简单,如下图 5 所示:
图 5:替代 Spark Mesos Dispatcher
不过还是有一些问题存在:
Checkpoint & Block
动态预留 & 持久化卷
setJars
清理无效的卷
关于 Checkpoint&Block,通过动态预留的功能可以把这个任务直接“钉死”在这台机器上,如果它挂的话可以直接在原机器上重启,并挂载 volume 继续工作。如果不用它预留的话,可能调度到其他机器上,找不到数据 Block,造成数据的丢失或者重复处理。
持久化卷是 Mesos 提供的功能,需要考虑它的数据永存,Mesos 提供了一种方案:把本地磁盘升级成一个目录,把这个转移到 Docker 里。每次写数据到本地时,能直接通过持久化卷来维护,免去手工维护的成本。但它目前有一个问题,如果任务已被回收,它持久化卷的数据是不会自己删掉的,需要写一个脚本定时轮巡并对应删掉。
临时文件
java.io.tmpdir=/mnt/mesos/sandbox
spark.local.dir=/mnt/mesos/sandbox
如果使用持久化卷,需要修改这两个配置,把这一些临时文件写进去,比如 shuffle 文件等。如果配置持久化卷的话,用户也可以写持久化卷的路径。
Coarse-Grained
Spark 有两种资源调度模式:细粒度和粗粒度。目前已经不太推荐细粒度了,考虑到细粒度会尽可能的把所有资源占满,容易导致 Mesos 资源被耗尽,所以这个时候更倾向选择粗粒度模式。
图 6:Storm on Marathon
上图 6 展示了基于 Storm 的 Marathon 调度,Flink 也是如此。结合线上的运维和 debug,需要注意以下几方面:
源生 Web Console
随机端口
openresty 配合泛域名
默认源生 Web Console,前端配置转发,直接访问固定域名。
Filebeat + Kafka + ELK
多版本追溯
日常排错
异常监控
大部分 WebUI 上看到的都是目前内部的数据处理情况,可以通过 ELK 查询信息。如果任务曾经运行在不同版本的 Spark 上,可以把多版本的日志都追踪起来,包括日常、问题监控等,直接拿来使用。
Metrics
第三个需要注意的就是指标。比如 Spark ,需要配合 Metrics 把数据源打出来就行。
ELK on Mesos
目前平台已有近 50 个集群,约 100TB+业务数据量,高峰期 1.2k QPS 以及约 110 个节点,Elasticsearch 需求逐步增多。
图 7:ELK on Mesos
上图 7 是 ELK on Mesos 结构图,也是团队的无奈之选。因为 Mesos 还暂时不支持 multi-role framework 功能,所以选择了这种折中的方式来做。在一个 Marathon 里,根据业务线设置好 Quota 后,用业务线重新发一个新的 Marathon 接入进去。对于多租户来讲,可以利用 Kubernetes 做后续的资源管控和资源申请。
部署 ES 以后,有一个关于服务发现的问题,可以去注册一个 callback,Marathon 会返回信息,解析出 master/slave 进程所在的机器和端口,配合修改 Haproxy 做一层转发,相当于把后端整个 TCP 的连接都做一个通路。ES 跟 Spark 不完全相同,Spark 传输本身流量就比较大,而 ES 启动时需要主动联系 Master 地址,再通过 Master 获取相应集群,后面再做 P2P,流量比较低,也不是一个长链接。
监控与运维
这部分包括了 Streaming 监控指标与报警、容器监控指标与报警两方面。
Streaming 监控指标与报警
Streaming 监控含拓扑监控和业务监控两部分。
Streaming 拓扑监控
业务监控
Kafka Topic Lag
处理延迟 mean90/upper90
Spark scheduler delay/process delay
Search Count/Message Count
Reject/Exception
JVM
拓扑监控包括数据源和整个拓扑流程,需要用户自己去整理和构建,更新的时候就能够知道这个东西依赖谁、是否依赖线上服务,如果中途停的话会造成机器故障。业务监控的话,第一个就是 Topic Lag,Topic Lag 每一个波动都是不一样的,用这种方式监控会频繁报警,90%的中位数都是落在 80—100 毫秒范围内,就可以监控到整个范围。
容器监控指标与报警
容器监控上关注以下三方面:
Google cAdvisor 足够有效
mount rootfs 可能导致容器删除失败 #771
–docker_only
–docker_env_metadata_whitelist
Statsd + Watcher
基于 Graphite 的千万级指标监控平台
Nagios
容器这一块比较简单,利用 Docker 并配合 Mesos,再把 Marathon 的 ID 抓取出来就可以了。我们这边在实践的过程发现一个问题,因为 Statsd Watcher 容易出现问题,你直接用 Docker 的时候它会报一些错误出来,这个问题就是 Statsd Watcher 把路径给挂了的原因。目前我们平台就曾遇到过一次,社区里面也有人曝,不过复现率比较低。用的时候如果发现这个问题把 Statsd Watcher 直接停掉就好。指标的话,每台机器上放一个 statsd 再发一个后台的 Worker,报警平台也是这个。
其实针对 Docker 监控的话,还是存在着一些问题:
基础监控压力
数据膨胀
垃圾指标增多
大量的通配符导致数据库压力较高
单个任务的容器生命周期
发布
扩容
异常退出
首先主要是监控系统压力比较大。原来监控虚拟机时都是针对每一个虚拟机的,只要虚拟机不删的话是长期汇报,指标名固定,但在容器中这个东西一直在变,它在这套体系下用指标并在本地之外建一个目录存文件,所以在这种存储机制下去存容器的指标不合适。主要问题是数据膨胀比较厉害,可能一个容器会起名,起名多次之后,在 Graphite 那边对应了有十多个指标,像这种都是预生成的监控文件。比如说定义每一秒钟一个数据点,要保存一年,这个时候它就会根据每年有多少秒生成一个 RRD 文件放那儿。这部分指标如果按照现有标准的话,可能容器的生命周期仅有几天时间,不适用这种机制。测试相同的指标量,公司存储的方式相对来说比 Graphite 好一点。因为 Graphite 是基于文件系统来做的,第一个优化指标名,目录要转存到数据库里做一些索引加速和查询,但是因为容器这边相对通配符比较多,不能直接得知具体对应的 ID,只能通配符查询做聚合。因为长期的通配符在字符串的索引上还是易于使用的,所以现在算是折中的做法,把一些常用的查询结果、目录放到里边。
另一个是容器的生命周期。可以做一些审计或者变更的版本,在 Mesos 层面基于 Marathon 去监控,发现这些状态后打上标记:当前是哪一个容器或者哪一个 TASK 出了问题,对应扩容和记录下来。还有 Docker 自己的问题,这样后面做整个记录时会有一份相对比较完整的 TASK-ID。
作者:csdn 郭芮
来源:CSDN
原文:https://blog.csdn.net/imgxr/article/details/80131452
版权声明:本文为博主原创文章,转载请附上博文链接!