作者 | 曹宇
编辑 | 蔡芳芳
引 言
近几年来,低代码和开发平台成为了技术圈子的热点话题。很多企业也开始尝试使用低代码来快速搭建应用,从而减少开发成本和运维成本。FreeWheel 核心业务开发团队在打造云原生微服务架构的过程中,搭建新服务的需求日趋增多。为了应对这一挑战,我们研发了基于 AWS 的低代码开发平台。本文从低代码和开发平台的基本概念讲起,带你体验 FreeWheel 核心业务开发团队低代码的实战之旅。
什么是低代码开发平台
低代码(Low-code)是一种全新的开发范式,开发人员仅仅需要少量代码甚至是 0 代码就可以快速完成服务的搭建。低代码开发平台(Low-Code Development Platform, LCDP)是基于低代码和可视化界面的开发平台。咨询公司 Forrester Research 在 2014 年 6 月首次给出了它的定义:
低代码开发平台旨在通过很少的代码降低服务在全生命周期的开发成本,从而实现业务快速交付。
从定义不难看出,低代码开发平台是一种提效降本的重要手段。为此,低代码开发平台应当具备以下 3 种能力:
- 可视化界面
我们可以把低代码开发平台理解成一种 IDE。用户可以从它的组件库里以可视化甚至是拖拽的方式,像搭积木一样完成服务的创建。另外,和传统的 IDE 如 Visual Studio 的 MFC 所支持的可视化能力相比,低代码开发平台应当有能力支持端到端的可视化编程。
- 规模化生产
用户往往需要搭建不同类型的服务,甚至是不同语言的服务,这就需要平台具备规模化生产的能力。我们可以通过提供服务模板功能来做到这一点。不同的模板对应不同的业务场景下的最佳实践,用户搭建服务时选择合适的模板即可。
- 全生命周期管理
低代码开发平台需要支持软件的全生命周期的管理。通过平台,我们不仅要能够轻松地设计并开发服务,也要能够一键部署服务,还要满足服务的运维需求。平台对服务生命周期的管理也会带来聚合效应,使得平台成为服务的百科全书。
低代码开发平台的优势
事实上,低代码开发的诞生可以追溯到 2000 年代初期,如快速应用开发(Rapid Application Development,RAD)、Visual Studio 的 MFC 等工具。但与这些早期工具不同的是,低代码不等于零代码,而是要少写代码,比如通过少写重复代码来提高生产力、通过少写基础代码来屏蔽底层技术细节等等。那么,低代码开发平台可以给企业带来什么呢?
- 提效降本
低代码开发平台致力于以工业化标准化的方式替代传统手工作坊式的软件开发。平台提供了众多的基础设施、公共组件、自动化流水线等功能。这就使得业务团队能够从重复的工作中释放出来,更多的聚焦在业务本身。
- 质量保证
质量保证始终是软件开发绕不开的话题。线上故障频发,项目延期交付甚至成为了行业常态。低代码开发平台的引入将规范化软件开发的流程,减少人工出错的可能。
- 团队协作
软件开发过程非常的复杂,往往也需要不同职能团队的配合。但在传统的开发模式下,各个团队往往各司其职,长期来看会形成团队壁垒,使得跨团队的沟通极其低效。而低代码开发平台的引入会使得诸如业务开发团队、基础设施开发团队、运维团队工作在同一个平台下,轻松打破团队间的壁垒,实现高效协作。
作为 FreeWheel 核心业务的开发团队,我们发现微服务的构建有很多重复的工作,例如微服务的脚手架、CICD 等等。同时,公司新业务线的拓展也意味着新的微服务的诞生。因此,如何快速地搭建新服务成为了我们急需解决的问题。
低代码开发平台构建之路
经过数月的开发、试错与重构,我们打造了基于 AWS 的云原生低代码开发平台,公司内部代号 bingo。我们自研的低代码开发平台包含了一套 Web UI,用户可以通过可视化界面创建新的服务;平台提供了规模化生产、CICD、监控、日志等功能的支持。随着平台功能不断完善,运维团队的小伙伴也加入了 Bingo 的开发团队,Bingo 平台也已经在公司范围内推广使用。
平台的技术选型
针对 Bingo 平台,我们围绕以下几个方面进行了技术对比和选型:
- 前端技术栈
前端技术栈选择了 React。一方面,React 有着非常成熟的社区与生态;另外一方面,我们团队有着丰富的 React 使用经验。
- 后端技术栈
后端编程语言选择了 Golang。和其他 Web 框架如 Ruby on Rails 相比,Golang 使用更加繁琐,但有着更好的性能。此外,这也与团队微服务的技术栈一致。
- 数据存储
数据库选择了 Amazon Relational Database Service(RDS)。文件存储选择了 Amazon Simple Storage Service(S3) 和 Amazon Elastic File System(EFS)。云原生的方式极大地降低了维护成本。
- 部署方式
Bingo 平台的部署可以考虑 Amazon Elastic Kubernetes Service(EKS)、Amazon Elastic Compute Cloud(EC2) 或者 Amazon Lambda。鉴于平台可以独立于微服务集群存在,没必要部署到 EKS 当中。另外,和 EC2 相比,Lambda 更加灵活,部署更为简单,成本也更为低廉。因此,平台选择了无服务器的架构。同时,平台的 CICD 是自服务的,即 Bingo 的上线发布采用了 Bingo 平台提供的 CICD 的功能。这就使得平台上线任何新功能都可以通过平台做一手的验证,然后再交付给我们的用户。
平台的架构
根据以上对比,我们选择了一套云原生 定制化组件的架构,如下图所示。与业内流行的低代码开发平台类似,Bingo 平台有一套可视化 UI,即 Web UI。Bingo 的后端包含模板管理、服务管理、服务创建、服务部署等功能,每个功能是一个单独的 Lambda。存储层包含数据库存储 RDS 与用于存储模板代码、服务代码的代码存储 GitHub。
图中左边展现的是日志收集的过程,我们通过 Amazon CloudWatch 收集 Lambda 的日志,并经由 Kafka 将日志通过 ElasticSearch Logstash Kiabana 呈现给用户。图中右边是 CICD 部分,CI 流水线会在每次服务代码改动后将服务打包并上传到远端仓库;CD 流水线会从仓库中获取 Lambda zip 包,然后上传到 S3,再完成部署。部署使用了运维团队提供的基础设施即代码(Infrastructure as Code),很轻松地做到了不同环境部署的自动化。
平台的落地实现
Bingo 平台提供了一套可视化界面,支持服务模板的管理和服务全生命周期的管理。
服务模板的管理
我们提供了服务模板的管理功能来做到规模化的生产。针对每一种类型的服务提供一种模板,每个模板定义了一个业务场景的最佳实践,团队成员使用 Bingo 创建新服务时,根据业务场景选择合适的模板即可。这里举几个例子:
- Amazon Gateway Lambda 模板提供外部 API
- Amazon ALB Lambda 模板提供内部 API
- Amazon EventBridge Lambda 模板处理异步任务、定时任务
- Amazon Kinesis Lambda 模板处理数据流
- gRPC 微服务模板搭建基于 gRPC 的微服务
模板管理功能提供了模板列表页面和模板详细信息页面。模板列表页支持模板的分页和搜索的功能。可以点击特定的模板进入详细信息页面。每个模板都有一个对应的详细信息页面。页面包含贡献者、模板名称、模板代码的 git 仓库、使用场景介绍、关键字标签等等。其中关键字标签支持编辑功能,可以选择添加已有标签或者创建新的标签,也可以按需删除标签。此外,还可以通过点击创建模板问题按钮,来对模板提出反馈。如下图是一个使用 ALB Lambda 构建 API 的模板。
模板管理还提供了新增模板的功能。新增模板时需要填写贡献者、名称、模板代码的 git 仓库、使用场景介绍、关键字标签等等。其中模板代码的 git 仓库需要预先准备好,并包含对应的模板代码文件。
模板代码是模板的核心内容。模板代码由说明书、Makefile、配置文件、部署描述文件、流水线文件等组成,并包含一个可执行的 hello world 程序。下图是 ALB Lambda 的模板代码的目录结构。
- 说明书即 README.md,包含模板的名称和使用说明。
- functions 目录包含了一段可执行的代码,例如一个返回 hello world 的 API,团队成员可以基于此进行二次开发,实现自己的业务逻辑代码。代码示例如下:
func GetHelloWorld(ctx context.Context, req *model.Request) (interface{}, error) {
InitConfig()
log.Println("Hello World!")
log.Printf("Request path: %v", req.Path)
//get parameter value from Lambda env
DomainServiceUrl := os.Getenv("Bingo Service")
message := fmt.Sprintf("Message: Hello, Bingo! Bingo service url is [%s] (Time: %s) ",
DomainServiceUrl, time.Now().UTC().String())
return message, nil
}
// Init Viper Config from Environment Variables.
func InitConfig() {
v := viper.New()
v.AutomaticEnv()
config.Set(&config.Config{Viper: *v})
}
- Makefile 文件定义了单元测试、测试覆盖率、打包等命令。这些命令是约定俗成的,会整合到持续集成的流水线中。
- 配置文件是给开发环境、预发布环境、生产环境等环境使用的配置变量。
- 部署描述文件是基于 yaml 的 DSL,用来描述 AWS 云原生的部署内容。在服务实际部署时,DSL 文件会被转成基础设施编排工具 Terraform 可以识别的 tf 文件。部署描述文件的代码示例如下:
# bingo-alb-template bingo config
application: bingo-alb-template
common:
tags:
Project: Bingo
App: ALBTemplate
# lambda functions
functions:
- name: HelloWorld # required!
handler: hello # required! binary name
runtime: go1.x # optional, default go1.x
description: API of bingo hello # optional
timeout: 10 #optional, default 6s
environment:
- hello_env
events:
alb:
- priority: 1
conditions:
path: # path is array to support multiple path
- /bingo/hello
method: # method is array to support multiple http METHOD
- GET
functions:
- name: HelloWorld
weight: 100
在示例中,模板名称叫做 bingo-alb-template,AWS Tag 是 Project: Bingo 和 App:
ALBTemplate。模板还包含了一个叫 HelloWorld 的 Lambda,一个环境变量 hello_env 以及一个指向此 Lambda 的的 ALB。ALB 对外暴露了一个 path 为 /bingo/hello 的 HTTP 接口。而 hello_env 则定义在配置文件当中。
- 流水线文件是用来触发持续集成流水线的模板文件。
全生命周期的管理
Bingo 平台支持服务的全生命周期的管理。全生命周期如下图所示,是指从设计到开发,从测试到部署再到运维,平台均提供相应支持。
- 设计阶段
在设计阶段,平台通过服务模板提供服务设计的最佳实践。团队成员可以参考最佳实践来进行需求的调研和 AWS 云原生的调研,从而避免了从零开始设计新服务。团队成员也可以提炼总结新的最佳实践,并以模板代码的形式贡献到 Bingo 平台上,供未来使用。
- 开发阶段
在开发阶段,平台支持快速搭建新服务。创建新服务需要选择模板,并填写服务的名称、描述、git 仓库的名称、git 组织的名称,持续集成流水线以及服务的标签,如下图所示。其中,git 仓库、git 组织用来唯一指定服务代码的位置。持续集成流水线会有一个默认值,指向一个预先创建好的公共的流水线。团队成员可以选择使用默认流水线,也可以填写单独搭建的流水线的地址。服务的标签特指 AWS Tag。我们使用 Project App Service 三级 tag 来区分不同的服务。
AWS Tag 对我们来说非常重要。首先,可以用 tag 定义服务的唯一标识,便于资源的管理;其次,可以基于 tag 对于 AWS 开销进行统计,并定期清理没有 tag 的资源;最后,可以基于 tag 做好资源权限的隔离。
在 Bingo UI 上填好服务信息后,点击创建按钮即可自动创建服务模板,其流程如图所示:
具体来说,自动搭建的功能包含下述步骤:
1)验证服务的 git 组织是否存在,如果不存在则退出。
2)验证服务的 git 仓库是否存在,如果存在则退出,否则创建服务的 git 仓库。
3)赋予当前用户 git 仓库的开发权限。
4)根据服务模板的名称找到对应的模板的 git 仓库,然后克隆到平台服务器端。
5)根据用户需求,对模板代码进行编辑。如将模板名称替换成服务名称、按需增加或者减少公共组件库等等。
6)将代码的 git 远端从模板的 git 仓库修改成服务的 git 仓库。
7)使用 git 命令提交代码,并 push 到远端,从而完成框架代码的生成。
8)平台服务器端清理临时文件,并将结果写入平台的数据库。
9)开发人员基于 git 仓库中的框架代码进行后续的业务开发。
新建的服务是一个可执行的 hello world 代码框架,同时会自动对接好持续集成持续部署流水线。团队成员可以直接打包和并在开发环境进行部署,也可以通过 ELK 查看服务日志。
- 持续集成阶段
持续集成的流水线由开发人员创建新服务时候指定。我们推荐使用默认的公用流水线,从而减少维护成本。以触发公共集成流水线为例,部分代码如下:
代码语言:javascript复制 stage("Bingo_CI_Trigger") {
steps{
script {
if ("${BRANCH_NAME}" =~ "^PR-") {
// subci trigger
Triggered_Build = build(job: "UI/UI_CI/bingo/Bingo_SubCI_Pipeline", propagate: false, parameters: [
string(name: "GITHUB_TRIGGER", value: "true"), string(name: "GITHUB_PR_COMMIT", value: "${GIT_COMMIT}"), string(name: "service_name", value: "${service_name}"), string(name: "repo_git_url", value: "${SSHGitUrl}")
])
} else {
// fullci trigger
Triggered_Build = build(job: "UI/UI_CI/bingo/Bingo_FullCI_Pipeline", propagate: false, parameters: [
string(name: "GITHUB_TRIGGER", value: "true"), string(name: "GITHUB_BRANCH", value: "${BRANCH_NAME}"), string(name: "service_name", value: "${service_name}"), string(name: "repo_git_url", value: "${SSHGitUrl}")
])
}
if (Triggered_Build) {
Downstream_Url = Triggered_Build.absoluteUrl
Downstream_Res = Triggered_Build.result
echo "DownStream Build Result: ${Downstream_Res} n${Downstream_Url}"
if (Downstream_Res != "SUCCESS") {
err_message = "Build Result: ${Downstream_Res}"
ep_tools.highlight_info("error", err_message)
}
}
}
}
}
每个 Pull Request 会触发 subci,验证代码是否可以编译,单元测试是否可以通过。只有 subci 成功后代码才可以被合并。代码合并会触发 fullci,触发单元测试、回归测试,并生成测试覆盖率报告。fullci 会调用平台提供的 bingo 命令行工具对部署描述文件做格式校验,并对部署描述文件和服务代码分别打包,再上传到远端的 Artifactory 服务器,供部署使用。团队成员可以使用命令行工具在本地环境验证部署描述文件的正确性。
- 持续部署阶段
开发人员完成开发后,可以在平台上完成一键部署。以 Serverless 服务的部署为例,开发人员选择 AWS 账号、AWS region、部署的环境,并填写服务的版本号,然后点击部署即可。
部署流水线会从 Artifactory 服务器下载服务的 tar 包,解压后,将 Lambda 的二进制文件以 zip 的形式上传到 S3 上,然后从 Artifactory 服务器下载部署描述文件包,并将其转成
Terraform 可以识别的 tf 文件,最后使用 Terraform 完成服务的部署,同时将配置文件以环境变量的形式应用到 Lambda 上。生成的 tf 文件会包含 AWS 标签、Lambda 对应 S3 的地址以及其他 AWS 配置参数。tf 文件会上传到 GitHub 代码库中。这就使得只需要简单地修改参数就能完成不同环境的部署,亦或者是对 Lambda 的 zip 包替换。
代码示例如下:
代码语言:javascript复制module "Alblambda-Bingo-BingoAPI-" {
source = "../module/bingo/alblambda/v1_0_0"
tags = {
ENV = "${var.ENV}"
Project = "Bingo"
App = "BingoAPI"
Service = ""
Owner = ""
}
ticket = "ODP-1000"
enable_alb = true
enable_log2elk = true
lambda_pkg_s3 = "${var.lambda_pkg_s3}"
lambda_pkg_version = "v39-rev20210816-123456-aws"
alb_dnsname = "bingo_demo.${var.domain_name}"
lambda_functions = [
... ...
]
alb_rules = [
{
priority = 1
conditions = {
http_request_method = ["GET"]
path_pattern = ["/bingo/hello"]
}
functions = [
]
target_groups = [
{
arn = "......"
weight = 100
}
]
}
]
}
- 运维阶段
Bingo 平台还对服务的运维进行了支持。Bingo 平台和我们团队使用的基于 ELK 的日志解决方案进行了自动对接,屏蔽了繁琐的配置细节。仍旧以 Serverless 服务为例,日志收集流程如下图所示。服务产生的原始日志会被 CloudWatch 收集。而 Log Lambda 会将 Cloudwatch 中的日志写入到 Kafka,再由 ELK 消费日志。从 Cloudwatch 到 ELK 的过程对团队人员透明,服务部署成功后开发人员即可在 ELK 中查看服务的日志。
此外,Bingo 平台也我们团队使用和基于 Jaeger 的分布式追踪系统进行了自动对接,从而对服务的上下游进行追踪。
展望未来
Bingo 平台作为云原生的低代码开发平台,短短数个月的就取得了巨大的成功。平台极大地缩短了团队搭建新服务的时间,减少了开发和维护的成本,加强了跨职能团队的协作。
未来平台会持续提供不同的服务模板,沉淀云原生最佳实践,进一步增强平台扩展的能力。越来越多的服务登陆 Bingo 平台又将促进应用黄页的诞生。我们期待着打造一体化的可复用平台,构建 Bingo 的生态体系。
作者介绍:
曹宇,FreeWheel Lead Software Engineer,清华大学软件工程硕士,《云原生应用架构 - 微服务开发最佳实践》作者之一,曾发表若干篇美国软件专利,毕业后就职于 FreeWheel,热衷于技术探索与分享。