那些年我们写过的容器调度和编排系统

自从 13 年容器技术进入开发领域之后,少部分立即意识到了单纯的用来做 OPS 便利性工具并不是容器技术的初衷。接着 Google 家著名的 Borg 论文被人反复提起,仿佛你没看过这篇实际上模糊了不少细节的论文千百遍你就是远离了主流,被浪潮给摁死在了沙滩上。但是这篇论文却让之前那部分人意识到容器技术恰好是一种很好的工具来满足大规模线上标准化调度和编排的最小单元,这个调度和编排的工具才是容器技术的圣杯。然而在 14 年的春夏之交,你却很难在市面上找到一款成熟的,可以用来「参考」或者开箱即用的调度编排系统,有的只有 G 家的思路和若干个起了个头的开源项目。在当时 mesos 的 Marathon 还没整利索,DC/OS 连他爸都不知道在哪里。现在炽手可热的 k8s,还活在不成熟的 Marathon 阴影之下并且时刻提防着自己人捅刀子。正逢国企老皇帝开恩,我和我的团队半桶水就开始上路,一路下来到今年也算是写过若干个调度和编排系统了,今天就来说说他们的历史和传承。

第一代调度平台 NBE 2014.07.21-2015.02.01 「进击的巨人」

第一代调度平台的核心思想是通过一个 master 连到每台机器上的 agent 去解决容器部署的问题。在这个阶段我们还没深入到资源怎么分配这个复杂的命题上,以当年我自己在豆瓣开发 DAE 的经验为基础,疯狂的执念于 websocket 用于结构中各组件通讯,同时也是这个团队第一次拿起 golang 写代码。生产中单主,agent 通过 websocket 反连,断了会动态感知并标记此节点 offline,所有的容器部署都通过其进行任务下发。应用层面我们做了不少 SDK 的工作,希望通过 SDK 配合 Image 的构建来使得 NBE 等同于 DAE 的加强版,然而在国企的伟光正下最后发现其实并没有什么卵用。

这一阶段在实际开发的时候很多问题是没得参考的,需要靠自己 XJB 弄。其一是日志如何获取,无意中找到了一个 logspout 的项目点亮了我们,一直到目前正在开发的最新一代调度平台我们还在利用在它身上学到的 attach 大法处理日志流,还一不小心绕开了新 log 接口面世之际的几个低级 bug。其二是容器的 metrics 如何获得,在这个时代我们主要是通过 libcontainer/proc file 结合的方式来获得容器 metrics 并发送到远端的 influxdb。其三的存储,毕竟只有2个人的团队,我们尽可能的选择了 Ubuntu 用于开发。但实际线上由于我们在运维部门面前没有话语权,经过长时间的争取最后采用了 CentOS 6 + 定制内核的方式支持 Devicemapper,从此走上了 CentOS 的不归路。

经过这一个系统的开发,我们逐渐把那些从无到有的东西都整利索了,也知道了和国企老爷们的战斗不是一个 SDK 可以解决的。通过半年左右的开发沉淀之后,我们结束了这一系统的开发周期转而投入了第二代目的开发。

这个系统最终为整个调度平台提供了这么些东西:

  1. 完整的 log/metric 解决方案。
  2. 容器调度层和 Application/Pod/Group 的关系组织。
  3. 系统 OS 层面的标准化定制。
  4. 镜像继承体系。
  5. 不要作死用 influxdb。

第二代调度平台 ERU 2015.02.27-2016.08.24 「魔戒」

当第一代平台因为和国企大老爷在 SDK 存废问题上分歧过大以至于陨落的时候,我们团队开始了第二代的调度的平台开发。这一次我们只关注于平台层面的调度,介于传统 IaaS 和 PaaS 之间,并在此之上着重考虑了资源分配的逻辑和周边基础设施的组成,不再追求由下往上的完整闭环。

在第一代的平台开发基础上,我们实现 ERU 并没花太多的精力和时间,虽然我们还是只有2个人。通讯协议上我们采用了 Core 通过 https 直连 docker daemon 操作为主,网络接口操作和节点感知等通过 HTTP 协议以及用 Redis 做发布来串通 Agent 在宿主机上操作。复用之前的 Application/Pod 体系,放弃了 Group 作为多机房的逻辑,放弃 Bridge 网络模式下的端口分配,一律采用 Macvlan 作为 SDN 等。资源层面以 CPU 为主维度,在 Core 中实现了一种贪心算法去尽可能的使得主机资源被最大化利用,对 Memory 和 NetIO 以监控报警为主。

这一代调度平台的生命周期里面,随着时间的推移我们尝试了诸多第三方组件来构建整个平台生态。如监控方面放弃 influxdb,转投 graphite,又再转成 open-falcon,如日志方面 logstash,moosefs,elasticsearch 的组合。我们也第一次把服务发现引入平台重要组成部分之一,采用 unbound + 魔改过的 skydns 方式实现内网域名实时增删查改,结合 openresty 技术构建的第一代 eru load balance(ELB)来满足 7层服务自动发布等。对 docker 本身的调用方面和第一代调度平台并无太多不同,该用 attach 接口还是 attach 接口,metrics 接口反反复复之后最终还是回归了宿主 proc file 的方式解决。

由于国企老爷的疏忽,我们在 2015 年底跨年晚会上接了 redis 的这个锅。在第三位小伙伴强力支援下,我们在这个平台上以 Redis 3.0RC1 版本为基础,开始构建起了容器化的 Redis Cluster 平台,并对外提供服务支撑了高达百万的峰值 TPS 跨年晚会直播。从此以后很长一段时间容器化 Redis 集群随着 Eru 平台的演进和扩大,成为前司的重要的服务基石之一,并顺手接下了前司 1/5 的主要业务全容器化支撑,直到我们离职。

经过年初换工作之后,我们发现之前的 SDN 技术基石 Macvlan 在 UCloud 上玩不通了,于是又花了不少时间在兼容 Calico上。在修修补补又一年的寂寞中,团队多了2个小孩子和本是同根生的宜信 Lain 主程之一的加入,我们获得更多的灵感和人力,于是下一代的 Eru 就在计划之中了。

这个系统最终提供了这么些东西:

  1. 经过生产环境反复验证的经验和坑。
  2. 完整的一键 Redis Cluster 解决方案。
  3. Macvlan/Calico 2/3 层 SDN 解决方案。
  4. 以 CPU 为本位的贪心分配器。
  5. 第一代动态发布 ELB。
  6. 更多的基础设置组合。

第三代调度平台 ERU2 2016.06.23至今 「魔戒+魔兽+彩虹小马」

随着在 ENJOY 启用 ERU 一段时间后,Lain 的主程和新加入的小孩的加入对于我们这个古老的团队来说简直就是 fresh meat。通过新的通讯结构,新的组件,新的 ERU2 调度和编排平台将会是我司未来平台层面的基石,支撑起新机房里面所有的服务。

首先我们抛弃了 HTTP 系的通讯,分离了调度和编排2个部分。与 Application/Pod 等关系型数据我们把它放到了新成员六姨夫写的 Citadel 项目里面,而与 Docker 交互的部分我们称为新的 Core,两者通过 grpc 进行交互。Agent 部分改动不大,但所有的 HTTP 接口的 publish 和 feedback 全改为了通过 etcd 来进行,网络部分被剥离,完全依赖 docker network plugin 进行操作。经过这一架构上的改动,我们获得了 Lain 的一个特性那就是自举,这意味着无论是 Core 还是 Citadel 的 HA 都将更加方便。资源层面从 CPU 本位换成了内存本位,从贪心算法换成了动态规划,通过更精确的资源分配和 CPU oversell 来提高集群的资源利用率。

日志方面在 JAVA 呕吐日志的冲刷下,无论是 mozilla 的弃儿 heka 还是 logstash 都已经不能满足我们的需求。在新团队成员小马的努力下,我们通过 rsyslog 串联和 kafka 的组合满足了线上实时日志的需求。放弃 open-falcon 后我们回归了传统的 graphite 技术栈,不过这次我们采用了激进的 carbon-c-relay 配合 go-carbon 组合满足平台层面以及应用层面的 metrics 收集。通过整合 etcd 的整合和发布,实现了 Core 和 Agent 之间的互相发现和通讯。服务发现的关键组件 ELB 在经历过我写了第一版(代号 洪武),随后写了第二版(代号 朱标 然而并没有上线),之后是六子写的 Pathlb(代号 建文),现在是已经发展到了小马写的第三版(代号 永乐 )上。通过多级模式过滤器,我们得到了在7层之上根据容器各种信息和特定条件精确分流的能力。

还有新加的 SSH Proxy 进各容器组件 mimiron,agent log stream API,deploy hook 什么的这种东西就不多说了。我们还把目前积攒的镜像体系通过 gitlab/ci 给完全自动化组织起来,达成了这一代平台系列最初一直想做的开发闭环。anyway,新机房的迁移必然是会有更多需求的,这不多机房的支持自从 ERU1 被废弃后这又提上议程了么。

至于为什么要自研,那是因为早些年没得用啊。况且从我的角度来看贸然用开源系统如 Mesos K8S 的二次开发成本其实也不低的,而且还要考虑向上游兼容,至于 Docker 的 swarm,不出幺蛾子就好了。不过如果当年的我在现在恐怕也不会再选择自己写了,就如同西部淘金年代一样,过去的年代那就是过去了,没必要一直造轮子,既然造了就要把它造好咯。