Serverless 实战:通过 Component 实现多地域部署容灾

时间:2022-07-22
本文章向大家介绍Serverless 实战:通过 Component 实现多地域部署容灾,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

作者 | 刘宇

策划 | 田晓旭

单点故障是实际生产中无法避免的,单副本的存储方案也早已无法满足业务的可靠性要求。现在,我们通常都会做双机存储架构,会涉及到主备、主从、主主模式。

在 Serverless 架构下,高可用方案或者容灾方案是否还需要主备、主从以及主主等模式?如果还需要,那么又是什么样子?

1 Serverless 与多地域部署

针对服务可用性,几乎每个云厂商都会有非常高的承诺,但是我们也不能掉以轻心,认为不会出现故障导致不可用,容灾方案是必须要有的。

在传统主机时代有主备、主从以及主主模式,这个模式更多针对的是单台机器或者某个集群。但是在 Serverless 架构下,没有机器和集群的概念(至少在用户层面没有),是不是就表示在 Serverless 架构下无法做容灾?

理论上,云厂商会保证整个服务的可用性,如果云厂商管理的某个机器出现故障,机器会被及时剔除,确保新的函数在安全、稳定、健康的环境下启动,正常提供服务。但在实际情况中,由于某些原因,云厂商也可能会在某个地域出现大规模故障,这时如何确保服务依旧可用,而不是苦苦等待云厂商的恢复?

针对单地域解析的网站,我们可以实现多地域的主备方案。在云函数中,多地域的主备方案更加经济实惠,因为函数是按量付费的,完全可以将函数复制到其他的地域,只要不进行触发,就不会产生额外的费用。相对于传统主机时代的主备模式,这种主备方案显得更加人性化。

一般情况下,单地域部署的服务中比较容易出问题的是 API 网关服务、云函数服务、数据库服务。其中,数据库服务可以考虑跨地域主从、跨区域同步,一旦出现问题,就在函数中做一个负载,确保整体数据不会出现问题,在腾讯云的云数据库描述下可以看到:

本地 IDC 机房 MySQL 数据库与云数据库 MySQL 之间可以通过数据迁移服务实时同步数据,本地 IDC 机房如遇到断电、网络故障等引起数据库服务中断,可迅速切换数据库服务至作为灾难备份的云数据库 MySQL 实例,实现数据库容灾;同时,云数据库 MySQL 可支持同城多可用区灾备 / 跨城灾备,保障高可用。

所以说,数据库相关服务的容灾可以通过备用数据库来提高可用性。

那么云函数和 API 网关怎么解决?

以多地域部署容灾为例,我们可以考虑这样的架构:

同样是作为单地域解析服务,相对来说,多地域部署更加安全稳定,一旦某个地域的服务出现问题(例如 API 网关,云函数),都可以通过监控程序及时发现,并且迅速切换解析到其它地域。多地域部署的监控函数与时间触发器进行结合,定期进行网站可用性的排查,一旦出现问题,就可以在云解析层面进行解析切换,实现单地域服务的多地域部署容灾方案:

这个方案的逻辑是先请求服务是否可用,如果不可用,则获取容灾列表,剔除不可用的服务,并通过云解析进行可用区的解析。在实际生产中,一旦确定某个服务不可用,还要进行精确告警,在获得到不可用解析记录对应的服务之后,通过邮件或者企业微信、短信等方法进行告警。

至此,一个简单版的“高可用”服务就算做好了,有的读者可能有所疑问:

对服务进行修复,是否比切换解析更加靠谱呢?

针对问题 1,其实切换解析是需要时间的,虽然一小部分的机房可能不会按照设定的 TTL 进行生效,但是大部分的机房都是可以按照设定及时生效,所以需要注意的是,在添加解析的时候要尽可能地确保 TTL 时间短一些,目前腾讯云的云解析付费版最低 TTL 可以设置为 1s,免费版是 600s。

针对问题 2,在云函数上运行服务很少会因为流量太高导致服务不可用,或者服务中存在 bug 导致整个项目不可用,因为云厂商会解决很大一部分的可用性,例如流量并发问题等。另外,就算是程序中存在 bug,在 Serverless 架构下,函数的前一次后一次运行实际上关联性不大,所以完全可以通过告警来确定,人为排查消灭隐患。事实上,在 Serverless 架构下出现大规模服务性灾难,多数情况都是云厂商的问题(此处已经排除掉用户代码层面的 bug),而这种问题一旦出现,就不是我们能够掌控的了,是否可以修复、什么时候修复。

如果是 API 网关层面出现问题,可以通过上一层来解决,例如云解析的切换;如果是函数层面出现问题,可以考虑切换到 API 网关到同区域的备用函数;如果是函数服务的整个区域性故障(概率非常低),可以考虑切换解析到备用区。

如果不是单地域提供服务,那么就需要考虑多地域部署、多地域就近接入以及多地域容灾方案。

从上图可以看到,我们将一个域名,划分地域进行解析,例如华北、华东两个地区,就可分别解析到北京 1 区、北京 2 区两个不同的机房中两台不同的服务器上。当其中一个服务不可用时,其他区域不受影响,可以使用云函数对解析进行修改,将其解析到每个地区的备用服务上。

整体结构图:

传统的服务需要每个地区部署多套服务,无疑大大增加了成本,但是在 Serverless 架构下,即使部署了多套云函数还是按量付费,大大节约了成本。

若采用 Serverless 架构作为后端服务,以华北地区为例,华北地区用户在访问后端服务的时候,通过 DNS 重定向到北京区的 API 网关,然后再由 API 网关触发北京区云函数,此时我们需要两个云函数对服务进行监控,一个函数对云函数进行监控,当云函数服务失效之后,可以将 API 网关绑定到备用的函数上,另一个监控是对 API 网关进行监控,当某个地域的 API 网关失效之后,可以对解析进行修改,重定向到备用地域的 API 网关上,尽可能快速的保证服务的可用性。

如果想要让服务更稳定,可以增加数据库 / 对象存储的主备与监控,以及数据库 / 对象存储封装函数的主备与监控,这样就可以在四个层次做监控告警以及服务保障:在我们的服务中,通常数据库或者存储模块不会同时在多个区域部署,也可能只有一套主备服务,此时,可以通过对数据库和对象存储做一层封装,即在外层增加一个对应的函数,通过内网对数据库以及对象存储进行数据转发等,通过外部云函数调用 invoke(专线调用)大幅度降低由于网络原因造成的延时,当云数据库 / 对象存储出现问题,在接入层(数据库 / 对象存储封装函数)一侧,进行切换,将云数据库 / 对象存储切换到备用服务上,并进行告警;当接入层发生故障,无法继续服务时,在逻辑函数初(北京区 / 上海区 / 广州区),切换封装函数,即通过内部专线(函数间调用)调用备用接入层函数;同理,当逻辑函数 / 业务函数出现故障,监控脚本对 API 网关侧的后端服务进行切换,切换到备用逻辑函数 / 业务函数上;如果当 API 网关出现故障,无法继续提供服务,则只能在解析层面做切换。在整个这样的一个流程中,每一阶段或者说每一层面都有自身的负载机制,主备策略,可以根据不同组件出现故障的实际情况,进行多层级的自动切换,进而保证业务可用时长的一个最大化。

此处,有读者可能有疑问:为什么某个函数会无法提供服务?底层服务的容灾机制,不是云厂商要提供的么?理论上,这个容灾机制是云厂商提供的,并且函数是无状态的,只要确保业务逻辑无问题,是不需要进行某些层级的主备容灾等。但是实际上,各个云厂商均没有办法保证某个服务 100% 可用。例如 2019 年 6 月 2 日凌晨 2 点,亚马逊云 AWS 突发受大规模故障,直到当天下午 1 点 48 分故障解除,故障时间将近 12 小时。所以,虽然云厂商会提供相对安全与可靠的容灾机制和服务,但还是很有必要在自身层面额外处理一下,尽可能保证服务安全与稳定。

2 通过 Serverless Framework 进行多地域部署与解析

函数的多地域部署

以腾讯云为例,基础的 Component 跨地域部署不是很容易实现。虽然修改区域将函数部署到多个地域是可以实现的,但实际部署时每个区域的函数还会有一些额外的配置,所以这个时候可以借助多地域部署的组件来实现:tencent-scf-multi-region

相对于传统的tencent-scf组件而言,这个组件的 yaml 将region字段变成了兼容list形式,同时增加了子地域的额外配置,例如 yaml:

hello_world:
  component: '@serverless/tencent-scf-multi-region'
  inputs:
    codeUri: ./
    description: This is a template function
    region: 
      -ap-guangzhou
      - ap-shanghai
    environment:
      variables:
        ENV_FIRST: env1
        ENV_SECOND: env2
    handler: index.main_handler
    memorySize: 128
    name: hello_world
    runtime: Python3.6
    timeout: 3
    events:
      - apigw:
          name: serverless_test
          parameters:
            protocols:
              - http
            description: the serverless service
            environment: release
            endpoints:
              - path: /users
                method: POST
              - path: /usersss
                method: POST
    ap-guangzhou:
      environment:
        variables:
          ENV_FIRST: env2
    ap-shanghai:
      events:
        - apigw:
            name: serverless_test
            parameters:
              protocols:
                - http
              description: the serverless service
              environment: release
              endpoints:
                - path: /usersd
                  method: POST

测试部署效果:

$ sls --debug

    DEBUG ─ Resolving the template's static variables.
    DEBUG ─ Collecting components from the template.
    DEBUG ─ Downloading any NPM components found in the template.
    DEBUG ─ Analyzing the template's components dependencies.
    DEBUG ─ Creating the template's components graph.
    DEBUG ─ Syncing template state.
    DEBUG ─ Executing the template's components graph.
    DEBUG ─ Compressing function hello_world file to /Users/dfounderliu/Desktop/ServerlessComponents/test/scf_test/.serverless/hello_world.zip.
    DEBUG ─ Compressed function hello_world file successful
    DEBUG ─ Uploading service package to cos[sls-cloudfunction-ap-guangzhou-code]. sls-cloudfunction-default-hello_world-1583816589.zip
    DEBUG ─ Uploaded package successful /Users/dfounderliu/Desktop/ServerlessComponents/test/scf_test/.serverless/hello_world.zip
    DEBUG ─ Creating function hello_world
    DEBUG ─ Updating code...
    DEBUG ─ Updating configure...
    DEBUG ─ Created function hello_world successful
    DEBUG ─ Setting tags for function hello_world
    DEBUG ─ Creating trigger for function hello_world
    DEBUG ─ Starting API-Gateway deployment with name hello_world.ap-guangzhou-hello_world.serverless_test in the ap-guangzhou region
    DEBUG ─ Service with ID service-p14470dc created.
    DEBUG ─ API with id api-pg3ihnoi created.
    DEBUG ─ Deploying service with id service-p14470dc.
    DEBUG ─ Deployment successful for the api named hello_world.ap-guangzhou-hello_world.serverless_test in the ap-guangzhou region.
    DEBUG ─ API with id api-op4jqvba created.
    DEBUG ─ Deploying service with id service-p14470dc.
    DEBUG ─ Deployment successful for the api named hello_world.ap-guangzhou-hello_world.serverless_test in the ap-guangzhou region.
    DEBUG ─ Deployed function hello_world successful
    DEBUG ─ Compressing function hello_world file to /Users/dfounderliu/Desktop/ServerlessComponents/test/scf_test/.serverless/hello_world.zip.
    DEBUG ─ Compressed function hello_world file successful
    DEBUG ─ Uploaded package successful /Users/dfounderliu/Desktop/ServerlessComponents/test/scf_test/.serverless/hello_world.zip
    DEBUG ─ Creating function hello_world
    DEBUG ─ Updating code...
    DEBUG ─ Updating configure...
    DEBUG ─ Created function hello_world successful
    DEBUG ─ Setting tags for function hello_world
    DEBUG ─ Creating trigger for function hello_world
    DEBUG ─ Starting API-Gateway deployment with name hello_world.ap-shanghai-hello_world.serverless_test in the ap-shanghai region
    DEBUG ─ Service with ID service-7daktopz created.
    DEBUG ─ API with id api-4v40ce4u created.
    DEBUG ─ Deploying service with id service-7daktopz.
    DEBUG ─ Deployment successful for the api named hello_world.ap-shanghai-hello_world.serverless_test in the ap-shanghai region.
    DEBUG ─ API with id api-emkl7ov4 created.
    DEBUG ─ Deploying service with id service-7daktopz.
    DEBUG ─ Deployment successful for the api named hello_world.ap-shanghai-hello_world.serverless_test in the ap-shanghai region.
    DEBUG ─ Starting API-Gateway deployment with name hello_world.ap-shanghai-hello_world.serverless_test in the ap-shanghai region
    DEBUG ─ Using last time deploy service id service-7daktopz
    DEBUG ─ Updating service with serviceId service-7daktopz.
    DEBUG ─ API with id api-2zag45hq created.
    DEBUG ─ Deploying service with id service-7daktopz.
    DEBUG ─ Deployment successful for the api named hello_world.ap-shanghai-hello_world.serverless_test in the ap-shanghai region.
    DEBUG ─ Deployed function hello_world successful

    hello_world:
      ap-guangzhou:
        Name:        hello_world
        Runtime:     Python3.6
        Handler:     index.main_handler
        MemorySize:  128
        Timeout:     3
        Region:      ap-guangzhou
        Namespace:   default
        Description: This is a template function
        APIGateway:
          - serverless_test - POST - http://service-p14470dc-1256773370.gz.apigw.tencentcs.com/release/users
          - serverless_test - POST - http://service-p14470dc-1256773370.gz.apigw.tencentcs.com/release/usersss
      ap-shanghai:
        Name:        hello_world
        Runtime:     Python3.6
        Handler:     index.main_handler
        MemorySize:  128
        Timeout:     3
        Region:      ap-shanghai
        Namespace:   default
        Description: This is a template function
        APIGateway:
          - serverless_test - POST - http://service-7daktopz-1256773370.sh.apigw.tencentcs.com/release/users
          - serverless_test - POST - http://service-7daktopz-1256773370.sh.apigw.tencentcs.com/release/usersss
          - serverless_test - POST - http://service-7daktopz-1256773370.sh.apigw.tencentcs.com/release/usersd

    35s › hello_world › done

通过以上部署,就可以成功将函数部署到不同的地域,并且针对不同地域进行额外的配置。
上层服务的多地域部署与解析
就目前来看,tencent-scf更多是一个基础组件,更多人则是在使用 Koa、Express 等组件,那么对于这种相对高阶的组件,是否可以多地域部署呢?从目前文档的描述来看是可以进行多地域部署,且可以进行自动解析,以tencent-tornado为例:
TornadoTest:
  component: '@serverless/tencent-tornado'
  inputs:
    region:
      - ap-guangzhou
      - ap-shanghai
    functionName: tornadoFunctionTest
    tornadoProjectName: app
    code: ./
    functionConf:
      timeout: 10
      memorySize: 256
      environment:
        variables:
          TEST: vale
      vpcConfig:
        subnetId: ''
        vpcId: ''
    apigatewayConf:
      protocols:
        - http
      environment: release
      customDomain:
        - domain: anycodestest1.com
          isDefaultMapping: 'FALSE'
          pathMappingSet:
            - path: /
              environment: release
          protocols:
            - http
        - domain: anycodestest2.com
          isDefaultMapping: 'FALSE'
          pathMappingSet:
            - path: /
              environment: release
          protocols:
            - http
    cloudDNSConf:
      ttl: 603
      status: enable
    ap-guangzhou:
      functionConf:
        timeout: 20
      apigatewayConf:
        protocols:
          - https
      cloudDNSConf:
        recordLine:
          - 电信
          - 联通

部署结果:

TornadoTest:
    functionName: tornadoFunctionTest
    ap-shanghai:
      apiGatewayServiceId: service-mdnjhsp3
      url:                 http://service-mdnjhsp3-1256773370.sh.apigw.tencentcs.com/release/
    ap-guangzhou:
      apiGatewayServiceId: service-nh6xgutk
      url:                 https://service-nh6xgutk-1256773370.gz.apigw.tencentcs.com/release/
    DNS:          Please set your domain DNS: f1g1ns1.dnspod.net | f1g1ns1.dnspod.net

通过这样一个组件,就可以完成框架的跨地域部署与解析。

3 总 结

至此,我们基本完成了一个基于 Serverless 的高可用框架的案例探讨,从对单解析的分析,到对多解析的策略制定,再到最后基于 Serverless 架构的高可用模型,虽然文中案例仅仅可以作为学习参考使用,在实际生产中会面临很多难题,有些地方还需要针对实际项目做一些特殊的修改和修正,但是这样一个简单的基于 Serverless 架构的高可用模型,基本上可以发挥出 Serverless 架构的特点,包括 Serverless 架构的按量付费功能,在不使用的时候则不需要收费,在部署多套云函数的时候不会因为部署量增加而产生额外的费用;在这个项目中,包括 API 网关等触发器对函数进行触发,也会包括函数间的编排和调用,更有 FaaS 与 BaaS 紧密结合,通过专线跨地域 Invoke,在外层还会增加多套监控告警函数以及 DNS 切换函数、云函数切换函数等尽可能保证服务的稳定与可用性;为了更加简单的进行多地域部署,还通过了 Serverless Framework 实现了多地域部署方案。

在实际生产生活中,无论是单地域服务还是多地域就近接入服务,多地域部署容灾都是很重要的,尤其在 Serverless 架构下,按量付费让主备模式成本骤降,100% 可用性几乎是一个不可能事件,但我们可以共同探讨相对优秀的高可用方案。