前文回顾
传统应用部署的特点
如果应用很长时间没有使用,大部分资源都是浪费的。如果想要按需使用,首先需要初始化虚拟机,再初始化应用运行环境,然后在启动应用,整个过程耗时达到分钟级别,对于业务而言这显然是不可接受的。而基于运行环境的重用能够实现毫秒级的热启动。
- • 直接进行处理无需启动应用
- • 一直消耗硬件资源
Serverless的优势
- • 应用的百毫秒启动
- • 资源利用率和业务性能的平衡
Serverless的特点
- • 组成Serverless应用的函数是事件驱动的但也可以直接用API调用函数
- • 函数可以同步调用或异步调用,定时触发器函数是异步调用的,异步调用函数建议主动记录并处理异步调用结果
- • 函数的启动过程分为下载代码、启动容器、启动运行环境、执行代码四个步骤,前三个步骤称为冷启动,最后一个步骤是热启动
- • 执行上下文重用可以提高Serverless应用性能,但在编写代码时要注意执行上下文重用带来的风险
- • 函数并发限制导致函数执行时间延迟
- • 执行上下文重用导致每次处理的都是同一份数据
如何提高应用开发调试和部署效率?
在实际应用中,绝大多数应用由很多函数组成,应用部署的时候需要将所有函数统一部署,并且函数运行依赖Faas环境,这样导致函数代码不能直接在本地运行,这样的一些限制给应用开发调试和部署流程带来了挑战。
新的挑战
- • 函数太多如何管理
- • 本地开发时如何进行调试
- • 代码开发完成后如何部署
什么是Serverless开发框架
Serverless的开发框架不是传统的Express.js、Spring Boot等代码框架,而是集成Serverless思想、贯穿Serverless应用从开发到上线全流程的工具。
基于Serverless开发框架很容易开发一个Serverless架构的应用,企业也能轻松的把现有的业务演化为Serverless架构。
除了底层的Faas和Baas基础设施,上层的开发框架也是非常重要的一部分,直接决定了开发者使用Serverless的成本。
从框架开发者的角度和思考如何去设计Serverless开发框架
Serverless开发框架所应具备的基本功能:应用管理、应用开发、应用调试、应用部署。
从形态上来看,Serverless开发框架可以抽象为一个平台或服务,用终端工具或控制台的方式提供给开发者使用。
应用管理
应用的管理主要是函数的管理,各个Faas平台也考虑到了这一点,比如函数计算的服务功能、Lambda的应用功能。
你可以把同一个应用的函数都在同一个服务下创建。一个服务代表一个应用。
那怎么描述服务和函数的关系呢?
因为它们是静态的,不会在代码运行的时候改变,可以使用yaml文件或js文件来表示。
在创建函数的时候,也需要指定函数的入口、编写语言、触发器。
假设终端工具是一个可执行脚本,可以提供Serverless init命令自动为用户创建一个管理Serverless应用的yaml配置文件,有了应用的配置文件之后,开发者就需要开发代码了。
为了近一步的简化用户操作,可以提供代码的开发模版,开发者基于模版一键生成Serverless应用。这样开发者就可以在本地用自己喜欢的编辑器来开发代码了。
也可以提供一个Web IDE,为开发者初始化本地运行环境,这样开发者就不用在本地安装编辑器就能开发,也不用关心运行环境的差异。
各个Faas平台也都提供了自己的Web IDE,比如AWS Cloud9、腾讯云函数计算的Code Studio(基于VS code开发的,微软19年5月份发布了vs studio online,让开发者轻松实现web ide)
目前体验最好的是Cloud9,但相比本地开发还是有一定差距的。
应用调试
应用调试非常麻烦,想要调试这个函数需要手动构建event,context,callback参数来测试函数的运行状态,而麻烦点在于这些参数依赖Faas环境,不同的运行环境不同触发器的入参都有差异。
如何让开发者方便调试
- • 远程调试
将代码部署到Faas平台,然后直接调用FaaS平台的接口执行函数,再得到函数运行日志及返回结果。
- • 本地调试
由开发框架模拟函数运行时环境,构造函数参数来执行函数。本地调试效率更高。
对于Serverless开发框架来说,这两种调试方式都需要,也就是需要实现serverless invoke和serverless local invoke两个命令。
根据yaml的配置解析出应用的服务名称和函数列表然后调用FaaS平台的接口来创建和更新服务和函数。
在创建函数的时候,FaaS当中的函数代码通常以压缩包的方式生成在文件的存储服务中。
部署函数之前现在本地把代码压缩成.zip文件。
部署应用的时候,将代码部署到FaaS当中进行调试,可能每次代码改动都会影响到线上服务,如果有版本控制就可以避免。
账号设置与多平台支持
把账号信息保存到用户自己的磁盘上,这样应用部署的时候就可以从磁盘读取账号信息,以用户账号调用FaaS平台接口,这样就可以把函数部署到用户自己的云账号下面。
一个优秀的Serverless开发框架需要支持不同的云平台和厂商的支持,因为不能限制用户只使用某一种云服务。
Serverless开发框架最好还要抹平不同Serverless平台的差异,让应用能够在不同Serverless平台中进行平滑迁移,甚至让开发者使用一个开发框架、一套开发流程就能够实现多云部署。
要支持多种Serverless平台需要在开发框架上去配置各个不同的平台,但现在各个平台都没有一个统一的标准,这是难点之所在。
所以在创造一个开发框架的时候首先要抽象出平台各个公共的部分,然后制定一套你的开发标准,再以适配器的方式适配不同的Serverless。
通过一个Serverless命令对开发者提供所有服务,贯穿Serverless应用开发到线上的全流程,这其实是主流Serverless开发框架的设计原理。
接下来介绍两个开发框架
- • Serverless Framework
- • 函数计算Fun
Serverless Framework
- • Serverless Framework是最完善的开发框架之一,不仅实现了前面应用开发、调试、部署等基础问题,还实现了多个Serverless平台的支持,支持AWS、谷歌、阿里云、腾讯云等公有云Serverless平台,还支持CloudFlare、OpenWhisk、K8S等私有Serverless平台。
- • Serverless Framework使用Node.js开发的
安装之后就可以直接使用Serverless命令了
账号设置
- • provider 具体的Serverless平台(通过该命令实现不同平台的支持,命令大多相同,不同平台功能的完整性有差异)
- • key AWS账号的aws_access_key_id
- • secret AWS账号的aws_secret_access_key
应用配置
初始化应用
- • 通过yaml配置来创建或更新资源
- • 使用aws s3存储桶
- • 通过配置代码来描述基础设施
应用调试
应用部署
在将函数部署到Lambda之前,Serverless现在本地将代码打包,最终代码是一个压缩包,路径是.serverless/{servername}.zip
也可以单独部署某一个函数
serverless deploy function -f functionname
Serverless framework 还提供日志查询功能;Fun是阿里云函数计算团队开发维护的一个Serverless开发框架,因为也支持函数计算。
函数计算Fun
设置账号
配置完成后 Fun将账号信息存储在~/.fcli/config.yaml中
也可以将账号信息配置在项目根目录.env中
这个优先级更高。
初始化一个项目
template.yaml和serverless.yaml功能一致,但内容的定义其实有较大差异
- • Resources 具体的资源对象
- • Resources.*.Type 资源类型
远程调试
通过fun invoke functionName命令对函数进行远程调试
也可以添加--local进行本地调试,在本地调试之前先安装docker,通过docker在本地启动一个代码运行环境来执行代码,而不是直接模拟函数参数,这样的好处是更接近FaaS平台的运行环境。
应用部署
通过fun deploy进行应用部署。
开发框架的意义在于帮助开发者提升Serverless应用的开发效率。
小结
- • 与FaaS、BaaS等基础技术一样,Serverless开发框架也是Serverless领域中非常重要的一部分
- • 一个优秀的Serverless开发框架,可以让开发者很容易开发一个Serverless架构的应用,也能让企业轻易将现有业务演化到Serverless架构
- • Serverless开发框架需要具备的基本能力是应用管理、应用调试和应用部署。
Serverless应用怎么安装依赖?
Serverless应用代码都是独立的函数,不涉及其他依赖,而在实际进行应用开发时,大部分情况下都会有第三方依赖。
上述代码引入了mysql的依赖。
为什么安装依赖很困难?
主要是因为它运行在FaaS平台上,而FaaS平台的运行环境由云厂商提供且预制开发者只能进行有限的定制。
没有服务器怎么登录服务器执行安装依赖的命令?
函数在被触发执行的时候会生成函数实例。
Serverless应用的依赖本质上是每个函数代码的依赖。
函数实例的实体就是容器,容器的实现方案可以是Docker等。
FaaS通过函数来隔离每个函数实例,也通过容器实现函数运行时的内存和cpu限制,比如给函数分配128M内存,函数实例所对应的容器内存资源只有128M。
运行环境
编程语言是你创建函数时指定的某个具体版本的编程语言,由FaaS平台提供。
内置模块
内置模块就是该编程语言的一些内置模块
比如Node.js中的http.js
为了让开发者使用更方便,FaaS平台一般还会默认安装一些依赖,比如函数计算的Node.js默认提供了aliyun-sdk模块。
函数实例创建时,会从存储服务中将你的代码下载下来并加载到运行环境中。
Serverless应用代码
函数实例中你能控制的只有函数代码,函数代码需要安装依赖实现方案就是将依赖安装到应用内部,将依赖和代码一并打包部署到FaaS平台当中。
实践难点
大多数编程语言的依赖通常安装在全局系统目录,比如maven工具,安装在项目之外的系统目录中。
- • 安装依赖过程中可能涉及代码编译,环境不统一会导致编译产物有差异
- • 系统依赖通常不可移植,应用运行时依赖一些系统级别的动态链接库和软件
- • 安装依赖的难度依次递增的编程语言:Java、Node.js、Python
在Faas中部署的Java应用就是编译后的jar包而不是原始代码。
如何为Java应用安装依赖
虽然部署jar包不用关心依赖了,但也带来了问题:部署前需要先编译。
通常本地开发的时候Java的依赖安装在本地的.m2/home/repository目录下,编译的时候根据本地的jdk版本和本机的repository编译代码,如果有多个同学同时开发部署一个应用,大家电脑上安装的jdk版本或依赖包可能不完全相同,就会导致每个人编译出来的jar包不同,甚至部署到FaaS上的jar包无法正常运行。
想要解决编译环境问题就要有统一的构建机
不让开发同学直接编译部署Java代码。
开发Java的时候可以通过pom.xml文件来定义代码的依赖,部署Java应用的时候需要将Java的业务代码和pom.xml都上传到构建机,在构建机上统一安装依赖编译代码得到一个可以直接执行的jar包,然后再将jar包部署到FaaS平台。
总的来说Java是编译型语言,只要统一编译环境就不要关心依赖的问题,可是对于Node.js解释型语言怎么安装管理依赖呢?
Node.js安装依赖
项目依赖可以通过package.json管理,在代码中可以通过require方法引入某些依赖。依赖的查找路径是先查询项目目录再查询系统目录。
这种查找依赖所在路径的方式得益与Node.js包管理机制。要让运行在FaaS中的Node.js代码能够找到依赖,解决方法就是避免使用全局依赖,把所有的依赖都安装在node_modules中,然后把node_modules和代码一同部署在FaaS上。
对于Node.js来说,除了可以直接安装在node_moudles中的JS依赖外,还会使用C++来编写一些Node.js扩展
那么这些C++的扩展怎么安装呢?
先把C++代码编译为.node文件,再把编译产物和代码一同部署在FaaS平台。
代码示例
https://gitee.com/pingfanrenbiji/serverless-class/tree/master/06
使用C++编译之后的可执行文件需要和FaaS平台的运行环境兼容,不然就没有办法运行,所以跟Java一样,编写C++扩展的时候,需要使用统一的构建机,并且构建机的运行环境和Faas平台的运行环境保持一致。
依赖包体积太大,导致函数无法部署的问题
FaaS平台对代码体积有限制,函数计算限制是100MB,限制本质上是为了提高函数性能,因为FaaS构建函数实例的时候需要从文件存储当中下载代码,代码体积过大的话,耗时就会越来越长,这就会影响函数启动速度。
在Node.js中代码体积问题尤为常见。
node_moudles包体积经常会非常大,虽然说node.js的npm生态为编程带来了便利,但node_modules嵌套层级冗余代码多模块当中还包含了很多测试用例和源代码,这就很容易导致代码体积快速膨胀。
可能大部分时候引入一个新的代码依赖真正需要执行的代码就几行但是包的体积却有几MB,部署函数到FaaS上的时候就会造成不必要的资源浪费。
想办法减少模块的体积
在编译的过程当中分析代码的依赖,只把需要使用的模块和需要引入的函数构建打包,丢弃无用的代码,对代码进行压缩,这个过程和前端使用的webpack构建的redis.js的过程一样,要实现Node.js代码的编译也可以使用webpack来实现。Vercel是基于webpack开发的ncc工具,使用ncc可以把复杂的Node.js项目编译为单个文件并且去掉不必要的依赖,很大程度上就减少了代码的体积。
https://gitee.com/pingfanrenbiji/ncc
总的来说Node.js安装依赖都是比较方便的,因为是它的依赖可以轻松的安装在项目目录当中,但对于Python这种依赖需要安装在系统目录当中的编程语言到底要怎么去安装依赖呢?
Python安装依赖
pip是当前python当中最流行的包管理工具,所以通常会使用pip来安装依赖。
给Python项目安装依赖比较麻烦的地方在于使用pip安装的依赖通常会散落在系统的各个文件中
比如Python类库和动态链接都会放在/{user}/lib/python/site-packages、可执行文件都会放在/user/bin目录下,想要将所有文件都存放在同一个目录当中就会比较麻烦,pip提供了多种把依赖安装在指定目录的方案。
方法一:使用--install-option参数
可以让你精确的控制某个类型的依赖安装路径。
将模块安装到指定目录
把所有目录安装到指定目录或者--prefex=${pwd}当前目录,这样更方便依赖的管理。
有一个很大的问题就是在安装的过程中需要根据源码重新的构建依赖,如果某一个依赖包没有源码那就没有办法使用了,而且install-options参数设置比较复杂。
方法二:--target参数
这个是简单些的方式。
target参数可以将依赖直接安装到当前目录,不会产生lib/python3.7/site-packages子目录结构,适合依赖较少的情况下。
方法三:使用virtualenv
把python包安装到一个独立的虚拟环境中,这样就可以在虚拟环境中为代码安装所有的依赖,形态上你的代码和依赖就完全在同一个目录中了,这样就可以把依赖和代码一同部署到FaaS上了。
好处是不会污染全局环境不会把包管理相关的工具都本地化。
缺点就是会增加包的体积。
Python解析依赖的路径
但是对python来说把包安装到当前目录还不够,还需要修改python解析依赖的路径,可以通过sys.path查看python解析依赖的路径。
如果你的依赖直接安装在当前目录还好,因为sys.path包含了当前目录。
代码当中可以直接引入依赖,但通常不会将依赖安装在当前目录,因为这样会导致当前目录下的文件十分混乱,而是将他们安装在当前目录的lib/python3.7/site-packages目录下。
将python依赖包目录添加到sys.path中
需要在代码中修改sys.path,将子目录加入到sys.path之后,你才能使用其中的依赖模块。
小结
- • 不同编程语言包管理机制不同,安装依赖的方式也不尽相同,但本质上,都是需要将依赖安装到应用项目中,并且随项目一起部署到FaaS平台。
- • 开发框架也在解决安装依赖的问题,让用户尽可能的最低成本完成应用的开发。
- • Serverless应用的代码依赖和系统依赖都需要安装在项目中和应用代码一起部署到FaaS平台
- • FaaS对代码体积大小有限制,所以最好要精简依赖体积
- • 如果代码或依赖需要编译,则编译环境需要和FaaS运行环境兼容,不然编译后的产物可能无法运行。