2019 年底,因为公司在业务研发的过程中,遇到了一些业务痛点,比如:公司的开发技术栈是 Java 相关的,而运维工程师擅长的则是 Shell 和 Python 脚本,无法直接对接;公司本身正处于快速增长期,开发工程师人力不足,无法支援日常的运维工作及运维平台的开发;在现有的运维平台中,使用了多种开源工具,而且没有整合,较难管理。因此我发起了自动化运维平台的项目,希望通过该运维平台实现快速上手的开发模型,可以实现运维工程师自己开发业务,并进行快速的迭代服务。
在进行网关选型时,我们进行了实际的测试。相对于其他网关,APISIX 基本上可以达到 NGINX 90% 的功能,并且支持了多种负载均衡策略以及支持多语言插件的机制,同时支持了软 WAF,可以覆盖我们95% 的安全业务场景。作为云原生 API 网关,APISIX 也提供了强大的日志功能,支持自定义日志格式,因此可以直接让 access log 对接 ELK。由于 APISIX 也支持自定义插件的开发,可以根据我们的需求灵活扩展。得益于 APISIX 的基础功能和强大的插件体系,可以有效降低开发成本。
自动化运维平台整体架构图如下:
存储层:核心是 CMDB,主要功能是记录和管理组织业务和 IT 资源的属性,以及其它们之间的关系。不但负责所有业务变更的起始状态查询,而且所有的业务资源的变更都要反馈记录在其中,实现业务标准规范的管控。存储层也包含一些权限管理的数据、业务工单的流转数据以及监控告警的时序数据;
公共基础服务层:提供原子业务的 API,也可以认为是基础中台,复用了大量的开源工具;
业务编排层:需要根据实际业务进行设计,工程师的工作就是把原子业务 API 按需求进行报文适配、流程组合、数据读写,并打包成为接口供前端调用;
网关层:APISIX 所在的层,是后台服务的业务边界,负责负载均衡、服务注册与发现、用户鉴权、基础网络报文数据转码、内外交互日志的统一记录、部分安全管控等等。与业务无关并且通用的服务统一放置到本层;
展示层:从用户角度出发,设计便利的交互界面。此处使用了一个开源的前端全响应式 admin 网页模板,即使开发者(运维)不熟悉 JavaScript ,也可以自己实现基本的表单和报表。
核心网关 Apache APISIX:主要负责日志记录、网络安全以及负载均衡。另外我们不但通过自定义插件实现了高级业务网关的部分功能,而且还通过 API 能方便的和其他服务整合,快速实现各种指定功能,有效降低开发成本;
API 管理工具 YAPI:负责对接口的规范定义,测试用例编写和作为 ACL 的数据源;
访问控制组件 Casbin:轻量级、多模式、强范式的跨语言开源访问控制框架,我们使用的是基于RESTful 的 PyCasbin;
数据存储:MySQL 5.7;
自研 Web 框架 mug-skeleton:使用自研的 Web 框架,主要是为了更深层次的技术控制能力。
对接的第三方平台相关组件
OpenLDAP:用于用户的账号验证,不负责鉴权;
工作流 Activiti:使用官方的 RestAPI 服务,由于是处于在网关后方,因此不需要考虑安全问题。
对于所有的 Web 框架,用户登录是一个必选项,接下来我将为大家介绍此场景。
首先,我们需要了解下场景中,我们的使用的相关组件,第一个就是访问的前端,这个是在网关之外的,其次使用 APISIX 云原生 API 网关作为业务边界。再之后的 Auth 服务,它是自定义开发的微服务,作用是校验前端的 URL 请求和用户登录请求,并对通过认证的用户发放 Token。LDAP 中存放的是公司内部的密码信息。CMDB 存储的是一些业务的相关信息,包括组织结构,可以访问的权限的一些组织信息,最后是前端需要访问的页面。
了解完以上组件后,接下来,为大家介绍整体流程:
用户登录的时候,首先需要通过网关查询,访问的页面是否在白名单中。因为部分页面是不需要权限验证的,比如:默认页面或者一些错误页面。如果访问的页面是需要验证登录的,那么这些请求就会通过相关插件,转发到权限认证服务。
在权限认证中,鉴权服务会根据传入的“用户名”和“密码”,从 LDAP 中查询账号是否正确。如果正确,就会通过 CMDB 查询该用户是属于哪个组织、可以查看哪些功能模块;得到结果后,使用 APISIX 的 JWT 插件,根据用户信息生成一个 Token,并添加过期时间,返回给前端;用户通过 Cookie 的方式进行Token存储。该用户后续如果继续访问,网关会从 Cookie 中把之前存储的 Token 调出来,验证当前用户是否可以继续访问后面的页面。
在这里,我们使用了 APISIX 的 consumer-restriction
插件,上述所讲的权限认证,实际上就是通过 consumer-restriction
插件来完成的,不需要我们在后台多次反复认证。
通过上述的描述,相信大家已经对正常的请求流程有了一定的理解,接下来将为大家介绍下如何判断这些用户权限不足的场景。在运维平台中,如果有涉及到数据变更的操作,必须要携带 Token,当这个 Token 被 ACL 的接口验证无权访问后,就会直接返回一个禁止访问的页面,让前端进行处理。以下是用户登录及权限验证场景的具体流程和其中更使用的相关组件。
在日常工作,我们经常会上线一些微服务,那么如何让这个微服务接入自动化运维平台呢?
我们内部会规定无论工程师使用哪种语言开发微服务,都需要使用 YAPI 对 API 进行定义。因此 YAPI 对我们所有可访问的那些 URL 进行管控,统一一个入口在这边。因为 YAPI 支持定义各种环境,所以我们在 YAPI 中定义了不同运行环境。最典型的示例就是:在生产环境中,我们会使用域名访问;而在开发环境,就直接使用 127.0.0.1
进行访问。完成 YAPI 的定义后,它就可以通过 mock 的方式,生成一系列请求用例,非常有利于后续进行生产环境的测试。所有的微服务接口,都可以通过 HTTP 请求的方式进行 mock 调用。
接下来,就是权限管理服务,这里所有的操作都是自动的:它会从 YAPI 中读取 API 的定义,然后生成一系列的 ACL 规则。对于权限的管理,我们在平台中使用了一个管理页面:管理员可以通过该页面管理 URL 的访问规则,设置完成后,表单数据就会变更为一系列的 ACL 权限定义,存入数据库中。在服务启动的过程中,平台使用的 cachebin 的访问模型就会直接从数据库中,把这些规则加载到内存里,然后生成一系列 APISIX 的 Consumer 的定义及路由表,写入 APISIX 的 etcd 中。完成上述操作后,当用户访问的时候,平台就可以直接通过 APISIX 进行一个权限管理。
该模型不但适用于自动化运维平台,也同样适用于各种中小型业务体系。
通过上述的场景描述,相信大家已经对整套体系有了大概的认识,接下来为大家介绍下部分技术细节。
因为 APISIX 是基于 NGINX + Lua 实现的,所以部分功能需要通过 NGINX 的库来实现。从上图中,我们可以看到各种 Lua 脚本可以在哪些点切入到 NGINX 当中。在本文中,主要是为大家介绍 Rewrite/Access 以及 Content 阶段可以进行的操作。
因为在 Rewrite/Access 阶段,报文还没有转给 Upstream,所以可以在该阶段进行各种各样的数据预处理。从上图中我们可以看到有个 access_by_lua
,在该阶段,可以使用 deny 命令进行权限的管理,包括接口权限以及 IP 准入白名单都可以在该阶段实现。后文所介绍的 acl_plugin.lua
的插件就是在该阶段实现的。
其次在 hard_filet_by_lua
这个阶段,常用于在请求访问时,额外的在 HTTP 请求头插入一些 key:value
,供后续使用。例如,当需要我们线上灰度发布时,就可以在用户的请求头中加入标志位,通过这些标志位,就可以控制这些请求转发哪些后端服务,从而实现灰度发布。当然我们也可以使用 APISIX 的 traffic-split
插件实现灰度发布。
最后就是 log_by_lua
阶段,在该阶段,我们可以把一些 trace 信息或者一些故障信息可以直接输入到 log 文件中。同样的,针对 Loggers
,APISIX 也提供了非常多的插件,包括 skywalking-lo``g``ger
、kafka-lo``gger
、rocketmq-logger
等等。
acl-plugin.lua
acl-plugin.lua
插件的实现非常简单。首先当用户在请求的过程中,我们会给用户添加相关的 JWT token 存储在 cookie 里面,之后该用户会从访问的 cookie 中提取 JWT token,然后对该 token进行解码并获取用户信息。
在 Rewrite 阶段,通过使用用户 ID、method及 URI,向后台 ACL 接口发起请求进行权限验证。如果通过了,就会把相关信息记录到 log 中,供以后的安全认证使用。如果失败了,就直接返回一个错误状态码并记录到 error log 中。
在 APISIX 1.1 版本中,当时 cors
插件还没有发布,因此对于跨域请求,我们也是通过该插件进行实现,当请求使用 GET 和 POST 的请求方法时,会进行相关的处理。如果是其他请求,则会直接通过,而现在可以直接使用 APISIX 的 cors
插件实现。APISIX 现在也可以使用多种语言进行插件的开发, 不仅仅是 Lua,详细信息可参考:https://apisix.apache.org/zh/docs/apisix/plugin-develop。
Auth 服务是与 acl-plugin.lua
插件配套的认证服务。该服务实现的功能非常简单,主要是读取请求报文中的信息,然后解码出所需的认证元素,之后再把它转发到相关的服务接口中。服务接口会根据认证信息返回相应的结果,APISIX 会根据结果拒绝或通过该请求。
Auth 服务中最核心的功能就是从数据库中把 ACL 规则加载到内存里面。主要功能分为两部分:
account
接口。该接口主要作用就是:用户访问的时候,如果需要权限认证,则会通过向 LDAP 服务发送用户的相关信息,进行认证。如果认证通过,则会从 CMDB 中查询出用户可访问的相关信息,然后和用户角色、过期时间等元素,一起组成 JWT Token,并生成一个 Cookie 返回给用户,并且同时把该用户信息在 APISIX 中注册一个 Consumer 。该接口还实现了一个 acl_check
的功能,负责对用户认证信息验证,判断该认证是成功还是失败。yapi
接口。该接口的主要作用是与 YAPI 进行交互。因为 YAPI 中有一个是供项目访问的token,带着这个 token,就可以读取到这个项目所有的 API定义。因此该接口的主要功能,就是从 YAPI 中读取 API 的 HTTP 接口定义,存储到数据库中,然后和权限管理的页面进行一个表单交互,组合成 ACL 表,最后生成一系列 Casbin 的规则存到数据库中。以上就是同程数科基于 Apache APISIX 的自动化运维平台的架构及部分场景的介绍。现在,APISIX 的功能越来越强大,已经支持使用 Wasm 和 Python 进行插件开发。Apache APISIX 的生态也非常强大,如果大家有任何问题欢迎到社区中进行交流讨论。