经历过去 O、静态化、异地多活、全链路压测、双 11 等多个高可用项目之后呢,我就会去思考说我们能不能把这些高质量的架构通过产品化的方式,让阿里之外的公司也能够享受到这样优质的架构,而且不需要踩我们之前所碰到的那些坑。这就是我今天主要给大家介绍的我们做的叫 EWS 的一个产品,以及我们做这个产品当中的一些思考。
首先什么是 EWS, EWS 是针对互联网应用提供的系统构建、发布、持续集成、运维管理的一站式解决方案。就可以说当你研发完成之后,所有的上线、运维都可以在这个平台里面完成的。
首先我们来看一下,一个通常的运维系统是什么样的。
一般来说会用脚本做系统发布,底层的话会用 SCP/FTP 中转的方式把代码包传到服务器上去,然后重启应用。为了保证发布的一致性,我们可能会批量比对代码包的 MD5 是不是一致。那监控我们会怎么做呢,可能就会使用一些像 Zabbix 这种开源方案。
对一些初创公司来说,这套发布方案已经是足够使用了。但是我们细想一下就会发现很多需求是没有办法满足的。
比如说我搭了一套监控系统,那这套监控系统本身的监控系统由谁来做。假如某一天,我的监控系统挂掉了,那我是不是就变成了一个瞎子。还有当我发布了之后,程序员写 Bug 是个很正常的事情,我就会需要回滚,那回滚怎么做,比如我想回到某一天的某一个版本,就像 Mac 上面的 TimeMachine,我希望回到上周二发布的版本,那这个问题也是没法解决的。当我们业务上涨之后还会有一些精细化运营的需求,比如灰度发布,AB Test 这样的需求,显然这种架构也是没有办法支持的。
回到刚才的问题:『我们为什么要做 EWS』,首先要解释一下什么是 EWS ,就是Enterprise Workstation Service,企业工作站服务。我们希望企业的运维工作可以在上面一站式地完成。
首先我们做了一个 TAE 2.0 的版本给阿里百川内部使用。它的第一个问题就是不够*,什么叫不够*呢,可能有很多用户以前用过 GAE、SAE 这些 PaaS 化的产品,那它的研发流程是什么样的呢?比如我基于 GAE 的 SDK 去开发我的产品,GAE 会提供给我们文件的存储、缓存、消息中间件服务。但是有个很严重的问题是,我是强绑定一个平台的,比如我某天不希望在 GAE 上开发我的产品了,但是我已经绑死了 SDK 这样我的迁移成本就很高。这是一个不*的问题。
第二个是成本问题,我们在做百川的时候,用户购买的是一个容器,而不是一台机器。他是不能登到机器上面去的,而我们背后怎么做的呢,简单来说,我们会创建很多 ECS 的虚拟机,将用户的程序混合部署在上面。当时 Docker 发展的还不是非常的完善,我们考虑到用户的安全问题,有的时候一台机器一个容器这样部署的。那这样就带来了很明显的成本问题。我有大量的1核1G的机器。在预算有限的情况下,几千上万台机器对成本的压力是很高的。
第三个是不支持多 Region 部署,我们之前是只支持杭州机房的一个部署。那很多公司近两年开始尝试异地多活,就是我的程序在全国各地都有部署,用户可以就近接入,而且如果我的机房挂掉了,或者某个城市的机房挂掉了,都不影响我的服务。这就要求我们支持多 Region 部署。
第四点就是我们当时做的弹性伸缩它是不够智能的。为什么说它不够智能,因为当你在做一个应用弹性的时候,你需要考虑到很多方面,比如一个 PHP 的应用,他是一个无状态的应用,或者像 Hadoop 这种离线计算,他的调度算法是完全不一样的。第二个是对于有状态和无状态的服务,他们的调度也是完全不一样的,比如无状态的,我只要在另外一个地方起一个容器,然后把流量倒过去就可以了,但是对于有状态的东西来说就没这么简单。
之前我们并没有充分发挥容器的优势,容器最大的优势在于它像一个 Box,一个盒子,我把它放在哪里都能跑起来,但是他下面是一个自建机房的机器,还是我买的公有云的虚拟机,还是一台物理机,这其实并不重要。
那同时,客户对我们提出新的需求,说在你们上面开发收到很大的限制和约束,在易用性和安全上面这是有一个权衡的,我们需要考虑要让用户用起来没有什么约束,又能保证他的整个代码是稳定安全的。第二点是我们有越来越多的内部用户,希望把自己的程序部署在阿里云的机房上面,比如说,我的客户在深圳那我需要部署到深圳机房,但内部目前并没有一套全面的管控系统来帮他做发布和监控的。
那么我谈谈我理解的创业公司的第一要务是什么,首先在互联网这个行业里面,速度是需要非常快的,你和竞争对手同时想到一个 idea,他先做出来了,你没做出来,那你可能就失败。对于一个创业公司来说他一定百分之一百投入在他业务上,假设我是一个 CTO,老板问我进度怎么样了,我说『给我一个月时间,设计一个高性能高可用的架构,然后在这个架构之上就可以具备水平拓展,异地多活这些能力。』老板说『我们业务没做起来,别想那么多。』显然对于创业公司来说,并没有必要聘请专门的 SA 和 DBA,请专人来做对成本是巨大的浪费。
那给大家简单介绍一下产品可以达到什么样的效果。
这是使用我们服务的一个应用,规模还是比较大的,有 400 台 ECS 虚拟机,那他发布的流程是什么样的呢,他会将文件传到公司跳板机,再传到部门跳板机,再批量拷贝覆盖,重启应用。那么问题是他的代码版本是难以追溯的,回滚也容易搞错,本来想回滚到这个版本的代码,结果回滚到另外一个版本去了。另外很多公司的应用因为担心故障,所以只能在低峰期发布。因为害怕影响到客户所以只能在半夜发布,程序员也因此苦逼了,老是熬夜嘛。还有运维的问题,运维同学写了一个脚本,脚本比较简单,只能做 CPU 内存的监控和报警,但是我的应用里面碰到异常情况,我会打一些日志出来,根据这些日志筛选出异常做报警,这个也做不到。
在他使用 EWS 之后,他的体验是怎么样的呢?图中表明了他应用的容器数,他可以进行的操作:重启、停止、上传代码到部署、编译、历史版本部署、单文件的发布,配置管理、WebSSH、监控详情。容器管理的界面里,可以看到他的每一个容器这里都有运行功能状态、IP、容器配置、占用 CPU 和内存的情况。还可以给容器加标签,方便管理。在监控图上可以看到 CPU、内存,还可以监控带宽。
在 WebSSH 里,用户可以无需经历很长的过程,只要选择一个容器,打开 WebSSH,就可以像使用 Shell 一样使用一个在线的 SSH,只要点击一下就可以开始排查问题,这个对他的体验是非常好的,当然在 WebSSH 中是有非常严格的权限校验的。
那么对于一个系统来说,核心的关注点应该在哪里,怎么构建一个高性能高可用的应用。主要要考虑哪些方面呢:
- 稳定
- 高性能
- 可扩展(水平扩展)
- 安全
这是我们产品功能的架构图。
最上面有防攻击的一些流量入口,下面是负载均衡的层,用 Nginx 和 SLB 来做负载均衡,中间是用户部署的一些容器,底层我们提供了一些比如数据库、文件存储还有缓存这样的一些服务。右边部分我们包含了从他上传部署开始、对象管理等等很多功能。
全球有一家因为猴子而出名的公司,叫 NETFLIX,一家在线视频的公司。为什么因为猴子出名呢,他们开发了一套非常著名的组件,叫『Chaos Monkey(混世猴王)』,这个猴子在干嘛呢。猴子启动之后,会在集群里随机挑选几台机器,然后直接停掉。通过这种方式验证系统是不是高可用的。检测在一些机器停电了、机房挂掉的情况下,程序能不能正常运行。全球没有几家公司能处理好这种实验,所以 NETFLIX 在高可用圈是非常出名的。
那么说到如何保证在我一些机器挂掉的时候,我的服务是可用的。这里面有很多细分的思考。
对于无状态的服务来说,他是需要有水平扩容能力的。我在负载均衡上面,多放一台机器或者去掉一台机器,他就能起到增加容量能力或者减少的效果。在这个基础上我们是准备做更加精细化。比如对于 Java 应用来说 我们会监控 JVM 的 GC Log,我们会判别你的 CMS GC 的频率是不是固定的,有没有发生Full GC,有没有出现Stop-The-World,有没有导致你的服务在抖动,如果你不从 GC Log 来看,而从外面黑盒的角度去看的话,那在我健康检查的那个点这个应用是 OK 的,但其实这个应用可能已经是有问题的了。
故障迁移有个很大的难点,就是我们怎么迁移有状态的应用。比如说MongoDB或者MySQL这样的应用,我们怎么对他做有状态的迁移,怎么样不丢数据,这是一个难点。首先有状态的服务,在迁移的时候,一定要是停机的。如果不停机的话,会导致你的文件正在写入的时候被打快照,那文件可能是损坏的,第二,你在写快照在做,这是永远追不上的一个过程。我们将有状态的停机之后,会将数据打包,放到另外一台机器上去,再跑起来。这是一个最简单的思路,当然中间会有很多问题。第二个解决方法是我们用共享存储的方式。就是我的存储其实是在远端的,那么通过iSCSI/IP-SAN这样的协议,就是我这台机器的数据在另外一个台远端机器上,当这台机器出问题的时候,我只要起另外一台机器,然后起这个网盘挂载上来,那我就在不做数据迁移的情况下,把有状态的节点做了一个迁移。
那么从流量入口的角度来说,我们客户他的服务会跑在我们接入层的后面。那我们接入层上面就要考虑几个点。第一,带宽要足够大,否则会导致一些网络的拥塞;第二个,我一定要是一个安全的接入层,就是当客户遭受攻击的时候,第一客户的应用不能挂,第二我们自己不能挂,目前我们采用的是阿里开源的一个 Tengine,fork自 Nginx 的一个负载均衡器,现在同时支持 HTTP 和 HTTPS 的服务。在 4 层上面我们是支持云盾的防 DDOS 攻击,7 层上面使用 TMD 来解决 CC 攻击。最后就是我们的接入层要直接回源到用户的机器,那需要用户的授权,我们会在他的安全组里添加一条接入层的白名单。
那么我们接入层做了这些安全工作以后达到的目的是什么样的呢。比如我以前一个店铺的应用,比如说一些店铺上新,那我们会去他的首页他的搜索页去逛,这个页面其实是经常被攻击的,主要是一些同行攻击,直接打到我们系统上面来,可能会是几百 G 的流量,几十万的 QPS 直接打过来,所以这些方面我们是有很多攻防的经验的,然后把这种经验沉淀到我们的产品上面去。
刚刚提到的系统关注点里面有一个就是高性能,那么我们怎么评判高性能。
我会采用一些常态化的压制手段来验证我的系统到底能支撑多少 QPS 的访问量。常用的手段有哪些呢,最简单的是我去造一个请求,比如说 AB,或者 HttpClient 写的一些工具,我去造这些请求。
另外一种就是说我用线上的流量拷贝一份,比如 TCPCOPY/HTTPCOPY 的方式来拷贝到我的线下服务器上面,我可以导五台机器的流量到一台上面,看能不能扛得住这些流量。这是一种流量拷贝的方式。
那么另外一种方式是说,可能大家在外面的一些资料上可以看到,就是全链路压测。你会发现当你的系统大了之后,特别是现在比较流行的叫微服务,一个系统可能包含了几十个微服务,而且它们之间是会互相调用的,那么这时候你保证一个系统的容量是不够的,根据木桶原理永远是最容易垮掉的那个系统最先垮掉,而且整个系统会受它拖累。
那么全链路压测的目的就是说,我会把一个业务的整个链路上所有的系统全部压到,这个时候我就能保证说比如在双十一的时候,交易、商品、营销、店铺这些系统,我能保证在几万笔每秒的下单的情况下都不会挂,但是在以前,可能交易说我能扛住多少笔每秒的下单,商品说我现在能扛住几百万每秒的商品查询,最后你会有疑问说到底双十一能不能扛住,因为大家给出的标准都是不一样的。全链路压测就是解决这个问题的。
那么我们可以看一下我们给用户提供的一个基于 URL 的压测。
它大概的参数会有这些:你的应用是什么,测试时间,测试URL是哪些,这边可以设权重,还有 HTTP 的 Method,这里可以增加 URL,参数、Header。然后这里面会有两种方式,一种方式是针对小白用户,可能我以前没有太多的性能压测经验,那么我可以根据我业务的 PV 量,抽象计算出我大概需要承担多少 QPS,那么另一种就是说我自己定义参数,多少 QPS,多少 RT,点确定,那么这时候压测任务就开始了。用户不需要做额外的事情,他就可以进行压测了,结束后会产生一个非常详细的压测报告,包括比如说 QPS、RT、Load、CPU、内存、网络还有诸如 JVM 的一些 GC 的情况。
这些工具想让用户具备的能力就是让用户随时随地,想发就发,就是你无论在高峰期还是低峰期,我在高峰期也能做发布,以及随时随地,想压就压,我在任何情况下,随时可以对系统做压测。那么之前说到了程序员和运维人员这么苦逼,为什么老是要半夜两三点爬起来干活,就是不敢在高峰期做发布做变更,那么我们希望把他们解放出来。不用再两三点爬起来,房间灯也没有开,对着一个漆黑的SSH的屏幕,觉得自己很苦逼。
这是一种能力,什么的能力?想发就发,想压就压的能力,这种能力,不是每个系统都能具备的。
我们希望未来的互联网的应用,每个都应该具备这样的能力。
然后再谈一些技术方面的东西,我们会在客户的机器上安装一个 Agent,会有一个 AccessServer 的接入层。当时我们在做这块通信的时候遇到一个网络的问题,客户的系统是在阿里云ECS上,我们的系统是在内部机房,出于安全的考虑,我们的机器是不能直接访问客户的机器的。可能有人不太清楚是不是我的数据放在阿里云上你就可以随便去看,其实不是这样的,你会发现我们在做的时候,你不能访问客户的任何端口。
那么我们就反过来,客户可以请求我们的服务,Agent 在启动的时候会主动连接 AccessServer,然后中间会建立一条 WebSocket 的长连接,而且这个长连接是带 SSL 加密的。为什么要带 SSL 加密后面我会提到。那么这种情况下只要 Agent 能访问一个云服务,基本上能解决 99% 的场景,即使你的服务器不能直接访问客户的机器,这个架构也是通用的。
大家知道 WebSocket 它是一个双向通信的协议,对 Agent 来说会发心跳给服务器做监控,我们会发一些指令比如部署、停止容器、重启这些指令。整个延迟是比较低的,整个交互是在1ms以内,而且支持高并发,我们一台 4 核 8G 的虚拟机能维持并发5万个连接。
然后说下异地多活,就是多 Region 部署,这个地方我们有一些思考就是说,第一个版本里面都是中心化部署的,所有系统部署在杭州的主站,指令会通过杭州发到青岛或者深圳的机器上去,那么我们当时对哪些系统应该放在 Region 内哪些应该放在 Region 外其实是有一些讨论的。最后决定,对于一些交互频繁的应用,放在 Region内,这样整个网络链路比较好,整个延迟、成本也比较低。对于一些需要有全局视角的应用,比如说一些全局调度器,它应该是中心化的,并且它大部分是一些控制流不是数据流,所以中心化的网络带宽占用不会特别大。
还有一个非常重要的概念就是 Region 自治,什么是 Region 自治,就是当时我们考虑一种比较极端的情况,当我们的中心节点和 Region 任意一方发生网络中断的时候,心跳也上不来,指令也下不去的时候,这时候我们要保证一个事情,虽然 Region 内已经脱离了中心的管控,但是客户的整个流量、功能、服务都是可以正常进行的,不会因为中心的系统影响客户 Region 内的服务。整个网络恢复后所有应用就应该恢复了,期间用户不应该感知到任何异常。跨地域网络通信我们是通过接专线来实现高性能。另外一个就是,我们现在在全国有 4 个 Region,分别是北京、青岛、杭州、深圳,我们未来可能会有新建 Region 到香港、北美、欧洲等等。那么我们的技术人员都是比较有追求的,当新建一个 Region 的时候我们希望第一是自动化的,就是一键部署,第二我们希望是非常高效的,那么我们给自己的 SLA 就是说,在半小时内把一个新的 Region 全自动地建立起来,这是我们自动化搭建的一个目标。
那么下面一个问题是,如何让系统可演进?什么是让系统可演进,就是说你的系统一定是在不断发展的,你一定是在不断更新你的系统、发新的版本,那么在这个过程中,我们作为一个 Provider,一个服务方,怎么让客户感知不到我们系统的升级,打个比方,我们的Agent会升级,我们可能会针对Docker版本做升级,那么这些升级客户度应该是感知不到的,就是所谓的「无缝」嘛。
那么我们就会做很多事情,特别是这个地方有一个隐含的前提条件就是说,我们没有办法在用户的机器上执行任何的命令。那么用户是怎么把他的机器挂到我们的平台上来的?他是用 curl 命令来安装我们的 Agent,然后直接注册上来的,但是只要连接断开或是出现了一些意外情况,我们是拿不到用户的密码的,也无法登陆他的机器,当然你可以告诉用户说我能不能上去看一下,但我们不希望出现这种情况出现,我们希望说不管什么情况都不要骚扰用户,不要因为我们的升级骚扰用户。
那么这里是我们 Agent 升级的一个流程图,其实它跟 nginx 的 reload 原理非常像。我们可以看到这里有一个 V1 版本的 Agent 和一个 V2 版本的,一开始 V1 在工作,那么我们要升级 Agent 的时候,首先通过 V1 的 Agent 在机器上再起一个 V2 的 Agent,它们两个用不同的端口,这个时候 V1 的 Agent 会受到一个指令,就是你现在不要再接受新的指令了,新的指令全部给到 V2 上面,你现在正在执行的指令,因为我有一个中心化的系统,每次新指令发出前会问一下 V1 现在这个容器有没有指令正在执行,如果有,那么就等你继续完成之后再执行,如果说你没有在执行这个容器的操作,那么我就直接把指令交给 V2 执行了,随着时间的流逝,V1 上面就不再有指令,没有指令之后,它就可以下线了,那么这时候 V2 就成为这个机器上唯一的Agent了。所以你会发现这个跟 nginx 的 reload 是非常像的。
那么这种方式就保证了说我的 Agent 尽管频繁在升级,但是通道不会中断,也不会给客户产生影响。所以从效果来看我们从整个产品上线以来,Agent升级了有大概几十次,没有客户感知到我们这个事情。
下面是一个安全的问题,在高性能、稳定、高可用之外,安全是一个日益严重的问题。你们会发现说今年 315 暴露了一个很大的问题是说,我去了一家咖啡店或者肯德基,然后我连了他家的 WiFi,结果我的个人信息全部泄露了。所以安全是一个平台从诞生之初就应该考虑的,我这里有一个建议是说,如果你正在使用的产品不是用 https 的,那么这个产品你今天就可以把它卸载掉了。
那么在一个云计算的网络上面会有一个很明显的问题就是说经典网络下有一个大二层的嗅探问题,什么意思,就是说虽然我可以通过安全组在路由这一层把我跟其它机器的网络断开,但是在二层链路层上面我们其实在一个大二层,所以只要我在二层上面做监听的时候可能会监听到别人的一些帧,那这个就有可能会造成安全的泄露了。那么你会发现说我们的服务很多是提供在内网的,也就是说是不暴露在公网的,只有 ECS 通过内网 IP 才能访问,但是对于这些服务我们也是全部做了 SSL 加密的,不管是我们前面说到的 WebSSH,还是 Docker Registry,还有 WebSocket,所有跟客户交互的链路我们全部采用了 SSL 加密,这是一个原则问题。
成本,我们之前提到了我们的 2.0 集群大概有接近 7000 台的虚拟机,容器有上万个,那么这 7000 台虚拟机对我们的成本压力是很大的,我们会想说怎么样去削减这个成本。我们会发现第一,很多用户是没有流量的,他可能上来用来一次就没再用了,第二有的用户流量是很低的,你给他 1 核 1G 他可能根本用不满,那么我们就会通过容器和调度的方式去将流量比较小的用户做超卖,对于付费和 VIP 用户,我们还是会把资源留给他,用独占的模式,那么在经过新平台的一个调度之后我们发现,成本是这样的。
蓝色是老版本,绿色是新版本,左边是根据机器数的,右边是根据CPU的,会发现机器数我们现在大概只需要三四百台,实际减少了 95%,从 CPU 核数来看的话,之前是 10000 多核的规模,现在大概 3000 多核,也是削减了差不多 2/3 的成本。
网络拓扑我就简单过一下,我们现在所有跨 Region 的通信全部是走专线,所有公网服务全部在 BGP 机房。
那么这里是我个人对公有云的一些思考,以前机器是我家的,我可能租了一个机柜,现在只是说我的机器放在别人家的机房,是这样吗?那么我觉得说,公有云并不是说只是以前机器是我的现在租用别人的机器这么简单。
公有云它的整个开发和运维模式和以前完全是两样的。
首先第一个,公有云赋予了你更多的能力,它开放了很多 Resource API 给你,你创建一台机器,收回一台机器,创建一个 Cache,清空一个 Cache,或者是一个文件的存储,或者是我的CPU分配、网络带宽的分配,这些都给了你一个动态调整的能力,这个能力能不能善于运用,那是另外一回事。
第二个他一定是一个利于 DevOps 的一个模式,可能以前我作为一个开发人员,机器是运维给我的,而且下面网卡是什么样就是什么样,是不是 SSD,都是他说了算的。开发人员对代码实际有更多掌控权,那么现在开发人员可以涉及到运维,我需要什么样的配置,我是计算密集型还是 I/O 密集型,我如果只需要打些 log,那么我完全可以买更便宜的普通磁盘,我如果放 MySQL 那么可以买 SSD 云盘。那么开发者是接触代码最近的一个群体,他对机器配置要求是最明确的。
第三点是资源变得更易于获得了,你会发现我要一台机器的时候是要走流程,比如说采购、搬迁、上架、检查有没有问题,这个流程其实比较长的,可能一个月两个月。那么在云上面,我什么时候想要机器就可以直接生产出来使用了,在资源的获得上面其实是降低了资源的获得成本的,但是对于你的经营成本来说是不是一定降低的?我们说不一定,我们会发现现在很多公司他不用公有云,还是玩自建机房,为什么?我们通常来说公有云便宜,但其实不一定的,有可能你自建机房更便宜,这个跟你的业务、配置、带宽直接相关。那么后面就是说我作为一个小团队,我没有能力自建机房,我也没有办法说买很多的机柜,这个时候,没关系,我也可以在云上面购买不同地区的机器,我可以自己实现跨城市甚至是跨国的容灾,那么这种能力你在10年前、20年前是没法想象的。