• Serverless入门


    ㅤㅤㅤ
    ㅤㅤㅤ
    ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ(信用就像一面镜子,只要有了裂缝就不能像原来那样连成一片。——(瑞士)阿米尔)
    ㅤㅤㅤ
    ㅤㅤㅤ
    ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ在这里插入图片描述

    Serverless

    serverless被称为无服务运算,依托于云厂商,用户不需要关注服务器,网关和数据库等配置,让用户更专注业务。至于这些服务器,数据库等配置项全部由云厂商托管
    更直白的说,用户不需要再关心服务器,数据库,证书,负载均衡等的复杂运维配置,也不用担心服务的扩容和伸缩,这些都交给云厂商来做。用户只需要开发代码,一键部署即可

    服务器架构演进路线

    本地部署

    • 在最传统本地部署架构中,我们需要自己配置服务器,配置硬盘,配置网络,然后装操作系统,开发软件并在自己配置的机器上运行。

    基础设施即服务(IaaS)

    • 随着云计算的兴起,各大云计算厂商(AWS, Azure, GCP, 腾讯云等)搭建自己的数据中心并提供。他们提供已经搭建好的基础硬件,用户可以在这些硬件的基础上购买虚拟机,并在虚拟机中配置操作系统,搭建数据库,配置安全并开发软件。如AWS的EC2,腾讯云的CVM。

    容器即服务(CaaS)

    • 随着容器的兴起,各大云计算厂商开始提供容器编排服务(主流Kubernetes),用户仅需将开发的软件打成各种镜像在容器编排服务中配置并部署即可,或使用数据库镜像部署数据库等,这时用户不再需要干涉虚拟机配置。如AWS的ECS和EKS,GCP的GKE,腾讯云的EKS。

    平台即服务(PaaS)

    • 容器编排服务有时候还需要很多额外的配置步骤,如配置日志系统,配置监控系统,配置负载均衡等,那么有没有什么服务可以直接提供这些服务而用户只关注制作镜像即可。这时候有很多厂商以Kubernetes为基础,扩展开发出自己的容器编排平台,如OpenShift和Rancher。至此用户只需要按照各平台要求进行必要的配置即可完成服务的部署。

    函数即服务(FaaS)

    • 虽然平台已经抽象掉了软件开发中相当多的底层配置,但是依旧不可避免的有很多配置项,如自动扩所缩容,集群的管理等。那么有什么方法可以连这些配置都不做,直接写业务逻辑代码就可以完成部署。这时候FaaS应运而生,以AWS的Lambda为例,AWS完全抽离掉了底层配置和运行时的配置,用户仅需要给Lambda填充业务代码和指定触发条件即可。AWS将完全托管你的代码,当触发条件满足时自动触发执行你的代码。GCP的GCF,腾讯云的SCF亦采用和Lambda相同的FaaS原理

    无服务器架构

    • 然后单纯的Lambda不足以完成复杂的业务逻辑,例如我们还需要数据库,缓存,长期储存空间,日志管理,网关等配合在一起才能实现实际的业务逻辑。各大云计算厂商围绕这些需求提供了相应的服务,如AWS托管的NoSQL数据库DynamoDB,长期储存桶S3,API Gateway网关,SQS消息队列等配合Lambda一起可以构建完整的业务逻辑,并且省去了很多其他架构中很多配置和运维的工作量,形成了AWS自己的无服务器架构的生态。其他云计算厂商也提供有类似AWS一样的无服务器架构生态。虽然无服务器架构带来了抽象掉绝大多数与业务代码无关的工作量的优点,它也有很多缺点,如高度依赖于一个云计算厂商提供的生态迁移成本高,以及因为多需要云计算厂商托管不便于本地开发等问题。
    Serverless架构演进路线

    控制台

    • 使用云计算厂商的网页控制台手动创建修改各个无服务器架构中的资源是最适合初期上手的方法。如使用AWS,可以在网页里完成Lambda函数的编写测试,DynamoDB的创建,配置自动扩缩容,手动修改表中的数据等操作。虽然在控制台中手动管理所有资源上手容易并且易于可视化,但是随着使用的资源逐渐增加和更复杂的系统结构,如越来越多的Lambda函数和递增的接口数量,这种方法不适合后续的维护和管理,也不具有可读性,仅适合上手练习。

    原生资源编排服务/软件

    • 针对上述的问题,云计算厂商大多都提供原生的资源编排服务(有的也没有),多以通过写声明式配置文件并配合使用提供的CLI工具完成对各种云计算资源的生命周期(创建,更新,删除)的自动化管理。例如修改配置文件后CLI工具会自动判定是否需要更新某个资源而不需要用户手动指明如何去更新某个资源。这种云资源的编排方式也被称为基础设施即代码(IaC),如AWS的CloudFormation和CDK,腾讯云的TIC。以AWS为例,AWS提供自家的资源编排服务CloudFormation,用户只需要按照CloudFormation的模版要求写yaml文件并使用AWS CLI部署,即可自动化管理AWS中任意资源(不局限于无服务器架构相关的服务),并且在有修改的时候更新yaml文件中的相应部分再重新使用CLI部署即可。因为CloudFormation功能强大并且太过复杂,后来AWS还推出了AWS CDK抽象简化了一些资源定义可以帮助用户更轻松地通过写代码的方式定义云资源,使用原生资源编排服务能非常有效的管理云资源,具有高可读性并且易于后续维护,缺点是要求用户对想使用的云资源有足够的理解,并且CLI工具不具备很好的可视化。

    第三方资源编排框架

    • Terraform是一款非常知名的第三方IaC软件,它可以根据定义的模板转换成各个云计算平台要求的格式进行部署,即用户只需要写一份资源模板就可以轻松部署在不同云计算平台之间。而我们后续要提到的重点Serverless框架也是一个第三方的资源编排框架,并且是专门针对无服务器架构的资源编排框架。它的资源模板提取抽象了几家主流云计算平台中无服务器架构相关服务的配置(FaaS服务为主,其他服务配置差异较大),可以让用户使用统一的Serverless框架模板并使用Serverless框架的CLI快速在各个云计算平台部署无服务器架构的软件;框架也会自动管理所有资源的生命周期,自动处理创建更新删除等操作。并且因为它专门为无服务器架构应用设计,非常适合将资源模板文件放在项目源码中,即可用指令实现源码编译打包部署云服务平台的工作流程。Serverless框架不是万金油,介于各个云计算平台产品和配置不同,Serverless框架有很大局限性;很多扩展功能,如实现DynamoDB的自动扩缩容,需要使用插件实现,而插件的维护和更新可能具有滞后和兼容性的问题。
    • 对于aws,如果serverless模板无法进行控制,则可以使用aws cdk来完成这些操作
    和传统研发模式区别

    传统开发模式下,同样从0-1需要15-30天的时间。但在serverless下,依托于云厂商提供的平台,用户不再需要进行繁琐的配置,极大的缩短了运维时间。
    资费上serverless也更有优势,传统服务器在没有用户使用的情况下也依然会产生费用,但serverless的按需收费,将会用多少收多少钱,更加的节约成本

    传统研发流程
    1. 在云厂商购买服务器,需要根据业务选定配置,cpu,内存,宽带等
    2. 购买数据库,需要根据业务选定配置,cpu,内存,宽带等。还需要选定副本集
    3. 购买,配置域名,通常流程3-7天
    4. 购买,配置https证书,通常流程3-7天
    5. 配置slb负载均衡。单点服务可跳过此步骤。集群模式下需要配置此项
      • nginx 3天左右
      • k8s 7天左右
    6. 配置CICD自动化集成部署流程。如果是手动上机则跳过此步骤,否则需要3天左右配置此流程
    7. 业务代码开发
    8. 代码发布
    serverless研发流程
    • 数据库和服务器根据使用的流量,内存按需收费。初期赠送的有免费额度,超额后开始收费。自动的扩缩容
    • 域名和证书直接使用云厂商提供好的,我们只需要进行简单配置即可
    • 负载均衡也由云厂商托管,自动伸缩
    • 代码部署流程更简单,比如亚马逊的直接一条命令即可部署,国内腾讯的扫码部署
    • 业务代码开发周期
    • 代码发布
    Serverless实践

    下面用AWS亚马逊云进行演示
    前提

    Serverless Framework

    Serverless Farmework官网
    Serverless Farmework代码仓库 百分之90以上使用js实现
    serverless脚本命令框架,它并不是传统的服务框架。只需要在你本地定义serverless.(.ts/.yml等)配置文件,再通过serverless脚本命令,就可以将你的服务快速的配置,部署到云厂商
    目前它支持亚马逊,阿里,腾讯,谷歌等主流的云厂商
    Serverless支持的云厂商图谱

    安装Serverless
    npm install -g serverless
    
    • 1

    验证是否安装成功

    serverless help
    
    • 1

    在这里插入图片描述

    初始化服务

    使用serverless命令,根据引导生成你的服务目录

    serverless
    
    • 1
    Creating a new serverless project
    
    ? What do you want to make? (Use arrow keys)
    ❯ AWS - Node.js - Starter
      AWS - Node.js - HTTP API
      AWS - Node.js - Scheduled Task
      AWS - Node.js - SQS Worker
      AWS - Node.js - Express API
      AWS - Node.js - Express API with DynamoDB
      AWS - Python - Starter
      AWS - Python - HTTP API
      AWS - Python - Scheduled Task
      AWS - Python - SQS Worker
      AWS - Python - Flask API
      AWS - Python - Flask API with DynamoDB
      Other
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    Serverless常用命令
    // 初始化新的serverless服务
    serverless
    
    // 发布应用程序
    // -s aws环境变量 在nodejs中使用process.env.STAGE获取
    // --aws-profile 可能存在多个环境问题,所以需要指定配置文件
    serverless deploy -s dev --aws-profile dev
    
    // 本地模拟serverless的模式进行调试 需要安装serverless-offline的npm包
    serverless offline -s dev --aws-profile dev
    
    // 查看服务调用日志
    serverless logs -f hello --tail
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    Serverless HttpApi Lambda

    接下来使用dynamodb数据库,s3文件系统来实现基本的数据和文件的增删改查
    前提

    • 对dynamodb数据库有基本的认识和了解
    • 对亚马逊s3有基本的认识和了解
    • 使用dynamoose完成对数据库的操作,不用必须了解,dynamoose只是基于dynamodb进行了封装,提供了语法糖
    • 对lambda函数有一些了解

    初始化dynamodb数据库

    const { Lambda, DynamoDB } = require('aws-sdk');
    const dynammoose = require('dynamoose');
    const dayjs = require('dayjs');
    
    // aws链接配置
    const dynamodbAwsConf = { 
    	// aws凭证id 非必填 默认使用default
        accessKeyId: 'xxx', 
        // aws凭证key 非必填 默认使用default
        secretAccessKey: 'xxx', 
        // 使用中国-宁夏地区
        region: 'cn-northwest-1' 
    };
    
    // 初始化dynamodb数据库
    const dynamodb = new DynamoDB(dynamodbAwsConf);
    
    // 初始化dynamoose
    const db = new dynammoose.aws.sdk.DynamoDB(dynamodbAwsConf);
    dynammoose.aws.ddb.set(db);
    // 定义数据库表
    const table = 'test_dynamodb';
    // 定义数据库模型
    // 其中id是主键 pid是排序键也是范围键
    const dynamodbModel = dynammoose.model(table, {
        id: {
            type: String,
            hashKey: true
        },
        pid: {
            type: String,
            rangeKey: true
        },
        name: String,
        email: String,
        mobile: String,
        nickName: String,
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    初始化s3对象存储

    const aws = require('aws-sdk');
    
    // aws的s3桶配置
    const s3AwsConf = {
    	// 使用中国-宁夏地区
        region: 'cn-northwest-1',
        s3ForcePathStyle: true,
        // aws凭证id 非必填 默认使用default
        accessKeyId: "xxx",
        // aws凭证key 非必填 默认使用default
        secretAccessKey: "xxx",
    };
    
    // 初始化s3桶
    const s3 = new aws.S3(s3AwsConf);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    数据库增删改查

    const uuid = require('uuid');
    
    // 创建
    module.exports.testCreate = async (event) => {
        const obj = JSON.parse(event.body);
        const { _id, name, email, mobile, nickName } = obj;
        let lastId = _id ?? uuid.v4();
        const data = {
            id: lastId,
            pid: uuid.v4(),
            name,
            email,
            mobile,
            nickName
        };
        await dynamodbModel.create(data);
        return {
            code: 200,
            message: 'success',
            data
        };
    };
    
    // 更新
    module.exports.tetsUpdate = async (event) => {
        const obj = JSON.parse(event.body);
        const filter = { id: obj.id, pid: obj.pid };
        const update = {
            name: obj.name,
            email: obj.email,
            mobile: obj.mobile,
            nickName: obj.nickName
        };
        const data = await dynamodbModel.update(filter, update);
        return {
            code: 200,
            message: 'success',
            data
        };
    };
    
    // 删除
    module.exports.testDel = async (event) => {
        const obj = event.pathParameters;
        const filter = { id: obj.id, pid: obj.pid };
        const data = await dynamodbModel.delete(filter);
        return {
            code: 200,
            message: 'success',
            data
        };
    };
    
    // 查询
    module.exports.testGet = async (event) => {
        const obj = JSON.parse(event.body);
        const data = await dynamodbModel.query({ pid: 'ed5c6f8c-c31f-44a4-9968-bc9437d6d7c4' }).exec();
        const get = await dynamodbModel.get({ id: 'f279b477-9a32-4548-8bbe-35a2afb11c8b', pid: 'ed5c6f8c-c31f-44a4-9968-bc9437d6d7c4' });
        return {
            code: 200,
            data,
            get
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    s3文件系统保存,删除,查询和上传

    // 保存文件
    module.exports.testS3Put = async (event) => {
        const obj = JSON.parse(event.body);
        const param = {
            Key: obj.key,
            Body: Buffer.from(JSON.stringify(obj)),
            Bucket: obj.bucket,
        };
        const data = await s3.putObject(param).promise();
        return {
            code: 200,
            data
        };
    };
    
    // 根据url上传文件
    module.exports.testS3Upload = async (event) => {
        const obj = JSON.parse(event.body);
        const body = obj.body;
        const url = obj.url;
        await axios.default.put(url, body);
        return {
            code: 200
        };
    }
    
    // 获取文件
    module.exports.testS3Get = async (event) => {
        const obj = JSON.parse(event.body);
        const stage = process.env.STAGE;
        const param = {
            Key: obj.bucketKey,
            Bucket: `${stage}-bucket`,
        };
        const data = await s3.getObject(param).promise();
        const body = data.Body;
        const resData = Buffer.from(body, 'utf-8').toString();
        return {
            code: 200,
            data: resData
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    服务间lambda函数调用

    module.exports.testLambda = async (event) => {
        // 发送lambda函数事件 服务间调用
        // https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/API_Invoke.html
        const invokeParam = {
            FunctionName: 'project-service-dev',
            InvocationType: 'RequestResponse',
            Payload: {
                "method": "budget",
                "payload": {
                    "action": "create",
                    "userID": "62e4df570be7ae89e307671d",
                    "projectID": "1246e309-1598-4784-9036-cec91da37504"
                }
            }
        };
        const lambda = new Lambda({
            accessKeyId: 'xxx',
            secretAccessKey: 'xxx',
            region: 'cn-northwest-1'
        });
        const result = await lambda.invoke(invokeParam).promise();
        console.log('result', JSON.parse(result.Payload));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    现在我们业务代码开完完毕,但想要调试或者发布还需要配置serverless,它可以控制我们的访问,配置,权限等等

    代码示例中的表已经手动创建好,serverless配置中仅用于演示

    service: aws-node-project # 服务名称
    
    frameworkVersion: "3" # 本地serverless框架版本
    
    plugins: # 配置插件
      - serverless-offline
    
    provider: # 服务基本配置
      name: aws # 云厂商名称
      runtime: nodejs14.x # 运行时
      region: cn-northwest-1 # 地区
      profile: default # 配置文件所属环境
      environment: # 环境变量
        DYNAMODB_CUSTOMER_TABLE: ${self:service}-customerTable-${sls:stage}
        STAGE: ${sls:stage}
      iam: # aws iam角色
        role: # 角色名称
          statements: # 角色策略
            - Effect: "Allow" # 允许任何操作
              Action:
                - "dynamodb:PutItem"
                - "dynamodb:Get*"
                - "dynamodb:Scan*"
                - "dynamodb:UpdateItem"
                - "dynamodb:DeleteItem"
              Resource: arn:aws-cn:dynamodb:${aws:region}:${aws:accountId}:table/${self:service}-customerTable-${sls:stage}
    
    custom: # 配置插件环境
      serverless-offline:
        lambdaPort: 5003 # serverless函数端口
        httpPort: 5004 # http端口
    
    resources: # 数据源
      Resources: # 资源项
        CustomerTable: # 资源名称
          Type: AWS::DynamoDB::Table # 资源类型
          Properties: # 资源属性
            AttributeDefinitions: # 表结构
              - AttributeName: primary_key # 字段名
                AttributeType: S # 字段类型
            BillingMode: PAY_PER_REQUEST # 表的收费模式 按量收费
            KeySchema: # 表主键,排序键设置
              - AttributeName: primary_key # key名称
                KeyType: HASH # 主键
            TableName: ${self:service}-customerTable-${sls:stage} # 表名 这里用到的模板语法符合aws规则
        CustomerS3:
          Type: AWS::S3::Bucket
          Properties:
            BucketName: local-bucket # s3桶名称
    
    functions: # 配置函数入口
      testGet: # 函数名称
        handler: dev/handler.js.testGet # 函数访问路径
        events: # 事件
          - httpApi: # 事件类型
              path: /test/get # http访问路径
              method: post # http访问类型
      testCreate:
        handler: dev/handler.js.testCreate
        events:
          - httpApi:
              path: /test/create
              method: post
      testUpdate:
        handler: dev/handler.js.tetsUpdate
        events:
          - httpApi:
              path: /test/update
              method: put
      testDel:
        handler: dev/handler.js.testDel
        events:
          - httpApi:
              path: /test/delete/{id}/{pid}
              method: delete
      testS3Put:
        handler: dev/handler.js.testS3Put
        events:
          - httpApi:
              path: /test/s3/put
              method: post
      testS3Upload:
        handler: dev/handler.js.testS3Upload
        events:
          - httpApi:
              path: /test/s3/upload
              method: put
      testS3Get:
        handler: dev/handler.js.testS3Get
        events:
          - httpApi:
              path: /test/s3/get
              method: post
      testLambda:
        handler: dev/handler.js.testLambda
        events:
          - httpApi:
              path: /test/s3/lambda
              method: post
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    serverless本地调试
    // 本地调试
    serverless offline -s dev --aws-profile dev
    
    • 1
    • 2
    使用vscode debug serverless
    {
        "version": "0.2.0",
        "configurations": [
            {
                "type": "node",
                "request": "launch",
                "name": "Debug Serverless",
                "runtimeExecutable": "node",
                "program": "C:\\Users\\17319\\AppData\\Roaming\\npm\\node_modules\\serverless\\bin\\serverless",
                "args": [
                    "offline",
                    "start",
                    "-s",
                    "local",
                    "--noTimeout",
                    "--aws-profile",
                    "local"
                ],
                "protocol": "inspector",
                "env": {
                    "tableName": "player-points"
                },
                "windows": {
                    "program": "C:\\Users\\17319\\AppData\\Roaming\\npm\\node_modules\\serverless\\bin\\serverless"
                }
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    【JavaScript】try/catch和Promise.catch捕捉错误的区别?
    LTV预测算法从开发到上线,浅谈基于奇点云DataSimba的MLOps实践
    跟着官方学jni&ndk
    java学习--day22(进程&线程)
    Redis实战案例及问题分析之分布式锁解决优惠券秒杀场景集群并发下的安全问题
    项链 (爱思创算法四)(期中测试)(答案记录)
    WSL安装异常:WslRegisterDistribution failed with error: 0xc03a001a
    具有通信时延的多自主体系统时变参考输入的平均一致性跟踪
    WeakMap 弱引用 不会被GC所考量
    Linux之租云服务器及配docker环境
  • 原文地址:https://blog.csdn.net/qq_42427109/article/details/127786503