乐趣区

关于云计算:Cloudpods-Golang实践

Cloudpods 是齐全自研的一套云平台,Golang 是该平台的次要后端开发语言。本文介绍咱们在平台开发迭代过程中对于 Golang 的教训以及在 Golang 上积攒的框架和库,包含积攒的 Golang 工具库,以及基于这些工具库实现的开发框架。

1、背景介绍

Cloudpods(云联壹云) 从 2017 年开始迭代开发。过后企业的 IT 环境曾经不仅仅是本地的虚拟机以及裸金属,不少企业曾经逐渐驳回多云。所以 Cloudpods 平台作为新一代的云平台,须要不仅能治理本地 IT 环境中的虚拟机和裸金属,还能治理其余的云平台的资源,特地是私有云。实现所有的资源在一个平台上对立运维,操作,起到升高运维复杂度并进步企业 IT 运维效率的目标。

为了实现一个对立的多云平台,咱们采纳了最适宜开发云原生利用的 Golang 作为后端开发语言。前端则采纳 Vue 框架。整个平台基于微服务框架。服务之间的认证鉴权基于 OpenStack Keystone 的框架(咱们用 Golang 从新实现了 Keystone)。

2、Cloudpods Golang 技术栈

云联壹云的 Golang 技术栈蕴含两局部:

首先是一个 Golang 的服务框架,所有的服务组件都基于同一套服务框架来开发,这个服务框架针对云平台的特点做出优化,适宜疾速开发云服务的 API 和异步工作逻辑。

其次是四个次要的 Golang 工具:

3、Golang 框架

大部分 Cloudpods 服务都基于同一套服务框架开发,此框架的特点是针对云服务开发进行了适配和优化。

Cloudpods 的服务框架能够简略地认为是一套不便 CRUD 的脚手架。云服务次要是对云资源的操作,比方云资源的创立、删除、更新等。服务框架内置了云资源的 CRUD API 的根底框架,再加上异步工作机制实现对云资源的简单操作以及资源模型的批改。

除 CRUD 脚手架外,服务框架把认证,权限、配额等云平台特有的性能内置集成,使得认证和权限成为云平台默认的标配,同时简化相应性能的实现。

首先,服务组件之间的通信基于 OpenStack Keystone 认证,咱们将 keystone 认证加到框架中,使得服务组件开发者不需关注 keystone 认证流程如何实现,就能人造反对 keystone 认证。其次,每一个 API 申请都受到权限的管制,因而把权限管制也集成到框架中。开发者在实现 REST API 时,根本不用为权限管制实现相应的代码,就可能人造地将权限管制集成到 API 中。

与此同时,每一个服务都有相应的配置参数,如何不便地治理各个服务的配置,容许对配置更新并同步到相应的组件使其失效,此过程绝对简单。咱们将服务配置的性能集成到框架中,开发者采纳框架不用思考配置的存储、更新、服务器读取更新并使配置失效,这些简单事宜已在框架中解决。

另外,还有异步工作的治理性能。平台是一个分布式的零碎,云控制器须要去操作和治理数据计算节点、裸金属的治理节点。协调组件之间的简单操作,例如将虚拟机、裸金属创立起来,这些都是分布式的工作治理,在平台中也嵌入了异步工作治理框架。如此即可较为不便地实现异步工作。

4、CRUD 脚手架原理

在平台中,每一种资源,例如主机,在底层对应到数据库 MySQL 表,资源的状态、相应的属性都记录到了 MySQL 表中。

用户通过调用 API 对数据进行操作,在数据操作的同时也能调用异步的工作去实现相应性能。落到底层代码中就是一种资源对应到一张 MySQL 表。

为了比拟不便地实现对数据库 MySQL 记录的操作,针对每一个资源,都会对应到一对 ModelManager 和 Model 的数据结构。ModelManager 数据结构对实现一类资源的汇合操作,例如创立资源或者列表,而针对单个资源的操作,则通过 Model 来实现,实现对单个具体资源的更新、删除的操作。

每个资源的 Model 对应到 Golang 的一个构造体,该构造体有若干字段,每个字段代表资源的属性,例如有一个用户的资源蕴含用户的 id、extra 属性, 用户是否 enabled,用户何时创立,归属的域等。一个属性就是 Golang 构造体的一个字段,通过构造体字段的 Tag 属性定义每个字段在 MySQL 的数据库中对应的 schema 的定义。

例如 Id 这个字段,属性中有 width:”64″ charset:”ascii” nullable:”false” primary:”true”

这些 tags 定义了 Id 这个字段是数据库外面的一个 VARCHAR(64) 的字段,并且他的字符集是 ascii 码,不能为空,并且是主键。所以通过 tag 将构造体的字段映射到了 MySQL 的 schema 的字段中,如此,每一个 model 通过字段的定义就可能清晰地映射到 MySQL 的数据表中。这样实现了 Model 的字段和 MySQL 的数据表定义的严格同步,每次程序启动时都会进行 schema 的同步查看,如果 Model 的定义和数据表的定义不统一,就会执行相应的 SQL 的变更操作,将表的 schema 定义和 Model 的定义变更为统一。

例如咱们将 Id 的宽度从 64 改成 80,在程序重启时就可能发现这个变动,而后将数据表该晓得的宽度变更成 80。如此实现通过代码定义的 Model 和数据库中的表定义的严格同步。(另外,也反对离线变更数据库 Schema,即只检测数据库 Schema 的变更,并且输入对应的 SQL 语句,通过专门的离线数据库 schema 变更工具实现数据库的变更。)

与此同时,每类资源都会提供一系列的 API,此处列出了对一个资源会实现的九类 API,包含创立、删除、更新、执行操作、获取详情、列表等操作。

每一个操作对应一个 REST API,每一个 REST API 对应到后端代码中就对应到了每一个资源对应的 ModalManager 或者 Model 的办法。

例如获取资源详情的 REST API 是: GET /resources/<res_id>

调用这个 REST API 其实就映射到相应的 Model 的 GetDetails 办法。为了实现获取资源的详情只须要去实现 Model 中的 GetDetails 的办法。

通过框架简化了实现云资源 REST API 的流程,只须要把相应的 Model 和 ModelManager 的办法依据输出实现相应逻辑,而后把正确的输入返回回去,这个 REST API 的性能即可实现。

如此诸如鉴权、认证、配置同步等周边的工作在框架中实现,从而大大晋升了开发效率并升高在开发过程中犯错的几率。

5、Golang 工具库

上面介绍在 Cloudpods 开发过程中积攒的 Golang 工具库。

  • jsonutils

jsonutils 是一个 JSON 序列化和反序列的工具库。

Golang 的规范库中带的 JSON 库是 encoding/json,encoding/json 是一个十分弱小、十分高效的 JSON 序列化和反序列的工具库。encoding/json 实现的是 Golang 的数据结构和对应 json 的字符串之间的互相转换。能够把 Golang 中的构造体通过 Marshal 的形式生成一个 Json 的字符串,或者把 Json 的字符串通过 Unmarshal 放到相应的构造体中的各个字段,这样即可拜访构造体去取得 json 中的这些值。

jsonutils 与 encoding/json 相比的显著区别是两头减少了一个两头态,在 jsonutils 库外面实现一个两头态的通用类型的数据类型 JSONObject。咱们能够把数据结构 Marshal(s)成 JSONObject,JSONObject 是 Golang 的 interface,该 interface 能够进一步地序列化成 json 字符串。

这个两头态就是应用 jsonutils 的重要起因,通过通用类型的 JSONObject 就能够实现任意的构造体都能够 Marshal(s)成 JSONObject 而后能够把 JSONObject 作为函数参数进行传递。

Golang 是一个严格类型查看的动态语言,它的每一个变量都有相应的类型,咱们的框架可能解决任意 API 的输入输出,如果没有两头的构造体,在解决 API 的输入输出时,输出是 json 字符串,为了在程序上拜访它,就必须把它反序列化成严格的有类型构造体,这样一来就无奈将框架变成通用框架。

如果引入通用的 JSONObject,在框架中输出了 json 字符串,先把它反序列化成 JSONObject,这个 JSONObject 是通用类型的,这样就能够将 JSONObject 作为参数再进一步的向下传递,直到传递到具体相应的 Model 或者 ModelManager 相应的办法中,而后进一步把它转换成相应的构造体。这样就容许框架中应用曾经反序列化好的 JSONObject 并进行操作,能够实现比拟通用的框架。

平台采纳 jsonutils 最次要的起因是不便实现通用的框架。同时 jsonutils 还有其它特别之处,JSONObject 不仅可能转换成 JSON 字符串,也能够转换成 QueryString 或者是把 QueryString 反序列化成 JSONObject 或者能够序列化成 YAML 的字符串。

这样能够实现更不便的性能,比方对于列表 GET 这种读取的这种 API,它的参数通常作为 QueryString 嵌入到 URL 中。如此咱们能够将 QueryString 参数在框架中反序列化成 JSONObject,把它作为 JSONObject 输出参数传入到框架中。

对于其余类型的申请,能够把申请参数放在 HTTP 申请的 body 中的 JSON 字符串,咱们同样能够把申请参数解析成 JSONObject。这样,能够以同样的逻辑去解决嵌入到 URL 中的 QueryString 的参数以及嵌入到 body 中的 JSON 的参数,能够做到对立解决出入参数的逻辑。

另外 jsonutils 还有还有一些针对云平台 API 做的一些特地的一些解决。较为特地的一点是反对构造体字段的版本变更。

以下为举例说明:

例如有一个输出参数的构造体称为 Input,有一个字段是 TenantId,用来标识用户的租户 ID。

随着版本的降级,心愿将 TenantId 对立改名为 ProjectId,这种降级如果不做任何解决,将可能呈现接口兼容性的问题,在变更之前这个字段是 TenantId,变更之后这个字段就只能是 ProjectId。降级后,应用 TenantId 的客户端就不能正确拜访这个接口。

在这个构造体中,咱们减少了特地的 tag:yunion-deprecated-by,把这个构造体 input 降级为新的 input 的构造之后,减少了 ProjectId 的字段,用它来代表新的 TenantId 的属性。

旧的 TenantId 依然保留,然而在构造体的 tag 中加了名称为 yunion-deprecated-by 的 tag,这个 Tag 的值是 ProjectId。表明 TenantId 的字段曾经被 ProjectId 这个字段 deprecated。

代码在解决时,如果旧客户端的参数中只有 TenantId,此时框架就会将 TenantId 的值依据 yunion-deprecated-by 这个 tag 指引同时 copy 到 ProjectId 的字段中。

这样,如果旧的客户端去拜访新的接口,在新的接口中同样能够用 ProjectId 这个字段去获取这个值。这样保障了旧的客户端能够拜访降级后的接口。

  • sqlchemy

Sqlchemy 是后面介绍的 Golang 服务框架中实现 Model 和数据库表映射的底层实现。

Sqlchemy 实现了 Golang 的数据结构到 MySQL 表单向同步,可能依据构造体字段的定义以及字段中 tag 的定义,生成准确的 MySQL 的 schema。

Sqlchemy 能保障数据库中 schema 总是严格地和构造体定义保持一致,如果不统一,可能主动地变更数据库,将数据库 Schema 和构造体的定义变更为统一。

Sqlchemy 另一个重要个性是可能实现结构化的数据库查问语句。

 

这里举一个简略的例子。如上图所示,咱们要对 UserTable 表进行一个查问。首先实例化 UserTable 实例,而后调用它的 Query 接口返回一个 SQuery 实例,再调用 Equals 办法,进行过滤。这样就示意在查问 user 表,并且要求 domain_id 这个字段要等于指定的值。Sqlchemy 在执行时会把构造体的结构化查问变成 SQL 的查问语句,并送到 MySQL 进行执行。

应用这种代码化的数据库查问形式的长处是防止了人工拼凑 SQL 容易呈现的问题, 并且 Golang 是一个严格的动态语言。能够通过 Golang 的语法查看保障查问语句的正确性。

另一个益处是因为把 SQL 的查问代码化,这样就能够把数据库查问的逻辑进行肯定水平的代码复用,这样就不必反复同样的查问语句,而是调用一个办法,这个办法中就嵌入了 SQL 的查问逻辑。

框架中所有数据库的操作查问都用到 sqlchemy,在肯定水平上保障框架代码中 SQL 语句的正确性以及执行效率。

  • structarg

还有一个比拟重要一个库是 structarg,这个库的作用是将程序的命令行参数或者配置文件信息和代码中的构造体做严格的映射。

这样能够基于构造体主动生成程序输出参数的提醒,并且可能依据命令行参数将参数中的值反序列化放到构造体中,或者是将对应配置文件中的参数反序列化到构造体中,在程序中就能够通过拜访构造体的字段去拜访相应参数的值。

如此即可比拟不便地在程序中应用配置的信息,这里举个例子。

假如定义了 UserOption 的构造体,这个构造体 tag 中蕴含一个 help 的 tag,这个 tag 的定义了构造体的每个字段的帮忙信息。将其初始化并进行程序编译之后,如果命令行中执行带 help 的参数的话,其将会把这些帮忙信息展示进去。

同时,用户也能够在命令行参数中去用这些参数把值传递到程序中,在程序代码中即可通过拜访构造体去拜访这些参数。

同时,也反对配置文件的输出,而且配置文件同时反对 Key=Value 格局和 YAML 格局。

这样能够将程序的配置文件的配置信息通过构造体定义下来,程序就可能主动地辨认配置文件的信息,而后将信息放到构造体中拜访。

框架中对配置管理的根底就是 structarg 的性能,利用该性能将每一个服务的配置用构造体来定义,并且配置的信息会存到数据库中,在数据库的信息进行批改之后,框架会把数据库中的配置信息拉取下来,而后把它反序列化到构造体中,程序中可能感知到配置的变更,并做出相应的配置变更解决。

  • pkg

除去三个比拟重要的库之外,还有一些其余辅助的工具库在 pkg 这个库中。包含模拟 python 的 prettytable 的格式化输入数据的库,错误处理库,保障 key 程序的 map 的实现等。这里就不一一介绍,感兴趣的读者能够浏览相干源码。

退出移动版