容器对前端开发真的有用吗?答案是肯定的。刚开始给前端同学介绍容器技术的时候,很多人会说:“容器?这不是后端技术吗?不懂,前端开发也用不上。”但实际上,我们今天讨论的“前端”已经不是传统意义上的“前端”了。首先体现在终端类型的多样性,如iOS、Android、小程序等;另外,随着Node.js等技术的兴起,前端开发的边界也在逐渐向服务器端延伸。在大前端时代,如何以工程化、服务化、自动化的方式实现应用开发,实现业务持续迭代、高可用、高并发,是每一个成功的互联网产品都在不断探索,并逐渐走向成熟的东西.容器技术大大提高了这个过程的效率。本文将结合马蜂窝容器化平台赋能前端应用构建的实践经验,介绍整个平台背后的设计与实现原理,实现的部分效果,以及针对问题的优化方案。容器与前端的结合一般来说,前端的开发流程是这样的:创建服务/项目→本地开发→开发环境测试→生产环境测试→生产灰度→上线。基于容器化平台进行前端开发的好处是前后端完全分离,我们只需要专注于前端的项目构建,而不需要和后端一起打包代码。每个构建版本和每个访问规则也是独立的,一个版本的构建失败不会影响其他版本的构建和访问。那么,容器和前端的结合点在哪里呢?容器的优势在前端应用开发中发挥了哪些作用?我们可以从开发、测试、生产三个阶段来看。开发环节的容器消除了线上线下的环境差异,保证了应用生命周期的环境一致性和标准化。对于前端开发来说,要完成的任务往往是完成内容的呈现和响应用户的输入。处理HTML、JS、CSS等静态资源,文件直接发送到客户端,无需运行环境。这里似乎不能使用容器。构建时间呢?毕竟不同的项目是用不同的Node版本构建的,不同的容器可以进入不同的Node版本,这样才不会污染本地的Node环境。但实际上并没有容器,前端也可以使用NVM来管理Node版本。切换起来非常简单,就是一两行命令就可以搞定。而且本地开发很方便,好像真的没必要用容器。可以说,容器本身并不能帮助前端在开发阶段更加方便。因此,如果不熟悉容器技术,开发阶段没有必要使用容器。在测试过程中,我们以往都是使用虚拟机进行测试。常见的解决方案是前端研发将自己的代码上传到虚拟机的某个目录下,QA直接通过域名进行测试。但问题是公司的产品线很多,可能同时测试很多项目。虚拟机消耗大量系统资源,数量有限,难以扩展,影响测试效率。使用容器化平台时不会出现这种担忧。因为容器非常轻量,低消耗,启动快,可以快速扩展,完全不用担心使用不足。生产容器的另一个优势是它们支持应用程序的版本控制。比如我们上线后,发现版本有问题,需要回滚。这种情况是不可避免的。传统的方法是通过Git或者SVN回滚。一旦合并后的代码想要回滚或者拆分,就很难操作,而且重新部署非常耗时。基于容器化平台,我们可以直接通过流控将流量切换到老版本,只需几秒,回滚效率大大提高。再比如,前端性能的一个重要指标就是页面加载时间。如果首页出现白屏,会极大的损害用户体验,尤其是我们在做活动的时候,几乎所有的流量都导向活动页面,白屏会非常严重。令人抓狂。找到运维排查后,发现是某台服务器挂了,只能重启解决。不过,重启机器存在诸多不确定因素。机器可能无法启动。这种情况很常见。但是如果运行在容器化的平台上,一个容器就是一个进程。如果一台机器挂了,集群会快速拉起另外一个节点的服务,秒级,所以基本不用担心用户访问有问题。综上所述,容器相对于虚拟机的主要优势在于可以实现快速扩容、秒级回滚、稳定keepalive。因此,对于前端开发而言,容器化对于保证服务的快速迭代和线上服务的稳定性更为重要。前端需要了解的容器知识点通过上面的介绍,相信大家对于容器技术给前端开发带来的改变,已经有了一些感受。所以前端同学为了更好的应用这项技术,还应该掌握一些容器的基础知识。什么是容器首先我们来看看什么是容器,为什么说它是轻量级和高性能的。通过下图,我们可以对虚拟机和容器做一个更直观的对比:虚拟机通过运行hypervisor来模拟物理服务器上层的硬件系统,从而提高服务器的能力和容量。每个虚拟机都有一个内核并运行不同的操作系统。启动后会做进程管理,内存管理之类的事情。但是对于前端应用的构建,可能只需要一个Nginx作为静态服务器即可。在这种场景下,使用虚拟机就显得过于沉重了。容器之所以是轻量级的,是因为容器没有hypervisor层和内核层,每个容器共享宿主机的内核和系统调用。因此,容器仅包含程序运行所需的最少文件。启动一个容器就是启动一个进程,需要的资源开销更小,也更容易维护。镜像、容器和Docker这是谈及容器技术时经常提到的三个词。下面说说它们各自的概念以及它们之间的联系。镜像:可以简单理解为一层层文件系统的集合,或者目录的集合。比如我们的前端代码,最底层的目录可能是Nginx运行所需要的binary,然后在它上面多一层目录就是我们的代码,比如index.html。这个镜像层的所有图层生成后,都是只读的,不能修改每一层的文件。Container:其实就是在上面的目录基础上再增加一层目录。但它实际上是一个空目录。不同的是容器的最上层是可读写的,也就是说容器=镜像+可读写层。比如我要修改之前的index.html,就是在之前的图片上加上新的版本。也就是说,容器生成后,所有的变化都会发生在最顶层的镜像可写层。下层不允许写入,但是可以累加,就像堆叠块一样,一直添加,而原始Images不会被容器修改,这就是Images可以被多个容器共享的原因。Docker:容器技术其实由来已久。Docker是用来实现容器化技术的工具,也是业界最常见的帮助我们创建镜像,然后将镜像作为容器运行并管理的方式。容器化平台如何赋能前端介绍完简单的概念,我们来看看马蜂窝容器化平台的整体架构,我们如何赋能前端,赋能哪些能力。我们构建了基于Docker和Kubernetes的容器云平台,将应用构建、部署、资源调度、应用管理等能力抽象出来,作为服务提供给研发人员,提高线上服务的稳定性和研发效率。从应用的角度来看,下图展示了前端应用在容器化平台上的生命周期:应用中心应用是容器云平台的基本运行对象。云平台一个非常大的优势就是屏蔽了项目的类型,不管是前端还是后端。所以在应用的外壳下,无论是前端代码还是后端代码,都可以享受到同样的服务。比如,传统上应用在后端的限流、熔断、服务治理等能力也可以交给前端,让前端的同学可以专注于业务开发而不是底层实现。这是申请中心的创建页面。创建一个应用程序并托管在我们的云平台上只需要几个步骤:版本管理创建应用程序后,将构建版本。通过使用容器,我们将应用、配置、依赖一一打包成代码镜像,然后告诉在线服务器如何让它们以容器化的方式运行。因此,版本管理包括两部分:代码镜像和运行时配置。1.代码镜像我们使用基于Pipeline+Docker的Drone作为CI工具,非常灵活,易于扩展。Drone的灵活性体现在Pipeline的配置上。你可以通过设置.drone.yml文件来控制项目中镜像的构建过程。为了更好的支持公司级应用,我们将一些常用的内部包注入到镜像中,构建一个通用的基础镜像。在构建的同时,会做一些CI,比如单元测试,漏洞检测等。2.运行时配置运行时配置分为两部分:Nginx配置和部署运行时配置(1)Nginx配置Nginx配置主要针对Node前端-结束项目。向应用开放Nginx配置有几个好处:前端同学可以自己配置history模式,不需要自己找服务器配合。自定义多个位置。面对多页面应用,可以配置Nginx将请求转发到指定入口文件,实现指定路由。自定义缓存缓存策略。缓存策略的选择更加灵活,提升用户体验,减轻服务器处理请求的压力。(2)部署和运行配置部署和运行配置是告诉系统平台如何运行版本包。其实扩容是为了后续部署到各种平台,比如KubernetesKVM主机。综上所述,在版本管理部分,我们实现了以下能力:配置文件驱动、应用多副本灵活扩展Nginx配置等,面向应用开放,遵循DevOps思想,高效赋能标准化版本产品,一处构建,处处运行部署管理接下来,我们需要将构建好的版本包部署到集群中运行。网上可能有很多机器,V1、V2、V3指的是各种版本。这个版本可以有多个实例。如果服务出现故障,我们主要通过两种方式来保证稳定性和高活跃度:高效调度:使用Kubernetes调度器将指定运行的容器调度到资源满足要求的最合适的节点上。多副本支持:自动部署一个容器应用多副本并持续监控。如果容器挂了,复制会自动启动。结合我们前面提到的首页白页的例子,我们继续在容器化平台上对容器进行监控。如果服务宕机,它会在其他节点上快速启动。这里要注意,“多副本”并不是说在两台机器上启动多副本就叫多副本。如果两台机器都在同一个机柜甚至同一个机房??,那么开多副本就没有意义了。至此,我们已经将服务部署上线并稳定运行。但是部署完成并不代表用户可以访问,也不代表可以访问正确的版本,所以接下来就是服务治理了。服务治理服务治理是一个比较大的概念,可以应用在很多场景中。其内容之一是允许用户访问指定的在线版本。技术方案首先介绍实现原理:我们使用支持xds协议的网关。当新的配置通过xds协议推送到网关后,会自动进行热更新、热重启,然后适配新的配置。比如网关一开始就指向V1版本。如果我们现在要指向V2版本,只需要将最新的配置通过xds协议推送到网关,它就会应用新的配置。这样就可以将指定的版本部署上线了。推送这里我们使用Pilot组件,它针对推送速度进行了优化。Pilot组件会不断的监控数据,发现有变化就取出来。应用场景对于这个设计,我们主要应用在三个场景:回滚、导流和ABTest。1.回滚所谓回滚,其实就是流量控制。比如网关一开始就指向了V2版本:如果有问题,我只需要向网关推送一个新的配置,它就可以指向之前的版本,非常快:2.SplittingSplitting主要用在文章开头提到的测试场景中。以往使用虚拟机时,由于不同的虚拟机域名不同,前端同学要么在测试时修改代码适配虚拟机,要么需要测试同学或产品修改自己机器的host学生。不方便。而使用容器化的方式,比如默认访问的是V2版本,但是现在我们需要测试V1或者V3版本,我们可以给网关推送一个配置,告诉它如果请求中的cookie中包含identifierV=V1,会将请求转发到V1版本;同样如果cookie中包含V=V3,则将请求转发给V3,所有转发都在网关层完成。为了让服务更容易使用,我们提供了一个插件,可以自动识别部署在云平台上的服务和版本。QA和产品同学在测试的时候,只需要点击版本,系统就会自动完成cookie的注入。那么在向服务器发送请求时,网关会发现携带了某个版本的cookie,自动完成转发:3、ABTest的原理相同,我们可以通过配置来控制用户在ABTest中访问不同的版本指定用户的UID,这里支持的方式有很多种,比如注入cookies,不同的header,不同的请求方式等等,非常灵活。以上就是服务治理的内容。一般来说,我们可以自动部署访问规则。可能只需要前端同学做一个git-pushtag操作,版本已经准备好,部署到开发环境甚至生产环境。整个过程对于平台用户来说非常重要。潜移默化:接入规则自动部署,完善的CI/CD灵活分发策略,带来秒级回滚,灰度,ABTest等功能结合chrome插件,体验流畅上面介绍了我们基于容器化可以做什么云平台前端赋予了哪些能力。经过一段时间的摸索,我们目前的流程还是比较顺利的,但是难免会遇到一些问题。那些年我们遇到了4041。上线后发现js访问404这种情况对用户体验非常不好。经过排查,我们发现问题是为了实现高可用,我们的网关配置了多个网关。因为网关的转发配置是push下发的,所以多个网关之间会有时间差。一些网关首先接收新的推送,而另一些则稍后接收。当用户的请求命中其中一个网关,得到一个html,就会告诉它应该访问哪个hashjs。但是如果不幸的是这个hashjs访问了另一个网关,然后转发到另一个版本,也就是另一个容器,那么这个hash值肯定不一样,找不到对应的文件,导致404。这个问题不仅仅存在于云平台,也有关于分布式部署的解决方案。我们的解决方案是让所有网关连接到同一个Pilot。因为网关的数量有限,所以下发配置时,一个组件负责推送所有的网关。因为xds协议本身是基于GRPC实现的,是长连接操作,所以速度很快。一个节点推送时,所有网关接收配置的时间差可以控制在毫秒级以内,影响很小。即当网关A收到新的配置时,基本上网关B也收到了新的配置。这时候无论请求命中哪个网关,都会指向同一个版本。这时候线上就不会出现404了。问。2.灰度环境,之前说过js访问404,我们的灰度解决方案是用插件做cookie。理论上只要cookie配置正确,就可以转发到指定版本。那么既然我的html没问题,为什么在js中会出现404呢?经过排查,发现是因为在请求js的时候有一个叫做“匿名标签”的标签,如果我们在使用js的时候输入一个匿名标签,那么浏览器在发送js请求的时候是不会携带任何身份标识的,网关会把它视为访问默认版本,即在线版本。这时候如果请求到V2版本,就会404。短期计划1.尽可能发布和构建Pipeline。目前我们主要使用npminstall和npmrunbuild这两个命令来构建镜像。之后我们会尽可能发布Pipeline,包括基础镜像、Node版本等,让前端同学实现更多的定制化需求。2.优化构建和部署时间。目前我们的镜像构建方案没有很好的利用Docker的缓存机制,会影响构建时间。我们目前还在优化以减少甚至消除大部分npm安装时间和构建时间。3.发布监控告警能力目前我们已经完成了部分监控告警能力的建设,主要用于平台维护团队监控QPS状态,服务是否稳定,是否有重启等,团队也会收到很多告警。但是我们认为这种告警其实应该发给服务的负责人。后续我们会逐步释放这部分能力,并不断完善和优化告警规则。总结最后简单总结一下:容器化后究竟为前端赋能的是什么?提升测试效率,让服务更稳定,高效运维的马蜂窝云平台如何进一步赋能前端?应用中心:一步上云,无差别享受云平台带来的服务。版本管理:践行DevOps思维,赋能Nginx配置;配置驱动,灵活易扩展部署管理:智能调度,稳定高活服务治理:秒级回滚,秒级恢复,灰度接入,ABTest等多项功能。目前,我们探索了如何通过容器化帮助前端完成应用开发,并通过云平台进一步赋能。希望能够给大家带来一些技术思维上的启发。本文作者:周磊,马蜂窝旅游网基础平台服务研发工程师。(题图来源于网络)关注马蜂窝技术,发现更多你想要的内容
