127-Serverless入门课

开篇词 | 说来说去，到底Serverless要解决什么问题？

你好，我是秦粤，欢迎你和我一起学习 Serverless。说起 Serverless 这个词啊，我估计你应该不陌生。就算你没详细了解过，但我确定你肯定在过去几年时间里听别人说过。去年我在参加GMTC 全球大前端技术大会的时候，也惊讶地发现国内几个大公司都已经有成熟的应用案例了，所以我当时就感叹说，“Serverless 终于要飞入寻常百姓家了”。

三个问题

作为一名 Serverless 的拥趸者，过去几年时间里，我总是喜欢向朋友和同事推荐 Serverless 技术。不过，在“推销”的过程中，他们经常会问我一些问题。那在今天的开篇词里，我就来统一回答下这些共性的问题吧，我估计你也会问到。

问题一：说来说去，到底 Serverless 要解决什么问题？

我不知道你有没有算过你们公司每年在服务器上的开销，反正在创业之前我是没算过，总觉得这钱算了也没必要，还是应该多花心思在怎么挣钱上。但后面当我真正自己开公司之后，才知道柴米贵。

咱们就拿自己部署一套博客来说吧，常见的 Node.js MVC 架构，需要购买云服务商的 Linux 虚拟机、RDS 关系型数据库，做得好的话还要购买 Redis 缓存、负载均衡、CDN 等等。再专业一点，可能还会考虑容灾和备份，这么算下来一年最小开销都在 1 万元左右。但如果你用 Serverless 的话，这个成本可以直接降到 1000 元以下。

Serverless 是对运维体系的极端抽象，就像 iPhone 当年颠覆诺基亚一样，它给应用开发和部署提供了一个极简模型。这种高度抽象的模型，可以让一个零运维经验的人，几分钟就部署一个 Web 应用上线，并对外提供服务。也就是说，你根本不需要再学习怎么在 Linux 上装 Web 服务器，怎么配置负载均衡等等这些繁琐的偏运维方向的工作。

所以，你要问我 Serverless 解决了什么问题，一句话总结就是它可以帮你省钱、省力气。

问题二：为什么阿里巴巴、腾讯这样的公司都在关注 Serverless？

首先，Serverless 可以有效降低企业中中长尾应用的运营成本。中长尾应用就是那些每天大部分时间都没有流量或者有很少流量的应用，你可以想想你们公司是不是也有很多。这一点我特别有感触，尤其是企业在落地微服务架构后，一些边缘的微服务被调用的概率其实很低。而这个时候，我们往往又很难通过人工来控制中长尾应用，因为这里面不少应用还是被强依赖的，不可以直接下线处理。Serverless 之前，这些中长尾应用至少要独占 1 台虚拟机。现在有了 Serverless 的极速冷启动特性，企业就可以节省这部分开销。

其次，Serverless 可以提高研发效能。我们专栏会讲到 Serverless 应用架构的设计，其中，SFF（Serverless For Frontend）可以让前端同学自行负责数据接口的编排，微服务 BaaS 化则让我们的后端同学更加关注领域设计。可以说，这是一个颠覆性的变化，它能够进一步放大前端工程师的价值。

最后我想说 Serverless 作为一门新兴技术，未来的想象空间很大。我看到有创业公司用 FaaS 来做基础设施编排和云服务编排；也有外包公司利用 Serverless 应用架构的快速迭代能力，提升开发效率，降低出错率，还可以给自己沉淀领域的解决方案；还有包括风险投资方也在逐渐开始关注 Serverless 领域，毕竟这也是一个新的风口。我讲大企业的使用方式只是希望给你一些灵感，不想过多限制你的想象，Serverless 的疆域边界还在等你去扩展。

这里是 GMTC 会议上几个大公司的分享资料，你感兴趣的话，可以先看看。

阿里跨境供应链前端架构演进与 Serverless 实践

Serverless 前端工程化落地与实践

从前端和云厂商的视角看 Serverless 与未来的开发生态

问题三：Serverless 对前端工程师来说会有什么机遇？为什么我们要学习 Serverless？

相对其他工种而言，Serverless 给前端工程师带来的机遇更大，它能让前端工程师也享受到云服务的红利。如果说 Node.js 语言的出现解放了一波前端工程师的生产力，那 Node.js+Serverless 又可以进一步激发前端工程师的创造力。口说无凭，到时候你看完咱们专栏里的例子就能体会到这句话的意思了。

另外，我觉得学习 Serverless 是成为云开发者的最适合的切入点。无论你是零基础，还是资深服务端运维，都可以从 Serverless 上学习到现代服务端运维体系的重要思想（我在第一节课就会讲）。

回答完这几个问题，我想你对 Serverless 已经有了初步了解。接下来我也不想绕弯子，我就再和你聊聊我准备怎么给你讲这门课吧。

课程设计

基础篇，我会继续带你理解 Serverless 要解决什么问题，以及 Serverless 的边界和定义。搞清楚了来龙去脉，我们会进入动手环节，我会通过一个例子来给你讲解 Serverless 引擎盖下的工作原理，以及 FaaS 的一些应用场景。

进阶篇，我们将一起学习 FaaS 的后端解决方案 BaaS，以及我们自己现有的后端应用如何 BaaS 化。为了更好地展现 Serverless 的发展历程和背后的思考，我也为你准备了一个基于 Node.js 的待办任务的 Web 应用，你要做好准备，这里我会给你布置很多动手作业。

GitHub 地址：https://github.com/pusongyang/todolist-backend

实战篇，我会通过 Google 开源的 Kubernetes 向你演示本地化 Serverless 环境如何搭建，并根据我的经验，和你聊聊 Serverless 架构应该如何选型，以及目前 Serverless 开发的最佳实践。

学习路径图

最后，我再来介绍下我自己吧。我叫蒲松洋，秦粤是我的花名。2006 年从电子科技大学毕业后，我就进入了 UT 斯达康（现在这公司已经谢幕，它是小灵通的主要生产厂商）做通讯相关的工作，当时的职位是 PHP 前端工程师。2013 年，我跳槽加入百度，从 PHP 前端工程师转为了 Node.js 前端工程师。2015 年开始又和朋友折腾创业，用 Node.js 做智能家居 IoT。2016 年底，创业没成，我又回到了国内某一线互联网公司，负责 Node.js 应用治理和 Node.js 微服务架构设计。

我在用 Node.js 做微服务时发现，微服务本身提出了很多理念。但微服务在服务端运维却缺少给力的支撑平台，后来我们就试着用容器集群搭建了自己的 Container Serverless。结果证明它不但可以支撑 Node.js 微服务运维，还可以提高 Node.js 中长尾应用的资源利用率。再后面，大家都意识到了 Serverless 带来的便利性，于是我们团队也就整体参与到了公司 Serverless 整体建设中了。

在研究并落地 Serverless 技术的过程中，我发现国外的 Serverless 开源社区其实比国内更加活跃。自从 2014 年 AWS 推出第一个 FaaS 服务 Lambda 后，国外的很多公司都在积极推进 Serverless 的生态发展，并且开始占领高地，制定了很多的规范。而放眼国内，目前还只有为数不多的大型互联网公司在重点跟进，其他人基本上只是在观望或者看热闹。

因此，我也特别希望通过这个专栏能够带你真正理解 Serverless，并让你尽快享受到技术的红利。Serverless 肯定是未来云计算发展的重点方向，作为工程师，特别是前端工程师，我们应该思考的是如何抓住这波机遇，如何利用 FaaS+ 各种创意，组合碰撞出各种化学反应，去为公司、为自己创造更大的价值。

以上就是今天的全部内容。有关 Serverless，不知道你的看法是怎样的？如果你有什么疑惑，或者在实践中遇到了哪些困难？欢迎在留言区中提出，我们共同探讨。

特别放送 | 为什么阿里要举集团之力趟坑Serverless？

你好，我是专栏编辑冬青。在课程正式开始之前，我要先向你推荐一篇 InfoQ 记者蔡芳芳的文章。为了探究 Serverless 的价值、生态位以及局限之处，蔡芳芳采访了阿里高级前端技术专家杜欢，并表达了自己的看法。以下为具体内容。

作为阿里巴巴经济体前端 Serverless 研发升级项目的负责人，杜欢过去两年花了大量时间推进集团内部的 Serverless 研发模式升级工作。这是一项牵涉整个阿里集团层面的技术升级工作。阿里 2018 年正式启动内部 Serverless 资源底座的准备工作，2019 年基于搭建好的底座建设上层前端框架，到 2019 年双十一，阿里已经在部分电商导购业务上开始实践这套新的研发模式。

Serverless 兴起于 2017 年，在最近两年伴随着云原生概念的推广愈发火爆。在这波 Serverless 浪潮里，阿里是国内走得最前面、说得也最多的一个。但为什么阿里会上升到整个集团的高度来推进 Serverless 研发模式升级，我们仍然感到好奇。对此，杜欢表示：“ 这件事本身好像是一件技术的事情，但其实它背后就是钱的事情，都是跟钱相关的。”

杜欢告诉 InfoQ 记者，为了保障业务的稳定性和可用性，阿里对每一个应用上线都有相应的规范和规则。哪怕是一个很小的内部应用，一天可能只有一两个访问量，上线也需要遵守既有的规范，这势必会消耗一些固定资源。单个应用消耗的资源可能很有限，但所有应用消耗的资源累积起来也是一个不小的数字。阿里内部自己做了分析发现，除了主要的核心应用之外，已经上线的应用中超过 80% 都是非核心的中长尾应用。目前阿里经济体的体量和业务量已经达到非常大的量级，在现有的研发模式下，这些中长尾应用会带来比较大的资源浪费。

其次，现有的研发形态并不能最大化地发挥部分工作岗位的价值，这同样是一种浪费。以导购类型的业务为例，开发这样一个业务通常需要前端开发工程师和后端开发工程师一起配合，但这两个开发岗位在该业务形态下并不能很好地发挥自己的全部价值。对于后端工程师来说，他在这个业务里要做的事情更多只是把现有的一些服务能力、数据组合在一起，变成一个新的数据提供给前端；而前端开发工程师负责把这个数据在页面上展示出来。这样的工作比较机械化，也没有太大的挑战，并不利于后端工程师的个人成长和岗位价值发挥；但在现有的研发模式下，由于缺乏前后端的连接点，前端工程师又不能去做这些比较简单的后端工作，业务上线也不可能给到前端工程师时间和机会去学习再实践。

而 Serverless 既可以满足资源最大化利用的需求，也能够调优行业内的开发岗位分层结构，让每个开发岗位都能够在最适合自己的地方发挥最大的价值。杜欢表示，阿里正是因为看到了经济体内存在的上述问题，在尝试寻找相应解决方案的过程中发现 Serverless 可能是一个好的解决方案，才开始研究 Serverless，研究后认为确实可行，才启动了“阿里巴巴经济体前端 Serverless 研发升级项目”这样一个项目，拉上大家一起来共建。

那么 Serverless 是只对阿里这样的公司才适用吗？什么样的公司、应用或场景应该选用 Serverless 的架构模式？

在杜欢看来，这是一个“伪问题”。他直言，并不存在什么样的场景和模式适合 Serverless，Serverless 应该被广泛地运用在不同的场景和实际开发需求中。从云厂商的角度来看，云计算未来一定会成为整个社会和商业的基础设施，届时使用云计算就应该像现在我们使用水电煤一样简单，不需要了解水从哪里来、怎么过滤、怎么铺设管道等一系列问题，只需要打开水龙头接一杯水而已。

而 Serverless 的概念正好可以帮助云计算朝这个方向往前走一步，它提倡的是人们不需要关心应用逻辑以外的服务相关的事情，包括管理、配置、运维等，用多少就付多少。从这个角度来看，Serverless 是真正让云计算变成社会商业基础设施的一个实现路径，也更接近现在业内提倡的云原生的方式，因此人们在使用云计算的过程中自然就应该按照 Serverless 的方式来使用。

杜欢认为，大家今天对是不是该用 Serverless 还有疑问，主要是因为还没有看到足够多 Serverless 成功应用的案例。这也是阿里巴巴先从自己内部实践 Serverless 研发模式升级的另外一个原因，希望通过这件事达成两个目的，一是向大家普及 Serverless 的概念，二是从自己的实践过程中总结出一套好的实践方式并共享出来，帮助大家更好地了解应该怎么落地 Serverless。

前端开发者看好、后端开发者观望，Serverless 为何如此？

过去这一年 Serverless 在前端领域被频繁提及，很多开发者非常看好，并认为它一定是未来前端大趋势之一，相比之下非前端领域还是观望态度居多。杜欢告诉记者，之所以会出现热度差，是因为 Serverless 的概念天然弥补了前端开发工程师的不足，但却跟现在很多后端开发工程师的能力有一定程度的重叠。

在杜欢看来，绝大多数后端开发工程师的成长路径是先做后端业务逻辑，慢慢了解应用的每个环节，再逐步成长为后端架构师，能 Hold 住整个应用架构。但云原生 Serverless 的出现，一定程度上会使这种后端架构能力变得普及，并且是以平台或服务的方式，而不是以人的方式。这乍看起来对于整个后端岗位可能是一种冲击，但杜欢认为 Serverless 并不会完全取代后端。当前整个开发生态依然缺少优秀的架构服务，尤其是低成本、可持续发展、针对特定行业优化的架构服务。未来后端工程师还是会成长为架构师，但是可能不是通用架构师，而是偏业务解决方案的行业架构师。此外，云厂商也需要专门构建 Serverless 方案的架构师，这是后端工程师的另一个新机会。

杜欢告诉 InfoQ 记者，Serverless 对前端和后端带来的影响总体都是正向的。对于前端来说，Serverless 不仅补足了前端工程师现有的能力，还可能使整个前端行业的定位发生变化。原来经常有人会认为前端的工作很简单，面向 UI 做好开发就行，剩下的工作可以交给后端。但是云端的应用开发模型出来之后，也就是前端和 Serverless 结合之后，大家对前端的诉求就不仅仅是开发一个页面了，而是要能交付整个应用的开发。

前端工程师除了要保持在 UI、交互逻辑方面的优势，还要理解整个业务和业务背后的意图，这意味着未来前端行业的思考模式会变成面向业务的思考模式。与此同时，前端的协同和开发模式、上下游流程也会发生变化，原来前端可能很少跟产品经理、设计打交道，未来前端要对整个应用负责，就需要天天跟产品经理、设计打交道。后端则要在最底层提供更深的能力付出，比如如何按照一亿流量的支出支撑十亿流量，这是更大的挑战。

目前 Serverless 最佳实践模式尚未出现

当前谈 Serverless，很多人会提函数计算、FaaS，但杜欢认为当前 Serverless 尚未出现一个最佳实践模式。在他看来，现在已经有一些 Serverless 的框架开始涌现，云厂商对 Serverless 的支持也越来越完善，确实是时候去尝试实践 Serverless 了。但要说最佳实践，就必须有足够多的人去实践过并表示认同，而现在实践 Serverless 的人本来就很少、实现的业务也很少，因此还没有哪一种方式谈得上是最佳实践。

不过杜欢提到 Serverless 实践过程中有一点需要重点关注——学习曲线是否足够平缓。要成为最佳实践模式，至少要做到能让开发者以一种方式专注于业务代码的开发，无需关注运行平台的差异性，一处编写可以处处运行，开发者只要掌握一种方式就可以在不同业务之间没有学习成本地切换。阿里巴巴近期开源的函数运行时框架 Midway FaaS Run Time 就是朝着这样的目标设计出来的，但还需要更多人尝试并认可，才有可能在未来变成最佳实践。

杜欢表示，目前 Serverless 在国内的发展和采用依然处于初期阶段，经过这两年的概念普及，大部分人都已经注意到并接受了 Serverless，但业务实践偏少，仍在不断探索之中。相比之下，国外整体要领先 1-2 年，国外几个大的云厂商前期对整个研发生态的教育和布道做得比较多，应用也比较早。现在国外已经出现不少 Serverless 框架，比较知名包括 Serverless.com 和 Zeit.com。

但对于 Serverless 未来的发展，杜欢信心十足。在他看来，未来云计算的普惠，一定是通过 Serverless 的方式去放大和落地实现的，而基于云端的应用模式也一定会是未来创新创业的选择。问题在于如何做好从传统开发模式到云开发模式的迁移。其中非技术层面的挑战主要来自于开发者，前后端工程师对 Serverless 的态度可能不一致，有的后端工程师会觉得 Serverless 抢了自己的工作很难接受，他没有看到更深层次对自己有收益的地方；有的前端工程师认为 Serverless 只是增加了自己要做的事情，而不能看到这个东西对自己提出了更高的要求，那他未来也未必能够胜任这项工作。

而技术层面的挑战主要包括两块：首先，云厂商自身要提供更多的 Serverless 能力，或者说现有云计算的能力要有更多被转换成能以 Serverless 的方式提供服务，未来云厂商要越来越多地提供这方面的支持；其次是研发模式，未来对于云时代的原住民来说，所有东西都在云上，开发方式必然会发生变化，如何去解决这些问题，让一个新人加入一项新业务之后可以更快地写下第一行代码，这是另一项挑战。

展望 2020 年 Serverless 的发展趋势，杜欢说道：“2020 年 Serverless 会进入初步实践阶段，还不能称之为大规模实践，可能到 2021 年才会进入大规模实践阶段。在这个过程当中，云厂商会进一步补充更多的 Serverless 服务，包括一些后端的 BaaS 服务，把基础打得更牢一点。”

今天的分享就到这里，下节课我们正式开始学习！

01｜定义：到底什么是Serverless？

你好，我是秦粤。Serverless 目前是大热的话题，相信你肯定听过。但如果你去百度、Google 或者维基百科上查的话，你会发现它连个准确的定义都没有。

作为本专栏的第一讲，今天我就想带你深入地了解下 Serverless，看看它都能解决哪些问题，以及为什么难定义。

Serverless 能解决什么问题？

理清 Serverless 要解决的问题其实很简单，我们可以从字面上把它拆开来看。

Server 这里指服务端，它是 Serverless 解决问题的边界；而 less 我们可以理解为较少关心，它是 Serverless 解决问题的目的。组合在一起就是“较少关心服务端”。怎么理解这句话呢？我们依然是拆开来分析。

什么是服务端？

我们先看 Server，这里我用 Web 应用经典的 MVC 架构来举例。

现代研发体系主要分为前端和后端，前端负责客户终端的体验，也就是 View 层；后端负责商业的业务逻辑和数据处理，也就是 Control 层和 Model 层。如果你有过一些开发经验，应该会了解自己的代码在本地开发和调试时的数据流。

MVC架构的Web应用

通常我们会在自己电脑上启动一个端口号，例如 127.0.0.1:3001。浏览器访问这个地址，就可以调用和调试自己的代码。但如果我们要将这个 Web 应用部署到互联网上，提供给互联网用户访问，就需要服务端的运维知识了。

MVC架构的Web应用

通常我部署运维一个应用时，由于要考虑容灾和容错，我都会保障异地多活，因此我会部署多个 Web 应用实例。每个 Web 应用实例跟我们在本地开发时是一样的，只是 IP 改为了私有网络 IP。

随着云服务商的兴起，现在已经很少有互联网企业还在自己维护物理机了。在云服务的运维体系中，各个环节都已经有了对应的成熟的云服务产品或解决方案。

为了使多个 Web 应用实例在容灾和容错的场景下稳定地切换流量，我就需要负载均衡服务和反向代理服务。负载均衡服务，正如其名是负责将流量均衡地分配到各个应用机器上。反向代理，常见的就是 Nginx，它的任务是从请求中解析出域名信息，并将请求转发到上游 upstream 的监听地址。

服务端的边界，就是上图中的整个蓝色部分，它是负责应用或代码的线上运维。Serverless 解决问题的边界，就是服务端的边界，即服务端运维。

服务端运维发展史，从 full 到 less

了解完 Server，我们再来看 less。

我们可以先看 Serverfull 的概念，对比 Serverfull 和 Serverless 之间的差别，相信这样可以加深你的理解。Serverfull 就是服务端运维全由我们自己负责，Serverless 则是服务端运维较少由我们自己负责，大多数的运维工作交给自动化工具负责。

可能这么说比较抽象，我举个例子来给你讲吧。这个例子有点长，但可以带你很好地了解下服务端运维的发展史，我们后面也会再次用到。

假设我有一家互联网公司，我的产品是“待办任务（ToDoList）”Web 应用——记录管理每个用户的待办任务列表。针对这个网站的研发，我们简化为两个独立角色：研发工程师小程和运维工程师小服。

做研发的小程，他是个精通前后端的全栈工程师，但他只关心应用的业务逻辑。具体来说就是，整个 MVC 架构 Web 应用的开发都归小程负责，从客户端界面 View 层，到业务逻辑 Control 层，再到数据存储 Model 层，整个 Web 应用的版本管理和线上 bug 修复。

负责运维的小服，则只关心应用的服务端运维事务。他负责部署上线小程的 Web 应用，绑定域名以及日志监控。在用户访问量大的时候，他要给这个应用扩容；在用户访问量小的时候，他要给这个应用缩容；在服务器挂了的时候，他还要重启或者换一台服务器。

史前时代，Serverfull

最开始运维工程师小服承诺将运维的事情全包了，小程不用关心任何部署运维相关的事情。小程每次发布新的应用，都会打电话给小服，让小服部署上线最新的代码。小服要管理好迭代版本的发布，分支合并，将应用上线，遇到问题回滚。如果线上出了故障，还要抓取线上的日志发给小程解决。

小程和小服通过工作职责任务上的安排，将研发和运维完全隔离开来了。好处很明显：分工明确小程可以专心做好自己的业务。缺陷也很明显：小服成了工具人，被困在了大量的运维工作中，处理各种发布相关琐碎的杂事。

这个时代研发和运维隔离，服务端运维都交给小服一个人，纯人力处理，也就是 Serverfull。

我们可以停下来想想，像发布版本和处理线上故障这种工作这些是小程的职责，都要小服协助，是不是应该小程自己处理？

农耕时代，DevOps

后来，小服渐渐发现日常其实有很多事情都是重复性的工作，尤其是发布新版本的时候，与其每次都等小程电话，线上出故障了还要自己抓日志发过去，效率很低，不如干脆自己做一套运维控制台 OpsConsole，将部署上线和日志抓取的工作让小程自己处理。

OpsConsole 上线后，小服稍微轻松了一些，但是优化架构节省资源和扩缩容资源方案，还是需要小服定期审查。而小程除了开发的任务，每次发布新版本或解决线上故障，都要自己到 OpsConsole 平台上去处理。

这个时代就是研发兼运维 DevOps，小程兼任了小服的部分工作。小服将部分服务端运维的工作工具化了，自己可以去做更加专业的事情。相对史前时代，小程负责的更多，看起来是不是小程负责的事情多（More）了？但实际这些事情本身就应该是小程负责的。版本控制、线上故障都是小程自己应该处理的。而且小服将这部分人力的工作工具化了，更加高效。其实已经有变少（less）的趋势了。

我们再想想能否进一步提升效率，让小程连 OpsConsole 平台都可以不用？

工业时代

这时，小服发现资源优化和扩缩容方案也可以利用性能监控 + 流量估算解决。小服又基于小程的开发流程，OpsConsole 系统再进一步，帮小程做了一套代码自动化发布的流水线：代码扫描 - 测试 - 灰度验证 - 上线。现在的小程连 OpsConsole 都不用登陆操作，只要将最新的代码合并到 Git 仓库指定的 develop 分支，剩下的就都由流水线自动化处理发布上线了。

这个时代研发不需要运维了，免运维 NoOps。小服的服务端运维工作全部自动化了。小程也变回到最初，只需要关心自己的应用业务就可以了。我们不难看出，在服务端运维的发展历史中，对于小服来说，小程的角色存在感越来越弱，需要小程参与的事情越来越少，都由自动化工具替代了。这就是“Serverless”。

到这里你一定会想，既然服务端都做到免运维了，小服是不是就自己革了自己的命，失业了？

未来

实现了免运维 NoOps，并不意味着小服要失业了，而是小服要转型。转型去做更底层的服务，做基础架构的建设，提供更加智能、更加节省资源、更加周到的服务。小程则可以完全不被运维的事情困扰，放心大胆地依赖 Serverless 服务，专注做好自己的业务，提升用户体验，思考业务价值。

免运维 NoOps 并不是说服务端运维就不存在了，而是通过全知全能的服务，覆盖研发部署需要的所有需求，让研发同学小程对它的感知越来越少。另外，NoOps 是理想状态，因为我们只能无限逼近 NoOps，所以这个单词是 less，不可能是 ServerLeast 或者 ServerZero。

另外你需要知道的是，目前大多数互联网公司，包括一线互联网公司，都还在 DevOps 时代。但 Serverless 的概念已经提出，NoOps 的时代正在到来。

Server 限定了 Serverless 解决问题的边界，即服务端运维；less 说明了 Serverless 解决问题的目的，即免运维 NoOps。所以我们重新组合一下这个词的话，Serverless 就应该叫做服务端免运维。这也就是 Serverless 要解决的问题。

Serverless 为什么难准确定义？

换句话说，服务端免运维，要解决的就是将小服的工作彻底透明化。研发同学只关心业务逻辑，不用关心部署运维和线上的各种问题。要实现这个终态，就意味着需要对整个互联网服务端的运维工作进行极端抽象。

那越抽象的东西，其实越难定义，因为蕴含的信息量太大了，这就是 Serverless 很难准确定义的根本原因。Serverless 对 Web 服务开发的革命之一，就是极度简化了服务端运维模型，使一个零经验的新手，也能快速搭建一套低成本、高性能的服务端应用（下一讲，我就会带你体验一下从零开始的 Serverless 应用）。

我们现在虽然知道了 Serverless 的终态是 NoOps，但它作为一门新兴的技术，我们还是要尝试给它定义吧？

到底什么是 Serverless?

我在日常和其他同事沟通的时候，我发现大家对 Serverless 概念的认知很模糊，往往要根据沟通时的上下内容去看到底此时的 Serverless 在指代什么。主要是因为 Serverless 这个词包含的信息量太大，而且适用性很广，但总结来说 Serverless 的含义有这样两种。

第一种：狭义 Serverless（最常见）= Serverless computing 架构 = FaaS 架构 = Trigger（事件驱动）+ FaaS（函数即服务）+ BaaS（后端即服务，持久化或第三方服务）= FaaS + BaaS

第二种：广义 Serverless = 服务端免运维 = 具备 Serverless 特性的云服务

Serverless定义

我用图片来阐明一下这两种概念。其实你不难看出，广义 Serverless 包含的东西更多，适用范围更广，但我们经常在工作中提到的 Serverless 一般都是指狭义的 Serverless。其实这是历史原因，2014 年 11 月份，亚马逊推出真正意义上的第一款 Serverless FaaS 服务：Lambda。Serverless 的概念才进入了大多数人的视野，也因此 Serverless 曾经一度就等于 FaaS。

我们再聚焦到图上“狭义 Serverless”这里。你注意看的话，图中有几个陌生的名词，我先来给你解释下。FaaS(Function as a Service) 就是函数即服务，BaaS(Backend as a Service) 就是后端即服务。XaaS(X as a Service) 就是 X 即服务，这是云服务商喜欢使用的一种命名方式，比如我们熟悉的 SaaS、PaaS、IaaS 都是这样。

先说 FaaS，函数即服务，它还有个名字叫作 Serverless Computing，它可以让我们随时随地创建、使用、销毁一个函数。

你可以想一下通常函数的使用过程：它需要先从代码加载到内存，也就是实例化，然后被其它函数调用时执行。在 FaaS 中也是一样的，函数需要实例化，然后被触发器 Trigger 或者被其他的函数调用。二者最大的区别就是在 Runtime，也就是函数的上下文，函数执行时的语境。

FaaS 的 Runtime 是预先设置好的，Runtime 里面加载的函数和资源都是云服务商提供的，我们可以使用却无法控制。你可以理解为 FaaS 的 Runtime 是临时的，函数调用完后，这个临时 Runtime 和函数一起销毁。

FaaS 的函数调用完后，云服务商会销毁实例，回收资源，所以 FaaS 推荐无状态的函数。如果你是一位前端工程师的话，可能很好理解，就是函数不可改变 Immutable。简单解释一下，就是说一个函数只要参数固定，返回的结果也必须是固定的。

用我们上面的 MVC 架构的 Web 应用举例，View 层是客户端展现的内容，通常并不需要函数算力。Control 层，就是函数的典型使用场景。MVC 架构里面，一个 HTTP 的数据请求，就会对应一个 Control 函数，我们完全可以用 FaaS 函数来代替 Control 函数。在 HTTP 的数据请求量大的时候，FaaS 函数会自动扩容多实例同时运行；在 HTTP 的数据请求量小时，又会自动缩容；当没有 HTTP 数据请求时，还会缩容到 0 实例，节省开支。

MVC架构的Web应用

此刻或许你会有点疑惑，Runtime 不可控，FaaS 函数无状态，函数的实例又不停地扩容缩容，那我需要持久化存储一些数据怎么办，MVC 里面的 Model 层怎么解决？

此时我就要介绍另一位嘉宾，BaaS 了。

BaaS 其实是一个集合，是指具备高可用性和弹性，而且免运维的后端服务。说简单点，就是专门支撑 FaaS 的服务。FaaS 就像高铁的车头，如果我们的后端服务还是老旧的绿皮火车车厢，那肯定是要散架的。而 BaaS 就是专门为 FaaS 准备的高铁车厢。

MVC 架构中的 Model 层，就需要我们用 BaaS 来解决。Model 层我们以 MySQL 为例，后端服务最好是将 FaaS 操作的数据库的命令，封装成 HTTP 的 OpenAPI，提供给 FaaS 调用，自己控制这个 API 的请求频率以及限流降级。这个后端服务本身则可以通过连接池、MySQL 集群等方式去优化。各大云服务商自身也在改造自己的后端服务，BaaS 这个集合也在日渐壮大。

MVC架构的Model层

基于 Serverless 架构，我们完全可以把传统的 MVC 架构转换为 BaaS+View+FaaS 的组合，重构或实现。

这样看下来的话，狭义 Serverless 的含义也就不难理解了。

第一种：狭义 Serverless（最常见）= Serverless computing 架构 = FaaS 架构 = Trigger（事件驱动）+ FaaS（函数即服务）+ BaaS（后端即服务，持久化或第三方服务）= FaaS + BaaS

Serverless 毋庸置疑正是因为 FaaS 架构才流行起来，进入大家认知的。所以我们最常见的 Serverless 都是指 Serverless Computing 架构，也就是由 Trigger、FaaS 和 BaaS 架构组成的应用。这也是我给出的狭义 Serverless 的定义。

那什么是广义 Serverless 呢？

将狭义的 Serverless 推升至广义，具备以下特性的，就是 Serverless 服务。你可以回忆一下小服的工作，要达成 NoOps，都应该具备什么条件？

小服的工作职责：

无需用户关心服务端的事情（容错、容灾、安全验证、自动扩缩容、日志调试等等)。

按使用量（调用次数、时长等）付费，低费用和高性能并行，大多数场景下节省开支。

快速迭代 & 试错能力（多版本控制，灰度，CI&CD 等等）。

广义 Serverless，其实就是指服务端免运维，也是未来的主要趋势。

总结来说的话就是，我们日常谈 Serverless 的时候，基本都是指狭义的 Serverless，但当我们提到某个服务 Serverless 化的时候，往往都是指广义的 Serverless。我们后面的课程中也是如此。

总结

今天我们一起学习了 Serverless 的边界、目标以及定义。我还介绍了即将贯穿我们整个专栏的 Web 应用，即任务列表。

现在我们可以回过头来看看最初的两个问题了，你是不是已经有答案了呢？

Serverless 能解决什么问题？Serverless 可以使应用在服务端免运维。

Serverless 为什么难定义？Serverless 将服务端运维高度抽象成了一种解决方案，包含的信息量太大了。

另外，Serverless 可分为狭义和广义。狭义 Serverless 是指用 FaaS+BaaS 这种 Serverless 架构开发的应用；广义 Serverless 则是具备 Serverless 特性的云服务。现在的云服务商，正在积极地将自己提供的各种云服务 Serverless 化。

作业

最后，留给你一个作业：请你注册一个云服务商的云账号，并根据云服务商的文档和教程，自己部署一个 Serverless 应用。下节课，我将用云上的 FaaS 部署一个 Serverless 应用，给你详细讲解 Serverless 引擎盖内的知识。如果今天这节课让你有所收获，也欢迎你把它分享给更多的朋友。

02 | 原理：通过一个案例，理解FaaS的运行逻辑

你好，我是秦粤。上一讲我们介绍了什么是 Serverless，从概念的角度我们已经对 Serverless 有了一个深入的了解；那从应用角度来看，Serverless 对于开发者究竟有什么魔力呢？这一讲，我准备通过快速部署纯 FaaS 的 Serverless 应用，给你讲一讲 FaaS 应用背后的运行原理。

为了让你更好地体验 Serverless 带来的变革，这节课我们以 Serverless 版本的"Hello World"实操例子进行展示。鉴于我的熟悉程度，我选择了阿里云，当然，你也可以选择你熟悉的云服务商（我在专栏的最后一课还会讲到如何解除云服务商的限制，混合使用多云运营商服务等等）。

另外，需要注意的是，如果你是跟着我一步步实操练习的，那么开通云服务可能会产生少量费用，遇到充值提示你要自行考虑一下。当然，如果你不着急体验，我觉得看我的视频演示也已经足够了。

Sorry, your browser doesn't support embedded videos.



00:00 / 02:20



我们从上面的演示也看到了，会用 Serverless 这个目标我觉得不难实现，但这不是我们这节课的终极目的。今天我就想带着你打开这个 FaaS "Hello World"应用的引擎盖，来看看它内部到底是如何运行的。为什么要急着给你讲原理呢？因为如果你不理解原理的话，后面在应用 Serverless 化的时候就无从下手了。

FaaS 是怎么运行的？

现在大家都觉得 Serverless 是个新东西，是个新风口，刚才在演示的视频里你也能看到，它确实很方便。但你也不用把它想得多复杂，运行应用的那套逻辑还没有变化，Serverless 只是用技术手段帮我们屏蔽了复杂性，这点它和其他的云技术没有任何差别。

你可以想想，在 Serverless 出现之前，我们要部署这样一个"Hello World"应用得何等繁琐。首先为了运行我们的应用，我们要在服务端构建代码的运行环境：我们要购买虚拟机服务，初始化虚拟机运行环境，安装我们需要的应用运行环境，尽量和本地开发环境保持一致；紧接着为了让用户能够访问我们刚刚启动的应用，我们需要购买域名，用虚拟机 IP 注册域名；配置 Nginx，启动 Nginx；最后我们还需要上传应用代码，启动应用。

你可以闭上眼睛想想是不是我说的这样，当然，为了方便你理解，我还画了张图。前面 5 步都准备好了，用户在第 6 步才能成功访问到我们的应用。

Hello World 应用部署的传统流程

与上面传统流程形成鲜明对比的是，我们刚刚的 Serverless 部署只需要简单的 3 步，而且目前这样操作下来，没有产生任何费用。上一课我们讲过，Serverless 是对服务端运维体系的极端抽象。注意，这句话里面有个关键词，“抽象”，我没有用“革新”“颠覆”之类的词语，也就是说，用户 HTTP 数据请求的全链路，并没有质的改变，Serverless 只是将全链路的模型简化了。

具体来说，之前我们需要在服务端构建代码的运行环境，而 FaaS 应用将这一步抽象为函数服务；之前我们需要负载均衡和反向代理，而 FaaS 应用将这一步抽象为 HTTP 函数触发器；之前我们需要上传代码和启动应用，而 FaaS 应用将这一步抽象为函数代码。

Hello World 应用的运行架构图

触发器、函数服务……咦，是不是发现开始出现了一些陌生名词？不用着急，还是对照着上面这张图，我给你再串下"Hello World"这个纯 FaaS 应用的数据请求链条。理解了这些链条，你自然就理解了这几个新名词的背景了。

咱们先从图的右边开始看，图上我标注了次序。当用户第一次访问 HTTP 函数触发器时，函数触发器就会 Hold 住用户的 HTTP 请求，并产生一个 HTTP Request 事件通知函数服务。

紧接着函数服务就会检查有没有闲置的函数实例；如果没有函数实例，就去函数代码仓库中拉取你的代码；初始化并启动一个函数实例，执行这个函数，传入这个 HTTP Request 对象作为函数的参数，执行函数。

再进一步，函数执行的结果 HTTP Response 返回函数触发器，函数触发器再将结果返回给等待的用户客户端。

如果你还记得的话，我们刚刚的视频演示，你可以看到我们的纯 FaaS "Hello World"应用例子中，默认创建了 3 个服务。

第一个"GreetingServiceGreetingFunctionhttpTrigger"函数触发器，函数触发器是所有请求的统一入口，当请求发生时，它会触发事件通知函数服务，并且等待函数服务执行返回后，将结果返回给等待的请求。

第二个"GreetingService"函数服务，当函数触发器通知的“事件”到来，它会查看当前有没有闲置的函数实例，如果有则调用函数实例处理；如果没有，则会创建函数实例，等实例创建完毕后，再调用函数实例处理事件。

第三个"GreetingServiceGreetingFunction"函数代码，“函数服务”在第一次实例化函数时，就会从这个代码仓库中拉取代码，并构建函数实例。

理解了 FaaS 应用调用链路，我想你可能会问，“真够复杂，折腾来折腾去，怎么感觉它的这套简化逻辑很像以前新浪的 SAE 或者 Heroku 那样的 NoOps 应用托管 PaaS 平台？”不知道你是不是有这样的问题，反正我当时第一次接触 Serverless 时就有类似的疑问。

其实，FaaS 与应用托管 PaaS 平台对比，最大的区别在于资源利用率，这也是 FaaS 最大的创新点。FaaS 的应用实例可以缩容到 0，而应用托管 PaaS 平台则至少要维持 1 台服务器或容器。

你注意看的话，在上面"Hello World"例子中，函数在第一次调用之前，实际的服务器占用为 0。因为直到用户第一次 HTTP 数据请求过来时，函数服务才被 HTTP 事件触发，启动函数实例。也就是说没有用户请求时，函数服务没有任何的函数实例，也就不占用任何的服务器资源。而应用托管 PaaS 平台，创建应用实例的过程通常需要几十秒，为了保证你的服务可用性，必须一直维持着至少一台服务器运行你的应用实例。

打个比方的话，FaaS 就有点像我们的声控灯，有人的时候它可以很快亮起来，没人的时候又可以关着。对比传统的需要人手动开关的灯，声控灯最大的优势肯定就是省电了。但，你想想，能省电的前提是有人的时候，声控灯能够找到比较好的方式快速亮起来。

FaaS 也是这样，它优势背后的关键点是可以极速启动。那它是怎么做的呢？要理解极速启动背后的逻辑，这里我就要引入冷启动的概念了。

FaaS 为什么可以极速启动？

冷启动本来是 PC 上的概念，它是指关闭电源后，PC 再启动仍然需要重新加载 BIOS 表，也就是从硬件驱动开始启动，因此启动速度很慢。

现在的云服务商，线上物理服务器断电重启几乎是不太可能的。FaaS 中的冷启动是指从调用函数开始到函数实例准备完成的整个过程。冷启动我们关注的是启动时间，启动时间越短，我们对资源的利用率就越高。现在的云服务商，基于不同的语言特性，冷启动平均耗时基本在 100～700 毫秒之间。得益于 Google 的 JavaScript 引擎 Just In Time 特性，Node.js 在冷启动方面速度是最快的。

100～700 毫秒的冷启动时间，我不知道你听到这个数据的时候是不是震惊了一下。

下面这张图是 FaaS 应用冷启动的过程。其中，蓝色部分是云服务商负责的，红色部分由你负责，而函数代码初始化，一人一半。也就是说蓝色部分在冷启动时候的耗时你不用关心，而红色部分就是你的函数耗时。至于资源调度是要做什么，你可以先忽略，我后面会提到。

例如从刚才演示视频的云服务控制台我们可以看到，"Hello World"的单次函数耗时是 0.0125 CU-S，也就是说耗时 12.5 毫秒，实际我们抓数据包来看，除去建立连接的时间，我们整个 HTTPS 请求到完全返回结果需要 100 毫秒。我们负责的红色部分耗时是 12.5 毫秒，也就是说云服务商负责的蓝色部分耗时是 87.5 毫秒。

FaaS应用冷启动过程图

注意，FaaS 服务从 0 开始，启动并执行完一个函数，只需要 100 毫秒。这也是为什么 FaaS 敢缩容到 0 的主要原因。通常我们打开一个网页有个关键指标，响应时间在 1 秒以内，都算优秀。这么一对比，100 毫秒的启动时间，对于网页的秒开率影响真的极小。

而且可以肯定的是，云服务商还会不停地优化自己负责的部分，毕竟启动速度越快对资源的利用率就越高。例如冷启动过程中耗时比较长的是下载函数代码。所以一旦你更新代码，云服务商就会偷偷开始调度资源，下载你的代码构建函数实例的镜像。请求第一次访问时，云服务商就可以利用构建好的缓存镜像，直接跳过冷启动的下载函数代码步骤，从镜像启动容器，这个也叫预热冷启动。所以如果我们有些业务场景对响应时间比较敏感，我们就可以通过预热冷启动或预留实例策略[1]，加速或绕过冷启动时间。

了解了冷启动的概念，我们再看看为什么 FaaS 可以极速启动，而应用托管平台 PaaS 不行？

首先应用托管平台 PaaS 为了适应用户的多样性，必须支持多语言兼容，还要提供传统后台服务，例如 MySQL、Redis。

这也意味着，应用托管平台 PaaS 在初始化环境时，有大量依赖和多语言版本需要兼容，而且兼容多种用户的应用代码往往也会增加应用构建过程的时间。所以通常应用托管平台 PaaS 无法抽象出轻量的可复用的层级，只能选择服务器或容器方案，从操作系统层开始构建应用实例。

FaaS 设计之初就牺牲了用户的可控性和应用场景，来简化代码模型，并且通过分层结构进一步提升资源的利用率。学到这里，我们得来看看隐藏在 FaaS 冷启动中最重要的革新技术：分层结构。

FaaS 是怎么分层的？

FaaS实例执行结构图

你的 FaaS 实例执行时，就如上图所示，至少是 3 层结构：容器、运行时 Runtime、具体函数代码。

容器你可以理解为操作系统 OS。代码要运行，总需要和硬件打交道，容器就是模拟出内核和硬件信息，让你的代码和 Runtime 可以在里面运行。容器的信息包括：内存大小、OS 版本、CPU 信息、环境变量等等。目前的 FaaS 实现方案中，容器方案可能是 Docker 容器、VM 虚拟机，甚至 Sandbox 沙盒环境。

运行时 Runtime [2]，就是你的函数执行时的上下文 context。Runtime 的信息包括：代码运行的语言和版本，例如 Node.js v10，Python3.6；可调用对象，例如 aliyun SDK；系统信息，例如环境变量等等。

关于 FaaS 的 3 层结构，你可以这么想象：容器层就像是 Windows 操作系统；Runtime 就像是 Windows 里面的播放器暴风影音；你的代码就像是放在 U 盘里的电影。

这样分层有什么好处呢？容器层适用性更广，云服务商可以预热大量的容器实例，将物理服务器的计算资源碎片化。Runtime 的实例适用性较低，可以少量预热。容器和 Runtime 固定后，下载你的代码就可以执行了。通过分层，我们可以做到资源统筹优化，这样就能让你的代码快速低成本地被执行。

理解了分层，我们再回想一下 FaaS 分层对应冷启动的过程，其实你就不难理解云服务商负责的就是容器和 Runtime 的准备阶段了。而开发者自己负责的则是函数执行阶段。一旦容器 &Runtime 启动后，就会维持一段时间，这段时间内的这个函数实例就可以直接处理用户数据请求。当一段时间内没有用户请求事件发生（各个云服务商维持实例的时间和策略不同），则会销毁这个函数实例。

具体你可以看下下面这张图，以辅助你理解。

FaaS分层对应冷启动示意图

总结

这一讲，我带你体验了只需要三步就能快速部署纯 FaaS 的 Web 应用上线，我们也打开了 FaaS 引擎盖，介绍了 FaaS 的内部运行机制。现在我们就来总结一下这节课的关键点。

纯 FaaS 应用调用链路由函数触发器、函数服务和函数代码三部分组成，它们分别替代了传统服务端运维的负载均衡 & 反向代理，服务器 & 应用运行环境，应用代码部署。

对比传统应用托管 PaaS 平台，FaaS 应用最大的不同就是，FaaS 应用可以缩容到 0，在事件到来时极速启动。Node.js 的函数甚至可以做到 100ms 启动并执行。

FaaS 在设计上牺牲了用户的可控性和应用场景，来简化代码模型，并且通过分层结构进一步提升资源的利用率，这也是为什么 FaaS 冷启动时间能这么短的主要原因。关于 FaaS 的 3 层结构，你可以这么想象：容器层就像是 Windows 操作系统；Runtime 就像是 Windows 里面的播放器暴风影音；你的代码就像是放在 U 盘里的电影。

作业

最后，给你留个作业吧。我知道整个原理你听起来肯定还不是那么好理解。实践是检验真理的唯一标准，如果你有时间并且方便的话，可以试着自己动手 Run 一个 FaaS 的 Hello World 例子，然后思考其中的原理。

当然，如果今天这节课让你有所收获，也欢迎你把它分享给更多的朋友。

参考资料

[1] 预留实例介绍，https://help.aliyun.com/document_detail/138103.html

[2] Node.js Runtime 介绍，https://help.aliyun.com/document_detail/58011.html

03 | 原理：FaaS的两种进程模型及应用场景

你好，我是秦粤。上一讲我们通过一个 Node.js 纯 FaaS 的 Serverless 应用，给你介绍了 Serverless 引擎盖下的运作机制，总结来说，FaaS 依赖分层调度和极速冷启动的特性，在无事件时它居然可以缩容到 0，就像我们的声控灯一样，有人的时候它可以亮起来，没人的时候，又可以自动关了。

听完了原理，我估计你肯定会问，FaaS 这么好，但是它的应用场景是什么呢？今天我们就来一起看下。不过，想要理解 FaaS 的应用场景，我们就需要先理解 FaaS 的进程模型，这也是除了冷启动之后的另外一个重要概念。

FaaS 进程模型

咱先回想一下上节课的 FaaS 的冷启动过程，我们知道容器和 Runtime 准备阶段都是由云服务商负责的，我们只需要关注具体的函数执行就可以了。而函数执行在 FaaS 里是由“函数服务”负责的，当函数触发器通知的“事件”到来时，函数服务就会根据情况创建函数实例，然后执行函数。当函数执行完之后，函数实例也随之结束自己的使命，FaaS 应用缩容到 0，然后开始进入节能模式。

上面这套逻辑是我们上节课讲的，课后有同学就问，函数执行完之后实例能否不结束，让它继续等待下一次函数被调用呢？这样省去了每次都要冷启动的时间，响应时间不就可以更快了吗？

是的，本身 FaaS 也考虑到了这种情况，所以从运行函数实例的进程角度来看，就有两种模型。我也画了张图，方便你理解。

用完即毁型：函数实例准备好后，执行完函数就直接结束。这是 FaaS 最纯正的用法。

常驻进程型：函数实例准备好后，执行完函数不结束，而是返回继续等待下一次函数被调用。这里需要注意，即使 FaaS 是常驻进程型，如果一段时间没有事件触发，函数实例还是会被云服务商销毁。

模型示意图

这两个模型其实也对应两种不同的应用场景。我举个例子，比如你要把我们第一讲中的“待办任务”应用部署上线。还记得小程同学吧，他完成了第一个版本，他用 Express.js[1] 框架开发的 MVC 架构，View 层他采用流行的 React[2]，并且使用了 Ant Design Pro[3] React 组件库。Model 数据库采用 MongoDB。小程的第一个版本，就是一个典型的传统 Web 服务。

从可控性和改造成本角度来看 Web 服务，服务端部署方案最适合的还是托管平台 PaaS 或者自己搭服务跑在 IaaS 上。正如我上一讲所说，使用 FaaS 就必须在 FaaS 的条件限制内使用，最佳的做法应该是一开始就选用 FaaS 开发。

但是小程的运气比较好，我们查了一下文档，发现 FaaS 的 Node.js 的 Runtime 是支持 Express 的，所以我们只需少量修改，小程的第一个版本就可以使用 FaaS 的常驻进程方案部署。

这里我要做个对比，在之前，假设没有 FaaS，我们要将应用部署到托管平台 PaaS 上。启动 Web 服务时，主进程初始化连接 MongoDB，初始化完成后，持续监听服务器的 80 端口，直到监听端口的句柄关闭或主进程接收到终止信号。当 80 端口和客户端建立完 TCP 链接，有 HTTP 请求过来，服务器就会将请求转发给 Web 服务的主进程，这时主进程会创建一个子进程来处理这个请求。

而在 FaaS 常驻进程型模式下，首先我们要改造一下代码，Node.js 的 Server 对象采用 FaaS Runtime 提供的 Server 对象，然后我们把监听端口改为监听 HTTP 事件。启动 Web 服务时，主进程初始化连接 MongoDB，初始化完成后，持续监听 HTTP 事件，直到被云服务商控制的父进程关闭回收。

当 HTTP 事件发生时，我们的 Web 服务主进程跟之前一样，创建一个子进程来处理这个请求事件。主进程就如我们上图中绘制的那个蓝色的圆点，当 HTTP 事件发生时，它创建的子进程就是蓝色弧形箭头，当子进程处理完后就会被主进程回收。

在我看来，常驻进程型就是为了传统 MVC 架构部署上 FaaS 专门设计的。数据库也可以使用原来的 DB 连接方式，不过这样做会增加冷启动的时间（我特意在图中用曲线代表时间增加），从而导致第一次请求长延迟甚至失败。比较适合的做法是我们[第 1 课] 中，讲 Serverless 架构时说的，数据持久化采用 BaaS 服务。

那么我们能否用用完即毁型来部署小程的这个 MVC 架构的 Web 服务呢？可以，但是我不推荐你这样做。因为用完即毁型对传统 MVC 改造的成本太大。

模型示意图

说到这里，我们再将上面对比两个模型的示意图镜头再拉远一点，加上 HTTP 触发器看看。其实从另外一个角度看，触发器就是一个常驻进程型模型一直在等待，只不过这个触发器是由云服务商处理罢了。

这里我再啰嗦强调下，还是我们上一讲说的，FaaS 只是做了极端抽象，云服务商通过技术手段帮助开发者屏蔽了细节，让他们尽量只关注代码本身。

所以，在用完即毁型中，我们只要将 MVC 的 Control 层部署到函数执行就可以了。这也意味着我们要将我们的 MVC 架构的 Control 函数一个个拆解出来部署，一个 HTTP 请求对应一个 Control 函数。Control 函数实例启动时连接 MongoDB，一个请求处理完后直接结束。你如果要提升 Control 函数的冷启动时间，Model 层同样要考虑 BaaS 化改造。这里你听着可能有点陌生，没关系，后面我会通过代码给你演示，你到时候再理解也不迟。

现在，理解了两种类型，我们再来看看 FaaS 是怎么收费的，以及常驻型进程这种模式是不是官方会多收费。云服务商 FaaS 函数服务的收费标准各不相同，但他们都会提供一定的免费额度。我给你归纳下 FaaS 的收费标准，主要有两个维度：调用函数次数和函数耗时。

调用函数次数，函数每次被事件触发，计数器加一。例如我们 Hello World 例子的 index.js 文件的 handler 函数，它每调用一次，计数就加一。这种模式因为不占资源，所以资源利用率高、收费低。

函数耗时，说的是函数执行的运行时长，它的计算单位是 CU-S，也就是 CPU 运行了多少秒。

例如我们上面“待办任务”改造的常驻进程型和用完即毁型，多数情况下其实他们两个的函数耗时是一样的。这里可能有些绕，需要给你解释一下。

常驻进程型改造后主要占用的是内存，而 FaaS 收费的是 CPU 计算时间，也就是说常驻进程的模式并不会持续收费。但常驻型应用的冷启动时间会增加，所以我们要尽量避免冷启动，避免冷启动通常又需要做一些额外的工作，比如定时触发一下实例或者购买预留实例，这地方就会增加额外的费用了。这样听起来，是不是觉得常驻进程型改造 MVC 应用用起来很别扭？是的，我们前面也说了，常驻进程模式就是为了传统 MVC 架构部署上 FaaS 专门设计的，算是一种权宜之计吧。

用完即毁型改造后，同样冷启动时间会增加。但是冷启动时间是云服务商负责的。我们 Control 函数的执行时间，和 MVC 部署在 FaaS 中 Control 的执行时间是一样的。每个请求都增加了冷启动时间，响应时间会更长一些，但我们不用考虑额外的成本。那学到这儿，相信你也可以感觉到，用完即毁型也不太适合传统 MVC 架构改造，也是一种权宜之计。

接下来，我们就继续把焦点放到用完即毁型上，来具体看看它可以用在哪些更加自然的场景里。

数据编排

我们做开发的多多少少都知道，目前最成功最广泛的设计模式就是 MVC 模式。但随着前端 MVVM 框架越来越火，前端 View 层逐渐前置，发展成 SPA 单页应用。后端 Control 和 Model 层逐渐下沉，发展成面向服务编程的后端应用。

这种情况下，前后端更加彻底地解耦了，前端开发可以依赖 Mock 数据接口完全脱离后端限制，而后端的同学则可以面向数据接口开发，但这也产生了高网络 I/O 的数据网关层。

Node.js 的异步非阻塞和 JavaScript 天然亲近前端工程师的特性，自然地接过数据网关层。因此也诞生了 Node.js 的 BFF 层 (Backend For Frontend)，将后端数据和后端接口编排，适配成前端需要的数据结构，提供给前端使用。

我们的程序员好朋友小程也跟进了这个潮流，将“待办任务”Web 服务重构成了第二个版本。他将原先的应用拆解成了 2 个项目：前端项目采用 React+AntDesignPro+Umi.js[4] 的单页应用，后端项目还是采用 Express。我们本专栏的示例也采用这个技术架构一步一步教你在云上部署 SPA+FaaS 混合框架演进。

BFF示意图

如上图所示，BFF 层充当了中间胶水层的角色，粘合前后端。未经加工的数据，我们称为元数据 Raw Data，对于普通用户来说元数据几乎不可读。所以我们需要将有用的数据组合起来，并且加工数据，让数据具备价值。对于数据的组合和加工，我们称之为数据编排。

BFF 层通常是由善于处理高网络 I/O 的 Node.js 应用负责。传统的服务端运维 Node.js 应用还是比较重的，需要我们购买虚拟机，或者使用应用托管 PaaS 平台。

因为 BFF 层只是做无状态的数据编排，所以我们完全可以用 FaaS 用完即毁型模型替换掉 BFF 层的 Node.js 应用，也就是最近圈子里老说的那个新名词 SFF（Serverless For Frontend）。

好，到这儿，我们已经理解了 BFF 到 SFF 的演进过程，现在我们再串下新的请求链路逻辑。前端的一个数据请求过来，函数触发器触发我们的函数服务，我们的函数启动后，调用后端提供的元数据接口，并将返回的元数据加工成前端需要的数据格式。我们的 FaaS 函数完全就可以休息了。具体如下图所示。

SFF示意图

另外，除了我们自己的后端应用数据接口，互联网上还有大量的数据供我们使用，比如疫情期间，你要爬取下各个地区的疫情数据、天气数据，这些工作，也都可以放到 FaaS 上轻松搞定。并且，基本还能免费，因为目前各大云服务商都提供了免费的额度，这个我刚给你讲过了。

编排后端接口、编排互联网上的数据，这俩场景我想你也很容易想到。不过，我觉得，编排云服务商的各种服务才能让你真正体会到那种触电的感觉。我第一次体验之后，就对我同事说，“变天了，真的变天了，喊了这么多年的云计算时代真的来了”。

服务编排

服务编排和数据编排很像，主要区别是对云服务商提供的各种服务进行组合和加工。在 FaaS 出现之前，就有服务编排的概念。但服务编排受限于服务支持的 SDK 语言版本，常见的情况是我们用 yaml 文件或命令行来编排服务。我们要使用这些服务或 API，都要通过自己熟悉的编程语言去找对应的 SDK，在自己的代码中加载 SDK，使用秘钥调用 SDK 方法进行编排。就和数据编排一样，服务端运维部署成本非常高。而且如果没有 SDK，则需要自己根据平台提供的接口或协议实现 SDK。

现在有了 FaaS，FaaS 拓展了我们可以使用 SDK 边界。这是什么意思呢？比如小程的“待办任务”Web 服务需要发送验证码邮件。我们可以用一个用完即毁型 FaaS 函数，调用云服务商的 SDK 发送邮件，再用一个常驻进程型 FaaS 函数生成随机字符串验证码，生成后记录这个验证码，并且调用发送邮件的 FaaS 将验证码发给用户邮箱。用户验证时，我们再调用常驻进程型 FaaS 的方法校验验证码是否正确。

我还是用阿里云来举例，我们查阅阿里云的邮件服务文档，发现它只支持 Java、PHP 和 Python 的 SDK。我们一直都是在讲 Node.js，这里没有 Node.js 的 SDK，怎么办？如果我们根据阿里云邮箱服务的文档，自己开发 Node.js 的 SDK，那肯定是饶了弯路，废了没用的力气。

因为我们发送邮件的用完即毁型 FaaS 函数功能很单一，所以我们完全可以参考邮件服务的 PHP 文档，就用 PHP 的 SDK 创建一个 FaaS 服务来发送邮件的。你会发现使用 PHP 邮件服务的成本居然如此之低。

Sorry, your browser doesn't support embedded videos.



00:00 / 00:00



你会看到在这个例子中，我用了我并不太熟悉 PHP 语言编排了邮件发送服务。不知道你意识到没有，这个也是 FaaS 一个亮点：语言无关性。它意味着你的团队不再局限于单一的开发语言了，你们可以利用 Java、PHP、Python、Node.js 各自的语言优势，混合开发出复杂的应用。

FaaS 服务编排被云服务商特别关注正是因为它具备的这种开放性。使用 FaaS 可以创造出各种各样复杂的服务编排场景。而且还是语言无关，大大提升了云服务商各种服务的使用场景。当然，这对开发者也提出了要求，它要求开发者去更多地了解云服务商提供的各种服务。

甚至我还知道，西雅图就有创业团队利用 FaaS 服务编排能力做了一套开源框架：Pulumi[5]，并且还拿到了融资。感兴趣的话，你可以去他们的官网看看。

总结

好，到这里，我们这节课的内容就讲完了。我再来总结一下这节课的关键点。

FaaS 的进程模型有两种：常驻进程型和用完即毁型。常驻进程型是为了适应传统 MVC 架构设计的，它看起来并不自然。如果你从现在开始玩 FaaS 的话，我当然首选推荐用完即毁型，它可以最大限度发挥 FaaS 的优势。

追溯历史，我给你梳理了前后端分离发展出的 BFF，然后 BFF 又可以被 SFF 替代。不管是内部的接口编排，还是外部一些数据的编排，FaaS 都可以发挥出极大优势，你看看我视频演示的例子就懂了。

从数据编排再进一步，我们可以利用 FaaS 和云服务商云服务的能力，做到服务编排，编排出更加强大的组合服务场景，提升我们的研发效能。并且通过我这么长时间的体验，我还想感叹说，依赖云服务商的各种能力，再通过 FaaS 编排开发一个项目时，往往可以做到事半功倍。

作业

今天的作业和上一讲类似，我视频中给你做了个简单的 Demo。你可以随便找个云平台去 run 一下试试，百闻不如一见，体验完之后，你可以在留言区谈谈你的感想。另外，如果今天这节课让你有所收获，也欢迎你把它分享给更多的朋友。

我 Demo 中的代码地址：https://github.com/pusongyang/sls-send-email

参考资料

[1] Express 是 Node.js 著名的 Web 服务框架 <https://expressjs.com/>。

[2] React 是 Facebook 开源的 MVVM 框架 <https://zh-hans.reactjs.org/>。

[3] AntDesignPro 是蚂蚁开源的 React 组件库 <https://pro.ant.design/>。

[4] Umi.js 是蚂蚁开源的 React 企业级解决方案脚手架 <https://umijs.org/>。

[5] Pulumi <https://pulumi.io/>

04 | 原理：FaaS应用如何才能快速扩缩容？

你好，我是秦粤。上一讲我们介绍了 FaaS 的两种进程模型：用完即毁型和常驻进程型，这两种进程模型最大的区别就是在函数执行阶段，函数执行完之后函数实例是否直接结束。同时，我还给你演示了用完即毁型的应用场景，数据编排和服务编排。

这里我估计你可能会有点疑虑，这两个场景用常驻进程型，应该也可以实现吧？当然可以，但你还记得不，我多次强调用完即毁型是 FaaS 最纯正的用法。那既然介绍了两种进程模型，为什么我要说用完即毁型 FaaS 模型比常驻进程型纯正？它背后的逻辑是什么？你可以停下来自己想想。

要真正理解这个问题，我们需要引入进来复杂互联网应用架构演进的一个重要知识点：扩缩容，这也是我们这节课的重点。

为了授课需要，我还是会搬出我们之前提到的创业项目“待办任务”Web 网站。这一次，需要你动动手，在自己本地的机器上运行下这个项目。项目的代码我已经写好了，放到 GitHub 上了，你需要把它下载到本地，然后阅读 README.md 安装和启动我们的应用。

GitHub 地址：https://github.com/pusongyang/todolist-backend

我给你简单介绍下我们目前这个项目的功能。这是一个后端项目，前端代码不是我们的重点，当然如果你有兴趣，我的 REAME.md 里面也有前端代码地址，你可以在待办任务列表里面创建、删除、完成任务。

技术实现上，待办任务数据就存储在了数组里。宏观上看，它是个典型的 Node.js 传统 MVC 应用，Control 函数就是 app.get 和 app.post；Model 我们放在内存里，就是 Todos 对象；View 是纯静态的单页应用代码，在 public 目录。

你先想一下，假如我们让 200 个用户同时并发访问你本地开发环境的“待办任务”Web 网站首页 index.html，你本地的 Web 网站实例，会出现什么样的场景？如果方便的话，你可以用 Apache[1] 提供的 ab 工具，压测一下我们的项目。

# 模拟1000个请求，由200个用户并发访问我们启动的本地3001端口
ab -n 1000 -c 200 http://localhost:3001/

我来试着描述下你 PC 此时的状态，首先客户端与 PC 建立了 200 个 TCP/IP 的连接，这时 PC 还可以勉强承受得住。然后 200 个客户端同时发起 HTTP 请求"/ GET"，我们 Web 服务的主进程，会创建“CPU 核数 -1”个子进程并发，来处理这些请求。注意，这里 CPU 核数之所以要减一，是因为有一个要留给主进程。

例如 4 核 CPU 就会创建 3 条子进程，并发处理 3 个客户端请求，剩下的客户端请求排队等待；子进程开始处理"/ GET"，命中路由规则，进入对应的 Control 函数，返回 index.html 给客户端；子进程发送完 index.html 文件后，被主进程回收，主进程又创建一个新的子进程去处理下一个客户端请求，直到所有的客户端请求都处理完。具体如下图所示。

PC状态示意图

理解了这一点，接下来的问题就很简单了。如果我问你，为了提升我们客户端队列的处理速度，我们应该怎么做？我想答案你应该已经脱口而出了。

纵向扩缩容与横向扩缩容

是的，我们很容易想到最直接的方式就是增加 CPU 的核数。要增加 CPU 的核数，我们可以通过升级单台机器配置，例如从 4 核变成 8 核，那并发的子进程就有 7 个了。

除了直接增加 CPU 的核数，我们还可以增加机器数（还是增加一个 4 核的），我们用 2 台机器，让 500 个客户端访问一台，剩下 500 个客户端访问另外一台，这样我们并发的子进程也能增加到 6 个。

纵向扩缩容 & 横向扩缩容

我画了张图，你可以看看。增加或减少单机性能就是纵向扩缩容，纵向扩缩容随着性能提升成本曲线会陡增，通常我们采用时要慎重考虑。而增加或减少机器数量就是横向扩缩容，横向扩缩容成本更加可控，也是我们最常用的默认扩缩容方式。这里我估计很多人知道，为了照顾初学者，所以再啰嗦下。

你理解了这一点，我们就要增加难度了。因为 index.html 只是单个文件，如果是数据呢？无论是纵向还是横向扩缩容，我们都需要重启机器。现在待办列表的数据保存在内存中，它每次重启都会被还原到最开始的时候，那我们要如何在扩缩容的时候保存我们的数据呢？

在讲解这个问题前，我们还是需要简化一下模型。我们先从宏观架构角度去看“待办任务”Web 服务端数据流的网络拓扑图，数据请求从左往右，经过我们架构设计的各个节点后，最终获取到它要的数据，然后聚合数据并返回。那在这个过程中，哪些节点可以扩缩容，哪些节点不容易扩缩容呢？

“待办任务”Web服务端数据流网络拓扑图

Stateful VS Stateless

网络拓扑中的节点，我们可以根据是否保存状态分为 Stateful 和 Stateless。Stateful 就是有状态的节点，Stateful 节点用来保存状态，也就是存储数据，因此 Stateful 节点我们需要额外关注，需要保障稳定性，不能轻易改动。例如通常数据库都会采用主 - 从结构，当主节点出问题时，我们立即切换到从节点，让 Stateful 节点整体继续提供服务。

Stateless 就是无状态的节点，Stateless 不存储任何状态，或者只能短暂存储不可靠的部分数据。Stateless 节点没有任何状态，因此在并发量高的时候，我们可以对 Stateless 节点横向扩容，而没有流量时我们可以缩容到 0（是不是有些熟悉了？）。Stateful 节点则不行，如果面对流量峰值峰谷的流量差比较大时，我们要按峰值去设计 Stateful 节点来抗住高流量，没有流量时我们也要维持开销。

“待办任务”Web服务端数据流网络拓扑图

在我们“待办任务”的项目中，数据库就是典型 Stateful 节点，因为它要持久化保存用户的待办任务。另外负载均衡也是 Stateful 节点，就跟我们思维试验中保存客户端队列的主进程一样，它要保存客户端的链接，才能将我们 Web 应用的处理结果返回给客户端。

回到我们的进程模型，用完即毁型是天然的 Stateless，因为它执行完就销毁，你无法单纯用它持久化存储任何值；常驻进程型则是天然的 Stateful，因为它的主进程不退出，主进程可以存储部分值。

进程模型实例

如上图所示，我们将待办任务列表的数据存储在了主进程的内存中，而在 FaaS 中，即使我们在常驻进程型的主进程中保存了值，它也可能会被云服务商回收。即便我们购买了预留实例，但扩容出来的节点与节点之间，它们各自内存中的数据是无法共享的，这个我们上节课有讲过。

所以我们要让常驻进程型也变成 Stateless，我们就要避免在主进程中保存值，或者只保存临时变量，而将持久化保存的值，移出去交给 Stateful 的节点，例如数据库。

进程模型实例

我们将主进程节点中的数据独立出来，主进程不保存数据，这时我们的应用就变成 Stateless。数据我们放入独立出来的数据库 Stateful 节点，网络拓扑图就是上面这张图。这个例子也就变成了我们上节课讲常驻进程型 FaaS 的例子，我们在主进程启动时连接数据库，通过子进程访问数据库数据，但这样做的弊端其实也很明显，它会直接增加冷启动时间。那有没有更好的解决方案呢？

换一种数据持久化的思路，我们为什么非要自己连接数据库呢？我们对数据的增删改查，无非就是子进程复用主进程建立好的 TCP 链接，发送数据库语句，获取数据。咱们大胆想象下，如果向数据库发送指令，变成 HTTP 访问数据接口 POST、DELETE、PUT、GET，那是不是就可以利用上一课的数据编排和服务编排了？

是的，铺垫了这么多，就是为了引出我们今天的主角：BaaS 化。数据接口的 POST、DELETE、PUT、GET 其实就是语义化的 RESTful API[2] 的 HTTP 方法。用 MySQL 举例，那 POST 对应 CREATE 指令，DELETE 对应 DELETE 指令，PUT 对应 UPDATE 指令，GET 对应 SELECT 指令，语义上是一一对应的，因此我们可以天然地将 MySQL 的操作转为 RESTful API 操作。

为了防止有同学误解，我觉得我还是需要补充一下。传统数据库方式，因为 TCP 链路复用和通信字段冗余低，同样的操作会比 HTTP 快。FaaS 可以直连数据库，但传统数据通过 IP 形式连接往往很难生效，因为云上环境都是用 VPC 切割的。所以 FaaS 直连数据库，我们通常要采用云服务商提供的数据库 BaaS 服务，但目前很多 BaaS 服务还不成熟。

再进一步考虑，既然 FaaS 不适合用作 Stateful 的节点，那我们是不是可以将 Stateful 的操作全部变成数据接口，外移？这样我们的 FaaS 就可以用我们上一课讲的数据编排，自由扩缩容了。

后端应用 BaaS 化

BaaS 这个词我们前面已经讲过了，在我看来，BaaS 化的核心思想就是将后端应用转换成 NoOps 的数据接口，这样 FaaS 在 SFF 层就可以放开手脚，而不用再考虑冷启动时间了。其实我们上一课在讲 SFF 的时候，后端应用就是一定程度的 BaaS 化。后端应用接口化只是 BaaS 化的一小部分，BaaS 化最重要的部分是后端数据接口应用的开发人员也可以不再关心服务端运维的事情。

SFF示意图

回顾一下，[第 1 课] 中我们说的 Serverless 应用 = FaaS+BaaS，相信此刻你一定会有不一样的感悟了吧。

BaaS 化的概念容易理解，但实际上要实践，将我们的网站后端改造 BaaS 化，就比较困难，这其中主要的难点在于后端的运维体系如何 Serverless 化，改造后端 BaaS 化的内容相比 FaaS 的 SFF 要复杂得多。在本专栏后续的课程中，我将通过我们的创业项目“待办任务”Web 服务逐步演进，带你一起学习后端 BaaS 化，不过你也不必有压力，因为我们在学习 FaaS 的过程中已经掌握的知识点，也是适用于后端 BaaS 化的。

另外值得一提的是，云服务商也在大力发展 BaaS，例如 AWS 提供的 DynamoDB 服务或 Aurora 服务。数据库就是 BaaS 化的，我们无需关心服务端运维，也无需关心 IP，我们只要通过域名和密钥访问我们的 DB，就像使用数据编排一样。而且 BaaS 的阵营还在不停壮大，不要忘了我们手中还有服务编排这一利器。

总结

用完即毁型之所以比常驻进程型更加纯正，就是因为常驻进程型往往容易误导我们，让我们以为它像 PaaS 一样受控，可以用作 Stateful 节点，永久存储数据。实际上，在 FaaS 中即使我们采用常驻进程型，我们的函数实例还是会被云服务商回收。

就像我们的“待办任务”Web 网站的例子，将数据 Todos 放在内存中，我们每次重启都会重置一样。我们用数据编排的思路，将后端对数据库的操作转为数据接口，那我们就可以将 FaaS 中的数据存储移出到后端应用上，采用上一节课讲的数据编排跟我们的后端进行交互。但后端应用我们不光要做成数据接口，还要 BaaS 化，让后端工程师在开发过程中，也能不用关心服务端运维。

现在我们再来回顾一下这节课的知识点：

扩缩容我们可以选择纵向扩缩容和横向扩缩容，纵向扩缩容就是提升单机性能，价格上升曲线陡峭，我们通常要慎重选择；横向扩缩容就是提升机器数量，价格上升平稳，也是我们常用的默认扩缩容方式。

在网络拓扑图中，Stateful 是存数据的节点；Stateless 是处理数据的节点，不负责保存数据。只有 Stateless 节点才能任意扩缩容，Stateful 节点因为是保存我们的重要数据，所以我们要谨慎对待。如果我们的网络拓扑节点想自由扩缩容，则需要将这个节点的数据操作外移到专门的 Stateful 节点。

我们的 FaaS 访问 Stateful 节点，那我们就希望 Stateful 节点对 FaaS 提供数据接口，而不是单纯的数据库指令，因为数据库连接会增加 FaaS 的额外开支。另外为了方便后端工程师开发，我们需要将 Stateful 节点 BaaS 化，BaaS 化的内容，我们将在后续的课程中展开。

作业

本节课我们创业项目“待办任务”中的数据处理并没有按照 RESTFul API 的 HTTP 语义化来开发，不太规范。作业中的 GitHub 仓库，这个版本我已经将请求方式转为语义化的 RESTFul API 了，你可以对比一下 master 分支中的代码，看看语义化带来的好处。另外我引入一个本地数据库 lowdb[3]，在你第一次启动后，创建本地数据库文件 db.json，我们的增删改查不会因为重启项目而丢失了，但是在 FaaS 上我们却无法使用 db.json 文件，原因是 FaaS 的实例文件系统是只读的。因此 FaaS 版本，我们用了内存来替换文件系统。

作业初始化项目地址：https://github.com/pusongyang/todolist-backend/tree/lesson04-homework

给你的作业是，你要将这个项目部署到云上函数服务，注意 FaaS 的版本是 index-faas.js。如果你条件允许的话，最好用自己的域名关联。我们[第 1 课] 已经讲过 FaaS 官方提供的域名受限，只能下载，这个链接就是我用 FaaS 部署的“待办任务”：http://todo.jike-serverless.online/list

期待你的作业。如果今天的内容让你有所收获，也欢迎你转发给你的朋友，邀请他一起学习。

参考资料

[1] http://httpd.apache.org/

[2] https://restfulapi.net/http-methods/

[3] https://github.com/typicode/lowdb

05 | 后端BaaS化（上）：NoOps的微服务

你好，我是秦粤。现在我们知道了在网络拓扑图中，只有 Stateless 节点才能自由扩缩容，而 Stateful 节点因为保存了重要数据，我们要谨慎对待，因此很难做扩缩容。

FaaS 连接并访问传统数据库会增加额外的开销，我们可以采用数据编排的思想，将数据库操作转为 RESTful API。顺着这个思路，我引出了后端应用的 BaaS 化，一句话总结，后端应用 BaaS 化就是将后端应用转换成 NoOps 的数据接口。那怎么理解这句话呢？后端应用 BaaS 化，究竟应该怎么做？接下来的几节课，我们会展开来讲。

我们先回忆一下上节课的“待办任务”Web 应用，这个项目前端是单页应用，中间用了 FaaS 做 SFF 数据网关，后端数据接口还要 BaaS 化。这个案例会贯穿我们的课程，所以你一定要动手 run 一下。为了让你对我们的项目有个宏观上的认识，我还是先交付你一张大图。

“待办任务”Web应用架构图

这个架构的优势是什么呢？我们将这个图变个形，你就更容易理解了。

架构变形示意图

咱从左往右看上面这张图。用户从浏览器打开我们网站时，前端应用响应返回 index.html；然后浏览器去 CDN 下载我们的静态资源，完成页面静态资源的加载；与此同时，浏览器也向前端应用发起数据请求；前端应用经过安全签名后，再将数据请求发送给 SFF；SFF 再根据数据请求，调用后端接口或服务，最终将结果编排后返回。

从图里你可以看到，除了数据库是 Stateful 的，其它节点都已经变成了 Stateless。如果你公司业务量不大的话，这个架构其实已经足够了。就像传统的 MVC 架构一样，单点数据库，承载基本的并发量不是问题，而且数据也可以通过主从结构和客户端读写分离的方式来优化。

但 MVC 架构最大的问题就是累积，当一个 MVC 架构的应用，在经历长期迭代和运营后，数据库一定会变得臃肿，极大降低数据库的读写性能。而且在高并发达到一定量级，Stateful 的数据库还是会成为瓶颈。那我们可以将自己的数据库也变成 BaaS 吗？

要解决数据库的问题，也可以选择我上节课和你说的云服务商提供的 BaaS 服务，比如 DynamoDB。但云服务商 BaaS 服务究竟是怎么做到的？如果 BaaS 服务能力不全，不够满足我们的需要时怎么办？今天我就先带你看看传统的 MVC 应用中的数据库怎么改造成 BaaS。

当然，BaaS 化的过程有些复杂，这也正是我们后面需要用几节课才能跟你解释清楚的核心知识点。正如我们本节课的标题：后端应用 BaaS 化，就是 NoOps 的微服务。在我看来后端应用 BaaS 化，跟微服务高度重合，微服务几乎涵盖了我们 BaaS 化要做的所有内容。

所以我们先来学习一下微服务是什么。

微服务的概念

微服务的概念对很多做后端同学来说并不陌生，尤其是做 Java 的同学，因为早些年 Java 就提出 SOA 面向服务架构。微服务算是 SOA 的一个子集，2014 年由 ThoughtWorks 的 Martin Fowler 提出。微服务设计之初是为了拆解巨石应用，巨石应用就是指那些生命周期较长的，累计了大量业务高度耦合和冗余代码的企业应用。

跟 Serverless 的概念还在发展中不同，微服务的概念在这么多年的发展中已经有了明确的定义了。下面是 AWS 官方的解释：

微服务是一种开发软件的架构和组织方法，其中软件由通过明确定义的 API 进行通信的小型独立服务组成，这些服务由各个小型独立团队负责。微服务架构使应用程序更易于扩展和更快地开发，从而加速创新并缩短新功能的上市时间。

那在我看来，微服务就是先拆后合，它将一个复杂的大型应用拆解成职责单一的小功能模块，各模块之间的数据模型相互独立，模块采用 API 接口暴露自己对外提供服务，然后再通过这些接口组合出原先的大型应用。拆解的好处是，小模块便于维护，可以快速迭代，跨应用复用。

我们的 Serverless 专栏并不打算给你详细地讲解微服务，但是希望你能一定程度上了解微服务。FaaS 和微服务架构的诞生几乎是在同一时期，它俩的很多理念都是来自 12 要素（Twelve-Factor App）[1]，所以微服务概念和 FaaS 概念高度相似，也有不少公司用 FaaS 实现微服务架构；不同的是，微服务的领军公司 ThoughtWorks 和 NetFlix 到处宣扬他们的微服务架构带来的好处，而且他们提出了一整套方法论，包括微服务架构如何设计，如何拆解微服务，尤其是数据库如何设计等等。

我们后端应用 BaaS 化，首先要将复杂的业务逻辑拆开，拆成职责单一的微服务。因为职责单一，所以服务端运维的成本会更低。而且拆分就像治理洪水时的分流一样，它能减轻每个微服务上承受的压力。

我在 Serverless 的专栏里向你介绍微服务，主要就是想引入微服务拆解业务的分流思想和微服务对数据库的解耦方式。

微服务 10 要素

谈起微服务，很多人都要说 12 要素[2]，认为微服务也应该满足 12 要素。12 要素当初是为了 SaaS 设计的，用来提升 Web 应用的适用性和可移植性。我在做微服务初期也学习过 12 要素，但我发现这个只能提供理论认识。

所以在 2018 年 GIAC 的大会上，我将我们团队在微服务架构落地中的经验总结为微服务的 10 要素：API、服务调用、服务发现；日志、链路追踪；容灾性、监控、扩缩容；发布管道；鉴权。

我们回想一下[第 1 课] 中小服的工作职责：

无需用户关心服务端的事情（容错、容灾、安全验证、自动扩缩容、日志调试等等)。

按使用量（调用次数、时长等）付费，低费用和高性能并行，大多数场景下节省开支。

快速迭代 & 试错能力（多版本控制，灰度，CI&CD 等等）。

你有没有发现跟微服务的要素有大量的重合。API 就是 RESTful 的 HTTP 数据接口；服务调用你可以理解为就是 HTTP 请求；服务发现你可以理解为我们只能用域名调用我们的 HTTP 请求，不能用 IP；日志、容灾、监控都不难理解；链路追踪，是微服务重要的一环，因为相对传统 MVC 架构，我们一个请求在后端的调用链增长了，为了快速定位问题，我们都需要打印整个调用链路的异常栈；发布管道和鉴权，和我们的 FaaS 也有很重要的关联，我将放在下一课讲解。

木桶效应示意图

接下来，我不准备按照微服务的 10 要素一一操作，但我希望可以通过 Serverless 帮你建立知识体系的索引，所以关联的技术栈我都尽量点出来，你可以自己进一步了解学习。

我们再次拿出我们的创业项目“待办任务”Web 网站，这次我将带你一起看看微服务如何让数据库解耦。

我们先分析一下“待办任务”Web 服务。上一讲末尾我的作业其实已经为你准备好了，我们一起看看 index.js 文件，这是一个典型的 MVC 架构的应用。View 层就是 index.html 和静态资源文件；Model 层，我们引入 lowdb 用一个 db.json 文件代替数据库；Control 层，就是 app.METHOD，处理 /api/* 的数据逻辑。微服务是解决后端应用的，所以我们只需要关注 Control 层。

index.js文件

API 的数据处理，主要有两个，一个是 /api/rule 处理待办任务的，一个是 /api/user 负责用户登录态，而且我们“待办任务”主要的数据模型也就是待办任务和用户这两个。那我们可以将后端拆分出两个微服务：待办任务微服务和用户微服务。这里我要强调下，我只是为了向你演示微服务才这样做，在实际业务中，这么简单的功能，没必要过早地拆分。

微服务拆解演示

初步拆解，我们将 index.js 中的数据库移走了，而且拆分后各微服务的数据库比原来混杂在一起简单且容易维护多了。user.js 负责用户相关业务逻辑，只维护用户信息的数据库，并且暴露 RESTful 的 HTTP 方法；rule.js 负责待办任务的增删改查，只维护待办任务的数据库，并且暴露 RESTful 的 HTTP 方法；index.js 只需要负责将请求 HTTP 返回给数据编排。

HTTP 协议要满足我们的服务调用与发现，发布的 user.js 和 rule.js 必须使用域名，例如 api.jike-serverless.online/user 和 api.jike-serverless.online/rule。这样新扩容的机器 IP，只需要注册到这个域名下就可以被服务发现 IP 并调用了。

我们现在拆分后只是将数据库从 index.js 移出，分给到了 user.js 和 rule.js，但两个节点到目前为止数据库仍然是 Stateful 的。我们如何再让这两个数据库节点变成 Stateless 呢？你可以按一下暂停键思考一下。

我们先想想，对于 rule.js 这个微服务来说，我要再扩容一个新的实例，而且让两边的实例保持一致，那么我们是否只需要让新的数据库和旧的数据库保持同步更新就可以了呢？

解耦数据库

没错，其实要解决这个问题的核心就是让数据启动时，可以更新到最新的数据库。在其中一个数据库更新时，通知另外一个数据库更新。

这里就要引出一个关键的 Stateful 对象：消息队列[3]。它是一个稳定的绝对值得信赖的 Stateful 节点，而且对你的应用来说消息队列是全局唯一的。解耦数据库的思路就是，通过消息队列解决数据库和副本之间的同步问题，有了消息队列，剩下的就简单了。

我们每个微服务中的数据库，例如 MySQL，写的操作都会产生 binary log，通过额外的进程将 binary log 变更同步到消息队列，并监听消息队列将 binary log 更新在本地执行，修改 MySQL。比较著名的解决方案就是领英开源的 Kafka，现在云服务商基本也都会提供 Kafka 服务。当然数据库和副本之间会有短暂的同步延迟问题，但问题其实也不大，因为我们通常对数据库进行写操作时也会锁表。

如果你要细究这个问题，那就会碰到分布式架构无法同时满足的 CAP[4] 课题。我们在此也不深入展开 CAP 话题了，目前异步同步的时间差在很多场景下我觉得是可以接受的。

微服务拆解演示

拿我们自己的例子来说也很简单，我们用消息队列 RocketMQ，写操作我们都写入 RocketMQ，rule.js 进程中我们监听 RocketMQ，一旦有 rule 写入的消息，我们就更新我们的 lowdb 数据。围绕消息队列建立的同步机制，让每个微服务的数据库和它的扩容副本之间自动同步了。对于微服务来说，它本身的数据库还是 Stateful 的，但在微服务外部看来，这个微服务是 Stateless 的。如下图所示：

同步机制

跟传统的主从数据库方式不同的是，我们每个微服务的单点实例都是独享一个数据库的，rule 这个微服务单点可以对外提供所有待办任务的 RESTful API 接口。

你要知道消息队列，例如 RocketMQ 的可靠性是 99.99999999%，而我们常用的虚拟机 ECS 的可靠性也只不过 99.95%。在高并发架构的设计中，我们通常会要求 Stateful 节点一定要稳定，而且越少越好，所以消息队列，对于微服务和 FaaS 来说，都是可以作为支撑业务的核心。我们依赖消息队列，会让整个架构更稳定。

总结

我们为了避免在 FaaS 中直接操作数据库，而将数据库操作变成 BaaS 服务。为了理解 BaaS 化具体的工作，我们引入了微服务的概念。微服务先将业务拆解成单一职责和功能的小模块便于独立运维，再将小模块组装成复杂的大型应用程序。这一点上跟我们要做的后端应用 BaaS 化相似度非常高。所以参考微服务，我们先将 MVC 架构的后端解成了两个微服务，再用微服务解耦数据库的操作，先将数据库拆解成职责单一的小数据库，再用消息队列同步数据库和副本，从而将数据变成了 Stateless。

现在我们再来回顾一下这节课的重要知识点。

微服务的概念：它就是将一个复杂的大型应用拆解成职责单一的小功能模块，各模块之间数据模型相互独立，模块采用 API 接口暴露自己对外提供的服务，然后再通过这些 API 接口组合成大型应用。这个小功能模块，就是微服务。

微服务 10 要素：API、服务调用、服务发现；日志、链路追踪；容灾性、监控、扩缩容；发布管道；鉴权。这跟我们要做的 BaaS 化高度重合，我们可以借助微服务来实现我们的 BaaS 化。

解耦数据库：通过额外进程让数据库与副本直接通过消息队列进行同步，所以对于微服务应用来说，只需要关注自身独享的数据库就可以了。微服务通过数据库解耦，将后端应用变成 Stateless 的了，但对后端应用本身而言，数据库还是 Stateful 的。

作业

由于 Kafka 云服务比较贵，所以这节课的作业我们采用表格存储[5]来模拟 Kafka，以实现 DB 的同步功能。你可以尝试自己部署一下 rule-faas 到一个新的域名。下面是具体的代码地址：

仓库地址：https://github.com/pusongyang/todolist-backend/tree/lesson05

前端地址：https://github.com/pusongyang/todolist-frontend/tree/lesson05

实现后效果：http://lesson5.jike-serverless.online/list

你可以根据自己的域名，替换一下前端文件中的这个地址：http://faas.jike-serverless.online/api/rule，或者用我的前端代码lesson5分支修改 /src/pages/ListTableList/service.ts 中的地址，自己 npm run build 一下，替换掉项目 public 目录，压缩上传到函数服务目录。

这次的部署会有些复杂，我们要部署两个 FaaS 服务，一个是 rule-faas.js，用来处理 /api/rule；另一个是 index-faas.js，用来处理我们的静态资源请求。原理图如下所示：

部署示意图

另外.aliyunConfig 文件格式如下，你需要填入自己的配置情况，切记这个文件不可以上传到 Git 仓库：

//.aliyunConfig文件，保存秘钥，切记不可以上传Git
const endpoint = "https://rule.cn-shanghai.ots.aliyuncs.com";
// AccessKey 阿里云身份验证，在阿里云服务器管理控制台创建
const accessKeyId = "AccessKey";
// SecretKey 阿里云身份验证，在阿里云服务器管理控制台创建
const accessKeySecret = "SecretKey";
// 在数据链表中查看
const instancename = "rule";
const tableName = 'todos';
const primaryKey = [{ 'key': 'list' } ];

module.exports = {endpoint, accessKeyId, accessKeySecret, instancename, tableName, primaryKey};

期待你的作业和思考，如果今天的内容让你有所收获，也欢迎你把文章分享给身边的朋友，邀请他加入学习。

参考资料

[1] https://en.wikipedia.org/wiki/Twelve-Factor_App_methodology

[2] https://12factor.net/zh_cn/

[3] https://help.aliyun.com/document_detail/29532.html?spm=5176.11065259.1996646101.searchclickresult.6936139bcS8BlU

[4] https://www.ruanyifeng.com/blog/2018/07/cap.html

[5] https://ots.console.aliyun.com/

06 | 后端BaaS化（中）：业务逻辑的拆与合

你好，我是秦粤。上一课中，我们学习了后端 BaaS 化的重要模块：微服务。现在我们知道微服务的核心理念就是先拆后合，拆解功能是为了提升我们功能的利用率。同步我们也了解了实现微服务的 10 要素，这 10 要素要真讲起来够单独开一门课的。如果你不熟悉，我向你推荐杨波老师的《微服务架构核心 20 讲》课程。

BaaS 化的核心其实就是把我们的后端应用封装成 RESTful API，然后对外提供服务，而为了后端应用更容易维护，我们需要将后端应用拆解成免运维的微服务。这个逻辑你要理解，这也是为什么我要花这么多篇幅给你谈微服务的关键原因。

上节课我们将“待办任务”Web 服务的后端，拆解为用户微服务和待办任务微服务。但为什么要这样拆？是凭感觉，还是有具体的方法论？这里你可以停下来想想。

微服务的拆解和合并，都有一个度需要把握，因为我们在一拆一合之间，都是有成本产生的。如果我们拆解得太细，就必然会导致我们的调用链路增长。调用链路变长，首先影响的就是网络延迟，这个好理解，毕竟你路远了，可能“堵车”的地方也会变多；其次是运维成本的增加，调用链路越长，整个链条就越脆弱，因为其中一环出现问题，都会导致整个调用链条访问失败，而且我们排查问题也变得更加困难。

反过来看，如果我们拆解得太粗，调用链路倒是短了，但是这个微服务的复用性就差了，更别提因为高耦合带来的复杂且冗余的数据库表结构，让我们后续难以维护。我画了个图，你感受下。

拆解程度示意图

拆之，领域驱动设计

那我们要合理地拆解微服务，应该怎么拆解呢？上节课其实我有提到，目前主流的解决方案就是领域驱动设计，也叫 DDD[1]。DDD 是 Eric Evans 在其 2004 年的同名书中提出来的一个思想，但一直仅仅局限在 Java 的圈子里，直到 2014 年，微服务兴起后大家才发现它可以指导微服务的拆分，这才走进了大多数人的视野。用一句话简单总结，DDD 就是一套方法论：通过对业务分层抽象，分析定义出领域模型，用领域模型驱动我们设计系统，最终将复杂的业务模型拆解为独立运维的领域模型。

实际我自己在使用微服务开发的过程发现，微服务整体应该是一个动态网络结构[2]，随着业务的发展，这个网络结构也会发生变化。DDD 能帮助我们前期分析出一个较好的网络结构，但实际上，我们更应该思考的是如何整体优化动态网络：减少核心节点，保护核心节点，降低网络深度等等。

怎么理解动态网络优化呢？我们可以做个思维实验：假设我们将所有的功能都拆解成微服务，任意的微服务节点相互之间都可以调用，调用越频繁它们之间的距离就越近。那么我们考虑一下，当我们网站的访问请求流量稳定后，我们整个微服务节点组成的网络状态是怎么样的？

首先网络节点的相互制约总会让那些相互之间强依赖的、高耦合的节点，越走越近，最后聚集成一团节点。其次那些跟业务逻辑无关的节点，逐渐被边缘化，甚至消失。我们看这些聚集成团的节点，如果团里的点聚合太近了，其实是不适合拆分的，它们整体应该作成一个微服务。等这些节点太近的团合并成一个微服务节点后，我们再看那些聚集在一起、又不太近的节点就是一个个微服务了。

所以，我们在启动项目时，不用太过纠结应该如何去拆解微服务。而应该持续关注，并思考每个微服务节点的合理性。就像看待动态网络一样，持续地调整优化，去除核心节点。最终它会伴随你业务的发展阶段，达到各个阶段的稳定动态网络结构。

就像我们上节课“待办任务”Web 服务一样，我们可以先简单地将我们的项目后端分为：用户微服务和待办任务微服务。当然这里我们目前的业务太简单了，用 DDD 去分析，也是大材小用。随着我这个项目的业务发展，我们添加的功能会越来越多。让微服务根据业务一起成长演变就可以了。这并不是说我们就放任微服务不管了，而是从整体网络的角度思考，去看我们的微服务如何演进。

微服务演进过程

合之，Streaming

看完拆解，我们再看合并。合并呢，换个高大上的词其实就是前面课程中提到的编排。目前为止，我们整个“待办任务”Web 应用架构的设计基本完成了，而且所有节点都是 Stateless 的了。变成 Stateless 节点后，其实对于前端的同学来说，一点都不陌生，比如 React 的单向数据流中的 State 也要求我们 Immutable，这 Immutable 其实就是 Stateless。

我们上面已经看到了，拆解后的架构是个动态网络，那我们应该怎么合并或者编排呢？当然你像 SFF 那样通过传统的函数，将每个 HTTP 数据的请求结果通过数组或对象加工处理，再将这些结果返回也是可以的。但我在这里想向你介绍另外一种编排思路，工作流。

数据流演示图

我们可以将用户的请求想象成我们的呼吸系统，我们的肺就是 SFF，而微服务和 FaaS 节点就是需要氧气的各个器官。我们吸一口气，氧气进入肺部，血液循环将氧气按顺序流经我们每个器官，这就是请求链路。每个器官一接收到新鲜血液，就会吸取氧气返回二氧化碳，最终血液循环将二氧化碳带到肺部呼出，这个就是数据返回链路。我们的各个器官，就被请求链路通过新鲜血液到来的这个事件串联起来了，这个就是事件流，也就是用一个个事件去串联 FaaS 或微服务。

现在我们用[第 3 课] 讲的，PHP 发邮件改造一下，举个例子。当用户注册时，我们完全可以将用户的信息和注册验证码存入数据库。PHP 发邮件的 FaaS 触发器改为数据库插入新记录触发事件。用户从邮箱验证获取验证码，把验证码写到输入框后，点击验证，则是另一个 HTTP 触发器，触发 FaaS 函数校验验证码通过，修改数据库注册成功，并且返回 302 跳转到登录成功页面。具体流程可参考下图：

案例流程图

当然现在这个解决方案也有成熟对应的云 BaaS 服务：Serverless 工作流[3]。

安全门神

理解了拆合的思想，我们就可以将目前“待办项目”的架构再演进一下：静态文件我们用 CDN 托管，前端项目只负责域名支撑和 index.html，剩下的请求直接访问 FaaS 微服务。这时候，我估计你会问，咱们数据的安全性如何保障呢？是的，到目前为止，我们的 FaaS 都一直在用匿名模式访问，完全没有任何安全防护可言，也就是说目前我们 FaaS 服务的接口一直都在互联网上“裸奔”。

鉴权

其实，FaaS 提供的安全防护通常是放在触发器上的。触发器的授权类型或认证方式我们可以设置为：匿名 anonymous 或函数 function。匿名方式就是不需要签名认证，匿名的用户也能访问。而函数方式，则是需要签名认证[4]。这个签名认证的算法，参数需要用到我们账户的访问秘钥 ak/sk[5]，ak/sk 相当于我们云账户的银行卡密码，这么重要的账户信息，我们只能限定在服务端使用，前端代码里绝对不可以出现。

也就是说，我们只能在服务端使用函数安全认证方式。如果是这种方案，我们的“待办任务”架构就演进成下图这样了。

“待办任务”架构图

那有没有针对匿名认证方式的安全策略呢？当然有，这里我们同样需要借鉴一下微服务的鉴权设计：JSON Web Token，简称 JWT[6]。JWT 简单来说，就是将用户身份信息和签名信息，一起传到客户端去了。用户在请求数据时，带上这个 JWT，服务端通过校验签名确定用户身份。JWT 存在于客户端，JWT 验证只需要通过服务端的 sk 和算法验证签名。同样，我画了张图，以帮助你理解。

JWT示意图

要解决后端互调的安全性，我们用 VPC 或 IP 白名单，都很容易解决。比较难处理的是前后端的信任问题，JWT 正好就提供了一种信任链的解决思路。当然，关于鉴权也有一些云服务商推出了一些更加安全易用的 BaaS 服务，例如 AWS 的 IAM 和 Cognito[7]。

安全性是我们考虑架构设计时重要的一环，因为安全架构设计的失败，会直接导致我们资产的损失。鉴权是识别用户身份，防止用户信息泄漏和恶意攻击使用的。但根据我统计的数据，我们在日常 99% 的问题，都发生在新版本上线的环节。

那我们该怎么稳定持续地快速迭代，发布新版本上线呢？我们可以回想一下[第 1 课]，小程和小服的例子，小程最后实现 NoOps 后，小服则只要将代码合并到指定分支就可以发布上线了。那现实中，这点该怎么实现呢？

当我们的项目 Serverless 化以后，代码的质量变得尤为重要。你可以想想，Serverless 化之前，你不小心上线了一个 bug，影响的范围最大也就只有一个应用。但是 Serverless 化之后，如果是核心节点发布了严重的 bug 上线，那么影响的范围就是所有依赖它的线上应用了。

不过，你也不用太担心，微服务和 FaaS 都具备快速独立迭代的能力。以前我们一个应用的迭代周期通常要一周到两周。但对于 Serverless 化后的应用来说，每个节点借助独立运维的特性，可以随时随地的发布上线。

综上，我们知道了，微服务和 FaaS 都是快速迭代的，修复问题很快，但我们也不能每次都等问题出现，再去依赖这个能力呀。有没有什么办法可以提前发现问题，保证我们既快又稳？目前软件工程的最佳做法就是代码流水线的发布管道。

发布管道

发布管道的流水线主要有 3 个部分：

代码发布前的验证，代码测试覆盖率 CI/CD；

模拟流量回归测试通过，发布到灰度环境；

代码正式上线，灰度环境替换正式环境。流水线的每个节点产生的结果，都会作为下一个节点必要的启始参数。

发布管道流水线

我们先看看上图，我来解释下这个流程。

我们的代码合并到指定分支后，通常我会用 Develop 分支。

Git 的钩子就会触发后续的流水线，开始进入构建打包、测试流程。

测试节点做的事情就是跑所有测试 Case，并且统计覆盖率。

覆盖率验证通过，代码实例用录制流量模拟验证。

模拟验证通过，发布代码实例到灰度环境。

线上根据灰度策略，将小部分流量导入灰度环境验证灰度版本。

在灰度窗口期，比如两个小时，灰度验证没有异常则用灰度版本替换正式版本；反之则立即丢弃这个灰度版本，止损。

这套流程，目前规模大一些的互联网公司发布流程基本都在这样跑，如果你不是很了解，可以自己尝试用我们介绍的 Serverless 工作流或者云服务商提供的工作流工具[8]动手搭建下。

在这套流程的基础上，很多企业为了追求更高的稳定性，还会设定环境隔离的流水线和安全卡口。比如隔离测试环境和线上环境，测试环境用来复现故障。每次代码进入发布管道，都必须先在测试环境跑通，跑通后安全卡口放行，才能进入线上环境的流水线。

总结

这节课，我们继续讲后端的 BaaS 化。我们再梳理一下这节课的重要知识点吧。

如何拆解 BaaS 应用，我们学习了微服务的重要拆解思想 DDD：通过对业务分层抽象，分析定义出领域模型，用领域模型驱动我们设计系统，最终将复杂的业务模型拆解为独立运维的领域模型。另外我也介绍了另一种更适合初创企业的拆分思路：动态网络演进。

拆解完之后，我们就要考虑合并。这里我们介绍了代码编排以外的另一种编排方式：事件流编排，它就是通过一个个事件顺序将我们的微服务或 FaaS 串联起来。

为了解决拆解后，微服务之间的信任问题。我们先了解了 FaaS 触发器的安全方案：数字签名。还借鉴了微服务的鉴权做法 JWT，将用户鉴权加密信息放在客户端，让鉴权服务变成 Stateless。最后，为了让微服务又快又稳地发布版本，我们借鉴了微服务的发布管道：打造自动灰度流水线。

作业

这节课的作业就是我们 JWT 鉴权的“待办任务”Web 应用，你来部署上线。

后端代码 GitHub 地址：https://github.com/pusongyang/todolist-backend/tree/lesson06

前端代码 GitHub 地址：https://github.com/pusongyang/todolist-frontend

演示预览地址：http://lesson6.jike-serverless.online/list

期待你的作业，如果今天的内容让你有所收获，也欢迎你把文章分享给身边的朋友，邀请他加入学习。

参考资料

[1] https://en.wikipedia.org/wiki/Domain-driven_design

[2] https://en.wikipedia.org/wiki/Dynamic_network_analysis

[3] https://www.aliyun.com/product/fnf

[4] https://github.com/aliyun/fc-nodejs-sdk/blob/master/lib/client.js?spm=a2c4g.11186623.2.15.16e016d7lo8NBQ#L840

[5] https://help.aliyun.com/document_detail/154851.html?spm=5176.2020520153.0.0.371a415dLXyltZ

[6] https://jwt.io/

[7] https://docs.aws.amazon.com/zh_cn/cognito/latest/developerguide/what-is-amazon-cognito.html

[8] https://www.aliyun.com/product/yunxiao/devops?spm=5176.10695662.1173276.1.6c724a38akCjgo

07 | 后端BaaS化（下）：Container Serverless

你好，我是秦粤。上节课，我重点给你讲了业务逻辑的拆和合，拆的话可以借助 DDD 的方法论，也可以用动态网络的思想让微服务自然演进；合的话，我们可以用代码编排，也可以用事件流来驱动。另外，我们还了解了微服务拆解后会带来的安全信任问题，这可以通过微服务的跨域认证 JWT 方案解决。我们还了解了后端应用要支持快速迭代或发布，可以参考微服务搭建灰度发布流水线，也就是发布管道。其实我们在使用 FaaS 过程中遇到的很多问题，都可以借助或参考微服务的解决方案。

现在我们再回顾一下 BaaS 化后的“待办任务”Web 服务，我们已经将后端拆解为用户微服务和待办任务微服务，前端用户访问我们的 FaaS 服务，登录后获取到 JWT，通过数据接口 +JWT 安全地访问我们的微服务。而且我们的 FaaS 和微服务都具备了快速迭代的能力。

BaaS化后的“待办任务”Web服务

到这里，我要指出我之前 rule-faas.js 的一个 Bug，如果你之前亲自动手做过实验的话，估计也有发现。这个 Bug 的直接表现是用户初次请求数据时，如果触发了冷启动，返回的待办任务列表就会为空。

究其原因，是因为冷启动时连接数据库会有延时，这直接导致了第一个请求返回的待办任务列表还未同步到消息队列的数据。要解决这个 bug，我们可以用之前讲过的预热 FaaS 或预留实例的方式，但你也要知道，FaaS 函数扩容时，新启动的函数副本也会出现同样的问题。

我前面卖了很多关子，其实 FaaS 在设计时已经考虑到这个问题了，所以在 FaaS 的“函数配置”里，都会提供一项“函数初始化入口”的选项。但你会发现，它同时也会让你配置初始化时间，最少 1 秒。还记得我们在[第 2 课]，讲函数执行阶段的一人一半的函数代码初始化时间吗？没错，就是那个时间。

当你配置了“函数初始化入口”，每次启动 FaaS 实例时，系统都会先等待初始化函数执行，而且在函数初始化时间结束后，才会继续执行函数。而从目前平台的配置来看，初始化时至少也需要 1 秒的时间（你去云服务商后台就能看到）。

对很多事件触发的应用场景，FaaS 增加几秒的初始化时间，影响并不大。但对很多后端应用则不然，尤其是 Java 等语言，如果软件包比较大，启动和初始化的时间会更长。

要缩短或绕过初始化的时间，我们要尽可能地利用 Runtime 里面给我们提供的内置能力，例如我们 BaaS 化一直提倡使用服务编排和 HTTP 接口，就是因为云服务商 SDK 和 HTTP 协议，所有语言的 Runtime 里都内置了。

那什么是 Runtime 呢？Runtime 其实就是运行我们代码所需要的函数库和二进制包。FaaS 中的 Runtime 是云服务商预先设计好的，会放一些常用的函数库和二进制包进去，相当于是平台的能力。

但是当我们后端应用 BaaS 化后，想采用 FaaS 方案部署的话则会碰到 Runtime 这个拦路虎。FaaS 虽然已经支持多数主流后端语言，但后端应用根据业务需求，要依赖特殊的函数库或二进制包，FaaS 的官方 Runtime 就无法支持。而且像 Java 等语言，在代码包较大的情况下，FaaS 启动速度很慢，也不适合。例如 Node.js 的 Runtime，其实也包括我们自己安装的 NPM 包，所以相当于我们可以部分定制。但是你也注意到了，我们是整个目录包括 node_modules 一起上传的，也就是说涉及编译的 NPM 包是无法跨操作系统生效的。这种场景下 FaaS 的 Runtime 不可控就会成为我们难以绕过的问题了。当我们遇到 FaaS 无法解决的场景，我们就可以考虑下降一层，使用 FaaS 的底层支撑技术 Docker 容器了。

广义Serverless

还记得我们[第 1 课] 中的广义 Serverless 的图吗？基础设施中的容器，一般情况下指的就是 Docker 容器。Docker 这个技术你肯定或多或少已经用过了，使用它们可以将应用代码和代码依赖的 Runtime 一起打包成一个 Docker 镜像。这个 Docker 镜像，可以在云上、自己的笔记本电脑、同事的电脑上无畅运行，并且完全不用担心环境依赖的问题，因为我们应用的依赖也打包在一起了。

到这里，我们又引入了 Docker 的概念，Docker 出现之后，CaaS（Container as a Service）很快也就流行起来了。为了帮助你理解 IaaS、PaaS、BaaS、CaaS 这几者的关系，我画了张图，希望能从云服务发展的角度，帮你梳理下脉络。

云服务发展进程

图片很好理解，我就不解释了。不过这里要解决一个你可能会有的疑惑：为什么 BaaS 的出现，比 Serverless FaaS 还要早三年？那是因为早期出现的 BaaS 其实是 mBaaS（移动后端即服务），这概念当时也曾经火过一段时间，现在已经被 Google 收购的 FireBase 其实就是做的这个生意。mBaaS 设计之初是专门为移动端提供后端服务的，例如用户管理、角色服务、文件存储服务等等。除了服务对象是移动端之外，跟我们说的 BaaS 概念很像。移动端可以将 BaaS 的所需鉴权算法放到客户端中，并随着客户端的版本定期更新秘钥。但前端却做不到，因此 mBaaS 只局限在移动端，没有火起来。直到 FaaS 的出现，才诞生了 BaaS 的使用场景，mBaaS 也开始转型，支持更广范围的前端场景了。

VM 是一种虚拟化技术，这个我们都知道，其实 Docker 也是一种虚拟化技术，只不过它是利用新版 Linux 内核提供 Namespace、Cgroups 和 Union File System 特性，模拟操作系统的运行环境。它跟虚拟机 VM 最大的区别在于，Docker 是进程模型的。怎么理解这句话呢？我们需要画一张图。

Docker进程模型

从图中我们可以看出，虚拟机是在宿主机上启动一个虚拟机监视器 HyperVisor 管理控制虚拟机的。而虚拟机自身包含整个客户操作系统、函数库依赖和用户的应用，虚拟机操作系统之间相互都是隔离的。经典的 HyperVisor 就是 VirtualBox[1]，你感兴趣的话可以下载体验一下。你也可以试想一下，如果我们我们一台虚拟机部署一个微服务，其实资源利用率是很低的。

容器实例则只包含函数库依赖和用户的应用，操作系统是依赖宿主机的用户态模拟的，也就是说容器之间是共享宿主机内核的。所以 Docker 更加轻量，启动速度更快，代码执行速度也更快。

Docker 的容器呢，因为只包含函数库依赖和用户的应用，可以部署到任意 Docker 引擎上，而 Docker 引擎呢，可以安装在你的个人电脑、公司机房或者云上。通常我们容器移动时，是移动容器的硬盘快照，内存中的状态我们比较难复制，这个硬盘快照就是 Docker 镜像。我们给 Docker 的镜像打上标签，标签就像是镜像的唯一标识符 URI，打上后它就可以到处移动了。这个也是 Docker 的核心概念：build、ship、run。

其实你会不会觉得，Docker 的这个层级结构有些眼熟？是的，这正是我们[第 2 课] 中讲的 FaaS 分层。我们回想一下，我当时所说的操作系统容器就是 Docker 模拟的，Runtime 其实就是 Bins/Libs 层。云服务商的冷启动加速，就是利用 Docker 镜像缓存加速。我想你也应该猜到了，其实很多云服务商 FaaS 和 PaaS 的底层技术就是容器即服务 CaaS。

那么接下来，我们就体验一下 Docker 的便利吧。我们的“待办任务”Web 服务，只要添加一个文件，就可以让它变成 Docker 镜像了。

Docker 再探

这里我建议你，最好自己在电脑上安装体验一下 Docker。在主流的操作系统数安装 Docker 都不难的，只需要下载安装包就可以了。

build: Dockerfile

构建是 Docker 最重要的一环。Docker 镜像是硬盘快照，那我们就看看标准的 Docker 硬盘快照如何制作吧。下面就是我们在代码根目录下增加的文件，供你参考：

# FROM是指我们的镜像基于哪个镜像来构建，我们这里是基于jessie linux的Node.js镜像
FROM registry.cn-shanghai.aliyuncs.com/jike-serverless/nodejs
# ENV是设置环境变量
ENV HOME /home/myhome/myapp
# RUN是执行一段命令
RUN mkdir -p /home/myhome/myapp/public
# COPY是将当前目录下的内容拷贝到容器中
COPY . /home/myhome/myapp
COPY public /home/myhome/myapp/public/
COPY node_modules /home/myhome/myapp/node_modules/
# WORKDIR是设置进入容器后的工作目录
WORKDIR /home/myhome/myapp/
# ENTRYPOINT是容器启动后执行的脚本
ENTRYPOINT [ "node","index.js" ]

通常我们使用 Docker 前，先去 Docker Hub 上，找合适的基础镜像。看看基础镜像上都安装了哪些函数库或二进制包，再对比一下要运行自己的应用还缺少哪些函数库和二进制包。在基础镜像的层级上，加上自己的依赖。这样我们就构建好适合自己的镜像了。以后我们就能 FROM 自己的基础镜像，构建自己的应用了。

然后你在项目下执行：docker build 命令，就可以帮你构建 Docker 镜像了。

docker build --force-rm -t myapp -f Dockerfile .

构建好的镜像，我们可以通过 docker run 这个命令在本地调试。

docker run -d -p 3001:3001 -e TEST=abc  myapp:latest

然后我们通过浏览器访问http://127.0.0.1:3001，就可以看到我们刚刚构建的 Docker 内容了。你会发现，这不就是我们云上运行的版本吗？是的，既然 FaaS 是用 CaaS 技术实现的。我们当然也可以利用 Docker 实现我们的“待办任务”Web 服务。

为了方便 Docker 例子的展示，这节课的项目代码 index.js，包含了 rule.js 的逻辑。你会发现 index.js 中，我们启动的时候，不用关心初始化需要等待多少秒了，而是直到初始化完成后，才监听 3001 端口，而 Node.js 连接数据库的时间通常也只需要几十毫秒。

Docker版本的“待办任务”Web服务

另外为了方面你观察和调试 Docker 容器实例，我这里给出一个登录 Docker 容器实例的命令。

docker exec -it 容器ID bash

ship: Docker Registry

本地调试完，我们再看看如何部署到云上。Docker 官方的镜像仓库[2]速度太慢，现在的云服务都支持私有的容器镜像仓库[3]，上面构建 Dockerfile 里面的 Node.js 镜像，就是用我自己的私有容器仓库搭建的。

你可以登录云服务的容器镜像服务，他们都会告诉你如何 Docker login 到私有 Registry，如何打标签，以及如何上传。

用我们的例子来说，大致会有下面的命令。

未登录过的话，要先登录 Registry。

docker login --username=极客时间serverless registry.cn-shanghai.aliyuncs.com

根据容器镜像仓库的 URI，打标签。镜像 ID 可以通过 docker images 查看。

docker tag 你本地的镜像ID registry.cn-shanghai.aliyuncs.com/jike-serverless/todolist

上传镜像到私有的 Registry 仓库。

docker push registry.cn-shanghai.aliyuncs.com/jike-serverless/todolist

run: Docker Engine

运行 Docker 镜像就很简单了，云服务都支持容器服务 CaaS。你只需要购买好容器服务，就可以从自己私有的容器镜像仓库 Docker Registry 中下载镜像，并运行了。你要是执行过上面的例子，那相信你也能体会到为什么 FaaS 的冷启动速度这么快了。不过需要提醒你一下，这里会产生费用。

运行Docker镜像示意图

另外，我们的专栏是讲 Serverless，但是为了让你理解 Serverless 底层的实现，所以我们也讲到了 Docker 容器的部分内容。对于我们专栏来说，你不用尝试部署云上容器了。

恭喜你，获得 DevOps 奖章一枚！

现在我们了解了容器，也知道了 FaaS 是构建在容器上的。我们的微服务和整个应用都可以部署在 CaaS 之上，容器对我们来说更加可控，因为我们可以通过 Dockerfile 安装我们需要的各种函数库依赖或者二进制文件。但这里还有一个问题，FaaS 的扩缩容是怎么做到的呢？我们如果采用了容器，容器如何做到扩缩容呢？

FaaS 与 Docker 的扩缩容

FaaS 中的扩缩容，我们可以通过云控制台看到在 FaaS 函数配置中的“单实例并发度”。FaaS 的做法比较简单粗暴，需要我们告诉函数服务，我们的单个函数实例可以承载多少的并发量，如果事件触发并发量超出了这个并发度，则会自动扩容。扩容的数量 N，就是这个事件触发量除以单机并发量取整。例如我们设定，我们 rule-faas 函数的单实例并发度为 10，那么当用户并发量是 11 时就会扩容 2 个实例。如果用户并发量达到 100，就会扩容出 10 个实例。

这种做法其实是比较机械式的，我们再看看 FaaS 的“函数指标”监控图。你有没有想到，我们其实可以利用实时监控，去控制扩缩容？例如当单个函数实例承受不了时，内存使用率会越来越高，它的执行时间也会越来越长。

FaaS的“函数指标”监控图

是的，我们在使用 Docker 时，要考虑的就是：监控指标 metrics 以及扩容水位。

监控指标 metrics 就像 FaaS“函数指标”监控图那样，是一系列需要我们关心的单个容器核心指标，包括 CPU 利用率、内存使用率、硬盘使用率、网络连接数等等。

我们将监控指标中的各项数值想象成蓄水，我们监控就像一个蓄水池，一旦某一项超过了蓄水池，水就会溢出。所以，我们要设定水位告警。我们在蓄水池里面画上刻度，当水位到这个刻度，我们就马上给这个蓄水池扩容。

蓄水池案例

Docker 容器本身并不提供扩缩容机制，但我们要让 Docker 自动化扩缩容，就可以用监控指标和水位去设计扩缩容机制。我们需要实时监控容器状态，当容器状态的某一项数值过高时，我们就需要给容器扩容。FaaS 的弹性做法很简洁，而 Docker 的扩缩容机制弹性更高、更加可控。但是 Docker 容器，通常需要我们至少保持 1 个容器实例在线。相信你也知道了 FaaS 的预留实例是怎么做到的了吧？

讲到这里，不知道你发现没有，我们 BaaS 化的三讲，已经默默地实现了微服务的 10 要素。这也是为什么我一直说 BaaS 化和微服务高度重叠。

我们再来回顾一下微服务 10 要素。其中，API、服务调用、服务发现，我们可以通过 RESTful HTTP 接口实现；日志、链路追踪，我们没有展开，但我们可以依赖云服务商提供的日志采集解决；鉴权我们可以用跨域认证 JWT 解决；发布管道，需要我们搭建流水线发布管道；容灾性、监控、扩缩容，我们可以通过实时监控和扩缩容解决。到这儿呢，我们的高铁车厢也终于完成了。

这节课的内容挺多的，需要你动手消化吸收一下。下节课我们再看看，如何利用 K8s 调度我们的 Docker 容器。

总结

BaaS 化的内容，到今天，我们用了三节课讲完了，现在我们一起回顾一下。

我们讲后端应用 BaaS 化的问题，转换为后端应用 NoOps 微服务。所以我们先了解了微服务的 10 要素，并且看到微服务中如何通过消息队列将 Stateful 的节点，变成 Stateless 的。在微服务的拆解过程中，我们学习了微服务的拆解思想 DDD 和动态网络演进，以及拆解后带来的信任问题，需要我们加密身份认证。合并时，除了代码里面编排 HTTP 请求结果，我们还学习了事件流触发获取结果。为了让微服务能够快速迭代，我们还需要自己搭建流水线发布管道。

这节课我们通过 FaaS 和 BaaS 的初始化问题，向你介绍了 FaaS 和 BaaS 依赖的底层实现容器 Docker。Docker 也是一种虚拟化技术，它的核心理念是：build、ship、run。通过将我们的应用代码和应用依赖的函数库、二进制包，打包在一起的方式，解决应用开发环境和运行环境的一致性问题。我们可以借助 Docker 容器维持我们的应用实例，来解决初始化慢的问题。

我们还了解了 FaaS 的扩缩容逻辑是通过用户配置的“函数初始化入口”，以及固定的“初始化超时时间”配合“单实例并发度”来实现的。而我们在容器 Docker，其实可以采用实时监控配合扩容水位，来做到更加弹性的扩缩容策略。

接下来我会通过 K8s 实践我们目前所学到的内容，我们的“待办任务”Web 服务，将在我们本地搭建的 CaaS 环境和 Serverless 环境中开发和调试。

作业

首先，拉取我们lesson07的代码，为“待办任务”部署的 rule-faas 函数添加初始化入口。

这节课的作业呢，就是我们要在本地完全通过 Docker 容器，搭建起我们的“待办任务”Web 服务。除了 css 和 js 静态资源是来自 CDN，其它内容都将运行在 Docker 容器里。

相信你可以通过这个作业，体验到 FaaS 的底层 CaaS 的运行机制。

当然如果你有预算，也可以将 Docker 镜像上传到云服务商的 Registry，在云上购买容器服务就可以部署你的 Docker 镜像，并在云上运行我们的“待办任务”Docker 版本了。这样你就拥有了一个永不停机的 Docker 服务。

另外，我也希望你可以帮助我继续优化我们的课程作业代码。如果你有更好的建议，也可以通过 Github 的 MergeRequest 告知我。

接下来就期待你的作业和建议了。如果今天的内容让你有所收获，也欢迎你把文章分享给身边的朋友，邀请他加入学习。

参考资料

[1] https://www.virtualbox.org/

[2] https://docs.docker.com/get-docker/

[3] https://hub.docker.com/

08 | 搭建私有Serverless（一）：K8s和云原生CNCF

你好，我是秦粤。上节课我们只是用 Docker 部署了 index.js，如果我们将所有拆解出来的微服务都用 Docker 独立部署，我们就要同时管理多个 Docker 容器，也就是 Docker 集群。如果是更复杂一些的业务，可能需要同时管理几十甚至上百个微服务，显然我们手动维护 Docker 集群的效率就太低了。而容器即服务 CaaS，恰好也需要集群的管理工具。我们也知道 FaaS 的底层就是 CaaS，那 CaaS 又是如何管理这么多函数实例的呢？怎么做才能提升效率？

我想你应该听过 Kubernetes，它也叫 K8s（后面统一简称 K8s），用于自动部署、扩展和管理容器化应用程序的开源系统，是 Docker 集群的管理工具。为了解决上述问题，其实我们就可以考虑使用它。K8s 的好处就在于，它具备跨环境统一部署的能力。

这节课，我们就试着在本地环境中搭建 K8s 来管理我们的 Docker 集群。但正常情况下，这个场景需要几台机器才能完成，而通过 Docker，我们还是可以用一台机器就可以在本地搭建一个低配版的 K8s。

下节课，我们还会在今天内容的基础上，用 K8s 的 CaaS 方式实现一套 Serverless 环境。通过这两节课的内容，你就可以完整地搭建出属于自己的 Serverless 了。

话不多说，我们现在就开始，希望你能跟着我一起多动手。

PC 上的 K8s

那在开始之前，我们先得把安装问题解决了，这部分可能会有点小困难，所以我也给你详细讲下。

首先我们需要安装kubectl，它是 K8s 的命令行工具。

你需要在你的 PC 上安装 K8s，如果你的操作系统是 MacOS 或者 Windows，那么就比较简单了，桌面版的 Docker 已经自带了K8s；其它操作系统的同学需要安装minikube。

不过，要顺利启动桌面版 Docker 自带的 K8s，你还得解决国内 Docker 镜像下载不了的问题，这里请你先下载第 8 课的代码。接着，请你跟着我的步骤进行操作：

开通阿里云的容器镜像仓库；

在阿里云的容器镜像服务里，找到镜像加速器，复制你的镜像加速器地址；

打开桌面版 Docker 的控制台，找到 Docker Engine。

{
  "registry-mirrors" : [
    "https://你的加速地址.mirror.aliyuncs.com"
  ],
  "debug" : true,
  "experimental" : true
}

预下载 K8s 所需要的所有镜像，执行我目录下的 docker-k8s-prefetch.sh，如果你是 Windows 操作系统，建议使用 gitBash[1]；

chmode +x docker-k8s-prefetch.sh
./docker-k8s-prefetch.sh

上面拉取完运行 K8s 所需的 Docker 镜像，你就可以在桌面版 Docker 的 K8s 项中，勾选启动 K8s 了。

现在，K8s 就能顺利启动了，启动成功后，请你继续执行下面的命令。

查看安装好的 K8s 系统运行状况。

kubectl get all -n kube-system

查看 K8s 有哪些配置环境，对应~/.kube/config。

kubectl config get-contexts

查看当前 K8s 环境的整体情况。

kubectl get all

按照我的流程走，在大部分的机器上本地运行 K8s 都是没有问题的，如果你卡住了，请在留言区告知我，我帮你解决问题。

K8s 介绍

安装完 K8s 之后，我们其实还是要简单地了解下 K8s，这对于你后面应用它有重要的意义。

我想你应该知道，K8s 是云原生 Cloud Native[2] 的重要组成部分（目前，K8s 的文档[3]已经全面中文化，建议你多翻阅），而云原生其实就是一套通用的云服务商解决方案。在我们的前几节课中，就有同学问：“如果我使用了某个运营商的 Serverless，就和这个云服务商强制绑定了怎么办？我是不是就没办法使用其他运营商的服务了？”这就是服务商锁定 vendor-lock，而云原生的诞生就是为了解决这个问题，通过云原生基金会 CNCF(Cloud Native Computing Foundation)[4]，我们可以得到一整套解锁云服务商的开源解决方案。

那 K8s 作为云原生 Cloud Native 的重要组成部分之一，它的作用是什么呢？这里我先留一个悬念，通过理解 K8s 的原理，你就能清楚这个问题，并充分利用好 K8s 了。

我们先来看看 K8s 的原理图：

通过图示我们可以知道，PC 本地安装 kubectl 是 K8s 的命令行操作工具，通过它，我们就可以控制 K8s 集群了。又因为 kubectl 是通过加密通信的，所以我们可以在一台电脑上同时控制多个 K8s 集群，不过需要指定当前操作的上下文 context。这个也就是云原生的重要理念，我们的架构可以部署在多套云服务环境上。

在 K8s 集群中，Master 节点很重要，它是我们整个集群的中枢。没错，Master 节点就是 Stateful 的。Master 节点由 API Server、etcd、kube-controller-manager 等几个核心成员组成，它只负责维持整个 K8s 集群的状态，为了保证职责单一，Master 节点不会运行我们的容器实例。

Worker 节点，也就是 K8s Node 节点，才是我们部署的容器真正运行的地方，但在 K8s 中，运行容器的最小单位是 Pod。一个 Pod 具备一个集群 IP 且端口共享，Pod 里可以运行一个或多个容器，但最佳的做法还是一个 Pod 只运行一个容器。这是因为一个 Pod 里面运行多个容器，容器会竞争 Pod 的资源，也会影响 Pod 的启动速度；而一个 Pod 里只运行一个容器，可以方便我们快速定位问题，监控指标也比较明确。

在 K8s 集群中，它会构建自己的私有网络，每个容器都有自己的集群 IP，容器在集群内部可以互相访问，集群外却无法直接访问。因此我们如果要从外部访问 K8s 集群提供的服务，则需要通过 K8s service 将服务暴露出来才行。

案例：“待办任务”K8s 版本

现在原理我是讲出来了，但可能还是有点不好理解，接下来我们就还是套进案例去看，依然是我们的“待办任务”Web 服务，我们现在把它部署到 K8s 集群中运行一下，你可以切身体验。相信这样，你就非常清楚这其中的原理了。不过我们本地搭建的例子中，为了节省资源只有一个 Master 节点，所有的内容都部署在这个 Master 节点中。

还记得我们上节课构建的 Docker 镜像吗？我们就用它来部署本地的 K8s 集群。

我们通常在实际项目中会使用 YAML 文件来控制我们的应用部署。YAML 你可以理解为，就是将我们在 K8s 部署的所有要做的事情，都写成一个文件，这样就避免了我们要记录大量的 kubectl 命令执行。不过，K8s 也细心地帮我们准备了 K8s 对象和 YAML 文件互相转换的能力。这种能力可以让我们快速地将一个 K8s 集群中部署的结构导出 YAML 文件，然后再在另一个 K8s 集群中用这个 YAML 文件还原出同样的部署结构。

我们需要先确认一下，我们当前的操作是在正确的 K8s 集群上下文中。对应我们的例子里，也就是看当前选中的集群是不是 docker-desktop。

kubectl config get-contexts

如果不对，则需要执行切换集群：

kubectl config use-context docker-desktop

然后需要我们添加一下拉取镜像的证书服务：

kubectl create secret docker-registry regcred --docker-server=registry.cn-shanghai.aliyuncs.com --docker-username=你的容器镜像仓库用户名 --docker-password=你的容器镜像仓库密码

这里我需要解释一下，通常我们在镜像仓库中可以设置这个仓库：公开或者私有。如果是操作系统的镜像，设置为公开是完全没有问题的，所有人都可以下载我们的公开镜像；但如果是我们自己的应用镜像，还是需要设置成私有，下载私有镜像需要验证用户身份，也就是 Docker Login 的操作。因为我们应用的镜像仓库中，包含我们的最终运行代码，往往会有我们数据库的登录用户名和密码，或者我们云服务的 ak/sk，这些重要信息如果泄露，很容易让我们的应用受到攻击。

当你添加完 secret 后，就可以通过下面的命令来查看 secret 服务了：

kubectl get secret regcred

另外我还需要再啰嗦一下，secret 也不建议你用 YAML 文件设置，毕竟放着你用户名和密码的文件还是越少越好。

做完准备工作，对我们这次部署的项目而言就很简单了，只需要再执行一句话：

kubectl apply -f myapp.yaml

这句话的意思就是，告诉 K8s 集群，请按照我的部署文件 myapp.yaml，部署我的应用。具体如下图所示：

通过获取容器的运行状态，对照上图我粗略地讲解一下我们的 myapp.yaml 文件吧。

首先我们指定要创建一个 service/myapp，它选中打了"app:myapp"标签的 Pod，集群内访问端口号 3001，并且暴露 service 的端口号 30512。

然后我们创建了部署服务 deployment.apps/myapp，它负责保持我们的 Pod 数量恒久为 1，并且给 Pod 打上"app:myapp"的标签，也就是负责我们的 Pod 持久化，一旦 Pod 挂了，部署服务就会重新拉起一个。

最后我们的容器服务，申明了自己的 Docker 镜像是什么，拉取镜像的 secret，以及需要什么资源。

现在我们再回看 K8s 的原理图，不过这张是实现“待办任务”Web 服务版本的：

首先我们可以看出，使用 K8s 仍然需要我们上节课的 Docker 发布流程：build、ship、run。不过现在有很多云服务商也会提供 Docker 镜像构建服务，你只需要上传你的 Dockerfile，就会帮你构建镜像并且 push 到镜像仓库。云服务商提供的镜像构建服务的速度，比你本地构建要快很多倍。

而且相信你也发现了，K8s 其实就是一套 Docker 容器实例的运行保障机制。我们自己 Run 一个 Docker 镜像，会有许多因素要考虑，例如安全性、网络隔离、日志、性能监控等等。这些 K8s 都帮我们考虑到了，它提供了一个 Docker 运行的最佳环境架构，而且还是开源的。

还有，既然我们本地都可以运行 K8s 的 YAML 文件，那么我们在云上是不是也能运行？你还记得前面我们留的悬念吧，现在就解决了。

通过 K8s，我们要解开云服务商锁定 vendor-lock 就很简单了。我们只需要将云服务商提供的 K8s 环境添加到我们 kubectl 的配置文件中，就可以让我们的应用运行在云服务商的 K8s 环境中了。目前所有的较大的云服务商都已经加入 CNCF，所以当你掌握 K8s 后，就可以根据云服务商的价格和自己喜好，自由地选择将你的 K8s 集群部署在 CNCF 成员的云服务商上，甚至你也可以在自己的机房搭建 K8s 环境，部署你的应用。

到这儿，我们就部署好了一个 K8s 集群，那之后我们该如何实时监控容器的运行状态呢？K8s 既然能管理容器集群，控制容器运行实例的个数，那应该也能实时监测容器，帮我们解决扩缩容的问题吧？是的，其实上节课我们已经介绍到了容器扩缩容的原理，但并没有给你讲如何实现，那接下来我们就重点看看 K8s 如何实现实时监控和自动扩缩容。

K8s 如何实现扩缩容？

首先，我们要知道的一点就是，K8s 其实还向我们隐藏了一部分内容，就是它自身的服务状态。而我们不指定命名空间，默认的命名空间其实是 default 空间。要查看 K8s 集群系统的运行状态，我们可以通过指定 namespace 为 kube-system 来查看。K8s 集群通过 namespace 隔离，一定程度上，隐藏了系统配置，这可以避免我们误操作。另外它也提供给我们一种逻辑隔离手段，将不同用途的服务和节点划分开来。

没错，K8s 自己的服务也是运行在自己的集群中的，不过是通过命名空间，将自己做了隔离。这里需要你注意的是，这些服务我不建议你尝试去修改，因为它们涉及到了 K8s 集群的稳定性；但同时，K8s 集群本身也具备扩展性：我们可以通过给 K8s 安装组件 Component，扩展 K8s 的能力。接下来我先向你介绍 K8s 中的性能指标 metrics 组件[5]。

我的代码根目录下已经准备好了 metric 组件的 YAML 文件，你只需要执行安装就可以了：

kubectl apply -f metrics-components.yaml

安装完后，我们再看 K8s 的系统命名空间：

对比你就能发现，我们已经安装并启动了 metrics-server。那么 metrics 组件有什么用呢？我们执行下面的命令看看：

kubectl top node

安装 metrics 组件后，它就可以将我们应用的监控指标 metrics 显示出来了。没错，这里我们又可以用到上一讲的内容了。既然我们有了实时的监控指标，那么我们就可以依赖这个指标，来做我们的自动扩缩容了：

kubectl autoscale deployment myapp --cpu-percent=30 --min=1 --max=3

上面这句话的意思就是，添加一个自动扩缩容部署服务，cpu 水位是 30%，最小维持 1 个 Pod，最大维持 3 个 Pod。执行完后，我们就发现会多了一个部署服务。

kubectl get hpa

接下来，我们就可以模拟压测了：

kubectl run -i --tty load-generator --image=busybox /bin/sh
$ while true; do wget -q -O- http://10.1.0.16:3001/api/rule; done

这里我们用一个 K8s 的 Pod，启动 busybox 镜像，执行死循环，压测我们的 MyApp 服务。不过我们目前用 Node.js 实现的应用可以扛住的流量比较大，单机模拟的压测，轻易还压测不到扩容的水位。

总结

这节课我向你介绍了云原生基金会 CNCF 的重要成员：Kubernetes。K8s 是用于自动部署、扩展和管理容器化应用程序的开源系统。云原生其实就是一套通用的云服务商解决方案。

然后我们一起体验了在本地 PC 上，通过 Docker desktop 搭建 K8s。搭建完后，我还向你介绍了 K8s 的运行原理：K8s Master 节点和 Worker 节点。其中，Master 节点，负责我们整个 K8s 集群的运作状态；Worker 节点则是具体运行我们容器的地方。

之后，我们就开始把“待办任务”Web 服务，通过一个 K8s 的 YAML 文件来部署，并且暴露 NodePort，让我们用浏览器访问。

为了展示 K8s 如何通过组件 Component 扩展能力，接着我们介绍了 K8s 中如何安装使用组件 metrics：我们通过一个 YAML 文件将 metrics 组件安装到了 K8s 集群的 kube-system 命名空间中后，就可以监控到应用的运行指标 metrics 了。给 K8s 集群添加上监控指标 metrics 的能力，我们就可以通过 autoscale 命令，让应用根据 metrics 指标和水位来扩缩容了。

最后我们启动了一个 BusyBox 的 Docker 容器，模拟压测了我们的“待办任务”Web 服务。

总的来说，这节课我们的最终目的就是在本地部署一套 K8s 集群，通过我们“待办任务”Web 服务的 K8s 版本，让你了解 K8s 的工作原理。我们已经把下节课的准备工作做好了，下节课我们将在 K8s 的基础上部署 Serverless，可以说，实现属于你自己的 Serverless，你已经完成了一半。

作业

这节课是实战课，所以作业就是我们今天要练习的内容。请在你自己的电脑上安装 K8s 集群，部署我们的“待办任务”Web 服务到自己的 K8s 集群，并从浏览器中访问到 K8s 集群提供的服务。

另外，你可以尝试一下，手动删除我们部署的 MyApp Pod。

kubectl delete pod/你的pod名字

但你很快就会发现，这个 Pod 会被 K8s 重新拉起，而我们要清除部署的 MyApp 的所有内容其实也很简单，只需要告诉 K8s 删除我们的 myapp.yaml 文件创建的资源就可以了。

kubectl delete -f myapp.yaml

快来动手尝试一下吧，期待你也能分享下今天的成果以及感受。另外，如果今天的内容让你有所收获，也欢迎你把它分享给身边的朋友，邀请他加入学习。

参考资料

[1] https://gitforwindows.org/

[2] https://www.cncf.io/

[3] https://kubernetes.io/zh/

[4] https://github.com/cncf/landscape

[5] https://github.com/kubernetes-incubator/metrics-server/

09 | 搭建私有Serverless（二）：基于K8s的Serverless

你好，我是秦粤。上节课我向你介绍了云原生基金会 CNCF 的重要成员 K8s，它是用于自动部署、扩展和管理容器化应用程序的开源系统。通过实践，我们在本地搭建 K8s，并将“待办任务”Web 服务案例部署到了本地 K8s 上。K8s 这门技术，我推荐你一定要学习下，不管是前端还是后端，因为从目前的发展趋势来看，这门技术必定会越来越重要。

今天这节课我们就继续学习如何搭建私有的 Serverless 环境。具体来说，我们会在上节课部署本地 K8s 的基础上，搭建 Serverless 底座，然后继续安装组件，扩展 K8s 的集群能力，最终让本地 K8s 集群支持 Serverless。

搭建 Serverless 的前提

在开始之前，我们先得清楚一个问题，之前在基础篇讲 Serverless Computing，也就是 FaaS 的时候，也有同学提问到，“微服务、Service Mesh 和 Serverless 到底是什么关系？”

这些概念确实很高频地出现在我们的视野，不过你不要有压力，我在学习 Serverless 的时候也被这些概念所困扰。我们可以先回顾下微服务，我们在用微服务做 BaaS 化时，相信你也发现了微服务中有很多概念和 Serverless 的概念很接近。

Service Mesh 简单来说就是让微服务应用无感知的微服务网络通讯方案。我们可以将 Serverless 架构的网络通讯也托管给 ServiceMesh。通过 Service Mesh，Serverless 组件可以和 K8s 集群复杂且精密地配合在一起，最终支撑我们部署的应用 Serverless 化。我们看下下面这张架构图：

通过图示，我们可以清楚地看到：Serverless 的底层可以基于 Service Mesh 来搭建。但 Service Mesh 其实只是实现 Serverless 网络通讯的其中一种方案，除此之外还有 RSocket、gRPC、Dubbo 等方案，而我选择 Service Mesh 的原因是这套方案是可以基于 K8s 组件的，而且还提供了可视化工具帮助我们理解 Serverless 的运行机制，比如：如何做到零启动？如何控制灰度流量？等等。如果要实践，Service Mesh 无疑是首选。

Serverless 底座：Service Mesh

有人把 Kubernetes、 Service Mesh 和 Serverless 技术称为云原生应用开发的三驾马车，到现在，我估摸着你也理解这其中的缘由了吧。这里我还是要说明下，我们后面几节课里引入了很多新名词，这些名词我基本都是走马观花大致给你过了下，让你有个宏观的了解。有时间的话，你还是应该再深入进去看看。

那么言归正传，我们现在具体看下 Service Mesh 的应用原理。

我们在讲微服务的时候，只是讲了拆解与合并的理论指导，并没有涉及到具体实现。那如果切换到实现的话，业界其实就有很多的微服务框架了，但大多数都是限定语言的 SDK，尤其是 Java 的微服务框架特别多。

而 SDK 版本的微服务框架，其实很重的一块都在于微服务之间网络通讯的实现。例如服务请求失败重试，调用多个服务实例的负载均衡，服务请求量过大时的限流降级等等。这些逻辑往往还需要微服务的开发者关心，而且在每种 SDK 语言中都需要重复实现一遍。那有没有可能将微服务的网络通信逻辑从 SDK 中抽离出来呢？让我们的微服务变得更加轻量，甚至不用去关心网络如何通讯呢？

答案就是 Service Mesh。我们举例来说明，还是用咱们的“待办任务”Web 服务来举例。

我们如果把拆解后的应用部署到 K8s 集群 Pod 中，过程就如下图所示，MyApp 应用会通过 HTTP 直接去调用集群内的用户微服务和待办任务微服务。

但 HTTP 直接访问，带来的安全性问题又怎么办呢？如果有人在我们的集群中启动一个 BusyBox 容器，那不就直接可以对我们的用户微服务和待办任务微服务进行攻击了吗？还有，当我们有多个用户微服务实例时，我们又该如何分配流量呢？

所以通常我们使用传统微服务架构 SDK，它里面会有大量的这种逻辑，而且有很多策略需要我们自己在调用 SDK 时去判断开启，我们的代码也将会和微服务架构的 SDK 大量地耦合在一起。

服务网格 Service Mesh 则是换了一种思路，它将微服务中的网络通信逻辑抽离了出来，通过无侵入的方式接管我们的网络流量，让我们不用再去关心那么重的微服务 SDK 了。下面我们看看 Service Mesh 是怎么解决这个问题的。

通过图示可知，Service Mesh 可以分为数据面板和控制面板，数据面板负责接管我们的网络通讯；控制面板则控制和反馈网络通讯的状态。Service Mesh 将我们网络的通讯通过注入一个边车 Sidecar 全部承接了过去。

数据面板中我们的应用和微服务，看上去直接跟 Sidecar 通信，但实际上，Sidecar 可以通过流量劫持来实现，所以通常我们的应用和微服务是无感的，不用有任何的改动，只是使用 HTTP 请求数据。

控制面板则复杂一些，它也是 Service Mesh 的运作核心。pilot 是整个 Control Plane 的驾驶员，它负责服务发现、流量管理和扩缩容；citadel 则是控制面板的守护堡垒，它负责安全证书和鉴权；Mixer 则是通讯官，将我们控制面板的策略下发，并且收集每个服务的运行状况。

现在你应该清楚 Service Mesh 是怎么回事了，它是怎么把微服务的网络通信逻辑从 SDK 中抽离出来的，我们又为什么说 Service Mesh 就是 Serverless 的网络通讯底座。

Serverless 底座搭建：K8s 组件 Istio

那接下来我们就要动手搭建 Service Mesh 了。

首先，我们得扩大一下 Docker Desktop 操作系统的占用资源，这是因为我们后面在搭建的过程中需要创建一堆容器。我推荐你将 CPUs 配置到 4 核，内存至少 8GB，这是从我的经验出发一个较为合适的参数。

设置好后，我们需要用到 CNCF 的另外一位重要成员 Istio[1] 了，它是在 K8s 上 Service Mesh 的实现方式，用于连接、保护、控制和观测服务。有关它的详细介绍，我们在这就不展开了，不清楚的话可以查看我们的参考资料。

Istio 的安装脚本，我已经放在我们这节课的Github 仓库代码中了。下面我们要进入根目录下的 install-istio，这里 Istio 官方其实提供了 Istio 1.4.3 K8s 安装包，但为了简化大家的操作，我直接放到项目代码中了。

然后 kubectl apply 安装 Istio。

kubectl apply -f .

安装完毕后，我们就可以通过命名空间 istio-system 来查看 Istio 是否安装成功。

kubectl get all -n istio-system

相信你也发现了吧，这跟我们上节课中安装 metrics 组件一样，Istio 也是采用 K8s 组件 Component 的方式来安装的。

不过使用了 Istio 以后，还有一个细节需要我们注意：默认 Istio 不会开启 Sidecar 注入，这个需要我们手动开启。

我们可以通过给命名空间 default 打上 label，让我们部署的 Pod 自动打开 Sidecar。

kubectl label ns default istio-injection=enabled

Istio 会根据我们的标签去判断，当前部署的应用是否需要开启 Sidecar。

kubectl describe ns default

好了，我们现在可以部署我们的应用了。我们将项目目录下的 Dockerfile、Dockerfile-rule、Dockerfile-user 都用 Docker 构建一遍，并且上传到我们的 Registry。

docker build --force-rm -t registry.cn-shanghai.aliyuncs.com/jike-serverless/todolist:lesson09 -f Dockerfile .
docker build --force-rm -t registry.cn-shanghai.aliyuncs.com/jike-serverless/rule:lesson09 -f Dockerfile-rule .
docker build --force-rm -t registry.cn-shanghai.aliyuncs.com/jike-serverless/user:lesson09 -f Dockerfile-user .
docker push registry.cn-shanghai.aliyuncs.com/jike-serverless/todolist:lesson09
docker push registry.cn-shanghai.aliyuncs.com/jike-serverless/rule:lesson09
docker push registry.cn-shanghai.aliyuncs.com/jike-serverless/user:lesson09

然后修改项目 istio-myapp 目录中的 YAML 文件，改成你自己仓库中的 URI。接着在 istio-myapp 目录下执行，kubectl apply“点”部署所有 YAML 文件。

kubectl apply -f .

部署完成后，我们通过 kubectl describe 查看一下 MyApp 服务暴露的端口号：

kubectl describe service/myapp

接下来我们用浏览器访问http://localhost:31947，就可以看到我们新部署的 MyApp 应用了。

你也许会好奇，这跟我们之前的有什么不同，Istio 都做了什么？

我们将 Istio 的控制面板服务 kiali 也通过 Service 暴露到端口上，访问一下看看。

kubectl expose deployment.apps/kiali --type=NodePort --port=20001 --name='kiali-local' -n istio-system

然后查看一下 kiali 的本地端口号。

kubectl describe svc kiali-local -n istio-system

接着用浏览器打开 kiali，进入后你就可以看到我们应用调用微服务的网络拓扑图了。怎么样，是不是很惊艳？

Service Mesh 可以协助我们处理应用和微服务网络的连接、保护、控制和观测问题。综上，再次总结下，Serverless 的底层，我们完全可以依赖 Service Mesh 来处理复杂的网络问题。

最后演示完，我们就可以通过 kubectl delete 清除刚刚部署的应用，注意要在 istio-myapp 目录下执行。

kubectl delete -f .

到这儿，部署好 Istio Service Mesh，Serverless 的底层部署，我们就已经实现了一大半。

Serverless 完整实现：K8s 组件 Knative

现在，就还剩最后一步，安装组件了。如果你能想到，在 Service Mesh 的底座上，还需要加上哪些组件才能满足我们 Serverless 的需求，说明你已经真正地理解了 K8s 的组件 Component 的威力了。

接下来我们就基于 Istio，看看 Serverless 在 K8s 的架构方案上还需要添加哪些内容。

Knative 是通过整合：工作负载管理（和动态扩缩）以及事件模型来实现的 Serverless 标准，也叫容器化 Serverless。

Knative 社区的主要贡献者有：Google、Pivotal、IBM、Red Hat，可见其阵容强大。还有，CloudFoundry、OpenShift 这些 PaaS 提供商都在积极地参与 Knative 的建设。参考资料中我还放了“由阿里云提供 Knative”的电子书，这里作为福利送给你。

接下来我们看看 Knative 是怎么做到 Serverless 的吧。

Knative 在 Istio 的基础上，加上了流量管控和灰度发布能力、路由 Route 控制、版本 Revision 快照和自动扩缩容，就组成了 Server 集合；它还将触发器、发布管道 Pipeline 结合起来组成了 Event 集合。

我们近几节课都有操作 K8s，其实你应该能感觉到，它还是有些复杂的。Knative 提供的应用托管服务可以大大降低直接操作 K8s 资源的复杂度和风险，提升应用的迭代和服务交付效率。

我们还是以“待办任务”Web 服务为例，看看 Knative 是怎么做到的吧。

首先进入项目 install-knative 目录，执行 kubectl apply 安装 Knative。

kubectl apply -f .

安装完毕，你就可以通过命名空间 knative-eventing 和 knative-serving，看到我们都安装了哪些组件。

kubectl get all -n knative-serving
kubectl get all -n knative-eventing

Knative 帮我们部署了一套自动运维托管应用的服务，因此我们要部署应用，才能看到效果。我们再进入项目 knative-myapp 目录，执行 kubectl apply。

kubectl apply -f .

到这，我们的应用就部署起来了。要访问应用也比 Istio 要简单，通过 get kservice 我们就可以查看部署的应用域名。

kubectl get ksvc

这个域名其实是通过 Istio-ingress 部署的，我们再看看 Istio-ingressgateway 的信息。

kubectl get svc istio-ingressgateway -n istio-system

我们可以看到，它绑定到我们本地 IP:localhost 上了，所以我们只需要添加一下 Host，绑定我们 MyApp 的域名到 127.0.0.1 就可以了。

// Mac上host文件地址是/etc/hosts
127.0.0.1 myapp.default.example.com

然后我们就可以用浏览器直接访问我们的域名 myapp.default.example.com，进入“待办任务”Web 服务了。如果你多刷新几次页面就可以看到右上角我的名字，有时是“秦粤”有时是“粤秦 D”。这其实是因为我的 user 服务开启了 2 个版本，而且我设置了 50% 的流量进入版本 v1，50% 的流量进入版本 v2。另外我们还需要在页面上多点击几下，这是为了让我们查看 Kiali 的网络拓扑图时有数据。

目前 Knative 还没有官方的控制台，所以最后我们再看看 Istio 中 Kiali 的网络拓扑图，如果忘记了如何访问 Kiali 请看上一节，介绍 Istio 中的内容。

你看，网络拓扑图是不是变得更复杂了？当然，因为 Knative 也是隐藏了一堆的内容，来简化应用和微服务的部署。好了，实践完，我们就再来看看原理，请看下图：

我来解释下这张图。为了让你更好地识别，我用红色表示 Knative 添加的组件，橙色表示 Istio 的组件。首先我们看到 Knative 又给每个 Pod 里面添加了一个伴生容器 queue，它是专门用来实时采集反馈我们部署应用容器的监控指标 metrics 的，收集到的信息会反馈到 autoscale，由 autoscale 决定是否需要扩缩容。

然后我们看看请求，从上面的操作，我们知道浏览器的 HTTP 请求是访问 Istio-ingressGateway 的，我们还需要绑定域名。这是因为 Istio-ingressGateway 是域名网关，它会验证请求的域名。我们的 MyApp 应用，接收到请求会调用用户微服务和待办任务微服务，这时就会访问 activator 了，这个是 Knative 比较重要的组件，它会 hold 住我们的请求，并查看一下目前有没有活跃的服务，如果没有，它会负责拉起一个服务，等服务启动后，再将请求转接过去。这也就是为什么 Serverless 可以缩容到 0 的原因。

另外，我再啰嗦一下，当我们的 autoscale 配置了可以缩容到 0，如果一段时间没有请求，那么我们每个应用都会处于很低的水位，这时 autoscale 就会缩容到 0 了。当然我们的 MyApp 应用不可以缩容到 0，因为它需要接收入口请求。

当 MyApp 有流量进来请求用户服务时，此时 activator 就会临时接收请求，让请求等待；然后通知 autoscale 扩容到 1；等待 autoscale 扩容到 1 完毕后，activator 再让用户容器处理等待的请求。

而我们在 Knative 中每次发布服务，都会被 Revision 组件拍一个快照，这个快照可以用于我们管理版本号和灰度流量分配。我们“待办任务”Web 服务中的用户微服务的 2 个版本流量切换，其实就是利用 Revision 和 Route 实现的。

总结

这节课我首先介绍了 Service Mesh。Service Mesh 通过 Pod 给我们的应用容器注入了一个伴生容器 Sidecar，配合控制面板来接管我们应用和微服务的网络流量，从而实现网络流量的连接、保护、控制和观测。

接着我介绍了 CNCF 的另一位重要成员：Istio，它是基于 K8s 组件实现的 Service Mesh。我们在 Istio 的基础上又搭建了 Knative，它也是基于 K8s 组件实现的一套 Serverless 方案。

总结来说就是，Service Mesh 是一种微服务网络通讯方案，它通过 Sidecar 和控制面板接管了上层的网络通讯，可以让上层开发者无感知地使用网络通讯。我们可以复用 Service Mesh 的网络通讯能力，部署 Serverless 架构。通过 Service Mesh、Serverless 组件和 K8s 集群复杂且精密地配合，支撑我们部署的应用 Serverless 化。

另外我需要提醒你一下，Knative 是容器 Serverless 方案，所以你在容器里面运行函数、微服务或者应用都可以，这完全取决于你的 Dockerfile 里面“编排”的是什么内容。我们这节课介绍的 Knative，可以让你既部署 FaaS 也部署 BaaS。还记得我们[第 1 课] 讲过：FaaS 和 BaaS 都是基于 CaaS 实现的，Knative 正是一种容器服务 Serverless 化的产物。我们将[第 1 课]和这节课的架构图映射一下，相信你对 Serverless 的实现会有新的体会。

作业

这节课是实战课，所以作业就是我们今天课程中的内容。请在上节课安装的 K8s 集群的基础上，先安装 Istio 组件，部署 Istio 版本的“待办任务”Web 服务；再安装 Knative 组件，部署 Knative 版本的“待办任务”Web 服务。实践结束后，你可以在 Docker Desktop 中关闭 K8s 集群，这样就能清掉我们这两节课创建的所有资源了。

大胆尝试一下吧，期待你能与大家分享成果。如果今天的内容让你有所收获，也欢迎你把文章分享给更多的朋友。

参考资料

[1] https://istio.io/

10 | 经验：Serverless架构应该如何选型？

你好，我是秦粤。通过前面的两节实践课，我们体验了在本地环境中搭建 K8s，并且我们利用 K8s 的组件扩展能力，在本地的 K8s 上安装了 Istio 和 Knative。正如我在前面课程中所说的，K8s 可以让我们的集群架构，轻松迁移到其他集群上，那么今天我就带你将我们本地 K8 上部署的“待办任务”Web 服务部署到云上的 K8s 集群。

实践课里还有这么个小细节，不知道你注意没，我们使用 Knative 时，应用和微服务部署都需要关心项目应用中的 Dockerfile，而我在使用 FaaS 函数时，连 Dockerfile 都不用管了，其实这就是 Serverless 带来的变革之一。当然，现在有很多应用托管 PaaS 平台，也做了 Serverless 化，让我们部署一个应用时只需要关心 Release 分支的代码合并，例如 Heroku、SAE 等等。

这里我需要先解释一下，K8s 集群的运维工作对于很多个人开发者来说，是有些重的。我们通常了解基本知识，用 kubectl 调用 K8s 集群就可以了。咱们课程里，我是为了让你更好理解 Serverless 的工作原理，所以才向你介绍 Knative 在 K8s 上的搭建和使用过程。

实际工作中 K8s 集群的运维，还是应该交给专业的运维人员。另外，云服务商的 K8s 集群，都会提供控制面板，一键安装组件。我们在使用 Serverless 的部署应用时，不用关心底层“被透明化”的类似 Knative、Istio 等等插件能力，这也是 Serverless 应用的价值所在，虽然它本身的底层构建在复杂且精密的各种服务上，但我们使用 Serverless 却极其精简。

在开始部署 K8s 上云之前，我们要先选择一个云服务商。正如我们上节课所说，K8s 整体架构迁移能力，可以帮我们破解 Vendor-lock，只要我们部署的云服务商是 CNCF 的成员，支持 K8s 集群就可以了。实际上目前几乎所有的云服务商都加入了 CNCF 阵营。因此，我们的 K8s 版本的“待办任务”Web 服务，可以任意选择云服务商部署，你完全可以横向对比云服务商的各项指标去选择适合自己的。当我们有了选择权，也反向促进了云服务商的良性竞争。

云服务商

我们先看看 2019 年的全球云服务商的市场占有率数据，我也将按照这个数据排名，依次向你介绍云服务商和他的主要特色：

亚马逊的 AWS 市占率 32.4%。亚马逊凭借庞大复杂的全球电商业务，让其机房做到了覆盖全球，并引领云服务的发展，提供最全面的生态和最高稳定性的服务。云服务商老大的地位近年内都难以撼动。

微软的 Azure 市占率 17.6%。依赖微软 Windows 全家桶的优势和近年的 JavaScrpit 技术社区的收购或者并购，他的市场地位紧跟亚马逊之后。整体云服务产品的报价也紧盯 AWS，所有服务价格略低于 AWS。

阿里巴巴的阿里云，市占率 6.0%。国内市场占有率第一，随着阿里电商业务出海，阿里云机房也部署到了海外。在国内云里生态建设得比较完备，每年都经受双十一流量的洗礼，不断打磨稳定性。客服响应速度是一大亮点。

谷歌的谷歌云，市场占有率 5.4%。谷歌是后起之秀，凭借 15 年提出 CNCF 云原生白皮书，通过建立规范和开源生态，迅速切入云服务领域并占有一席之地。价格策略上紧盯 AWS，并依靠 Google 的搜索引擎对大规模集群调度能力的积累。云服务商中最高的物理机资源利用率，让谷歌云的价格做到了云服务商中的最低。

其他云，市场占有率 38.5%。腾讯云、华为云等其他的云服务商都归并到了这里，还有一些专门做专有云服务的，比如 CDN 全球加速的 Akamai，PaaS 应用托管的 Heroku 等等。值得一提的是腾讯云，腾讯云从 2019 年开始大力发展 Serverless，并积极和 Serverless 生态合作，估计是希望以此为突破点提升自己的市场占有率。

下面是我按我目前（注意只是目前）掌握的数据和认知整理的表格，在选择云服务商时你可以作为参考。其中访问限制应该是最优先考虑的，国内运营部署的应用，肯定是要首选国内云服务商。

当我们完成云服务商的选择后，理论上我们可以通过 Docker 容器，创建我们所需要的各种服务，例如 Redis、MySQL、Kafka 等等。具体怎么做，你应该很熟悉了，先去 Docker Hub 官网，找到我们所需要的服务镜像，在这个镜像的基础上加上我们自己的用户名和密码，生成私有 Docker 镜像上传到我们的 Registry，然后在 K8s 集群中就可以部署了。这也是前面我们讲到的 Docker 容器带来的颠覆式体验。

不过，我们在重度使用 Docker 技术的同时，也必须深入了解 Docker 和我们所用的具体镜像的限制。比如，如何解决应用镜像硬盘持久化的问题、如何解决 MySQL 镜像的容器扩缩容的问题、Kafka 镜像集群如何搭建等等。这些都是新技术引入的新的问题，而且解决方案和传统运维虚拟机也不一样。

另外，为了提升我们的研发效能，我们还应该进一步了解云服务商还能为我们提供哪些能力，节省我们的时间和成本。当我们开发一个云上项目时，云服务商已经为我们准备了各种行业解决方案，来提升我们的开发速度，例如文件存储服务、视频媒体流转码服务、物联网 MQTT 解决方案等等。利用这些服务和我们前面讲的服务编排，可以进一步加速我们的研发速度。

云服务如何选型？

现在云服务商都提供数以百计的各种服务，但大体上我们可以分为以下 3 类：IaaS、PaaS 和 SaaS。

我们先看看图示中的金字塔，这里我需要引入新的概念服务级别协议 SLA：服务提供商与受服务用户之间具体达成了承诺的服务指标——质量、可用性、责任。看上去有些绕，简单来说，就是服务不达标，我们可以向云服务商索赔损失。

我们前面课程中所说的，消息队列的稳定性达到 10 个 9，其实就是指 SLA 指标数据可靠性为 99.99999999%，但是，消息队列的服务可用性其实是 99.95%，也就是说消息队列服务服务一年中不可使用时间为 4.38 小时，一旦不可用时间超过了这个要求，云服务商则需要向客户赔付（如果这部分知识你没接触过的话，可以看下赵成老师的这篇文章）。

因此对于云服务商来说，要维持资源的高可用性，必须保证资源调度及时，宁可浪费部分资源，也不能牺牲用户的可用性。而云服务的价格则和物理机虚拟化比例强相关，虚拟化比例越低，说明你对这个资源独占性越高，当然价格也就越高。物理机虚拟化比例，也是云服务商的资源调度能力，对云服务商来说核心指标就是 CPU 利用率。

了解了一些前提，接下来我们具体看看这 3 类。

IaaS 层是面向运维人员，服务器或虚拟机服务。可用性也最高，通常可以到 4 个 9，99.99%。可控性高，虚拟机从操作系统开始，你可以登录虚拟机，并且任意安装各种自己所需的函数库和二进制包。资源的物理机虚拟化比例，通常是 2:1 的，性能是最稳定的。

PaaS 层是面向开发者，通常部署在 IaaS 层之上，服务种类最为繁多。可用性低于 IaaS，通常是 99.95%。可控性中等，PaaS 通常都提供特定的服务，例如应用托管、数据库等等，我们只能通过提供的控制台登录。资源的物理机虚拟化比例，通常是 4:1，性能较稳定。

SaaS 层是面向终端用户，通常部署在 PaaS 层之上。可用性低于 PaaS，通常是 99.9%。可控性低，SaaS 直接面向用户提供服务，我们只能登录后台操作部分数据进行增删改查。资源的物理机虚拟化比例不太确定，但肯定超过 8:1，性能一般。

FaaS 则是一个特例，虽然它也属于面向开发者的，但利用 FaaS 极速的冷启动特性，它并不需要关心底层的高可用性。反而用它可以填满闲置的机器资源，提升物理机的资源利用率。这也是为什么在云服务商这么高的运作成本下，FaaS 还能免费提供给大家使用。

我介绍 SLA，主要是希望你能对云服务商提供的服务层级有个认识。我们在设计和运维自己的应用时，需要综合考虑到可用性和价格。FaaS 是性价比最高的，所以我们在日常使用时，如果有适合 FaaS 的场景，应该尽可能地使用 FaaS。

如果要深入了解云服务，我的经验是可以从云服务商网站的行业解决方案出发。先粗略了解一下，有哪些行业解决方案，便于我们掌握云服务商的能力边界。如果感觉比较凌乱的话，最好自己用“脑图”梳理一次。另外再说句题外话，我不建议你学习别人的脑图，因为脑图都是自己梳理思考的过程，你自己大脑的 Map 不一定适合别人，别人的 Map 也不适合你。

言归正传，我们自己在云上搭建 K8s 集群主要有 2 种方式：购买虚拟机自建和购买 K8s 集群。当然首推购买 K8s 集群，可以节省我们更多成本。K8s 集群的 Master 节点，阿里云 K8s 集群是不收费的，而我们自己搭建则需要至少一台虚拟机。虚拟机自建，比较适合大型或拥有强大运维团队的互联网公司。但无论是自建还是购买 K8s 集群，我们搭建的 K8s 集群的底层都是 IaaS。

云上部署 K8s 集群 Knative

了解完选型相关的知识，接下来我们还是动手实操一下。

我们这节课的 K8s 例子，选择了阿里云的 Serverless K8s 集群：ASK。这个 K8s 集群的特点是，Master 节点是免费的，只收取网关的费用，Worker 节点是虚拟节点，而我们 Pod 中的容器是通过 ECI 容器创建的。传统的 K8s 集群 ACK 的 Worker 节点，需要我们自己购买虚拟机授权 K8s 集群，初始化成 Worker 节点。ECI 是轻量级的 Docker 容器，同时具备高性能和低价格的优势。另外，ASK 的 Knative 功能是新上线公测的，推荐它还是因为性价比。

我们使用 K8s 集群，同样可以自己安装 Istio，再安装 Knative，只需要注意 K8s 集群的版本就可以了。但云服务商提供的 K8s 集群，通常都已经帮你准备好了控制台操作。所以实际上我们使用云端的 K8s 集群，要比本地搭建还要简单。所以，我们只需要在 ASK 控制台，左边 Knative（公测）中选择我们的 K8s 集群，点击“一键部署”就可以了。当然如果你选择的云服务商不支持“一键部署”，你可以通过查看 K8s 集群的版本号，选择对应的 Istio 版本和 Knative 版本，按照我们上节课所讲的内容，自行安装 K8s 组件。

另外为了方便新手，我还是需要提示一下如何在本地同时管理多个 K8s 集群。

首先我们打开本地的 kubectl 的配置文件：$HOME/.kube/config，我们可以看到，这个 K8s 集群的配置文件主要分为 3 个部分：clusters、contexts、users。

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: xxx
    server: https://kubernetes.docker.internal:6443
  name: docker-desktop
- cluster:
    certificate-authority-data: xxx
    server: https://k8s集群IP:6443
  name: kubernetes
contexts:
- context:
    cluster: docker-desktop
    user: docker-desktop
  name: docker-desktop
- context:
    cluster: kubernetes-ask
    user: kubernetes-ask-admin
  name: kubernetes-admin-id
current-context: docker-desktop
kind: Config
preferences: {}
users:
- name: docker-desktop
  user:
    client-certificate-data: xxx
    client-key-data: xxx
- name: kubernetes-admin
  user:
    client-certificate-data: xxx
    client-key-data: xxx

我们将云上 K8s 给我们提供的集群凭据的 cluster、context、user，分别添加到 config 文件对应的 clusters、contexts、users 部分下面，就可以了。

我们再次查看 kubectl config get contexts，就可以看到新添加的云上 K8s 集群了。

kubectl config get-contexts

剩下的操作就跟我们上节课保持一致了。我们只需要在 knative-myapp 里面执行 kubectl apply，就可以让我们的例子运行在云上的 K8s 集群了。

我们想访问云上 K8s 集群版本的“待办任务”Web 服务时，同样也是用 kubectl 查看 kservice，我们的域名。

kubectl get kservice

紧接着通过查看 ingress-gateway 了解 K8s 集群的外网入口 IP。

kubectl get svc -n knative-serving

我们在本地通过 Host 绑定域名和 EXTERNAL-IP，就可以访问了。我再啰嗦一句：如果是你自己的域名，你可以通过修改域名的 DNS 解析 A 值，指向这个 EXTERNAL-IP 就可以了。

我们同样也可以通过命名空间 namespace，看看这个 K8s 集群中都给我们安装了哪些组件。还记得我们讲过的 FaaS 的 HTTP 触发器认证方式吗？我们部署在云上的 K8s 集群，调用我们的 FaaS 函数，就可以通过我们自己的容器实现函数鉴权的算法，走函数鉴权流程了。

到这儿，云上部署 K8s 集群 Knative 这个例子我们就实践完了，不知道你有没有跟着我一起动手操作？最后，还有一点需要提示你一下，如果你为了体验我们这节课的内容，在云上自己购买了 K8s 集群测试，那等部署完成后，云上的 K8s 集群你一定要清理干净了，除了通过 kubectl delete 清除我们部署的应用，还要在云上删除 K8s 集群和 worker 节点，否则还会持续产生费用。

总结

这节课我们学习了如何让本地的 Knative 应用打破云服务商的锁定，部署上云。因为 CNCF 的 K8s 集群的可移植性，我们可以在 CNCF 的云服务商成员中任意选择。我根据我自己的经验，总结了一份云服务商的对比表格，这个表格的内容对比了我们自身业务的特点，还有价格等因素，让我们自由选择适合自己的云服务商。

我们在云上创建好 K8s 集群，使用 K8s 集群就跟我们本地使用是一样的，而且很多云服务商还提供“一键部署”让我们快速安装 K8s 组件。最后我们就可以将 Knative 的应用部署上云了。

然而我们虽然可以用 Knative 解决 Container Serverless 的云服务商锁，但却无法解决云服务商用 FaaS 构建起的新壁垒。每个云服务商 FaaS 的 Runtime 都不一样，我们的函数代码要兼容多个云服务商部署，要写很多额外的代码，处理兼容性的问题。那么下节课我们就再看看如何破解 FaaS 的新 Vendor-lock。

作业

这节课，建议你创建一个云上的 K8s 集群，并且将我们上节课的内容部署到云上的 K8s 集群。感受一下云上的 K8s 如何打通部署和提供给互联网用户访问。

期待你的实践总结，欢迎留言与我交流。如果今天的内容让你有所收获，也欢迎你把文章分享给更多的朋友。

11 | 经验：Serverless开发最佳实践

你好，我是秦粤。上节课，我们了解了利用 K8s 集群的迁移和扩展能力，可以解决云服务商锁定的问题。我们还横向对比了各大云服务商的特点和优势，纵向梳理了云服务商提供的各种服务能力。最后我们可以看到，利用 Knative 提供的 Container Serverless 能力，我们可以任意迁移部署我们的应用架构，选择适合我们的云服务商。

但同时我们也发现，FaaS 相对于 Knative，反而是我们的瓶颈，我们无法平滑地迁移 FaaS 的函数。云服务商大力发展 FaaS，其实部分原因也是看中了 FaaS 新建立起来的 Vendor-lock，因此目前各大运营商都在拼 FaaS 的体验和生态建设。

那这节课，我们就来看看 FaaS 是如何解除云服务商锁定的吧。但在正式开始之前呢，我们先得了解一些 FaaS 的使用场景，以免一些同学误会，要是你的实践是为了 Serverless 而去 Serverless，那就不好了，我们还是应该从技术应用的角度出发。

FaaS 场景

我从[第 5 课] 开始就在讲 FaaS 和 BaaS 的底层实现原理 Container Serverless，是希望通过底层实现原理，帮助你更好地掌握 Serverless 的思想。但在日常工作中，使用 Serverless，只需要借助云服务商提供的 Serverless 化的 FaaS 和 BaaS 服务就可以了。

就像我上节课中所讲的，FaaS 因为可以帮助云服务商利用碎片化的物理机算力，所以它是最便宜的。在日常工作中，我十分鼓励你去使用 FaaS。

那么说到使用 FaaS，以我的经验来说，我们可以将 FaaS 的最佳使用场景分为 2 种：事件响应和 Serverless 应用。

FaaS 事件响应

FaaS 最擅长的还是处理事件响应，所以我们应该先了解清楚，你选择的云服务商的 FaaS 函数具体支持哪些触发器，例如阿里云的触发器列表[1]、腾讯云的触发器列表[2]。因为这些触发器通常都是云服务商根据自身 Serverless 的成功案例所制定的下沉方案，也就是说这些触发器都具备完整的使用场景。基于 FaaS 支持的触发器，设计事件响应函数，我们就可以最大限度地享受 FaaS 的红利，而且不用担心用法不正确。

Serverless 应用

那如果 FaaS 是作为 Serverless 应用的使用场景，我们则需要关注大型互联网公司的成功案例所沉淀出来的成熟方案。这样既可以轻松简单地搭建出 Serverless 应用，还能帮我们节省不少费用。

因为我们尝试用 FaaS 去解构自己的整个应用时，如果超出了目前 FaaS 的适应边界，那么技术挑战还是比较大的。所以我并不推荐你将整个现存的应用完全 Serverless 化，而是应该去找出适用 FaaS 事件响应的场景去使用 FaaS。

如果你关注 Serverless 就不难发现，至少目前它还处在发展阶段，很多解决方案还在摸索之中。大厂具备技术实力，也乐于在拓展 Serverless 边界的同时发现痛点、解决痛点并占领技术高地。那如果你是一般用户，最好的选择还是持续关注 Serverless 动向，及时了解哪些方案可以为自己所用。当然，这是 Serverless 的舒适圈，但技术挑战也意味着机遇，我确实也看到有不少同学因为拓展 Serverless 的边界而被吸收进入大厂，去参与 Serverless 共建。

FaaS 痛点

以上就是我对 FaaS 应用场景的一些看法和建议。虽然 FaaS 最擅长是事件响应，但是如果可以改造 Serverless 应用，无疑 FaaS 的附加值会更高。接下来我们就看看 FaaS 在 Serverless 应用过程中切实存在的痛点，相信随着课程一路跟下来的同学一定深有体会。

首先，调试困难，我们每次在本地修改和线上运行的函数都无法保持一致，而且线上的报错也很奇怪，常见的错误都是超时、内存不足等等，让我们根本无法用这些信息去定位问题。

其次，部署麻烦，每次改动都要压缩上传代码包，而且因为每个云服务商的函数 Runtime 不一致，导致我们也很难兼容多云服务商。

最后，很多问题客服无法跟进，需要自己到处打听。

但如果你有仔细阅读文档的话，你就会发现各大云服务商都会提供命令行调试工具，例如阿里云提供的"fun"工具[3]、腾讯云合作的 Serverless Framework 等等。不过云服务商提供的工具，都是云服务商锁定的。为了破解 FaaS 的新 Vendor-lock，我从目前热门的 Serverless 社区中，选择了 3 个 Serverless 应用框架和 1 个平台，介绍给你帮助你破解 Vendor-lock，同时也能更好地解决痛点。

Serverless 应用框架

Serverless Framework [4]

Serverless 框架作为第一款支持跨云服务商的 Serverless 应用解决方案，一旦进入首页，我们就可以看到：Serverless X Tencent Cloud。其实最早的 Serverless 框架官方例子是 AWS 的，不过从去年开始被腾讯云强势入驻了。正如我上节课中所讲，腾讯云将 Serverless 作为切入云服务商市场的突破口，正在大力推进 Serverless 腾讯云的生态建设。

Serverless 框架具备先发优势，而且已经有很多成熟的应用方案，我们可以直接拿来使用。我们可以通过 Serverless Component 用 yaml 文件编排整个 Serverless 应用架构。

另外 Serverless Framework 也支持将应用部署到其他 Provider：AWS、阿里云、Azure，还有 Knative 等等，正如以下图示。不过这里有个小问题：就是我们只有在英文版 Serverless 框架的官网上才能看到其他云服务商的 Provider 和文档介绍（中文版 Serverless 框架官网一并给出）。

Serverless Framework 官方本身主要解决跨云服务商 Serverless 应用的部署运维问题。它利用类似 K8s 组件 Component 的概念，让开发者可以自己开发并且共享组件。通过组件来支持多语言的模板，而且 Serverless 组件的扩展能力还让 Serverless 框架具备生态优势。

目前，Serverless 的确是前端参与的比较多，因此在 Serverless 应用框架中，除了 Node.js 外，其他语言的选择并不多。下面我就再介绍 2 款 Node.js 新的 Serverless 框架。

Malagu [5]

Malagu 是基于 TypeScript 的 Serverless First、可扩展和组件化的应用框架。为了提升 Serverless 的开发体验，只专注 Node.js Serverless 应用。

首先，Malagu 的理念是支持前后端一体化，基于 JSON RPC，前端像调用本地方法一样调用后端方法。前后端支持 RPC 和 MVC 两种通信形式，MVC 可以满足传统纯后端 Rest 风格接口的开发。

其次，Malagu 框架本身也是基于组件化实现的，将复杂的大型项目拆解成一个个 Malagu 组件，可以提高代码的复用能力、降低代码的维护难度。

最后，命令行工具插件化，默认提供初始化、运行、构建、部署能力，通过插件可以扩展命令行的能力。

我有幸与 Malagu 的开发者共事过，Malagu 汲取了众家所长，我觉得这是一个值得关注的 Serverless 框架。而且目前 Malagu 已经有很多合作伙伴，他们正在积极使用和推广 Malagu 的解决方案。

Midway FaaS [6]

接着是我今天想给你介绍的重点，就是：Midway Faas 和它的工具链"f"。Midway FaaS 是用于构建 Node.js 云函数的 Serverless 框架，可以帮助你在云原生时代大幅降低维护成本，更专注于产品研发。

Midway FaaS 诞生于 Node.js 老牌框架 Midway，我也有幸与 Midway 的开发者一起共事过。Midway FaaS 是阿里巴巴集团发起的开源项目，由一个专业的 Node.js 架构团队进行维护。目前已大规模应用于阿里集团各 BU 线上业务，稳定承载了数千万的流量。

Midway FaaS 有这样一些特点，也可以说是特色。

跨云厂商：一份代码可在多个云平台间快速部署，不用担心你的产品会被云厂商所绑定。

代码复用：通过框架的依赖注入能力，让每一部分逻辑单元都天然可复用，可以快速方便地组合以生成复杂的应用。

传统迁移：通过框架的运行时扩展能力，让 Egg.js 、Koa、Express.js 等传统应用无缝迁移至各云厂商的云函数。

我们的“待办任务”Web 服务，也将采用这个方案进行重构。

首先，我们通过命令行全局安装工具链"f"。

npm install -g @midwayjs/faas-cli

然后，拉取我们最新的lesson11 代码分支，注意这个分支跟之前的分支相比改动比较大。因此，我们需要重新安装 NPM 依赖包。

npm install

前端代码还是在 public 目录，后端代码在 src 目录，项目里面的代码逻辑也比较简单，你可以自己看一下。我们需要讲解的是比较核心的配置文件 f.yml，这里有我放的 f.yml 的文件结构。

service: fc-qinyue-test

provider:
  name: aliyun
  runtime: nodejs10

functions:
  index:
    handler: index.handler
    events:
      - http:
          path: /*

  getUser:
    handler: user.get
    events:
      - http:
          path: /user/234534
          method: get

package:
  include:
    - public/*

aggregation:
  agg-demo-all:
    deployOrigin: false
    functions:
      - index
      - getUser

custom:
  customDomain:
    domainName: lesson11-ali.jike-serverless.online

这里我们主要关注的是 Provider，目前 Midway FaaS 支持阿里云和腾讯云，后续还会陆续增加其他云服务商。我们切换云服务商只需要修改 Provider 就可以了。其次，你也可以看到 Functions 部分，我们定义了所有的入口函数和路径 path 的映射关系。最后我们在部署环节，自己指定了域名。我们在配置域名解析时，根据云服务商提供的方式，通常都是修改域名的 CNAME，指向云服务提供的 FaaS 地址就可以了。

Midway FaaS 的强大之处就在于我们可以借助它提供的工具链"f"，根据我们的 f.yml 文件配置内容，从而直接在本地模拟云上事件触发，调试我们的应用。

这里你只需要执行：

f invoke -p

我们就可以通过浏览器访问：http://127.0.0.1:3000 调试我们的 FaaS 函数代码了。并且因为执行就在我们本地，所以调试时，我们可以借助现代 IDE 提供的 Node.js debug 能力逐行调试。而我们部署时也只需要一行代码：

f deploy

然后 Midway FaaS 就会根据我们的 f.yml 文件配置内容，帮我们部署到云上。

下面这 2 个 URL 地址，就是我用 Midway FaaS 分别部署在阿里云和腾讯云上的“待办任务”Web 服务。

阿里云“待办任务”Web 服务：http://lesson11-ali.jike-serverless.online/

腾讯云“待办任务”Web 服务：http://lesson11-tx.jike-serverless.online/

这里我需要提醒你一下，这 2 个云服务都是以阿里云的 OTS 服务作为数据库存储。这也是 FaaS 服务编排的强大之处，现在有些云服务商提供的 BaaS 服务是支持其他云服务商通过 ak/sk 访问的。另外如下图所示，如果我们采用 ak/sk 编排云服务商的 HTTP API 服务，那么利用 FaaS 就可以实现混合云编排服务了。

如果你留意过 GitHub，你会发现 Midway FaaS 和 Malagu 都是非常新的 GitHub 仓库。Serverless 作为一门新兴技术，还有非常大的想象空间。

另外阿里云也推出了自己的云开发平台，而且将 Serverless 应用的生态集成了进去。目前 Midway FaaS 和 Malagu 也都在其中作为应用创建模板供你使用。

阿里云开发平台 [7]

阿里云开发平台就是由咱们专栏特别放送中的被采访者，杜欢（笔名风驰）负责的。

他的核心理念呢，就是将大厂成熟的 Serverless 应用场景抽象成解决方案，并且透出到云开发平台上来，为各个中小企业和个人开发者赋能。

正如下图所示，云开发平台是将大厂的行业应用场景沉淀成了解决方案，解析生成 Serverless 架构，这样我们就可以把云开发平台上实例化的“解决方案”快速应用到我们的 Serverless 应用中来。基于成熟 Serverless 的应用，“反序列化”快速沉淀为解决方案，这也是云服务平台会收录 Midway FaaS 和 Malagu 的原因。

而且我相信以后还会有更多的 Serverless 应用解决方案被收录到云开发平台中的。另外阿里云开发平台并不限制开发语言，但截止到咱们这节课的发布时间，阿里云开发平台的模板仅支持 Node.js 和 Java 的 Serverless 应用，其它语言还需要等成功案例沉淀。

总结

这节课我向你介绍了如何打破 FaaS 的 Vendor-lock，并且介绍了我经验中 FaaS 的最佳使用场景：事件响应和 Serverless 应用。事件响应无疑就是 FaaS 的最佳使用场景；而 Serverless 应用则需要我们在云服务商提供的 FaaS 服务上，利用我们专栏前面学习到的知识和原理，挑战将完整的应用完全 Serverless 化。

为了解决 FaaS 的痛点和云服务商锁定，我向你介绍了 3 个框架和一个平台。

目前由腾讯云主导的 Serverless Framework，它支持多语言，具备组件扩展性。目前仅在英文版官网可以看到其支持的云服务商 Provider 文档。

由阿里云 FC 团队主导的 Malagu 解决方案，目前仅支持 Node.js，同样具备扩展性。目前正在积极的和很多开发团队合作共建 Serverless 应用生态。

由阿里巴巴 Node.js 框架 Midway 团队主导的 Midway FaaS 解决方案，它仅支持 Node.js，扩展性由 Midway 团队官方保障。Midway 团队的社区响应迅速，很多 GitHub 上的 issue 会在第一时间响应。

由阿里云主导的阿里云开发平台，它支持多语言，具备模板扩展性，扩展性由官方提供的模板保障。目前收录了 Serverless 生态比较成熟的方案，可以快速部署到阿里云 FC 上。虽然开发平台有一定的云服务商锁定嫌疑，但其采用的模板却可以支持解除云服务商锁定，例如 Midway FaaS。

作为本专栏的最后一课，我还想再啰嗦一句。Serverless 确实是一门新兴技术，其未来无限可能，接下来该由你探索了！

想要我的财宝吗？想要的话就给你，去找出来吧，这世上所有的一切都放在那里。

世界吗？没错！去追求自由。

去超越吧！在信念的旗帜的带领下。

—— 海贼王 罗杰

作业

Serverless 技术实践还是很重要的，最后一节课的作业我们就继续实操。请你将这节课“待办任务”Web 服务的 Midway FaaS 版本，在本地调试运行起来，并且通过 f deploy 把它部署到阿里云或者腾讯云上。

期待你的成果，有问题欢迎随时留言与我交流。如果今天的内容让你有所收获，也欢迎你把文章分享给更多的朋友。

参考资料

[1] https://help.aliyun.com/document_detail/74707.html

[2] https://cloud.tencent.com/document/product/583/31927

[3] https://help.aliyun.com/document_detail/140283.html

[4] https://www.serverless.com/cn/framework/docs/getting-started/

[5] https://www.yuque.com/malagu/guide/puw7p0

[6] https://github.com/midwayjs/midway-faas

[7] https://workbench.aliyun.com/

结束语 | 带你整体回顾我们的Serverless案例

你好，我是秦粤。在经过了 11 节课的学习后，相信此刻，你对 Serverless 一定有了一些新的认识。那到了尾声，今天这节课我们就结合“待办任务”Web 服务的演进过程，带你整体回顾一下本专栏的内容，希望能对你自身沉淀知识有所助益。

一路认真学习并动手实践课后作业的同学其实很容易发现，这个专栏并不是教大家写代码的，而是一堂服务端技术架构课。我们的实践内容和作业，主要也是让你通过部署项目代码体验一下运维的工作，更深刻地理解“Serverless 是对服务端运维的极端抽象”这句话。

下面我们就分几个阶段去回顾“待办任务”Web 服务这个大案例。

“待办任务”Web 服务

我们的代码都在GitHub上，我建议你一定要跟着我的节奏 run 一下。

All-in-one

第一个版本master 分支，以下是这个版本的示意图。

你可以看到这个 master 分支的版本，采用的是 Express.js 框架，这是一个典型的 MVC 架构。而且所有的请求，无论 index.html、数据 API 请求，还是静态资源，都放在了一个文件 index.js 中处理。

这里我特意给出了 2 个文件：index.js 和 index-faas.js。index.js 是用于本地开发和调试的，而 index-faas.js 是用于部署到阿里云函数服务的。我们可以对比一下，其实不难发现这 2 个文件只有细微的差别。

index.js 因为我们在本地调试，所以它需要在我们本地 IP 上监听一个端口号：3001。通过端口号将用户的 HTTP 请求转入到我们上面注册的 Express 函数中。

index-faas.js 因为部署在云上的函数服务中，它是通过事件触发的，因此它从函数服务提供的 Runtime 中获取了 fc-express 库的 Server 对象，这个 Server 对象其实是一个适配器，它将函数服务 HTTP 事件对象适配成 Express 的 request、response 和 context 对象，这样我们的其他代码就可以复用 index.js 了。

“待办任务”Web 服务，第二个版本是lesson04-homework 分支，以下是这个版本的示意图。

这个版本也是 index.js 接收所有的请求，不同于上一个版本的是，待办任务的数据我们存储在了 lowdb 仓库中。我们对比一下 index.js 和 index-faas.js。

可以看出我们在本地能利用机器的硬盘持久化 Todos 数据，但是在函数服务上却不行，就像我们专栏中介绍的 FaaS 实例要求我们无状态 Stateless，因为我们的函数实例每次启动都是一个全新的机器，然后加载我们的代码。你如果尝试在函数服务上使用 index.js 的写文件的方式，将数据放入 db.json，你就会发现我们的函数实例的机器硬盘是只读的。我需要指明一下，即使我们可以读写 /tmp 目录，但每次启动的函数实例都是无法保存状态的。

“待办任务”Web 服务，第三个版本是lesson05 分支，以下是这个版本的示意图。

为了解决前面 lesson04 中的函数服务的数据持久化问题，我们引入了消息队列。当然我实际代码中是采用了表格存储 OTS，而且我们将数据库操作的 rule 抽离出来，放在了一个独立的文件 rule-faas 中，rule-faas 提供 RESTful 的 HTTP API 给客户端访问。这样做是为了 index-faas 和 rule-faas 独立部署，避免它们的逻辑耦合在一起，当触发扩缩容时造成资源浪费。例如用户的前端资源请求量大时，也会触发实例扩容，如果 rule-faas 没有独立部署，就会导致额外的数据库对象创建。

这种 All in one 的做法最简单直接，所有的内容都放在一起，部署和调试都很方便。而且我们本地开发和云端的差异很小，方便我们去验证、测试函数服务的一些功能。

静态资源分离

“待办任务”Web 服务，第四个版本是lesson06 分支，以下是这个版本的示意图。

这个版本和上个版本相比，最大的改变是，我们将静态资源从 public 中移出，部署到 CDN 上了。另外为了增加安全性，避免我们的 rule 服务直接被 HTTP 请求篡改，我们引入了微服务概念中的 JWT。用户需要先登录 (/api/currentUser)，Cookie 获取到 JWT，才能顺利通过 rule.js 的 JWT token 校验。

Docker 容器

“待办任务”Web 服务，第五个版本是lesson07 分支，以下是这个版本的示意图。

这个版本和上一个版本相比，区别就是将所有的内容都放回到 index.js，并且运行在容器中了。然后我们在代码中引入 Dockerfile，构建我们的第一个容器镜像。这里我啰嗦一下，为了简化我们这节课的体验，我将 rule.js 合并到 index.js 中了。这样做其实也是动态化网络的思想，我们没必要为了划分微服务而去划分微服务。在实际工作中，合并、拆解节点应该是常见的操作，具体根据我们的业务需求来就行。

Kubernetes

“待办任务”Web 服务，第六个版本是lesson08 分支，以下是这个版本的示意图。

为了更好地管理 Docker 容器，我们这个版本引入了 K8s。通过上面的示意图我们可以看出，K8s 通过策略配置或指令将我们的“待办任务”Web 服务的生命周期管理了起来。我们应用开发者除了关心 Dockerfile，不用再关心我们的容器重启、奔溃、扩缩容等等问题了，但我们应用在 K8s 集群的运维状态，还是需要运维人员手动维护的。

Knative

“待办任务”Web 服务，第七个版本是lesson09 分支，以下是这个版本的示意图。

这个版本和上个版本相比，我们在 K8s 集群中安装了 Istio 组件和 Knative 组件。这 2 个组件都会给我们应用的 Pod 注入伴生容器 Sidecar，就像一个监护人，监视着我们应用容器的真实状态，利用控制面板和数据面板的配合，自动化运维我们的应用在 K8s 集群中的状态。

研发人员还是只关心 Dockerfile，所以对于研发人员来说是 DevOps。运维人员只需要在 K8s 集群中安装好 Knative 组件，并维护 Knative 组件就可以了。应用的状态由 Knative 自动维护，所以我们也称之为 Container Serverless。要注意这里的应用可以是单个函数，也可以是微服务，也可以是数据库，具体内容完全由你的 Dockerfile 去编排。所以我们可以看出，FaaS 和 BaaS 的底层就是由容器服务实现的，但具体的容器方案，可能是 Docker，也可能是虚拟机。每个云服务商都有自己的策略，不过我们通过 Knative 可以了解到 Container Serverless 的工作原理。

Serverless 应用

“待办任务”Web 服务，第八个版本是lesson11 分支，以下是这个版本的示意图。

这个版本是个重大的重构，因为跟我们前面的写法都不一样了。我们的“待办任务”Web 服务采用了 Midway-FaaS 框架，依托 Midway-FaaS 去适配云服务商，支持我们的应用可以自由选择部署在阿里云或腾讯云的 FaaS 上。而且这个版本，我们的代码写成的 TypeScript，逻辑也更加清晰了。这个也是现在 FaaS 部署 Serverless 应用的最佳体验，大厂将自己的成功的 Serverless 业务，做成方案沉淀，作为框架输出。我们依赖这些 Serverless 框架，可以快速开发迭代我们自己的 Serverless 应用，享受 FaaS 的红利。

以上就是贯穿咱们专栏的案例——“待办任务”Web 服务的整个演进过程了。

结语

我搜集了部分关注 Serverless 技术的同学的提问，在这里我也想统一回答一下，你也可以看看自己是否也有同样的疑问。

就像我[第 1 课] 中所讲的，Serverless 是对现代互联网服务端运维体系的极端抽象，对开发者的变革较大。降低了服务端运维的门槛，就意味着即使服务端运维经验是零，也可以将自己开发的应用快速部署上云，这点对前端工程师是很大的利好。

对于后端工程师和运维工程师，掌握 FaaS 和服务编排，无疑也是一大利器。FaaS 的低成本、高可用，还有事件响应机制，都可以在现有的后端微服务或者应用架构中发挥出巨大的优势。

对于云服务商，FaaS 还可以利用碎片化的物理机计算资源，提升资源利用率，而且还可以帮助云服务商提升云服务的利用率。

有同学让我讲一下大厂的成功案例，其实大家看到的很多 FaaS 创建的模板，都是来自于大厂案例的沉淀。FaaS 诞生的过程，其实是大厂或云服务厂商将自己的应用运维能力逐渐下沉的一个过程，并不是先有了 FaaS，大家再去思考什么场景适合 FaaS。这也是为什么我要用“待办任务”Web 服务这个案例，一步步升级运维体验，向你讲解整个 Serverless 的发展史。

最后，我想说，我并不想用我们的最佳实践来束缚你的思想。我一直觉得，Serverless 虽然是在大厂运维能力的基础上诞生并成长来的，但是利用 Serverless，再结合我们的想象力，是可以创造出更多的可能的。总的来说，一句话，不要让它束缚我们的想象力，Serverless+AI、Serverless+IoT、Serverless+ 游戏等等，才应该是我们下一步要探索的方向。

未来，加油！