在进入千禧年后,随着计算机技术的发展和业务创新的不断涌现,许多大公司内的 IT 计算中心也在酝酿着变革。一方面,各部门相对独立的 IT 管理平台已经难以满足日益增长和不断变化的计算管理需求;另一方面,IT 计算中心也越来越多的成为业务创新的发源地,从一个成本中心向营收来源发展。相应的,一种围绕着资源和负载管理平台的技术领域逐渐称为学术界和产业界的热点。它在不同的发展阶段和不同的应用场景有着不同的名字,从最早的集群(Cluster),网格(Grid),到后来的数据中心操作系统(DCOS),云计算(Cloud Computing)。同时,在资源管理和负载管理这两大方面也不断的拓展着自身的边界。
目前,以 Kubernetes 为核心代表的云原生技术逐渐称为这一领域的主流。它所带来的不可变基础设施,以资源为管理对象,描述性的 API,最终一致性等等理念,不仅改变了平台使用者的习惯,能够更好的在资源弹性变化的环境中保持业务访问的连续性;更改变了服务和应用研发人员的思维模式,逐渐以这种云原生的方式来设计和开发,提高创新效率。
阿里云有“公共云”、“专有云”两种产品形态,使用同样基于飞天操作系统的技术路线,为用户提供从服务器开始一整套资源和负载的管理能力,以及运行在上面的各种服务。与此同时,也有很多客户由于种种原因无法完全使用公有云服务,也无法采购和部署一整套大专。在基于云原生的产品领域中,这些客户或者接触了 Kubernetes 等开源项目,或者试用过红帽,Rancher 等厂商提供的 Openshift,Rancher 等平台产品,甚至体验了围绕这些产品构成的整个产品家族生态,如 IBM Cloud Pak 等。他们惊喜与这些云原生技术带来的敏捷,开放,韧性等特点。同时,也希望在此基础上获得支持不同业务场景的丰富服务,以便快速提升创新能力。
应对这种需求,云原生 PaaS 团队经过长时间的技术和经验积累,历时近半年的研发,创建了 CNStack 2.0 云原生技术中台产品。在接下来的内容里,将分享在 CNStack 2.0产品,架构,研发中主要的设计思想和心得体会。
产品目标
阿里云使用云原生技术在资源和负载管理这一领域的探索已经有一段历史了。在长期与解决方案团队,产品团队,外部客户的沟通合作中,发现使用者对云原生技术带来的以下特点最为关注。
敏捷
敏捷并不简单的等同于轻量,它更多的代表灵活性和因此而带来的效率的提升。
用户希望平台和服务能够随着不同的应用场景和规模,提供不同的部署配置选项。既能够管理三五台服务器,提供一两种服务或者公共负载类型,也能够管理百台服务器,满足几个业务团队的不同诉求,甚至管理数千上万台服务器,跨越多个地域,支持不同行业线的复杂服务。
针对这些场景和规模的差异,用户需要的只是做好资源规划,在部署平台和服务时,选择不同的配置选项。无论场景和规模的差异,平台都会提供一致的使用体验。同时,所需的管理资源开销最好能够随着规模的增大和功能的增加,对数或者线性级别的增长。此外,用户也可以很方便的获取产品,不需要复杂的硬件资源就可以部署和试用,例如公有云申请的服务器上,甚至是自己的笔记本电脑上。
敏捷还体现在产品功能的组合和升级,可以根据需要,增加和减少功能和服务,以响应不断变化的创新诉求。
开放
用户希望能保护已有的技术投资,也希望能方便,快速的补充开源社区或者其他厂商的技术创新。这就需要产品具备开放的特点。这里的开放主要是指采用具有开源社区生命力的技术框架和标准的协议规范,产品自身也拥有丰富的扩展机制。
一个开放的产品,可以让用户
- 方便的获取技术资料和试用环境,降低学习门槛
- 通过标准的协议规范和扩展机制,集成其他厂商的产品或者被其他厂商产品集成,保护技术投资
- 支持更多的基础设施类型和工作负载类型
- 与开源社区一起不断发展,保持技术先进性
生态
技术中台作为业务创新的核心,需要支持多种业务类型,涵盖业务创新的生命周期。从微服务框架,中间件,到 AI,数据处理;从研发设计,制品管理,到应用发布,容灾高可用;从本地数据中心,到边缘,公有云。
为了支持如此丰富的能力,需要围绕技术中台构建产品生态,创建云服务,云组件的标准规范和支持标准规范的工具链。
- 云服务:通过服务的形式在平台提供能力扩展,可以使用平台提供的用户,租户,鉴权,审计,许可证,多集群部署,UI 框架等基础能力,与平台既有能力或其他服务无缝的协作。和整个平台的运维一样,云服务的生命周期管理由管理员负责。
- 云组件:通过部署声明的方式为平台用户提供软件的部署和运维,所部署的软件可以和用户自研软件一起编排实现业务流程。和用户自研软件一样,云组件的生命周期管理由具体的使用者负责。
上述是 CNStack 2.0 当前所支持的云服务一览。这些云服务与平台自身的发布完全解耦,阿里云研发团队或者合作伙伴可以方便的针对业务场景扩展技术中台能力,独立发布云服务。需要重点强调的是,CNStack 平台自身也是以云服务方式开发,运维,管理的。
CNStack 2.0 为云服务和云组件的集成,测试和发布提供了一整套规范的工具链,它就是**阿里云云原生应用交付平台(Application Delivery Platform,简称 ADP) [ 1] **。CNStack 2.0 自身也是使用 ADP 开发和发布的。
在 ADP 平台上,开发者可以将构成云服务的组件以 helm charts 的形式上传。平台根据研发规范扫描检查后,以自有组件的形式进行版本管理。此后,开发者可以把这些自有组件和 ADP 平台上提供的公共研发中间件,如数据库,消息,微服务框架等一起编排为云服务,并在指定的公有云环境创建验证环境,部署 CNStack 2.0(也被称为底座)一起完成功能测试和一系列验证,如高可用等,最终打包为云服务(或者云组件)。云服务(云组件)的发布包规范遵循云原生 PaaS 团队贡献给社区的 CNCF Sealer 开源项目。交付时,在用户环境部署的 CNStack 2.0 平台上,管理员将云服务(云组件)发布包导入能力中心。在能力中心,管理员可以完成云服务的部署,升级,变配等一系列生命周期管理。
安全合规
随着隐私和资产保护重要性的日益提升,安全合规越来越成为业务创新中必须要解决的问题。作为在企业环境部署的产品,CNStack 2.0 需要满足严格的安全和合规规范。包括但不限于以下方面:
- 控制管理服务在网络的暴露面,尽量收敛为一个端口,支持符合安全标准的网络隔离规划
- 提供用户管理能力或与现有的用户管理系统的集成
- 提供多租户的隔离能力和灵活的权限管理
- 提供与域名相关的证书,密钥管理或与现有的证书,密钥管理系统集成
- 确保加密的网络传输和敏感数据的加密存储
- 提供审计能力,支持合规检查
- 为在平台运行的服务和应用提供静态和动态的安全合规扫描
架构设计
为了满足上述产品目标,研发团队制定了以下设计原则
面向 API 开发
与面向 API 开发相对应的是面向 UI 开发。之前,对于产品功能的实现方式经常会听到类似“白屏”,“黑屏”的称谓。白屏就是在 UI 实现功能操作,而黑屏指通过命令行实现功能操作。因为功能逻辑通常在 UI 组件完成,就造成了通过命令行实现的功能,缺乏完整能力或者安全合规的要求,所以“黑屏”通常被认为是 hack 的实现。类似这种面向 UI 开发的方法还有很多弊端,无法满足前文提到的产品目标。
- 产品设计以 UI 驱动,UI 的调整对功能实现影响大
- 难以被其他产品或工具链集成,局限于页面嵌套,难以支持开放性要求
- 难以建立云服务,云组件的规范,提供方便的平台能力集成接入
- 功能的实现逻辑分散,调用链长,难以维护,排查错误
- 认证,鉴权,审计等公共能力无法集中控制,难以保障安全合规
与此相反,在面向 API 开发的架构中,UI 只是 API 能力的一种展示形式,UI 模块的前端或者直接调用管理服务 API,或者由 UI 的后端服务封装一个或多个管理服务 API。但是 UI 的后端服务自身并不实现任何运行期对象的管理能力,只是一个或多个 API 调用流程的封装,以及输入和输出数据的整理。
在面向 API 开发的架构中,还有一项重要的设计准则就是:“没有隐藏的内部 API”(No Hidden Internal API)。这就要求管理服务组件之间的调用接口,也是用户对服务访问的调用接口。只要保证 API 的一致性,服务的组件可以容易的替换,确保产品具有敏捷,开放的能力。此外,它还消除了安全隐患,对所有 API 的调用使用一致的认证,鉴权,审计等安全合规能力。
所有管理对象都是资源
以 RESTful API 为代表,围绕资源进行管理的编程模型不仅带来了使用体验的提升,也改变了管理组件的实现方式。它将管理对象都作为资源,通过创建和改变资源的声明来实现管理功能,查询资源的状态来了解管理结果。Kubernetes 更是为这种编程模型提供了最佳实践,带来了具体的实现方法。
在 CNStack 2.0 的架构设计中,采用“所有管理对象都是资源”的编程模型,具有以下技术特点和优势:
- 声明式的 API 让调用者无需持续关注和处理管理动作发出后的执行阶段,管理服务需要确保管理对象的状态和资源声明最终保持一致
- 声明式的 API 让调用者无需持续关注和处理管理动作发出后的执行阶段,管理服务需要确保管理对象的状态和资源声明最终保持一致
- 创建和改变管理对象的 API 都是异步的,API 的执行者只需要确保持久化资源的最新声明后就可以让 API 返回,简化 API 的处理,提高响应率
- 因为一切都是资源,所以 API 执行者的处理可以共同化,方便实现统一的认证,鉴权,审计,筛选,分页等能力,也易于扩展,满足高可用和韧性的要求
- 因为一切都是资源,所以 API 客户端的处理也可以共同化,无论在 UI 还是 CLI 上都是创建和改变管理对象的资源声明,展示管理对象的资源状态。
- 处理资源状态和声明一致性(reconcile)的控制器可以独立实现,与 API 的执行者解耦,便于升级,扩展和管理。同时,它的故障不会影响 API 自身的处理,具备可扩展,高可用,韧性,敏捷,开放的特点。
将 Kubernetes 作为编程框架
Kubernetes 为“所有管理对象都是资源”的编程模型提供了最佳实践,带来了具体的实现方法。Kubernetes 的 operator 模式,自定义资源(Customized Resource Definition)等能力让开发者可以方便,快捷的扩展功能,由此看来,Kubernetes 不仅仅是一个管理容器化工作负载和服务的编排平台,更是一个可扩展的编程框架。
在 CNStack 2.0 的架构设计中,Kubernetes 作为编程框架的价值还体现在更多的方面
- 租户管理
在共享基础设施的环境下,管理对象的访问隔离是一个重要的能力。Kubernetes 提供了“命名空间”的概念,用户资源与命名空间关联,实现访问隔离和配额,策略等管理控制。但是,Kubernetes 中的命名空间并不支持层级关系,基于**Hierarchical Namespace Controller (HNC) [ 2] **开源项目,CNStack 2.0 将 Kubernetes 的命名空间扩展至多级。CNStack 2.0 的管理对象大都以 Kubernetes 的资源方式管理,利用层级化的命名空间实现租户管理。
- 权限管理
Kubernetes 提供了基于角色的访问控制(RBAC)。其中,角色定义了对 API group,资源和操作的允许范围。角色可以通过聚合方式灵活的组合。通过将角色和用户,用户组,服务账号以及租户关联,可以控制用户和服务对资源的访问控制。对于不以 K8s 资源管理的对象,一样可以通过在角色中映射管理对象API的相关信息,定义访问策略,甚至对于无法以资源管理的执行命令,RBAC 也支持 nonresourceurls 的形式。CNStack 2.0 以 Kubernetes 作为权限控制引擎,将各种管理对象的权限点(permission)定义为 Kubernetes 的角色,通过 Kubernetes 角色的聚合实现灵活的自定义访问策略,最终调用 SubjectAccessReview API 获取策略执行结果,判定指定用户或服务对 API 的访问许可。
- API 管理
在“面向 API 开发”的设计原则下,API 是 CNStack 2.0 的核心。Kubernetes 提供了 API 的扩展能力,基于自定义资源(Customized Resource Definition)和整套研发框架。开发者只需要专注于自定义资源的运行期管理逻辑,通过 Kubernnetes API 获取自定义资源的声明和当前状态,处理资源声明和当前状态的差异,让它们最终达成一致。同时,将 API 对象声明的处理,持久化等工作完全交给 Kubernetes 组件。这样不仅极大的提高了研发效率,也让整个架构具备敏捷,开放,可扩展,韧性的特点。
无论是以 Kubernetes 自定义资源方式实现的 API,还是以其他方式实现的 API,CNStack 2.0 基于 CNCF 的 **Emissary-Ingress [ 3] ** 项目,创建了管理 API 网关,实现了管控 API 统一的路由,认证,鉴权,审计等能力。管控API网关收敛所有管控 API 对外暴露的访问点为一个端口,允许管理服务,或者云服务(云组件)开发人员以 Kubernetes 自定义资源声明的方式描述API的路由,认证,鉴权和审计要求。这种方法大幅降低产品的被攻击面,也降低了管理服务,云服务(云组件)的研发负担。
- 证书管理
X.509 证书被应用于数字安全的方方面面,例如 HTTPS 安全通信。和域名一样,证书有规范的颁布机制,例如适用于 PoC 或私有范围使用的自颁发;抑或是基于用户从认证的颁布机构获取的根证书(BYOK:Bring You Own Key),甚至是用户有自己的证书管理平台,如 Vault。Kubernetes 有内置的证书资源,可以基于自身的根证书根据用户请求创建证书。CNCF 的 cert-manager [ 4] 项目,以 Kubernetes 自定义资源的方式,提供了更全面的证书管理能力,支持上述多种场景。CNStack 2.0 基于 cert-manager,建立了证书管理服务。创建的证书可以自动的应用与管理服务和用户自建服务的安全通信。
- 灾备恢复管理
因为“所有管理对象都是资源”,所以管理对象的持久化都以资源的形式存储在Kubernetes 中。基于开源项目 **Velero [ 5] **,CNStack 2.0 建立了备份恢复服务,允许管理人员以 Kubernetes 自定义资源声明的方式说明备份策略,如周期,资源类型,范围,备份源等,以及恢复策略。
- 多集群管理
CNStack 2.0 天生就需要支持多集群,以满足用户对不同场景的诉求,例如资源和负载的物理隔离,多地域部署,容灾多活等。基于 Kubernetes,云原生 PaaS 团队和红帽等技术伙伴开源了 CNCF **Open Cluster Management(OCM) [ 6] **项目。基于 OCM,CNStack 2.0 建立了多集群的创建,纳管等生命周期管理服务,作为“多集群服务”这一云服务。它允许用户以 Kubernetes 自定义资源声明的方式描述需要创建或者纳管的集群。在“多集群服务”的帮助下,云服务(云组件)也支持多集群下的部署和管理,区分管理面和数据面组件,并声明部署的集群选择条件。同时,对租户的资源控制,配额,管理策略等也扩展至多集群。
安全无处不在
安全合规是产品需求的一个重要方面,它体现在架构设计的方方面面。
- 管控 API 统一收敛到管理 API 网关,最小化攻击面,所有的 API 访问都需要完成认证,鉴权,审计,没有后台 API
- 管控 API 统一收敛到管理 API 网关,最小化攻击面,所有的 API 访问都需要完成认证,鉴权,审计,没有后台 API
- 服务根据访问需求设置拥有最小化访问权限的角色
- 所有的网络通信都是加密的,安全通信证书统一管理,实现自动的更替
- 所有的敏感数据都以 secret 方式加密保存
- 所有管理和输出组件都需要经过 ADP 组件上架标准流程,完成镜像和运行期扫描检查
- 节点和集群的纳管以零信任为前提,实现管理和被管理对象的双向认证
CNStack 2.0 架构
CNStack 2.0 围绕 Kubernetes 为编程框架,它的管控组件大都以 Kubernetes 控制器的方式实现,以 Kuberentes API 扩展作为管控 API。其中,主要的组件如下
- ACK Distro
ACK Distro 是云原生 PaaS 团队和公有云 ACK 团队一起发布的阿里云 Kubernetes 发行版。它以 Sealer 为发布和部署工具,支持在众多异构环境中部署和运维 Kubernetes 集群。其中 Kubernetes 核心组件和公有云 ACK 上的 Kubernetes 核心组件一致,经历了严格的安全检查和调优。所包含的网络(**Hybridnet [ 7] ),存储(Open-Local [ 8] **)组件也是云原生 PaaS 团队和其他阿里云,蚂蚁基础设施团队共同研发的开源项目,经历了内部长时间的验证。ACK Distro 构成了 CNStack 2.0 的 Kubernetes 基础。
- cn-app-operator
cn-app-operator 是云服务,云组件生命周期的管理者。在内部,它基于 OAM 的设计思想,支持 helm 作为 Kubernetes 资源的渲染引擎,围绕云服务和云组件的发布包,实例,自运维策略,告警管理创建了一系列 Kubernetes 自定义资源实现管理能力。它也是后续云原生 PaaS 团队计划开源的一个项目。
- Account Mgr
Account Mgr 实现用户的账号管理或者对接支持标准协议,如 LDAP,AD 等的用户管理系统集成。它也负责用户和云服务的认证,创建访问凭证。Accout Mgr 包含开源项目 **KeyCloak [ 9] ** 实现用户的账号管理。
- UI Backend
UI Backend 是 UI 访问的后台服务,它只是 Kubernetes API 或者其他管理服务 API 的封装。在内部访问其他管理服务API时一样通过 Management Gateway 调用。
- 审计/日志/监控/告警
审计/日志/监控/告警组件对平台管理组件,云服务/云组件,基础设施提供审计,日志,监控,告警服务。它基于 **Loki [ 10] **和 **Victoria Metrics [ 11] ,Grafana [ 12] ** 实现日志和监控指标的采集,存储,查询和展示。其中,审计信息来自 Management Gateway 上对 API 的分析处理。
- 管理组件
CNStack 2.0 的众多管理组件都是以 Kubernetes Controller 的方式实现,通过 Kubernetes 自定义资源的方式提供 API。
- Management Gateway
Management Gateway 是 CNStack 2.0 的一个核心组件,它负责所有管控 API 的路由,认证,鉴权,审计,加密传输等能力。Management Gateway 基于 **Emissary-Ingress [ 13] **,并在之上实现了认证,鉴权等一系列 filter 插件。
- Ingress
Ingress 在 CNStack 2.0 中负责处理数据浏览,例如日志,监控指标在多集群间的收集。也负责云服务在管控集群部署的管理面组件和在被管理集群上部署的数据面组件之间的数据传输。
CNStack 2.0 的网络通信设计以暴露面最小化为原则,所有管控 API 收敛在 Management Gateway,数据访问收敛在 Ingress。在多集群环境下,对被管理集群的访问通过云原生 PaaS 团队开源的 Cluster Gateway [ 14] 项目实现,具备路由透明,权限一致,通信安全的能力。管控集群对被管理集群 ingress 的访问,以及被管理集群对管控集群 Management Gateway 和 Ingress 的访问都通过 Kuberetes Headless Service 实现路由,屏蔽网络联通的复杂性。
CNStack 2.0 还包含众多的云服务,例如提供微服务应用发布,监控,运维,高可用等能力的应用管理服务;提供共享存储服务的 VCNS-OSS;提供虚拟机类型工作负载管理的虚拟化服务;提供边缘场景资源,设备和工作负载管理的云边协同服务;提供云原生服务网格能力的服务网格服务,等等。它们的能力和架构设计会在后续专文介绍。03
研发提效
CNStack 2.0 是一个包含多个云服务的产品集合。一个有效的研发流程以及相关的集成,测试工具链很大程度上影响着产品的研发效率和品质,并最终左右产品在市场上的口碑和表现。不存在一个完美的研发流程和相关工具,而是根据人员,环境和产品需求不断演进和迭代的。在 CNStack 2.0 的开发过程中,研发团队收获了以下经验。
论每日构建的重要性
每日构建是指在每天晚上或者早上的时候,将产品的各个组件编排为完整产品,在模拟环境完成部署和验证,并创建可发布的版本。是否能完成每日构建是产品研发效率和品质的一个比较好的衡量标准,从一定程度上也说明了产品部署的灵活和易用性。每日构建在研发过程中具有很重要的价值,包括
- 及时获取最新的稳定产品发布包,为相关云服务的集成,测试创建工作基础
- 及时发现组件集成或者服务集成的问题,这些在组件或者服务自身的开发过程中是难以发现的
- 模拟产品交付的实际场景,让演练成为日常工作
上图是 CNStack 2.0 云服务每日构建的概念流程,其中组件的研发人员在日常开发中将镜像和部署配置,例如 helm charts 等,提交到 git repo 或者内部镜像仓库。当每日构建触发时,建立镜像和部署配置的快照,编排为产品并自动部署和测试,测试通过后完成产品发布。
ADP 平台提供了产品编排,验证环境创建,产品部署等能力。如上图所示,云原生 PaaS 团队基于 ADP 和 Chorus 构建了从开发,集成到测试,发布一整套流水线机制。
- 每日构建触发时,Chorus Job 记录当前配置仓库的 commit-id,并将组件上传至 ADP 平台,更新 latest 标识的产品版本,完成产品集成。
- 此后,调用 ADP API 在公有云创建测试环境,并部署产品。通过产品内置的测试 Job 完成自动化测试,并收集测试报告,发送到指定的 IM 平台(如钉钉,邮件群等)完成产品测试。
- 产品发布负责人根据测试报告,判定本次构建是否满足发布要求。如果满足,在 ADP 平台根据 Semantic Version 规范,完成产品发布。在日常可以以日期作为发布版本的 Patch Version,在产品预发布阶段可以以 RC 作为 Patch Version。
- 每日构建触发时,Chorus Job 记录当前配置仓库的 commit-id,并将组件上传至 ADP 平台,更新 latest 标识的产品版本,完成产品集成。
吃自己的狗粮
阿里云 ADP 平台作为 CNStack 2.0 的一部分,不仅负责云服务/云组件的编排和发布,同时也负责云服务/云组件的集成和测试。CNStack 2.0 基础平台自身也是由 ADP 完成编排,发布和集成,测试的。另一方面,ADP 也基于 CNStack 2.0 基础平台为云服务/云组件创建测试环境,组装发布包。在服务第三方云服务/云组件研发团队的同时,也在服务自身,吃自己的狗粮。
“吃自己的狗粮”让 CNStack 2.0 产研团队能够和使用者感同身受,切身体会到产品在易用性和功能上的差距。在产品研发的过程中,很多需求和问题的发现都来自产研团队自身。
为自己的研发效率负责
研发提效关系到每个研发,测试工程师自身的工作幸福感。以云原生技术驱动的 CNStack 2.0 和相关的工具链提供了丰富的 API 和可扩展能力。云原生社区也有一系列可供集成使用的开源项目。在云原生技术流行的时代,工程师需要保持不断学习的精神,挖掘和创建能够提升自身工作效率的方法和工具,为自己的研发效率负责。
相关资料
CNStack 产品官网
https://www.aliyun.com/activity/middleware/cnstack
CNStack 社区版(免费下载使用)
https://github.com/alibaba/CNStackCommunityEdition
ADP 产品官网
https://help.aliyun.com/product/191542.html
ACK Distro
https://github.com/AliyunContainerService/ackdistro
CNCF Sealer 项目
https://github.com/sealerio/sealer
CNCF OCM 项目
https://open-cluster-management.io/
相关链接
[1] 阿里云云原生应用交付平台(Application Delivery Platform,简称 ADP)https://www.aliyun.com/product/aliware/adp
[2] Hierarchical Namespace Controller (HNC)
https://github.com/kubernetes-sigs/hierarchical-namespaces
[3] Emissary-Ingress
https://www.cncf.io/projects/emissary-ingress/
[4] cert-manager
https://www.cncf.io/projects/cert-manager/
[5] Velero
[6] Open Cluster Management(OCM)
https://open-cluster-management.io/
[7] Hybridnet
https://github.com/alibaba/hybridnet
[8] Open-Local
https://github.com/alibaba/open-local
[9] KeyCloak
https://github.com/keycloak/keycloak
[10] Loki
https://github.com/grafana/loki
[11] Victoria Metrics
https://github.com/VictoriaMetrics/VictoriaMetrics
[12] Grafana
https://github.com/grafana/grafana
[13] Emissary-Ingress
https://www.cncf.io/projects/emissary-ingress/
[14] Cluster Gateway
https://github.com/oam-dev/cluster-gateway
点击此处,查看 CNStack 产品官网详情
标签:原生,CNStack,服务,Kubernetes,API,组件,2.0 From: https://www.cnblogs.com/alisystemsoftware/p/17102915.html