[.com原稿]雪球目前有上千个容器,项目数量也就一百个左右,规模不是很大。但是,得益于容器技术,Snowball的部署效率非常高。Snowball的开发者只有几十人,但每月的发布量却高达2000多个。2018年5月18-19日,由主办方主办的全球软件与运维技术峰会在北京召开。在“开源与容器技术”环节,雪球SRE工程师董明鑫带来了《容器技术在雪球的实践》的主题分享。本文主要分为以下三个方面与大家分享雪球在业务中引入和使用容器技术的心路历程:雪球的技术实践和后续演进中为什么引入DockerDocker雪球是一个投资人交流的社区,用户可以你可以在上面买卖股票,卖出基金等金融衍生品,还可以通过雪盈证券买卖沪深港美股。为什么引入Docker随着业务的发展,不同社区业务之间相互影响的概率逐渐增加,所以我们希望每个业务都可以不受干扰,同时也可以在资源上、机器之间、甚至网络上使用不同的隔离级别根据监管要求实施。早在2014年,我们就发现容器技术具有镜像小、灵活、启动速度快等特点,其性能更适合我们当时物理机很少的大规模环境。相比较而言,传统的虚拟化技术不仅实施成本高,而且性能损失超过10%。因此,综合考虑镜像大小、启动速度、性能损耗、隔离需求等因素,我们选择了两种容器引擎:LXC和Docker。我们将MySQL等有状态服务放在LXC中;和无状态服务,如Docker中的在线业务。众所周知,Docker是以类似于单机的软件形式问世的。它最初的口号是:Build/Ship/Run。因此,它早期的Workflow(流程)是:先在一个Host主机上运行DockerBuild。然后使用DockerPull从镜像仓库中拉取镜像。***使用DockerRun,就有一个正在运行的Container。需要解决的问题上述流程方案伴随着以下需要解决的问题:网络连通性,由于是单机软件,Docker最初默认使用Bridge模式,不同主机之间的网络是不连通的.因此,早期最常见的通信就是如何解决网络连通性的问题。在推出多节点服务部署和更新的容器方案后,我们发现由于相对较小的性能损失,节点数量会爆炸。因此,经常会出现几十个节点可以运行在一台物理机上的情况。容器节点的绝对数量会比物理节点的数量大一个数量级,甚至更多。所以这么多节点的服务部署和更新直接导致工作量成倍增加。监控。同时,对于这么多节点的运行状态,我们需要采用合适的监控方案。Docker在雪球的技术实践网络模式首先介绍一下我们早期的网络方案:上图左侧,我们默认使用Docker的Bridge模式。如您所知,默认情况下,Docker会在物理机上创建一个名为docker0的网桥。每当创建一个新的Container时,它都会相应地创建一个veth,然后将其连接到容器的eth0上。同时,每个veth都会被分配一个子网IP地址,以保持与同一主机上各种容器的互通。由于生产环境的网卡不止一张,我们对其进行了修改。我们创建了一个“NICbond”,也就是创建了bond0网卡。我们通过创建一个br0网桥来替换原来的docker0网桥。在br0网桥中,我们配置的网段与物理机所在网段相同。由于容器和物理机在同一个网段,连接核心的交换机可以看到容器和不同主机的MAC地址。这是一个网络二层互通的解决方案。这种网络方式既有优点也有缺点:优点:由于连接和互通是在二层网络实现的,而且只用了内核转发,所以整体性能非常好,真正网卡的效率物理机也不甘落后。缺点:管理比较复杂,需要我们手动管理容器的IP和MAC地址。由于整体处于网络二层,一旦系统达到一定规模,网络中的ARP包就会产生网络广播风暴,甚至偶尔会出现PPS(PackagePerSecond)高、间歇性网络故障。由于底层网络连接,实现网络隔离也比较复杂。服务部署对于服务的部署,我们最初是沿用了虚拟机的做法。容器启动后,不会停止。因此:如果需要添加节点,我们使用Salt来管理机器的配置。如果节点需要更新,我们使用Capistrano来分发服务,部署多个节点来改变容器中的业务程序。其中,优势在于:与原有基础设施相比,迁移成本非常低。由于我们通过复用原有的基础设施,直接在原有的物理机上部署了各种服务,所以很容易就迁移到了容器中。对于开发者来说,看不到容器层,就像使用原来的物理机一样,没有“违和感”。与虚拟机相比,启动速度更快,运行过程中不会丢失虚拟化。最重要的是在一定程度上满足我们对隔离的需求。缺点是:迁移和扩展非常麻烦。例如:当一个服务需要扩展时,我们需要有人登录物理机,生成并启动一个空容器,然后将服务部署进去。这效率较低。各种历史版本缺乏统一的管理和维护平台。我们需要通过文档记录整个机房的容器数量和每个容器的IP/MAC地址,出错的可能性极高。缺乏流程和权限控制。我们基本上使用原来的控制方式。自研容器管理平台针对以上不足,我们需要自研容器管理平台,对各种物理机、容器、IP、MAC地址进行管理,并进行流程控制。所以,我们改的整个发布流程是:开发者提交代码到代码仓库(比如Github)。触发一个Hook来构建镜像,在构建的同时做一些CI(持续集成),包括静态代码扫描和单体测试。将报告附加到镜像信息中,存放在镜像仓库中。部署测试环境。小流量上网。上线后做一些自动化的APIDiff测试,判断是否可用。继续全面上线。镜像构建有了容器管理平台,就会涉及到镜像的自动化构建。与业内其他公司类似,我们也使用基于通用操作系统的镜像。然后把我们公司内部会专门用到的那些包添加到镜像中,得到一个通用的基础镜像,再添加不同语言的依赖,得到不同的镜像。每发布一个商业版本,将代码放入对应语言的镜像中,得到一个商业镜像。构建镜像时需要注意尽量避免无用的图层和内容,有助于提高存储和传输效率。该系统依赖于我们一整套解决方案,涉及到以下周边开源项目和技术:由于负载均衡频繁增减节点,如何通过流量调度和服务发现自动加入负载均衡?对于那些非Http协议的RPC,如何自动安全的移除一个节点呢?这里我们使用Nginx+Lua(即OpenResty)实现逻辑,动态改变Upstream。当一个节点启动时,我们可以自动注册并加入它;并且当一个节点被销毁时,也可以及时移除。同时我们内部使用Finagle作为RPC框架,通过ZooKeeper实现服务发现。日志收集由于节点较多,我们需要收集各种日志。这里,我们大致分为两种收集方式:一种是Nginx,不易侵入代码。我们没有尝试改变日志流向,而是让它直接“打”在物理机的硬盘上,然后使用Flume收集并传输到Kafka。另一类是我们自己的业务。我们实现了一个Log4Appender,将日志直接写入Kafka,然后从Kafka传输到ElasticSearch。网络模式本场景我们采用上述改进的Bridge+Host模式。监控系统监控系统由上图所示的几个组件组成。它会收集(Collector)不同的监控指标数据并传输给Graphite,Grafana可以读取Graphite信息并图形化展示。同时,我们还根据内部业务的适配需求,修改定制了告警组件Cabot。这个时候我们的平台已经和虚拟机的使用方式有很大的区别了。如上图所示,主要区别体现在编译、环境、分发、节点变更、流程控制、权限控制等方面。我们的使用更加自动化。由于是自研的容器管理平台,给我们带来的直接好处包括:流程和权限控制。代码版本和环境固化,多版本发布,镜像管理。大幅提升部署和扩容效率。但它也有一定的缺点,包括:在过程控制逻辑、机器和网络管理、耦合度本身等方面存在缺陷。因此,它不是一个很好的架构,也不能真正做到“高内聚低耦合”。由于是自主研发的产品,其功能并不完善,无法实现自愈。不能根据新增节点自动选择物理机,自动分配和管理IP地址。引入Swarm2015,我们着手改造容器管理平台。由于该平台之前是基于DockerAPI构建的。而Swarm正好可以对Docker的原生API提供很好的支持,所以我们觉得引入Swarm的话,改造之前代码的成本会降到最低。那么我们如何改造原有的网络二层方案呢?前面说了,我们一直在实现的是让容器的IP地址对应到物理机的IP地址。因此,不存在网络故障。同时,我们的Redis是直接部署在物理机上的。因此,基于上图中列表的对比,我们觉得Calico方案更适合我们的业务场景。所以我们在上层使用Rolling来操作各种进程,在中下层使用Swarm+Calico来管理容器和网络。Calico采用DownwardDefault模式,即使用BGP协议在不同机器之间分发路由信息。默认情况下,Calico是Nodes之间的Mesh方式,即任意两个Nodes之间存在BGP连接。当我们在物理机上启动一个容器时,它会向物理机添加一条包含容器IP地址的路由。由于多台物理机在同一个Mesh中,每台机器都会学习到路由信息。随着我们系统规模的逐渐增大,每台物理机上的路由表都会相应的增加,从而影响到网络的整体性能。所以,我们需要采用这种DownwardDefault的部署方式,这样就不需要每台物理机都有一个完整的路由表,而只针对交换机。众所周知,BGP会给每台物理机分配一个AS(自治域是BGP中的一个概念)号,那么我们就可以为每台物理机分配相同的AS号。并为它们的上行交换机分配另一个AS号,同时为核心交换机分配第三个AS号。通过这种方式,每台物理机只会用自己的上行交换机分发路由,那么当一个新的节点启动时,我们就可以将这条路由信息插入到该节点自己的路由表中,然后通知与其相连的上行交换机。上行交换机学习到这条路由后,进一步推送给核心交换机。总结一下,这种模式的特点是:单个节点不需要知道其他物理机的相关信息,只需要将数据包发送给网关即可。因此单台物理机上的路由表也会大大减少,其数量可以维持在“单机容器数+一个常量(自配置路由)”的水平。每台上行链路交换机只需要掌握自己机架上所有物理机的路由表信息即可。核心交换机需要保存所有的路由表。而这正是其自身性能和功能的体现。当然,这种模式也带来了一些不便,就是对于每一个数据流,即使目标IP在全网不存在,他们仍然需要一步步向上查询到核心交换机,***然后判断数据包是否真的需要丢弃。随后的演进在这之后,我们也逐渐将DevOps的思想和模式引入到现在的平台中。具体包括以下三个方面:通过更加自助化的流程,解放运维。让开发人员以自助服务的方式创建、添加和监控自己的项目。我们只需要知道平台中各个项目所占用的资源,就可以将精力集中在平台的开发和完善上。在Kubernetes基本成为行业标准的今天,我们逐渐替代了之前使用的Swarm,通过Kubernetes实现了更好的调度方案。支持多机房、多云环境,实现更高级别的容灾,满足业务发展需求,完善集群管理。上图是一个嵌套关系:在我们每个Project中,可以有多个IDC。而且每个IDC都有不同的Kubernetes集群。同时,在每个集群中,我们为每个项目分配一个Namespace。根据不同的环境,这些项目的Namespaces会有不同的Deployment。比如我们要分开部署和发布,我们相应的做了多个Deployment,不同的Deployment标记不同的环境。默认情况下,流量被引入到第一个Deployment中。第二个Deployment部署好后,需要发布的时候,我们直接“切”流量。同时,鉴于我们的平台已经有了日志、负载均衡、监控等解决方案。而Kubernetes本身就是一个比较全面的解决方案,所以我们本着降低成本的原则谨慎过渡到Kubernetes,尽量保持平台兼容性,不让开发者有“违和感”。今天,我们只有一千多个容器和大约一百个项目。但是,我们在部署效率上的提升还是非常显着的。我们几十个开发者一个月可以发布2000多次,每个交易日的日志量在1.5T左右。雪球网运维开发架构师董明鑫,曾供职于百度,2014年加入雪球,目前主要负责保障雪球的稳定性,提高资源利用率,提高开发效率。关注容器生态的技术发展。【原创稿件,合作网站转载请注明原作者和出处为.com】
