2

背景

在互联网高速迭代的浪潮下,相信每位研发人员都经历过新项目的需求研发、测试验收、部署上线、功能迭代、甚至项目下线的全过程。其中“需求研发”、“测试验收”、“功能迭代”、“项目下线”这类环节对于研发人员来说,基本上可以有效的把控,在整个项目的生命周期内,也是投入最多的几个环节,因此可以提前预估出合理的时间计划。

这几个关键的环节中,将项目部署上线,往往是最后一个环节。对项目组成员和需求方来说,软件研发完成以后,投入生产环境,是一个水到渠成的事情,需要立马完成。

然而在网校大背景下,一个新项目投入到生产环境是一个非常严肃的事情,实际上需要经历的几个环节如下:

  1. 新域名备案(准备必要的资料)
  2. 服务器申请(机房选择、单机硬件配置、系统选型、网段规划、机器命名、容量预估)
  3. 服务器生产(必要软件初装、参数校对)
  4. 研发授权(准备dev or guest人员名单、准备机器标签进行批量授权)
  5. 投入发布系统(创建发布项目、填充机器信息、项目人员分工)
  6. 配置网关(集群选择、vhost配置、upstream配置)
  7. 域名解析(是否经过全站加速/高防等防护产品、提供解析地址生效)
  8. 若是容量预估不足,还需要再经历一遍#2~#6(多批生产的机器存在环境配置不一致的隐患)
  9. 如果遇到项目下线,往往域名又被容易忽略掉,造成一些无用且已经备案的域名,不易管理

整个过程最快也要以天为单位,隔天交付生产;慢的话要数天才能完成生产环境的部署,并且需要跨多个部门协同完成。2020年公益直播高峰前夕,“连轴转”完成服务扩容的伙伴,尤其铭心刻骨。

因此,为了解决新项目部署周期长的问题,基础架构部启动了两个项目:《域名收敛》、《容器平台》。

《域名收敛》项目,主要为了缩短:#1、#7、#9这3个环节上消耗的时间和精力。

《容器平台》项目,主要为了缩短:#2、#3、#4、#5、#6、#8这6个环节上消耗的时间和精力。

并且现在两个项目已经无缝结合,在同一个平台上完成。

下面将详细的介绍,我们如何给开发完成的新项目,在1小时内发布上线提供便利,实现小时级交付生产,以及当前的项目应用规模。

容器平台

物理机与虚拟机时代整个新服务上线流程严重依赖机房资源、运维部署、多个系统的环境配置、以及性能压测。

漫长的处理流程

资源申请流程 -〉 资源采购 -〉 机器授权 -〉 服务运行环境配置(环境、网络) -〉 配置发布系统 -〉 配置网关/日志 -〉 性能压测

硬件资源不足时,需要排队采购审批,到资源上架初始化过程可能长达数月,漫长的处理流程非常打击开发人员的热情。

在资源充足的时候,也需要数天的处理流程。我们需要去解决这些漫长的流程的问题 ,在互联网时代敏捷性决定了业务的生与死。

我们是如何做到便捷性的呢?

  1. 通过容器的环境一致性
  2. 网校云的统一调度硬件资源使我们能有效盘活有效固定资产池
  3. 通过打通网关、日志体系,每位研发老师完全自助的完成所有配置。

应用快速发布

驱动于git devops的设计,用户只需要提供git仓库地址及项目域名,平台会调用pipeline引擎构建镜像,通过配置基于git事件的触发全自动构建镜像,然后按照模板创建 deployment,service,及ingress的kubernets等应用及资源。流水线作业设计,一气呵成完成应用发布与镜像构建。

与网关进行了自动化打通, 应用部署完成后我们将集群的网关ingress的地址提交给网关, 触发网关的upstream更新操作,从而实现了服务的发现以分流。

快速发布.png

同时与公司发布系统进行打通,在用户发布完应用后,可以获得一个hook地址,发布系统设置hook后,在发布的时候出发k8s的滚动升级,以实现混合部署应用的同步升级。

云原生的PIPELINE引擎

gitlab事件触发充分借鉴了K8S 自带的CI/CD系统Prow的系统设计,实现gitlab的自动事件通知支持:

  1. Tag事件
  2. Push事件
  3. Merge事件

引擎通过向注册相关事件并监听, 当事件到来时会根据事件类型分发事件给相应的插件来处理。已经实现了和网校云容器平台的无缝连接, 通过在简单的web配置就可以构建出基于指定Tag或指定Commit的镜像。

引擎完全运行在容器之上,多插件和PIPELIE、灵活多变的设计让应用的发布不会拘泥于特定的运行与构建环境,助力新项目的新环境的快速发布于迭代。

弹性扩容

以基于horizontal pod autoscalers实现的 集群应用的自动弹性伸缩, 根据CPU使用量指标, metrics server从kubelet中的cAdvisor组件中获取cpu的资源消耗,默认情况下,每10s采样一次,每30s计算一次cpu使用量。当业务高峰来临, 监控到pod的CPU占用量超过预定的阈值, 会自动增加服务的副本数量(在设定范围),反之会减小副本数量,释放集群资源,由此实现集群资源的高效利用。

hpa.png

krt.png

自动化日志收集

我们采用了filebeat作为日志采集工具。

  1. filebeat以daemon set模式运行,保证了每个节点都会有一个filebeat容器运行。
  2. filbeat容器可以采集宿主机以及该宿主机上其他容器的日志。
  3. 可以在我们的容器平台,选择需要收集日志的应用,即可完成日志采集。
  4. 通过configMap(配置中心)的方式,对采集配置进行管理

因为我们已经和网校的日志中心打通,我们所采集的日志将均被采集到kafka中,然后落地到日志中心ES集群。

域名收敛

一、为什么要做域名收敛

随着网校用户体量飞速发展,业务越来越多样化,复杂化,各服务之间的耦合度也越来越高,依赖严重,相互影响。为了减轻依赖,各项目都希望有独立域名来提供服务,业务方也开始频繁申请新域名,使网校对外暴露的域名越来越多。这种做法逐渐造成了网校域名泛滥现象。据不完全统计,截止2019年年中,网校申请的域名有数千个,而且以每周数十的速率增长,其中经过网关的域名就已经破千,而且很多域名已经处于无人认领的状态,没有人敢确认这些服务是否在线上running,因此也不敢轻易去修改或下线。除此之外,域名的泛滥成灾也给网校的研发与运维带来了很多痛点问题:

  1. 研发人员:
  • 上线困难: 域名申请、域名备案、域名解析、网关创建应用、vhost、upstream、申请后端机器、后端nginx+fpm配置、发布代码。周期长(1周~1月),流程冗长,效率极低,研发人员将大量精力耗费在项目上线上。
  • 开发困难: 无论是app、H5、web等客户端,开发需要适配不同域名的不同接口,代码臃肿,难以维护,单app端依赖的接口就有涵盖了近百域名,开发人员已经不堪重负。
  1. 运维人员:
  • 管理维护: 上千的域名维护管理困难,只增不减,很多域名没有访问量,无人维护,无人认领。
  • 配置困难: 运维对接各种开发配置需求,造成网关层配置毫无规范,复杂冗长,频繁修改,出错率高,风险性大。
  • 安全问题: 暴露域名过多降低了系统的安全性,大量域名需要备案审计、安全扫描,也暴露出了很多安全漏洞。

2020年2月网校公益直播期间,由网校技术委员会发起的‘竞品分析’专项进行了多项技术评测指标,其中在服务端域名管控上,我们实施的力度远远不及对手,网校的研发和运维人员还没有形成域名收敛的意识,不仅没有制定相关的规范文档,也没有对应的技术手段实现域名复用。

针对以上这些痛点问题,网校服务端技术委员会、运维团队、集团安全组携手发起了《网校域名收敛专项》,项目目的是要解决网校域名泛滥的问题,一方面要对现有的域名进行收敛与规范,另一方面对后续新上线项目流程进行调整与优化,加速推进项目的自动化与标准化上线流程,为网校云的建设铺路。

二、如何实现域名收敛

2.1 域名规划

那么该如何实现域名收敛呢?首先我们将网校的服务大致分为进行了分类,大致如下:

  • 按照服务类型分类:WEB服务与API服务,前者输出的是html,后者输出的是json数据。
  • 按照服务范围分类: 对内服务与对外服务,前者只提供内网或者办公区服务,后者提供公网服务。

如此一来,就有四种类型的服务: 对外WEB,对内WEB, 对外API,对内API。

因此我们设计了四个域名与之对应: app.xueersi.com; app.xesv5.com; api.xueersi.com;api.xesv5.com; 这四个域名的作用如下。

  • app.xueersi.com : 主要代理对外WEB应用,面向公网用户,例如站点首页。
  • app.xesv5.com : 主要代理对内WEB应用,面向内部用户,例如admin管理后台,各种监控平台,告警平台等对内系统。
  • api.xueersi.com : 主要代理对外API服务,例如app,ios,pc端调用的api服务,阿里云、微信等第三方回调api。
  • api.xesv5.com : 主要代理内部api服务,例如用户数据等中台类api接口。

收敛域名的作用

2.2 方案制定

2.2.1 服务标签化

以网校的站点架构特点来说,几乎所有的服务都由统一的Nginx网关进行反向代理,一个域名对应一个vhost与upstream,通过Nginx的proxy_pass进行路由转发。为了实现域名收敛,我们决定对网关层进行技术改造,实现基于收敛域名的动态路由分发,所谓的收敛域名就是指上面提到的四个专用域名app.xueersi.com,app.xesv5.com,api.xueersi.com, api.xesv5.com, 除特殊情况外,尽量让所有的服务都能够复用这四个收敛域名(也可以叫做网关的专用’代理域名’)。

如果大家都共用同一个域名,怎么识别不同的服务呢? 很简单,根据url来识别, 我们会将url中第一个’/'后面的字段作为每个项目的标签,网关层会根据"标签"识别不同服务,比如新上线一个学习应用,想使用study.xueersi.com, 则业务方可以拿"study"作为项目标签,同时复用app.xueersi.com这个域名,当用户访问http://app.xueersi.com/study/...,网关会通过改写host,upstream,uri来实现以下转发规则:

  • 改写upstream:从proxy_pass http://app.xueersi.com 改写成 proxy_pass http://study.xueersi.com
  • 改写host : 从 app.xueersi.com 改写成 study.xueersi.com
  • 改写uri : 从 /study/course/getInfo?a=1 改写成 /course/getInfo?a=1

此时对于后端服务来说,用户请求的是http://app.xueersi.com/study/...,经过网关转化后,后端真正接收到的请求为: http://study.xueersi.com/cour...

同理,其他服务也是类似,复用同一域名,独占一个标签。

2.2.2 上线流程简化

在服务标签化之后,服务的暴露形式就从‘域名’依赖转换成了‘标签’依赖,那么对于研发人员则省去了域名申请,域名解析,网关创建vhost等步骤,以部署在kvm的服务为例,上线流程简化为:

image20191225_20589.png

其中开发需要做的只有两件事情:

  • 域名备案 :从立项到上线之间任意时间备份即可(安全部门硬性要求,公网域名必须备案,私网域名可以不用)。
  • 创建upstream: 通过网关后台创建即可。

对于接入了网校云的服务,依赖于k8s的容器运行环境,这种服务的上线会更快,网关已经和k8s进行了打通,只需要在k8s上进行应用创建,选择好对应的网关集群,一键切流即可上线一个新项目。

2.3 技术实现

从上述的技术方案中不难看出,实现的关键点在于网关需要对每一个以收敛域名打头的请求,按照url进行切割,提取出来请求的"标签",同时改写请求的host、upstream、uri,于是在网关层我们开发了dyroute插件实现了上述功能。该插件不仅支持host,url,header,body,ip,referer,cookie等十几种筛选条件,也支持URL的多段分割和提取,可以按需对host、upstream、uri进行定制。

三、问题与解决

理想很丰满,现实很骨感。真正推进域名收敛的过程,我们遇到了很多棘手的问题,这里记录了主要的几个。

3.1 path池

按照网关的这套转换规则会存在一个漏洞,以 app.xueersi.com 为例,如果有人请求 app.xueersi.com/A 则会间接的访问到 A.xueersi.com,访问 app.xueersi.com/B 则会访问到 B.xueersi.com,也就是他可以任意试探访问我们的不通服务。你可能会问,app.xueersi.com 的设计初衷不就是代理公网服务吗?就算试探中了也没有关系,本来就是面向公网用户的。网校的域名有一个"不成文"的规则,就是 .xueersi.com 都是对外的,.xesv5.com 都是对内的,但是由于历史原因,很多域名并没有准守这套规则,例如 oa.xueersi.com 本来应该是对内的OA系统,但是却用了 .xueersi.com 的对外域名。如果有用户通过 app.xueersi.com/oa 发送请求,则可以毫无阻拦的访问到我们对内的系统。同理,对于本来对外确用了 .xesv5.com 的服务,也会有类似的问题。

针对此问题,我们设计了path池机制,每一个收敛域名需要和一个path池进行绑定,我们会在创建upstream的时候把对应的标签加入到path池,只有加入到了对应path池的url才能正常通过网关,其他胡乱试探的/A,/B,/C的请求会被网关拦截。

3.2 日志切割

以 app.xueersi.com 为例,在网关上会配置一个对应的vhost,当请求到来时Nginx会匹配中这个vhost,也就是所有接入到这个域名的服务都会共用vhost。一个vhost就有一份log文件的问题,如果后期这个域名接入了上百个服务,那这上百个服务的access log全部会记录到
app.xueersi.com_access.log 这份文件中,势必会造成单份日志文件过大的问题,同时会对ELK的搜索造成困扰,用户很难找到自己服务对应的日志。

针对此问题,我们采取了一种"变量式"日志记录方式,即"access_log /home/nginx/logs/app.xueersi.com_{host}_access.log", 当接入app.xueersi.com的请求到来时,网关层会识别标签信息并将host进行对应的改写,到了Nginx的log阶段,会识别当前的host变量决定往哪份log文件里写日志。例如 app.xueeris.com/A 则会写入app.xueersi.com_A_access_log,而 app.xueersi.com/B 则会写入app.xueersi.com_B_access_log。

3.3 cookie爆炸

当多个服务共用一个域名时另一个棘手的问题是cookie,服务端如果都往app.xueersi.com里set cookie,一是不同cookie key可能会重复,二是如果没有规范后期很可能会超出浏览器的最大cookie长度限制。

针对此问题,我们从两方面着手解决。一方面设计了一套cookie管理系统,后端服务如果想要在 app.xueersi.com 里设置cookie,都必须在管理系统上进行申请,如果该cookie已经有人申请过,则不允许重复申请,cookie管理系统上会记录每个cookie对应的服务信息。 另一方面对dyroute插件进行改造,在发送响应给客户端之前,会对cookie进行校验,不属于该域名的cookie会被拦截。

3.4 跨域

跨域问题也是令人头疼的一个问题,集团安全组反馈,这些收敛域名会存在跨域风险,需要在有跨域限制。但是有的服务希望能够在网关层统一处理跨域请求,有的服务则希望后端自己处理跨域,有的服务希望后端自行处理,有的希望能够任意origin可以跨域访问,有的则希望只能‘http_origin’的访问,甚至会有希望自定义添加allow header头的业务。要想在一个域名vhost里cover所有的需求很难做到,我们和业务方以及安全组讨论了一个折中方案,即所有接入域名收敛的服务统一在网关层进行跨域处理,网关会允许集团内的域名相互访问,例如’.xesv5.com,.xueersi.com, .100tal.com’之间的跨域访问没有限制,而外部域名则会被拦截。另外我们在allow header里添加了浏览器识别的通用header头,同时设置有其他变量供业务方选定作为自定义字段。

3.5 双活

接入了域名收敛的服务中,有一部分服务是有双活需求的,但是这些域名是解析到了世纪互联IDC,无法满足双活需求,于是我们将这些域名的公网解析前移到了切流网关上,并利用切流网关的能力实现了双活需求,默认会直接转发到世纪互联, 如果有业务需要转发到阿里云会根据提前设定好的路由规则进行阿里云转发。

其实拦在我们面前的问题远远不止于此,例如前端js动态配置问题,网关插件执行顺序问题,多环境、多事业部域名收敛规划、多协议支持问题、接入全站加速问题、静态资源cdn缓存问题等等。而且面临的阻力也非常大,并不是所有人都乐意接受这个事情,特别是针对一些老的服务,没有人愿意去改造,变革总会面临着走出舒适区,适应新的规则,因此很多人会有抗拒。我们要推动全网校的前端、后端、运维、集团安全以及各部门的项目负责人,制定一套网校级的域名收敛规范,同时将这套历史经验推广到全集团。

经过了半年的努力,我们也取得了一点小小的成果,不仅在网校落地了一套域名收敛规范,以网关和容器为核心,成功实践于网校云平台,收敛的域名总数也突破500大关。

image2020812_151117.png

我们相信,既然认定了是对的,就一直干下去,解决麻烦的最好方法就是不怕麻烦,总有一天会看到收获。

作者简介

陈朝飞为好未来PHP/Golang开发高级专家


好未来技术团队
416 声望1.3k 粉丝

好未来作为一家科技驱动的教育企业,始终坚持“爱和科技让教育更美好”的使命。