构建集群的历史
物理机器的时代(2004年-2014年)
在2014年之前,我们公司的应用程序都部署在物理机器上。在物理机器时代,为了给即将上线的应用程序分配物理机器,我们平均需要等上一周的时间。由于缺乏隔离机制,应用程序会彼此影响,导致了许多潜在风险。那时候,每个物理机器上的Tomcat实例的平均数量至多9个。物理机器的资源严重浪费,而且调度缺乏灵活性。由于物理机器的故障,应用程序迁移的时间要花数小时。无法实现自动扩展。为了提高应用程序部署的效率,我们开发了编译-包装、自动化部署、日志收集、资源监控及其他一些系统。
容器化时代(2014年-2016年)
2014年秋天,JD.COM的首席架构师刘海峰领导的基础设施平台部门(IPD)寻求一种新的解决方法。Docker进入了我们的视线。当时,docker一直在不断崛起,但是还没有完全成气候,而且在生产环境缺乏实践经验。我们反复测试了docker。此外,我们对docker进行了定制,解决了几个问题,比如设备映射机制(device mapper)引起的系统崩溃和Linux内核的一些缺陷。我们还为docker添加了许多新的功能特性,包括磁盘速度限制、容量管理和镜像构建中的层合并,等等。
为了适当地管理容器集群,我们选择了OpenStack + Novadocker驱动程序这种架构。容器作为虚拟机来加以管理。它名为第一代京东容器引擎平台:JDOS1.0(京东数据中心操作系统)。JDOS 1.0的主要用途是对基础设施实行虚拟化。此后,所有应用程序在容器里面运行,而不是在物理机器里面运行。至于应用程序的操作和维护,我们充分利用了现有的工具。开发人员在生产环境请求计算资源所花的时间由原来的一周缩短到了短短几分钟。汇集计算资源后,哪怕扩展1000个容器都可以在短短几秒内完成。应用程序实例彼此隔离开来。应用程序的平均部署密度和物理机器的利用率都提高了三倍,这带来了显著的经济效益。
我们在每个IDC部署了集群,并提供统一的全局API,以支持跨IDC的部署。在我们的生产环境中,单单一个OpenStack分布式容器集群中最多有10000个计算节点,至少也有4000个计算节点。第一代容器引擎平台(JDOS 1.0)成功地支持了2015年和2016年的开展的“6.18”和“11.11”促销大活动。到2016年11月,投入运行的容器的数量就已经有150000个。
“6.18”和“11.11”是JD.COM最受欢迎的两次网上销售大活动,类似美国的黑色星期五促销活动。2016年11月11日完成的订单达到了3000万笔。
在开发和推广JDOS 1.0的过程中,应用程序直接从物理机器迁移到了容器。实际上,JDOS 1.0是实现IaaS的系统。因此,应用程序的部署仍然高度依赖编译-包装和自动化部署工具。然而,实施JDOS1.0非常有意义。首先,我们成功地将业务工作负载迁移到了容器中。其次,我们对容器网络和存储有了深入的了解,知道如何最充分地完善它们。最后,所有的经验教训为我们开发一套全新的应用程序容器平台奠定了坚实的基础。
新的容器引擎平台(JDOS 2.0)
平台架构
JDOS 1.0的容器规模由2000个扩大到100000后,我们推出了一个新的容器引擎平台(JDOS 2.0)。JDOS 2.0的目的不仅仅是成为一种基础设施管理平台,还成为一种面向应用程序的容器引擎平台。在JDOS 1.0和Kubernetes的基础上,JDOS 2.0 整合了JDOS 1.0的存储和网络,完成了持续集成/持续交付(CI/CD)的整个过程:从源代码到镜像,最后到部署。另外,JDOS 2.0提供了一站式服务,比如日志、监控、故障排除、终端和编排。JDOS 2.0的平台架构如下所示。
在JDOS 2.0中,我们定义了两层:系统和应用程序。系统由几个应用程序组成,而应用程序由几个提供同样服务的Pod组成。通常来说,一个部门可以申请一个或多个直接对应Kubernetes命名空间的系统。这意味着,同一系统的多个Pod会在同一命名空间里面。
JDOS 2.0的大多数组件(GitLab/ Jenkins/ Harbor/Logstash/Elastic Search/Prometheus)也都实现了容器化,部署在Kubernetes平台上。
一站式解决方案
- JDOS 2.0将docker镜像作为实施持续集成和持续部署的核心。
- 开发人员将代码推送到git。
- Git触发jenkins主端,生成build任务。
- Jenkins主端调用Kubernetes,生成jenkins从属Pod。
- Jenkins从属Pod获取源代码后,编译和包装。
- Jenkins从属Pod将软件包和Dockerfile发送到镜像构建节点。
- 镜像构建节点构建镜像。
- 镜像构建节点将镜像推送到镜像仓库Harbor。
- 用户创建或更新在不同区域的应用程序Pod。
JDOS 1.0中的docker镜像主要包含操作系统和应用程序的运行时软件堆栈。所以,应用程序的部署仍然依赖自动部署和另外一些工具。而在JDOS 2.0中,应用程序的部署是在镜像构建过程中完成的。而镜像包含整个软件堆栈,这包括应用程序。有了镜像,我们就能实现这个目标:让应用程序可以按设计的方式在任何一种环境中运行。
网络和外部服务负载均衡
JDOS 2.0采用了JDOS 1.0的网络解决方案,它是与OpenStack Neutron的虚拟局域网(VLAN)模式一同实施的。该解决方案让容器之间能够实现高效的通信,因而它对公司里面的集群环境而言再理想不过。每个Pod占据Neutron中的一个端口,有独立的IP地址。基于容器网络接口(CNI)标准,我们开发了一个新的项目:Cane,用于整合Kubernetes的核心组件kubelet和Neutron。
与此同时,Cane还负责管理Kubernetes中的负载均衡器(LoadBalancer)这项任务。LoadBalancer被创建/删除/修改后,Cane就会调用Neutron中lbaas服务的创建/删除/修改接口。此外,Cane项目中的Hades组件为Pod提供了内部DNS解析服务。
Cane项目的源代码目前已开发完成,很快就会发布在GitHub上。
灵活调度
JDOS 2.0经常访问诸多应用,包括大数据、Web应用、深度学习和另外一些类型的应用,采取了更多样化、更灵活的调度方法。在一些IDC,我们尝试性地混合部署了在线任务和离线任务。相比 JDOS 1.0,总的资源利用率提高了大约30%。
结束语
Kubernetes的丰富功能让我们得以更加关注平台的整个生态系统,比如网络性能,而不是仅仅关注平台本身。尤其是,网站可靠性工程师(SRE)非常喜欢复制控制器的功能。有了它,扩展应用程序就可以在短短几秒内实现。JDOS 2.0现在访问大约20%的应用,部署的2个集群每天运行大概20000个Pod。我们计划访问公司中更多的应用,取代当前的JDOS 1.0。我们还很高兴将这个过程中获得的经验与业界同行交流。
感谢Kubernetes及其他开源项目的所有贡献者。