本文围绕美图业务,与大家分享美图容器基础平台建设的探索经验,以及业务落地过程中的具体问题及相应解决方案。美图从2016年开始容器相关的探索,2018年基本实现容器化。今天我们主要围绕美图的业务情况,谈谈容器基础平台建设探索中遇到的一些问题,以及如何落地。希望能给大家一些参考。美图公司成立于2008年10月,以“成为全球懂美的科技公司”为愿景,打造了美图秀秀、美颜相机、短视频社区美拍、美图拍照手机等一系列软硬件产品.美图产品的多元化也催生了复杂多样的服务端技术,亿级MAU对服务端的技术要求越来越严苛。2016年,我们开始研究容器化相关技术。2017年,我们开始拥抱Kubernetes。2018年,容器平台基本建成,全面推进业务容器化。我们期待容器化能够提升公司研发人员的在线支撑能力,提升持续开发和集成能力,提升整体资源利用率和服务可用性。美图的容器化建设实践容器化之前业务容器化之前,我们的业务以物理机的形式部署到北京、宁波等多个IDC,部分业务部署到公有云。大部分业务由单个IDC部署,部分业务跨IDC调用,再通过专线连接IDC。当时存在几个重要的问题:服务部署不隔离,业务组合需要非常小心地控制,资源利用率很低。业务种类繁多,缺乏完全统一、完善的自动化运维手段。业务的增长将伴随着维护人力的增加。测试环境和生产环境有很大的区别,这也导致了一些生产环境的问题在测试的时候没有发现。开发者上线意识薄弱,上线失败率持续居高不下。面对机房级故障,业务迁移非常困难。出现问题时,我们只能尴尬地等待机房恢复。对此,我们希望通过积极的调整来解决存在的各种问题,容器化是一个很好的机会,可行性也比较高。同时,我们希望借此机会,全面提升我们的技术架构和相关人员从意识到技能,为未来的技术演进铺平道路。选择Kubernetes2017容器编排的“战争”已经结束,Kubernetes已经一马当先,走向成熟。我们在Kubernetes上也是全力投入,容器系统的大规模落地离不开成熟的容器编排系统。Kubernetes的容器编排、资源调度、强大的可扩展性,极大地方便了我们平台的建设。集装箱这样一个单一的集装箱,其统一的标准方便了调度和运输。Kubernetes提供了集中调度容器的码头和渡轮,使一切井然有序且易于实施。基于容器的平台就像一个完整的基于容器和Kubernetes的交通系统。它需要集装箱、码头、渡口和高速公路的完整系统。服务容器化的实际过程并不是一个简单的装箱和调度规划的过程。大多数服务都离不开外界,需要与外界保持联系。进驻集装箱的服务更像是住在集装箱房子里的用户。她需要相应的基础设施,比如水、电、气等。所以我们先提供各种基础设施,让服务可以不水土不服地进驻集装箱。比如要做好资源隔离,打通底层网络,做好负载均衡,处理应用日志等等。对于容器平台的建设,我们首先将多地的机房和云资源进行梳理,成为我们的计算和存储资源池。同时构建了基础的容器网络、日志系统、存储服务等底层基础,进而完成了基于Kubernetes的多租户容器管理平台的构建,提供全面的项目管理、服务编排、资源调度、负载均衡等能力。我们在同一个集群中提供了多租户模式,所以对集群内的业务隔离、资源调度、弹性伸缩会有很高的要求。同时我们也有多集群混合云的应用场景,所以对于集群间跨机房容器网络互通、多集群负载均衡等都有具体的需求。①基础设施建设的网络我们先再看基础设施建设的网络层。网络属于底层建设,要解决的问题非常关键。容器网络解决的问题主要包括:Pod内部容器之间的通信Pod与Pod之间的通信Pod与Service之间的通信Service与集群外的通信跨集群跨网段通信除了解决以上五个问题外,容器网络还需要考虑如何最小化网络层的性能损失。接下来,让我们看看我们在网络方案选择中的一些考虑因素。Kubernetes通过CNI提供了非常强大的扩展能力,活跃的社区也提供了更多的网络插件选择。CNI是CNCF下的一个项目。作为一个容器网络标准,它由一套配置网络接口的规范和库组成,还包括一些插件。CNI只关心容器创建时的网络分配和容器删除时网络资源的释放。CNI支持范围广,规范易于实施,社区支持非常丰富,网络选择多。在网络方案选择上,我们会更加关注性能、稳定性和可维护性。在详细对比分析了Flannel、Opencontrail、Contiv、Weave、Calico、Romana等开源方案后,最终选择了Calico方案。经过我们的实际压测验证,Calico的性能与Host比较接近。在社区活跃度、程序成熟度和稳定性方面考虑Calico也是不错的,其基于BGP的方案也为我们后续的网络扩展提供了可能。那么在Calico方案中,Kubernetes创建Pod的流程是怎样的呢?Kubernetes在创建Pod时,首先创建一个Sandbox虚拟网络,然后Pod中的容器直接继承Sandbox网络。在创建网络时,Kubelet服务会通过标准的CNI接口调用CalicoCNI插件,为容器创建一个veth-pair类型的网卡,写入路由表信息。节点上的路由表通过CalicoBird组件以BGP的形式广播给其他邻居。其他节点收到路由条目后,进一步聚合路由并写入自己的节点。Calico在同一子网内直接使用BGP,跨子网时使用IPIP。但是IPIP由于其单队列设计,存在性能瓶颈,严重限制了节点的吞吐量,尤其是作为LB使用时。因此,我们需要避免IPIP的问题。另外,由于我们的多机房建设需要打通不同机房、不同集群、不同网段的网络,因此需要进一步推进网络优化。图1中进一步的网络构建主要包括三个方面:多集群容器网络与物理网络打通,去除IPIP,关闭NAT,优化性能,提高限速,保护节点网络图1是一个简化的网络拓扑图,集群中的Calico-RR(反射器)和物理网关通过BGP连接,实现了机房物理网络和容器网络的打通,解决了多集群网络互通的问题。同时由于网络已经拉到同一平面,也直接避免了IPIP的性能问题。从上图可以看出,每个机房作为一个AS部署一个Kubernetes集群。机房内有多个RR(反射器)用于冗余。RR与机房网关建立iBGP连接,机房路由器之间通过OSPF进行同步。之间的路线。除了私有云,我们还需要解决混合云场景,实现集群网络的跨云连接。受协议支持的限制,我们没有采用与私有云相同的连接方式。因为云网段比较固定,规划完成后变化不大,所以我们采用静态路由的方式,在机房网关上配置对应网段的静态路由规则,在机房网关上配置对应的路由规则同时云路由,最后打开路由路径。我们在实施过程中遇到了很多细节问题。比如老集群单个集群跨越三个机房,网络打通后出现环路,需要静态路由来规避问题。做网络限速的时候,插件有bug,社区没有解决(目前新版本已经解决),需要手动修复。但是,在问题一一解决后,网络基础也完成并实施了。②基础设施LBKubernetes在设计上实际上充分考虑了负载均衡和服务发现。它通过kube-proxy和CloudProvider提供Service资源,适配不同的应用场景。除此之外,还有一些其他的负载均衡机制,包括Service、IngressController、ServiceLoadBalancer、CustomLoadBalancer。但是Kuernetes的设计有其实际的适用场景和局限性,不能完全满足我们复杂场景的实现。同时,考虑到社区方案的成熟度,我们最终采用了定制开发的CustomLoadBalancer。对于七层负载的选择,我们采用了比较成熟的NginxCustomController方案。我们也仔细对比了Envoy等方案,但是考虑到我们公司对Nginx有非常成熟的运维经验,我们很多业务都依赖Nginx的一些第三方扩展功能。因此,从促进业务容器快速落地和保持稳定性的角度出发,我们最终选择了Nginx作为早期的落地方案。但是在与Kubernetes的集成方面,Nginx还有很多细节问题,我们一直在推进解决方案。同时,我们也在考虑Envoy等后续的优化方案。CustomLoadBalancer由Nginx、KubenernetController和管理组件组成。KubenernetController负责监控Kubernetes资源,并根据资源情况动态更新Nginx配置。NginxUpstream直接配置相应的ServiceEndpoints,并添加相应的生存检测机制。因为物理网络和容器网络已经在网络层面拉平了,Nginx和各个集群的服务端点是完全链路可达的,所以也可以直接支持多集群负载均衡。LB提供友好的UI界面,提高发布效率,减少人为失误。同时LB还具备灰度升级、流量控制、故障降级等相关基础功能,并提供丰富的指标可视化运维监控。③基础设施建设用的日志我们再来看看另一个重要的基础设施——日志。日志其实是一个比较关键的基础设施,审计、故障排除、监控告警等都是必不可少的,日志标准化一直是一个很难推进的事情,尤其是在老系统数量众多的情况下。一方面面临业务代码的改造,另一方面面临开发和运维习惯的改造。而容器化恰好是推动日志标准化的好机会。对于图2的日志架构,我们选择Cluster-Level的方式,使用Fluentd作为节点采集代理。Docker日志驱动使用Jsonlog-driver,将业务容器日志输出到标准输出,最终放在容器所属目录下。Fluentd收集Docker输出日志并将它们写入Kafka队列。Logstash负责将消费队列数据合并到Elasticsearch中,而Kibana则提供统一的日志查询接口。在业务容器化的过程中,日志也暴露出不少问题。比如:兼容性问题,标准输出的日志可能会被截断或者经过多层封装后增加额外的内容,比如PHP-FPM、Nginx等。再比如日志格式不统一,不同类型的业务日志格式不同,很难完全统一。再比如业务日志的可靠性要求,有的允许在极端情况下丢失,有的则不允许丢失。为了让企业能够更快地将老业务迁移到容器平台,我们针对每一类业务都定制了解决方案,协助业务快速接入。比如PHP,业务输出日志到管道,然后tail容器读取管道数据,输出到标准输出。另一个例子是大数据业务。因为统计日志和事件日志是分开的,一起输出到标准输出需要大量的转换,耗时较长。因此对采集方式进行适配调整,业务直接将日志输出到rootfs。而hostmachineagent直接采集rootfs约定目录的日志数据。总之,由于日志与其他系统和人员习惯的耦合度太高,完成标准化、完成系统解耦和人员依赖变更,既费时又费力。④基础设施的灵活调度让我们看一下有关调度的一些构造。容器调度,其实就是解决资源利用的问题,本质上是一个整数规划问题。Kubernetes的调度策略源自Borg,但为了更好地适应新一代容器应用和各种规模的部署,Kubernetes的调度策略变得更加灵活,更易于理解和使用。Kubernetes通过两个阶段来调度Pod。Predicates阶段用于筛选出满足基本要求的节点,Priorities阶段用于获取更好的节点。但是由于调度是基于分配的数量而不是实际的使用率,业务需要准确预估自身的资源使用情况。如果评价不准确,可能造成资源浪费或影响服务质量。图3比如我们看图3的例子,左边是空闲的服务器,中间的每个Pod都申请了内存请求和限制。scheduler根据Request计算出server可以容纳这些Pod,所以将它们调度到server上。可以看出实际的Limit是机器可用资源的两倍。那么如果Pod1的内存使用量超过了Request,但是还远未达到Limit,那么服务器可能存在Swap。此外,当机器资源不足时,可能会出现OOM,内存最多且Request/Limit比值最小的Pod中的进程会被OOM杀死。而这种Kill会引起业务抖动。同时,如果存在大量的Swap,也会造成硬盘IO瓶颈,影响同机其他业务。在这样的场景下,目前Kubernetes调度器的实现会面临一些问题,因为它是基于配额进行调度的,而业务用户不合理的配额需求会导致很多意想不到的场景,所以无法轻易解决。针对该场景,我们总结了以下优化点:优化业务Request的值,根据历史业务数据调整Request。添加了运行时指标以考虑节点的当前利用率。为具有特殊质量保证的服务设置保证级别。避免Pod内存用于Swap。完善IO、网络等资源的隔离机制。其实在业务开发测试集群中,我们遇到过资源受限、调度不均衡导致大量OOM的场景,一度影响业务访问。本质上这是资源利用率过高时调度不合理造成的。只有经过优化和改进,我们才摆脱了这种困境。再看另外一个场景,当我们将集群分配率压缩到50%以上时,就容易出现资源碎片。这个时候,一些资源需求量大的Pod可能无法调度。在这种情况下,需要一些人工干预来进行调度调整。对于这种场景,我们其实需要通过一些策略调优来优化我们的调度,包括:Reschedule、MostRequestedPriority、priority来优化集群调度。图4图4是简化后的单个资源示例。在实际应用中,我们更希望集群有足够的冗余来进行资源调度。同时,在混合云场景下,我们更倾向于在扩充新节点之前尽可能用完一些节点。比如图4,希望有一些大的空白节点,尽量减少分片空间。但是如果提高容器的利用率,就会遇到前面场景中提到的利用率过高时Pod资源不足的问题。因此,首先需要解决资源使用的估计和调度优化问题。而且,还需要平衡利用率和冗余度,设置相应的策略权重,通过一定的水位限制,比如MostRequestedPriority的水位限制,来保证节点仍然有一定的冗余度。水位控制与我们期望的集群利用率直接相关。图5前面的考虑往往是从单一的维度出发来考虑问题。在实际场景中,我们有多个维度。多维度会复杂很多,更容易出现碎片化的情况。在这种情况下,很多情况下只能得到局部的更好的调度,而不是全局的更好的调度。有时需要进行一定量的重新调度以更接近全局最优分配。这就需要我们自定义controller来实现特定的调度策略。同时需要考虑大量Pod可能带来的业务抖动,尤其是一些没有做好的优雅关闭,需要更严格的保护规则和时序控制。.例如,当机器资源紧张时,只调整优先级较低的服务。在实际业务容器化过程中,业务对资源的依赖是复杂多样的。根据业务的实际需求,我们进一步引入了一些IO、网络、内存带宽等资源需求的调度。我们也在调度策略中添加了相应的资源。拓展资源。⑤基础设施的弹性伸缩调度解决了业务资源的合理分配问题,弹性伸缩组(HPA)是在提高资源利用率的同时为业务保障资源的重要手段。保证业务量增加时及时扩容,业务量下降时及时回收资源。HPA根据业务设定的具体指标和目标值增减Pod数量。这部分需要考虑三个方面:扩容指标的扩容Peak错峰调度实现扩容时的业务抖动优化图6图6左边是我们扩容指标的架构图。CME为扩展指标构建一个collectionsurface,通过predictors输出预测指标,通过custom-metrics提供HPA需要的扩展指标。伸缩指标我们主要扩展了四个指标,包括QPS、网络入带宽、网络出带宽、消息队列积压长度。错峰调度是通过定时策略实现的。我们这边有一个云处理业务,会对视频进行H.265转码、编码优化等CPU密集型操作。经常会有突然的转码需求高峰,导致这个业务需要大量的CPU资源。在这种情况下,HPA的扩缩容有时会跟不上节奏,导致业务处理延迟时间变长。主要原因是伸缩组算法计算伸缩值比较简单,容易在业务量下降后立即过度收缩。这样会导致业务量波动时重复扩容,影响业务稳定性。因此,我们引入了慢缩放机制,增加了缩放滑动窗口,以达到消峰的效果。图7图7是错峰调度的案例。我们的一项处理业务白天需要大量资源,高峰期需要2500个核心,非高峰期需要的资源很少,最小时只需要60个核心。另外一个服务的一些离线计算任务,时间紧迫性要求不强,可以放在晚上处理,一些统计定时任务也可以放在晚上,这样可以充分利用集群整体资源。⑥基础设施监控监控是我们稳定生产的重要保障。在容器化之前,我们的运维系统已经有了成熟的监控机制。容器化之后,相应的监控不需要完全推倒重做。部分系统可以复用,比如物理机监控,在其之上引入了一个新的容器监控。系统。容器平台主要监控几个方面:基础物理机监控,主要监控硬盘、IO、内存、网络等业务指标,包括异常监控和性能监控容器行为监控,监控Pod资源、容器事件和其他容器组件中事实上,监测指标正处于不断丰富和优化的过程中。最初我们只监控主要的四个方面指标,最后从集群、服务、Pod、业务等角度进行汇总分析,汇总分析输出。图8中的监控报表将为我们的问题分析和排查提供极大的方便。图8是CPU监控图。从图中可以看出,Pod的CPU在两个高峰期都处于满负荷状态。它实际上对应于某个在线业务异常。当时业务的SLA在流量高峰的时候处理延迟很高,引起了告警。基础平台建设涉及的内容较多,如多集群管理、多租户管理、DNS优化、镜像服务优化、混合云实现等,限于篇幅。在业务落地之前,我们多谈了基础平台的建设。下面说说业务接入的一些事情。我们知道容器化给业务带来了很多好处,但是我们也需要考虑它可能给业务带来的困难。考虑到各种迁移和转型问题,我们需要进一步优化平台,以尽可能降低业务接入成本。为了提供更简单、更方便的CI/CD流程,我们需要提供友好统一的操作界面、完备的接入指导、快速排错工具、定期培训等。图9。例如,我们优化CI/CD流程。整个构建和发布过程对开发尽可能透明。图9的右侧是新流程。代码提交开发后,Gitlab-CI会自动触发测试和构建过程,并将镜像推送到仓库。开发者只需要在统一入口操作相应的版本发布,平台会自动生成一个Deployment发布到相应的集群。再比如,我们提供友好的管理平台,可以降低业务学习成本和出错概率。它还提供灵活的软件阶段定制支持。可以通过简单的方式自定义多个阶段,包括:Dev、Test、Pre、Beta、Canary、Realse...综上所述,其实我们的基础平台建设是远远不够的,还需要考虑很多其他的因素在实际业务接入过程中。同时,业务的需求也在不断优化我们的平台架构,最终实现整体落地。事实上,我们所做的确实远远不够。业务在接入过程中还需要面对很多问题。所以我们需要在各个方面进一步提高。其实业务一直在教我们如何做好平台,我们也在不断的学习和吸收。这也是我们不断进步的动力之一。展望未来未来我们将长期运行多集群混合云架构,逐步引入多个公有云,优化调度系统,进一步提高我们的资源利用率。同时我们也会保持ServiceMesh、Serverless、边缘计算的方向。关注将结合业务需求进一步优化基础容器平台。目前就职于美图技术支持部架构平台,主要从事基础容器平台建设和流媒体系统建设。负责容器平台基础组件建设、调度系统研发、多机房和混合云建设、流媒体基础服务建设、用户体验优化。在容器化技术和流媒体方向有多年的积累和丰富的实践经验。
