阿里云故障演练平台获得可信云最高等级认证,为企业数字韧性能力保驾护航

alicloudnative阅读(2168)评论(0)

7月27日,2021可信云大会在北京召开。会上,阿里云故障演练平台入选可信云最佳技术实践,并首批通过可信云混沌工程平台能力要求最高等级-先进级认证。同时,由信通院牵头,阿里云计算有限公司联合多家企业共同发起的“混沌工程实验室”宣布成立。

lALPDgfLSAcLsDLNBTXNB9Y_2006_1333.png_720x720g.jpg

双项认证,阿里云故障演练平台获可信云最高等级认证

随着企业对于云计算的理解和实践不断深入,基于云计算的分布式架构成为越来越多企业应用构建的首选方案,如何通过混沌工程提升云原生系统稳定性和保障业务连续性成为业内普遍关注的话题。

混沌工程是主要通过故障注入的方式,提前发现系统稳定性等问题,旨在提升系统和组织韧性,打造韧性的架构,保障业务连续性。在信通院可信云混沌工程平台测评中,阿里云故障演练平台以最高分成绩通过资源支持、故障场景、场景管理、实验流程、实验防护、实验度量、权限管理、安全审计等8项能力测评,并入选2021可信云最佳技术实践,双项认证,再一次证明了阿里云在混沌工程领域的技术和产品实力。

图片 1.png

故障演练随着阿里巴巴系统架构从微服务,到容器化,再到云原生一起发展,内部已有近10年的混沌工程落地实践经验。阿里云故障演练平台将阿里巴巴内部的实践经验以产品化的方式对外输出,提供丰富的实验场景和专家经验库、领域化的解决方案,满足用户的故障场景需求,在灵活的流程编排和开放的集成能力下,提供监控、报告等实现混沌工程实施闭环,通过权限管控和演练防护来控制故障演练的风险,帮助企业在云迁移、云就绪、云原生过程中提升系统稳定性和业务连续性。

2.png

自混沌工程理论提出以来,很多企业在探索和实践,但落地形式不同,阿里云故障演练平台有何不同?

  • 灵活的流程编排:制订了一套标准化的演练流程,在此基础上可以添加所需的流程节点。同时支持多场景的运行方式。
  • 可视化故障演练:与架构感知集成,在架构拓扑可视化的基础上,实现故障注入,同时可以配合架构巡检,发现系统风险点,使用故障演练进行验证。
  • 多样的专家经验库:将阿里巴巴内部多年的故障演练经验沉淀到演练模板中,具备演练场景的真实性和实用性,极大的提升演练创建的效率,同时解决用户上手混沌工程难的问题。
  • 领域化的解决方案:提供对服务组件、系统架构等稳定性验证的产品化解决方案,通过架构感知、依赖分析等动态识别组件和架构,自动生成演练方案,达到快、准、全的演练目的。

使用故障演练平台做混沌工程,可以衡量微服务的容错能力,估算系统容错红线,衡量系统容错能力。并且,故障演练平台可以验证容器编排配置是否合理,测试PaaS层是否健壮,验证监控告警的时效性,提升监控告警的准确和时效性。通过故障突袭,随机对系统注入故障,考察相关人员对问题的应急能力,以及问题上报、处理流程是否合理,达到以战养战,锻炼人定位与解决问题的能力。通过故障注入的方式,提前发现系统稳定性等问题,旨在提升系统和组织韧性,打造韧性的架构,保障业务连续性。

阿里云故障演练平台自2019年商业化以来,通过多样化的实验工具,自动化的工具部署,多维度的演练方式,灵活的流程编排,丰富的故障场景,实用的演练模板,专业的解决方案,安全的演练防护,深度的云产品集成,已经拥有近千个企业客户,服务了包括华泰证券、比心科技、亲宝宝等客户,助力企业在云原生时代构建数字韧性能力。

推动标准统一,打造ChaosBlade 开源项目,缩短构建混沌工程路径

近几年,越来越多的企业开始关注并探索混沌工程,渐渐成为测试系统高可用,构建对系统信息不可缺少的工具。但混沌工程领域目前还处于一个快速演进的阶段,最佳实践和工具框架没有统一标准。实施混沌工程可能会带来一些潜在的业务风险,经验和工具的缺失也将进一步阻止 DevOps 人员实施混沌工程。混沌工程领域目前也有很多优秀的开源工具,分别覆盖某个领域,但这些工具的使用方式千差万别,其中有些工具上手难度大,学习成本高,混沌实验能力单一,使很多人对混沌工程领域望而却步。

阿里巴巴集团在混沌工程领域已经实践多年,为了帮助企业更好地构建混沌工程路径,阿里巴巴在2019年开源了混沌工程项目 ChaosBlade,并在今年成为 CNCF Sandbox 项目。将”自研技术”、”开源项目”、”商业产品”形成统一的技术体系,阿里云通过三位一体的正向循环,实现了技术价值的最大化。

ChaosBlade 是一款遵循混沌工程原理的开源工具,包含混沌工程实验工具 chaosblade 和混沌工程平台 chaosblade-box,旨在通过混沌工程帮助企业解决云原生过程中高可用问题。实验工具 chaosblade 支持 3 大系统平台,4 种编程语言应用,共涉及 200 多个实验场景,3000 多个实验参数,可以精细化地控制实验范围。ChaosBlade 已成为阿里云故障演练平台基础能力底座服务众多企业客户。

3.png

未来,ChaosBlade 将继续以云原生为基础,提供面向多集群、多环境、多语言的混沌工程平台和混沌工程实验工具;后续会托管更多的混沌实验工具和兼容主流的平台,实现场景推荐,提供业务、系统监控集成,输出实验报告,在易用的基础上完成混沌工程操作闭环。

业内首个混沌工程实验室正式成立,推动混沌工程实践落地

在数字化产业对系统稳定性和云计算高可用要求越来越高的大背景下,由中国信通院牵头,阿里云等众多企业共同参与的混沌工程实验室正式成立。混沌工程实验室将推动混沌工程在各领域典型应用场景中的实践落地,联动云计算上下游企业来共同推进混沌工程快速发展。

阿里云拥有国内最丰富的混沌工程实践经验,并致力于打造云原生时代的混沌工程标准体系。阿里云在海量互联网服务以及历年双11场景的实践过程中,沉淀出了包括全链路压测、线上流量管控、故障演练等高可用核心技术,并通过开源和云上服务的形式对外输出,以帮助企业用户和开发者享受技术红利,提高开发效率,缩短业务的构建流程。

Rainbond Helm 应用商店对接管理实现分析

好雨云阅读(2357)评论(0)

本文作者:Rainbond 项目维护者:黄润豪

Rainbond 是一个完全开源,简单易用的云原生应用管理平台。除了支持内置的本地组件库, 云原生应用商店, 还支持 Helm 应用商店. 用户把常用的 Helm 仓库对接到 Rainbond 后, 可以简单, 方便地对Helm应用进行配置和安装。

Rainbond Helm 应用商店的功能

添加 Helm 应用商店

当前版本支持基于Helm v3协议添加外部应用商店。

添加 Helm 应用商店

Helm 应用列表

Helm 应用列表

搜索 Helm 应用

Helm 应用列表

Helm 应用商店实现

在开始介绍基本原理时, 我们先简单介绍下 Helm 仓库, 让大家对 Helm 仓库有基础的理解.

Helm 仓库

Helm 仓库的应用模板列表索引会放在 index.yaml 里面,可以通过 <仓库地址>/index.yaml 获取。

比如: https://openchart.goodrain.com/goodrain/rainbond/index.yaml

index.yaml 详情如下:

apiVersion: v1
entries:
 nfs-client-provisioner:
 - apiVersion: v1
 appVersion: 3.1.0
 created: "2021-07-08T08:20:40Z"
 description: nfs-client is an automatic provisioner that used your *already configured*
 NFS server, automatically creating Persistent Volumes.
 digest: 1197847e6ee3720e4f7ee493985fce3703aa42813fa409dc6c0ebaa4fef3f175
 home: https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client
 keywords:
 - nfs
 - storage
 maintainers:
 - email: bart@verwilst.be
 name: verwilst
 name: nfs-client-provisioner
 sources:
 - https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client
 urls:
 - charts/nfs-client-provisioner-1.2.8.tgz
 version: 1.2.8
 rainbond-console:
 - apiVersion: v2
 appVersion: 1.16.0
 created: "2021-07-08T08:36:05Z"
 description: Goodrain Rainbond-console Helm chart for Kubernetes
 digest: 5ecba097ee7425d16a1a4512c6bb82ca3e7d04b8491d098226c4a9517e3507be
 home: https://github.com/goodrain/rainbond
 icon: https://www.rainbond.com/images/rainbondlog.png
 name: rainbond-console
 sources:
 - https://github.com/goodrain/rainbond/
 type: application
 urls:
 - charts/rainbond-console-5.3.1.tgz
 version: 5.3.1
generated: "2021-07-08T08:36:12Z"
serverInfo: {}

可以看到 index.yaml 记录了应用模板列表, 每个应用模板的元数据, 包括 版本号(version), 说明(description), charts 包地址(urls) 等等.

基本原理

┌────────────────┐        ┌───────────────────────┐
│                │        │                       │
│   index.yaml   ├───────►│ application templates │
│                │        │                       │
└────────────────┘        └───────────────────────┘

添加 Helm 应用商店

添加Helm 应用商店需要把 Helm 应用商店的元数据记录下来, 持久化到数据库中. 在正式添加前, 还需要检查商店的可用心.

代码如下:

func (a *appStoreRepo) Create(ctx context.Context, appStore *domain.AppStore) error {
 // Check the availability of the app store.
 if err := a.isAvailable(ctx, appStore); err != nil {
 return err
 }
 return a.appStoreDao.Create(&model.AppStore{
 Name: appStore.Name,
 URL: appStore.URL,
 Branch: appStore.Branch,
 })
}

Helm 应用列表

Helm 应用商店的主要逻辑:

  • 建立 HTTP 请求去获取 index.yaml 文件

  • 将 HTTP 响应反序列化到结构体 IndexFile 中. IndexFile 是 Helm 官方定义的结构体.

  • 遍历 IndexFile 的 Entries, 生成我们想要的应用模板列表(appTemplates)

代码如下:

func (h *helmAppTemplate) fetch(ctx context.Context, appStore *domain.AppStore) ([]*domain.AppTemplate, error) {
 // 建立 HTTP 请求去访问 index.yaml
 req, err := http.NewRequest("GET", appStore.URL+"/index.yaml", nil)
 ...
 body, err := ioutil.ReadAll(resp.Body)
 ...
 jbody, err := yaml.YAMLToJSON(body)
 ...
 // 解析返回结果, 反序列化到结构体 hrepo.IndexFile 中
 var indexFile hrepo.IndexFile
 if err := json.Unmarshal(jbody, &indexFile); err != nil {
 return nil, errors.Wrap(err, "read index file")
 }
 ...
 // 解析成应用列表
 var appTemplates []*domain.AppTemplate
 for name, versions := range indexFile.Entries {
 appTemplate := &domain.AppTemplate{
 Name: name,
 Versions: versions,
 }
 appTemplates = append(appTemplates, appTemplate)
 }
 return appTemplates, nil
}

应用列表的缓存

为了加快响应速度, 应用模板列表会被缓存起来, 目前支持的缓存是内存。

代码如下:

appTemplates, err := s.appTemplater.Fetch(ctx, appStore)
if err != nil {
 return nil, err
}
appStore.AppTemplates = appTemplates
s.store.Store(appStore.Key(), appStore)

防止缓存击穿

使用缓存时, 我们还应该考虑高并发情况下缓存击穿的问题. 缓存击穿的意思是在某个数据过期后, 有大量的并发访问该数据, 从而导致数据库压力剧增. 当然, 我们获取应用列表不是去访问数据库, 而是发出 HTTP 请求, 获取 index.yaml。

为了解决防止缓存击穿, 引入了 singleflight. singleflight 会确保同一个 key, 同一时刻, 只会有一个请求在执行. 也就是说, 对于同一个 Helm 应用商店, 在同一个时刻, 只会有一个获取 index.yaml 文件的 HTTP 请求。

代码如下:

type helmAppTemplate struct {
 singleflight.Group
}
func (h *helmAppTemplate) Fetch(ctx context.Context, appStore *domain.AppStore) ([]*domain.AppTemplate, error) {
 // single flight to avoid cache breakdown
 v, err, _ := h.Do(appStore.Key(), func() (interface{}, error) {
 return h.fetch(ctx, appStore)
 })
 appTemplates := v.([]*domain.AppTemplate)
 return appTemplates, err
}

Charts 包的缓存

每次安装应用时, Helm 会先拉取应用的 Charts 包, 缓存到本地存储中.

Helm 对 Charts 包的缓存

在 Mac 中, Helm 缓存 charts 包的路径是 $HOME/Library/Caches/helm/repository:

.
├── bitnami-charts.txt
├── bitnami-index.yaml
├── mysql-8.5.1.tgz
├── nginx-8.8.3.tgz
├── phpmyadmin-8.2.4.tgz
├── rainbond-charts.txt
├── rainbond-index.yaml
├── rainbond-operator-1.3.0.tgz
├── redis-cluster-5.0.1.tgz

所有 Helm 仓库的 chart 包都会缓存到该目录下. 需要注意的是, 该目录下的 charts 包是不区分仓库的. 也就是说, 如果 bitnami 和 rainbond 仓库都有版本为 8.5.1 的 mysql 应用, 那么 bitnami 的 mysql-8.5.1.tgz 就可能会被 rainbond 的 mysql-8.5.1.tgz 覆盖掉, 导致 bitnami 仓库的缓存失效. 详情请查看 #4618

Rainbond 对 Charts 包的缓存

Rainbond 没有直接沿用 Helm 对 Charts 包的缓存方式, 而是做了一些扩展, 使其支持区分仓库的 Charts 包的缓存:

  • 构造 Charts 包的缓存路径, 格式为 主缓存路径/仓库名/应用名/应用版本/.

  • 构造应用的包名 应用名-版本号.tgz.

  • 检查存储中时候已经有目标 Charts 包, 并检查他们的 hash 是否一样.

  • 如果缓存红没有目标 Charts 包, 则进行下载, 将Charts 包缓存起来.

代码如下:

func (h *Helm) locateChart(chart, version string) (string, error) {
 repoAndName := strings.Split(chart, "/")
 ...
 // Charts 包的路径为: 主缓存路径/仓库名/应用名/应用版本/
 chartCache := path.Join(h.settings.RepositoryCache, chart, version)
 cp := path.Join(chartCache, repoAndName[1]+"-"+version+".tgz")
 if f, err := os.Open(cp); err == nil {
 defer f.Close()
 // 检查 hash
 // check if the chart file is up to date.
 hash, err := provenance.Digest(f)
 ...
 // get digiest from repo index.
 digest, err := h.getDigest(chart, version)
 ...
 if hash == digest {
 return cp, nil
 }
 }
 cpo := &ChartPathOptions{}
 cpo.Version = version
 settings := h.settings
 // 下载 Charts 包
 cp, err := cpo.LocateChart(chart, chartCache, settings)
 ...
 return cp, err
}

接下来

目前的实现还有非常大的优化的空间, 比如应用列表缓存插件化, 应用列表缓存可过期, 清理 Charts 包缓存.

应用列表缓存插件化

应用列表缓存目前只支持内存. 随着 Helm 商店数量的越来越多, 应用模板的数量也将越来越多, 这对缓存的大小的要求将会比较高. 这场景下, 内存显然不是合适选择. 所以, 有必然将缓存抽象出来, 与内存解耦, 变得插件化, 可以轻松得对接其它的缓存, 比如 redis.

应用列表缓存可过期

细心的同学可能已经发现, 目前的内存缓存是不支持过期的, 也就是说, 在不重启应用的情况下, 缓存会越来越大. 所以, 不管是缓存在内存中, 还是缓存在 redis 中, 都非常有必要支持缓存过期. 缓存的过期策略可以是 LRU, LFU 等等, 同时结合过期时间, 清理长时间不被使用的缓存.

另外, 减少应用模板中不必要的字段, 从而减小每个应用模板的大小, 也是个不错的优化点.

清理 Charts 包缓存

Charts 包缓存在磁盘上, 不会过期, 目前也没有清理的机制, 这可能会使得磁盘的占用非常大. 接下来, 需要设计合适的方案对其进行定期的清理.

总结

本文结合代码和大家一起分析了Rainbond Helm 应用商店对接实现.

Reference

  1. Helm: https://github.com/helm/helm

  2. What is cache penetration, cache breakdown and cache avalanche?: https://www.pixelstech.net/article/1586522853-What-is-cache-penetration-cache-breakdown-and-cache-avalanche

  3. singleflight: https://pkg.go.dev/golang.org/x/sync/singleflight

社区

如果您对Rainbond项目感兴趣,如果您有一些疑问,如果您对云原生、Kubernetes等技术感兴趣,欢迎加入Rainbond 社区钉钉群。

dingding-group.png

KubeSphere 3.1.1 发布,可以接入集群已有的 Prometheus

KubeSphere阅读(10314)评论(0)

KubeSphere 作为一款面向应用的开源容器平台,经过 3 年的发展和 10 个版本的迭代,收获了两百多位开源贡献者,超过十万次下载,并有数千名社区用户用 KubeSphere 作为企业容器平台。

7 月 7 日,KubeSphere 3.1.1 版本正式发布,现在部署 KubeSphere 时可以指定 Kubernetes 集群中已有的 Prometheus 啦!!!

鉴于最近有很多 KubeSphere 社区用户询问 KubeSphere 3.1.1 的发布进度,本文将会详细介绍 KubeSphere 3.1.1 比较重要的新特性。

KubeSphere 3.1.1 不是大版本更新,只是针对 v3.1.0 的 patch 版本,也就是修修补补的工作。本文只列出优化增强的功能,问题修复请查看完整的 Release Notes。

安装与升级

优化增强的功能

用户体验

  • 删除工作负载时支持批量删除关联资源 #1933
  • 优化页面弹框 #2016
  • 允许在 system-workspace 下的项目中使用容器终端 #1921

可观测性

  • 优化了通知设置中端口号的格式限制#1885
  • 支持安装时指定使用已有的 Prometheus. #1528

微服务治理

  • 优化 trace 页面增加时间选择器 #2022

DevOps

  • 支持 GitLab 多分支流水线按分支名筛选过滤 #2077
  • 更改了 b2i 页面的“重新执行”按钮为“执行”#1981

多集群管理

  • 优化了 member 集群配置错误时的提示信息 #2084 #1965

计量计费

  • 计量计费部分的 UI 调整 #1896
  • 修改了计量计费按钮的颜色 #1934

应用商店

安全

  • 切换 jwt-go 的分支,用于修复 CVE-2020-26160 #3991
  • 升级 protobuf 版本至 v1.3.2 用于修复 CVE-2021-3121 #3944
  • 升级 crypto 至最新版用于修复 CVE-2020-29652 #3997
  • 移除了 yarn.lock 文件以避免一些 CVE 漏洞误报 #2024

存储

  • 提升了 s3 uploader 并发性能 #4011
  • 增加预置的 CSI Provisioner CR 配置 #1536

KubeEdge 集成

下载与升级

大家可以到 KubeSphere GitHub 主页查看完整的 英文版 KubeSphere 3.1.1 Release Notes,了解更多与升级有关的注意事项。国内用户也可以访问下方链接来查看完整的 Release Notes:

关于 KubeSphere

KubeSphere (https://kubesphere.io)是在 Kubernetes 之上构建的开源容器混合云,提供全栈的 IT 自动化运维的能力,简化企业的 DevOps 工作流。

KubeSphere 已被 Aqara 智能家居、杭州数跑科技、本来生活、新浪、华夏银行、四川航空、国药集团、微众银行、紫金保险、中通、中国人保寿险、中国太平保险、中移金科、Radore、ZaloPay 等海内外数千家企业采用。KubeSphere 提供了开发者友好的向导式操作界面和丰富的企业级功能,包括多云与多集群管理、Kubernetes 资源管理、DevOps (CI/CD)、应用生命周期管理、微服务治理 (Service Mesh)、多租户管理、监控日志、告警通知、审计事件、存储与网络管理、GPU support 等功能,帮助企业快速构建一个强大和功能丰富的容器云平台。

 ✨ GitHub:https://github.com/kubesphere
 💻 官网(中国站):https://kubesphere.com.cn
 👨‍💻‍ 微信群:请搜索添加群助手微信号 kubesphere

基于Rancher安装Rainbond v5.3.1控制台

好雨云阅读(2346)评论(0)

本文适用于正在使用 Rancher 或对 Rancher 有所了解的用户。本文作者:好雨云交付工程师 张齐。

本文将介绍:

  • 如何在Rancher中安装Rainbond控制台。

  • Rainbond如何对接Rancher管理的Kubernetes集群。

  • 演示安装完整流程供用户参考。

参考视频:https://www.bilibili.com/video/BV1a54y1n7Ch/

前提要求

  1. 具有一套稳定可用的 Rancher 环境,若还没有可参考 Rancher 安装部署文档

  2. Kubernetes 集群具有至少 4GB 以上的空闲调度内存

  3. Kubernetes 版本在 1.13 ~1.19区间

  4. Kubernetes 集群至少有一个 80⁄443 端口未被占用的节点,用于Rainbond网关部署。

  5. Kubernetes 集群存在可用的 StorageClass

    如果集群内没有 StorageClass,可以参考 https://artifacthub.io/packages/helm/kvaps/nfs-server-provisioner

  6. StorageClass是NFS,则需要在集群内安装NFS 客户端。如果没有安装,可以参考:

    # CentOS 系统
    yum install -y nfs-utils
    # Ubuntu/Debian 系统
    apt install -y nfs-common

开始安装

添加 Rainbond Console 到应用商店

将 Rainbond Console 添加到 Rancher 的应用商店中。

  1. 应用商店页面中,单击添加应用商店

  2. 输入名称(比如 Rainbond-Console)和 商店 URL 地址 输入 https://gitee.com/rainbond/rainbond-console.git

  3. 分支选择masterHelm 版本选择Helm v3

  4. 单击创建完成应用商店添加。

image-20210708163003499

  1. 回到应用商店页面中,单击启动

  2. 单击刷新,等 Rancher 同步完后,就可以看到刚才新加的 Rainbond Console 了。

安装 Rainbond Console

在 Rancher 商店中启动 Rainbond Console

  1. 单击识别出的 rainbond-console Chart,开始 Chart 的安装。

  2. 选择命名空间或新创建命名空间。

  3. 修改一些启动配置:

    • 可选使用默认镜像或自定义镜像。

    • 可选择手动填写或自动生成Mysql、Redis密码。

    • 选择存储类。

    • 设置Rainbond-Console NodePort端口。

image-20210708170910377

  1. 填写完成后,点击启动,创建Rainbon-console。

  2. 等待所有POD启动完成后,访问上面设置的Rainbond-Console NodePort端口,默认是 30707

访问 Rainbond-Console,对接已有集群

根据页面提示完成账号注册,进入集群安装页面,选择 接入Kubernetes集群,填写集群的kubeconfig文件内容,开始Rainbond集群的安装。

image-20210712174937793

在安装之前,如需要配置集群初始化参数,则点击下图红框中的内容填写初始化参数配置。

image-20210712175145269

集群初始化配置参考示例:

集群初始化详细参数配置请参考文档

Rainbond集群初始化默认会安装 NFS-Server,集群中已存在 storageClassName 可以复用此存储。

 metadata:
 name: rainbondcluster
spec:
 #设置Rainbond对外网关IP,可以是公网IP或负载均衡
 gatewayIngressIPs:
 - 39.103.228.113
 #设置网关节点IP
 nodesForGateway:
 #内网IP
 - internalIP: xxxx
 # 节点hostname
 name: xxxx
 #指定集群中的 storageClass
 rainbondVolumeSpecRWX:
 storageClassName: nfs-server

了解 Rancher 用户使用 Rainbond 的优势

  • 无需深入学习 Kubernetes 各类资源的使用方式

Rainbond 使用云原生应用模型的方式提供给用户智能化、简单的应用开发管理模式。不管是简单应用还是复杂的微服务架构,整个开发部署过程无需开发者深入学习 Kubernetes 相关知识。

  • 标准的云原生 12 要素应用管理模式

你或许听说过云原生 12 要素,作为目前推荐的云原生应用开发模式。Rainbond 应用模型对云原生 12 要素进行了充分的实践,使用 Rainbond,天然地使你的代码满足云原生要求。

  • 从源代码到云端

常用的开发语言(Java、PHP、Python、Golang、NodeJS、.NetCore)无需定义 Dockerfile、无需定义 Kubernetes 部署方式即可完成持续构建、持续部署。

  • 标准应用多集群交付

Rainbond 提供多种方式便于开发者在多个集群,多个环境中快速交付应用,获取 SaaS 化应用交付体验。

  • 微服务架构

Rainbond 内置 ServiceMesh 微服务架构治理框架,所有部署组件按照微服务的治理思路进行管理,微服务治理功能开箱即用的。

常见问题

  • Rancher 已经部署的应用能否直接由 Rainbond 接管

这个问题是大多数用户的疑问,我们希望达成 Rainbond 可以自动化的接管 Rancher 部署的应用。然而遗憾的是由于 Rancher 即同类型平台部署应用时目前都不会遵循标准规范(比如OAM),导致我们很难 100% 兼容的转换 Rancher 已经部署的应用成为 Rainbond 应用模型。因此目前我们还是推荐用户直接使用 Rainbond 提供的基于源代码、基于镜像快速的重新部署应用(相对于部分转化后再进行人工干预优化更节省时间)。同时也便于用户在这个过程中了解 Rainbond 应用管理的机制和流程。

  • Rainbond 部署的应用是否可以从 Rancher 视图中进行管理

Rainbond 部署到 Kubernetes 集群中的资源都是由 Rainbond 控制器进行创建、升级和回收,使用 Rainbond 定义的资源创建规范。我们并不推荐用户在 Rancher 中直接对这些资源进行修改。但可以进行观测,比如日志观测、资源监控观测等等。

  • 通过Rancher安装Rainbond-console POD一直处于不可用状态

首先排查POD之间是否可以通信,如不能通信则需做些优化,比如:开启内核转发、关闭Firewalld、关闭交换分区等等。

(更多…)

KubeSphere Helm 应用仓库源码分析

KubeSphere阅读(2109)评论(0)

作者:蔡锡生,LStack 平台研发工程师,近期专注于基于 OAM 的应用托管平台落地。

背景介绍

KubeSphere 应用商店简介

作为一个开源的、以应用为中心的容器平台,KubeSphere 在 OpenPitrix 的基础上,为用户提供了一个基于 Helm 的应用商店,用于应用生命周期管理。OpenPitrix 是一个开源的 Web 平台,用于打包、部署和管理不同类型的应用。KubeSphere 应用商店让 ISV、开发者和用户能够在一站式服务中只需点击几下就可以上传、测试、部署和发布应用。

KubeSphere 中的 Helm 仓库功能

  • KubeSphere Helm 仓库添加

  • Helm repo list

  • KubeSphere Helm 仓库中的应用模版查询

Helm 仓库简介

Helm charts 是存放 K8s 应用模版的仓库,该仓库由 index.yaml 文件和 .tgz 模版包组成。

[root@ningbo stable]# ls -al 
总用量 400
drwxr-xr-x. 26 root root   4096 6  22 17:01 .
drwxr-xr-x.  4 root root     86 6  22 16:37 ..
-rw-r--r--.  1 root root  10114 6  22 17:12 index.yaml
-rw-r--r--.  1 root root   3803 6   8 2020 lsh-cluster-csm-om-agent-0.1.0.tgz
-rw-r--r--.  1 root root   4022 6   8 2020 lsh-mcp-cc-alert-service-0.1.0.tgz
-rw-r--r--.  1 root root   4340 6   8 2020 lsh-mcp-cc-sms-service-0.1.0.tgz
-rw-r--r--.  1 root root   4103 6   8 2020 lsh-mcp-cpm-metrics-exchange-0.1.0.tgz
-rw-r--r--.  1 root root   4263 6   8 2020 lsh-mcp-cpm-om-service-0.1.0.tgz
-rw-r--r--.  1 root root   4155 6   8 2020 lsh-mcp-csm-om-service-0.1.0.tgz
-rw-r--r--.  1 root root   3541 6   8 2020 lsh-mcp-deploy-service-0.1.0.tgz
-rw-r--r--.  1 root root   5549 6   8 2020 lsh-mcp-iam-apigateway-service-0.1.0.tgz
  • index.yaml 文件
apiVersion: v1
entries:
  aliyun-ccm:
  - apiVersion: v2
    appVersion: addon
    created: "2021-06-21T08:59:58Z"
    description: A Helm chart for Kubernetes
    digest: 6bda563c86333475255e5edfedc200ae282544e2c6e22b519a59b3c7bdef9a32
    name: aliyun-ccm
    type: application
    urls:
    - charts/aliyun-ccm-0.1.0.tgz
    version: 0.1.0
  aliyun-csi-driver:
  - apiVersion: v2
    appVersion: addon
    created: "2021-06-21T08:59:58Z"
    description: A Helm chart for Kubernetes
    digest: b49f128d7a49401d52173e6f58caedd3fabbe8e2827dc00e6a824ee38860fa51
    name: aliyun-csi-driver
    type: application
    urls:
    - charts/aliyun-csi-driver-0.1.0.tgz
    version: 0.1.0
  application-controller:
  - apiVersion: v1
    appVersion: addon
    created: "2021-06-21T08:59:58Z"
    description: A Helm chart for application Controller
    digest: 546e72ce77f865683ce0ea75f6e0203537a40744f2eb34e36a5bd378f9452bc5
    name: application-controller
    urls:
    - charts/application-controller-0.1.0.tgz
    version: 0.1.0
  • .tgz 解压缩后的文件目录
[root@ningbo stable]# cd mysql/
[root@ningbo mysql]# ls -al
总用量 20
drwxr-xr-x.  3 root root   97 5  25 2020 .
drwxr-xr-x. 26 root root 4096 6  22 17:01 ..
-rwxr-xr-x.  1 root root  106 5  25 2020 Chart.yaml
-rwxr-xr-x.  1 root root  364 5  25 2020 .Helmignore
-rwxr-xr-x.  1 root root   76 5  25 2020 index.yaml
drwxr-xr-x.  3 root root  146 5  25 2020 templates
-rwxr-xr-x.  1 root root 1735 5  25 2020 values.yaml
  • Chart.yaml
[root@ningbo mysql]# cat Chart.yaml 
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: mysql
version: 0.1.0

添加 Helm 仓库代码介绍

接口实现分析

  1. 路由注册
  2. handler,参数解析,调用 models 方面
  3. models ,调用 models 方法
  4. crd client,调用 K8s api 存储
    webservice.Route(webservice.POST("/repos").
        To(handler.CreateRepo). // 跟进
        Doc("Create a global repository, which is used to store package of app").
        Metadata(restfulspec.KeyOpenAPITags, []string{constants.OpenpitrixTag}).
        Param(webservice.QueryParameter("validate", "Validate repository")).
        Returns(http.StatusOK, api.StatusOK, openpitrix.CreateRepoResponse{}).
        Reads(openpitrix.CreateRepoRequest{}))
func (h *openpitrixHandler) CreateRepo(req *restful.Request, resp *restful.Response) {

    createRepoRequest := &openpitrix.CreateRepoRequest{}
    err := req.ReadEntity(createRepoRequest)
    if err != nil {
        klog.V(4).Infoln(err)
        api.HandleBadRequest(resp, nil, err)
        return
    }

    createRepoRequest.Workspace = new(string)
    *createRepoRequest.Workspace = req.PathParameter("workspace")

    user, _ := request.UserFrom(req.Request.Context())
    creator := ""
    if user != nil {
        creator = user.GetName()
    }
    parsedUrl, err := url.Parse(createRepoRequest.URL)
    if err != nil {
        api.HandleBadRequest(resp, nil, err)
        return
    }
    userInfo := parsedUrl.User
    // trim credential from url
    parsedUrl.User = nil

    repo := v1alpha1.HelmRepo{
        ObjectMeta: metav1.ObjectMeta{
            Name: idutils.GetUuid36(v1alpha1.HelmRepoIdPrefix),
            Annotations: map[string]string{
                constants.CreatorAnnotationKey: creator,
            },
            Labels: map[string]string{
                constants.WorkspaceLabelKey: *createRepoRequest.Workspace,
            },
        },
        Spec: v1alpha1.HelmRepoSpec{
            Name:        createRepoRequest.Name,
            Url:         parsedUrl.String(),
            SyncPeriod:  0,
            Description: stringutils.ShortenString(createRepoRequest.Description, 512),
        },
    }

    if strings.HasPrefix(createRepoRequest.URL, "https://") || strings.HasPrefix(createRepoRequest.URL, "http://") {
        if userInfo != nil {
            repo.Spec.Credential.Username = userInfo.Username()
            repo.Spec.Credential.Password, _ = userInfo.Password()
        }
    } else if strings.HasPrefix(createRepoRequest.URL, "s3://") {
        cfg := v1alpha1.S3Config{}
        err := json.Unmarshal([]byte(createRepoRequest.Credential), &cfg)
        if err != nil {
            api.HandleBadRequest(resp, nil, err)
            return
        }
        repo.Spec.Credential.S3Config = cfg
    }

    var result interface{}
    // 1. validate repo
    result, err = h.openpitrix.ValidateRepo(createRepoRequest.URL, &repo.Spec.Credential)
    if err != nil {
        klog.Errorf("validate repo failed, err: %s", err)
        api.HandleBadRequest(resp, nil, err)
        return
    }

    // 2. create repo
    validate, _ := strconv.ParseBool(req.QueryParameter("validate"))
    if !validate {
        if repo.GetTrueName() == "" {
            api.HandleBadRequest(resp, nil, fmt.Errorf("repo name is empty"))
            return
        }
        result, err = h.openpitrix.CreateRepo(&repo) //跟进
    }

    if err != nil {
        klog.Errorln(err)
        handleOpenpitrixError(resp, err)
        return
    }

    resp.WriteEntity(result)
}
func (c *repoOperator) CreateRepo(repo *v1alpha1.HelmRepo) (*CreateRepoResponse, error) {
    name := repo.GetTrueName()

    items, err := c.repoLister.List(labels.SelectorFromSet(map[string]string{constants.WorkspaceLabelKey: repo.GetWorkspace()}))
    if err != nil && !apierrors.IsNotFound(err) {
        klog.Errorf("list Helm repo failed: %s", err)
        return nil, err
    }

    for _, exists := range items {
        if exists.GetTrueName() == name {
            klog.Error(repoItemExists, "name: ", name)
            return nil, repoItemExists
        }
    }

    repo.Spec.Description = stringutils.ShortenString(repo.Spec.Description, DescriptionLen)
    _, err = c.repoClient.HelmRepos().Create(context.TODO(), repo, metav1.CreateOptions{}) // 跟进
    if err != nil {
        klog.Errorf("create Helm repo failed, repo_id: %s, error: %s", repo.GetHelmRepoId(), err)
        return nil, err
    } else {
        klog.V(4).Infof("create Helm repo success, repo_id: %s", repo.GetHelmRepoId())
    }

    return &CreateRepoResponse{repo.GetHelmRepoId()}, nil
}
// Create takes the representation of a HelmRepo and creates it.  Returns the server's representation of the HelmRepo, and an error, if there is any.
func (c *HelmRepos) Create(ctx context.Context, HelmRepo *v1alpha1.HelmRepo, opts v1.CreateOptions) (result *v1alpha1.HelmRepo, err error) {
    result = &v1alpha1.HelmRepo{}
    err = c.client.Post().
        Resource("Helmrepos").
        VersionedParams(&opts, scheme.ParameterCodec).
        Body(HelmRepo).
        Do(ctx).
        Into(result)
    return
}

查询 Helm 仓库应用模版代码介绍

接口实现

  1. 路由注册
  2. handler,参数解析,调用 models 方面
  3. models ,调用 models 方法
  4. crd client,调用 K8s api 存储
webservice.Route(webservice.GET("/apps").LiHui, 6 months ago: • openpitrix crd
        Deprecate().
        To(handler.ListApps).  // 跟进
        Doc("List app templates").
        Param(webservice.QueryParameter(params.ConditionsParam, "query conditions,connect multiple conditions with commas, equal symbol for exact query, wave symbol for fuzzy query e.g. name~a").
            Required(false).
            DataFormat("key=%s,key~%s")).
        Param(webservice.QueryParameter(params.PagingParam, "paging query, e.g. limit=100,page=1").
            Required(false).
            DataFormat("limit=%d,page=%d").
            DefaultValue("limit=10,page=1")).
        Param(webservice.QueryParameter(params.ReverseParam, "sort parameters, e.g. reverse=true")).
        Param(webservice.QueryParameter(params.OrderByParam, "sort parameters, e.g. orderBy=createTime")).
        Metadata(restfulspec.KeyOpenAPITags, []string{constants.OpenpitrixTag}).
        Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}))
func (h *openpitrixHandler) ListApps(req *restful.Request, resp *restful.Response)
    limit, offset := params.ParsePaging(req)
    orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, openpitrix.CreateTime)
    reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, false)
    conditions, err := params.ParseConditions(req)

    if err != nil {
        klog.V(4).Infoln(err)
        api.HandleBadRequest(resp, nil, err)
        return
    }

    if req.PathParameter("workspace") != "" {
        conditions.Match[openpitrix.WorkspaceLabel] = req.PathParameter("workspace")
    }

    result, err := h.openpitrix.ListApps(conditions, orderBy, reverse, limit, offset)  // 跟进

    if err != nil {
        klog.Errorln(err)
        handleOpenpitrixError(resp, err)
        return
    }

    resp.WriteEntity(result)
}
func (c *applicationOperator) ListApps(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {

    apps, err := c.listApps(conditions)  // 重点跟进
    if err != nil {
        klog.Error(err)
        return nil, err
    }
    apps = filterApps(apps, conditions)

    if reverse {
        sort.Sort(sort.Reverse(HelmApplicationList(apps)))
    } else {
        sort.Sort(HelmApplicationList(apps))
    }

    totalCount := len(apps)
    start, end := (&query.Pagination{Limit: limit, Offset: offset}).GetValidPagination(totalCount)
    apps = apps[start:end]
    items := make([]interface{}, 0, len(apps))

    for i := range apps {
        versions, err := c.getAppVersionsByAppId(apps[i].GetHelmApplicationId())
        if err != nil && !apierrors.IsNotFound(err) {
            return nil, err
        }
        ctg, _ := c.ctgLister.Get(apps[i].GetHelmCategoryId())
        items = append(items, convertApp(apps[i], versions, ctg, 0))
    }
    return &models.PageableResponse{Items: items, TotalCount: totalCount}, nil
}

// line 601
func (c *applicationOperator) listApps(conditions *params.Conditions) (ret []*v1alpha1.HelmApplication, err error) {
    repoId := conditions.Match[RepoId]
    if repoId != "" && repoId != v1alpha1.AppStoreRepoId {
        // get Helm application from Helm repo
        if ret, exists := c.cachedRepos.ListApplicationsByRepoId(repoId); !exists {
            klog.Warningf("load repo failed, repo id: %s", repoId)
            return nil, loadRepoInfoFailed
        } else {
            return ret, nil
        }
    } else {
        if c.backingStoreClient == nil {
            return []*v1alpha1.HelmApplication{}, nil
        }
        ret, err = c.appLister.List(labels.SelectorFromSet(buildLabelSelector(conditions)))
    }

    return
}
func (c *cachedRepos) ListApplicationsByRepoId(repoId string) (ret []*v1alpha1.HelmApplication, exists bool) {
    c.RLock()
    defer c.RUnlock()

    if repo, exists := c.repos[repoId]; !exists {
        return nil, false
    } else {
        ret = make([]*v1alpha1.HelmApplication, 0, 10)
        for _, app := range c.apps {
            if app.GetHelmRepoId() == repo.Name { // 应用的仓库ID相同则追加
                ret = append(ret, app)
            }
        }
    }
    return ret, true
}

既然 app template 是从缓存中获取的,那么缓存中的数据又是什么时候录入的呢?

  1. 创建全局缓存变量
  2. 添加新 Helm 仓库,K8s 中已安装 crd 控制器 HelmRepoController 发现有新的 HelmRepo 创建,更新 .Status.Data 内容
  3. informer 发现有更新,同时更新缓存

缓存更新的实现

  1. 创建全局变量,通过 init 函数初始化
  2. 通过 HelmRepo 的 informer 实现缓存同步更新
  3. 在每次调用接口的时候,hanlder 类中包换了缓存变量

创建接口类 openpitrix.Interface

type openpitrixHandler struct {
    openpitrix openpitrix.Interface
}

func newOpenpitrixHandler(ksInformers informers.InformerFactory, ksClient versioned.Interface, option *openpitrixoptions.Options) *openpitrixHandler {
    var s3Client s3.Interface
    if option != nil && option.S3Options != nil && len(option.S3Options.Endpoint) != 0 {
        var err error
        s3Client, err = s3.NewS3Client(option.S3Options)
        if err != nil {
            klog.Errorf("failed to connect to storage, please check storage service status, error: %v", err)
        }
    }

    return &openpitrixHandler{
        openpitrix.NewOpenpitrixOperator(ksInformers, ksClient, s3Client),
    }
}

NewOpenpitrixOperator

  • 通过在 informer 中添加通知函数,执行缓存更新
  • once.Do 只执行一次
var cachedReposData reposcache.ReposCache
var HelmReposInformer cache.SharedIndexInformer
var once sync.Once


func init() {
    cachedReposData = reposcache.NewReposCache() // 全局缓存
}

func NewOpenpitrixOperator(ksInformers ks_informers.InformerFactory, ksClient versioned.Interface, s3Client s3.Interface) Interface {

    once.Do(func() {
        klog.Infof("start Helm repo informer")
        HelmReposInformer = ksInformers.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmRepos().Informer()
        HelmReposInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
            AddFunc: func(obj interface{}) {
                r := obj.(*v1alpha1.HelmRepo)
                cachedReposData.AddRepo(r) // 缓存更新, 点击跟进
            },
            UpdateFunc: func(oldObj, newObj interface{}) {
                oldR := oldObj.(*v1alpha1.HelmRepo)
                cachedReposData.DeleteRepo(oldR)
                r := newObj.(*v1alpha1.HelmRepo)
                cachedReposData.AddRepo(r)
            },
            DeleteFunc: func(obj interface{}) {
                r := obj.(*v1alpha1.HelmRepo)
                cachedReposData.DeleteRepo(r)
            },
        })
        go HelmReposInformer.Run(wait.NeverStop)
    })

    return &openpitrixOperator{
        AttachmentInterface:  newAttachmentOperator(s3Client),
    // cachedReposData used
        ApplicationInterface: newApplicationOperator(cachedReposData, ksInformers.KubeSphereSharedInformerFactory(), ksClient, s3Client),
    // cachedReposData used
        RepoInterface:        newRepoOperator(cachedReposData, ksInformers.KubeSphereSharedInformerFactory(), ksClient),
    // cachedReposData used
        ReleaseInterface:     newReleaseOperator(cachedReposData, ksInformers.KubernetesSharedInformerFactory(), ksInformers.KubeSphereSharedInformerFactory(), ksClient),
        CategoryInterface:    newCategoryOperator(ksInformers.KubeSphereSharedInformerFactory(), ksClient),
    }
}

缓存更新逻辑

// 缓存结构体
type cachedRepos struct {
    sync.RWMutex

    chartsInRepo  map[workspace]map[string]int
    repoCtgCounts map[string]map[string]int

    repos    map[string]*v1alpha1.HelmRepo
    apps     map[string]*v1alpha1.HelmApplication
    versions map[string]*v1alpha1.HelmApplicationVersion
}
  • ByteArrayToSavedIndex:将 repo.Status.Data 转换为 SavedIndex 数组对象
  • 遍历 SavedIndex.Applications
  • 保存(app.ApplicationId:HelmApplication)到 cachedRepos.apps
func (c *cachedRepos) AddRepo(repo *v1alpha1.HelmRepo) error {
    return c.addRepo(repo, false)
}

//Add new Repo to cachedRepos
func (c *cachedRepos) addRepo(repo *v1alpha1.HelmRepo, builtin bool) error {
    if len(repo.Status.Data) == 0 {
        return nil
    }
    index, err := Helmrepoindex.ByteArrayToSavedIndex([]byte(repo.Status.Data))
    if err != nil {
        klog.Errorf("json unmarshal repo %s failed, error: %s", repo.Name, err)
        return err
    }
    ...

    chartsCount := 0
    for key, app := range index.Applications {
        if builtin {
            appName = v1alpha1.HelmApplicationIdPrefix + app.Name
        } else {
            appName = app.ApplicationId
        }

        HelmApp := v1alpha1.HelmApplication{
        ....
        }
        c.apps[app.ApplicationId] = &HelmApp

        var ctg, appVerName string
        var chartData []byte
        for _, ver := range app.Charts {
            chartsCount += 1
            if ver.Annotations != nil && ver.Annotations["category"] != "" {
                ctg = ver.Annotations["category"]
            }
            if builtin {
                appVerName = base64.StdEncoding.EncodeToString([]byte(ver.Name + ver.Version))
                chartData, err = loadBuiltinChartData(ver.Name, ver.Version)
                if err != nil {
                    return err
                }
            } else {
                appVerName = ver.ApplicationVersionId
            }

            version := &v1alpha1.HelmApplicationVersion{
            ....
            }
            c.versions[ver.ApplicationVersionId] = version
        }
        ....
    }
    return nil
}

HelmRepo 协调器

HelmRepo.Status.Data 加载流程

  1. LoadRepoIndex: convert index.yaml to IndexFile
  2. MergeRepoIndex: merge new and old IndexFile
  3. savedIndex.Bytes(): compress data with zlib.NewWriter
  4. 将 savedIndex 数据存入 CRD(HelmRepo.Status.Data)

关键结构体

// HelmRepo.Status.Data == SavedIndex 压缩后的数据
   type SavedIndex struct {
       APIVersion   string                  `json:"apiVersion"`
       Generated    time.Time               `json:"generated"`
       Applications map[string]*Application `json:"apps"`
       PublicKeys   []string                `json:"publicKeys,omitempty"`

       // Annotations are additional mappings uninterpreted by Helm. They are made available for
       // other applications to add information to the index file.
       Annotations map[string]string `json:"annotations,omitempty"`
   }

   // IndexFile represents the index file in a chart repository
   type IndexFile struct {
       APIVersion string                   `json:"apiVersion"`
       Generated  time.Time                `json:"generated"`
       Entries    map[string]ChartVersions `json:"entries"`
       PublicKeys []string                 `json:"publicKeys,omitempty"`
   }

代码位置

func (r *ReconcileHelmRepo) syncRepo(instance *v1alpha1.HelmRepo) error {
       // 1. load index from Helm repo
       index, err := Helmrepoindex.LoadRepoIndex(context.TODO(), instance.Spec.Url, &instance.Spec.Credential)

       if err != nil {
           klog.Errorf("load index failed, repo: %s, url: %s, err: %s", instance.GetTrueName(), instance.Spec.Url, err)
           return err
       }

       existsSavedIndex := &Helmrepoindex.SavedIndex{}
       if len(instance.Status.Data) != 0 {
           existsSavedIndex, err = Helmrepoindex.ByteArrayToSavedIndex([]byte(instance.Status.Data))
           if err != nil {
               klog.Errorf("json unmarshal failed, repo: %s,  error: %s", instance.GetTrueName(), err)
               return err
           }
       }

       // 2. merge new index with old index which is stored in crd
       savedIndex := Helmrepoindex.MergeRepoIndex(index, existsSavedIndex)

       // 3. save index in crd
       data, err := savedIndex.Bytes()
       if err != nil {
           klog.Errorf("json marshal failed, error: %s", err)
           return err
       }

       instance.Status.Data = string(data)
       return nil
   }

Question:

Q1:Helm 仓库发包时如何进行 Helm release 版本控制

A:修改 Charts.yaml 中的字段 version,然后 Helm package, 等于新增一个 .tgz 包,老版本的不要删除,这时候执行 index 的时候会吧所有的 .tgz 包包含在内。

$ Helm repo index stable --url=xxx.xx.xx.xxx:8081/
  $ cat index.yaml
  ....
  redis:
  - apiVersion: v1
    appVersion: "1.0"
    created: "2021-06-22T16:34:58.286583012+08:00"
    description: A Helm chart for Kubernetes
    digest: fd7c0d962155330527c0a512a74bea33302fca940b810c43ee5f461b1013dbf5
    name: redis
    urls:
    - xxx.xx.xx.xxx:8081/redis-0.1.1.tgz
    version: 0.1.1
  - apiVersion: v1
    appVersion: "1.0"
    created: "2021-06-22T16:34:58.286109049+08:00"
    description: A Helm chart for Kubernetes
    digest: 1a23bd6d5e45f9d323500bbe170011fb23bfccf2c1bd25814827eb8dc643d7f0
    name: redis
    urls:
    - xxx.xx.xx.xxx:8081/redis-0.1.0.tgz
    version: 0.1.0

Q2:KubeSphere 版本同步功能有缺失?用户添加完 Helm 仓库后,如果有新的应用发布,查询不到

A:解决方案:使用 3 种同步策略

  • 定时同步 Helm 仓库(HelmRepo 设置一个定时协调的事件)
  • 企业仓库,用户可以设置 hook,发布新版本的时候主动触发更新
  • 用户主动更新 charts

Q3:index.yaml 缓存位置

A:某些仓库的 index.yaml 比较大,如果 1000 个用户,1000 个 charts 会太吃内存。建议常用 index.yaml 的放在内存中,不常用的 index.yaml 存储在本地磁盘。

Rainbond 5.3.1 发布,支持100+组件一键云原生交付

好雨云阅读(2220)评论(0)

2021年7月5日,Rainbond 5.3.1 正式发布。

Rainbond 是云原生且易用的应用管理平台。云原生应用交付的最佳实践。专注于以应用为中心的理念。赋能企业搭建云原生开发云、云原生交付云。

对于企业: Rainbond 是开箱即用的云原生平台,借助 Rainbond 可以快速完成企业研发和交付体系的云原生转型。

对于开发者: 基于 Rainbond 开发、测试和运维企业业务应用,开箱即用的获得全方位的云原生技术能力。包括但不仅限于持续集成、服务治理、架构支撑、多维度应用观测、流量管理。

对于项目交付: 基于 Rainbond 搭建产品版本化管理体系,搭建标准化客户交付环境,使传统的交付流程可以自动化、简单化和可管理。

近一年,使用Rainbond 云原生应用交付流程(见下图)的开源用户成为主流,面对不同用户的业务复杂性,对Rainbond交付流程的性能提出了新的要求。从 5.3.0 版本发布以来4个月的时间,Rainbond 开发者以交付链路的性能优化为主要迭代方向。

2bdevops

在工业互联网、园区建设、智慧城市建设等等领域中,一个应用解决方案大多具有50-100个服务组件。在这些行业中,通常会有多家应用厂商来合作完成一个解决方案。Rainbond 在这个过程中提供多项能力:

(1)标准化应用交付模型,统一各个应用厂商的应用交付标准,使行业集成商低成本集成解决方案。

(2)统一交付环境,一键实现应用交付。大大降低销售过程中的演示场景、POC场景和成本和最终交付的成本。

(3)智能应用运维管理,客户维度运维低成本。

关键变更

支持100+组件规模应用交付

基于上述的Rainbond应用交付流程,当前版本在应用模型发布、应用模型安装、应用升级、应用生命周期管理四个维度进行性能优化。以应用模型安装为例,100个组件的安装过程需要调用大量的计算资源,控制组件的启动顺序,控制微服务系统注册和配置分发。

多组件应用安装和升级演示

Helm应用安装与管理(Beta)

Rainbond 不支持Helm应用安装和管理早已是一个痛点。Rainbond 的产品形态是以管理自定义的应用规范为核心的云原生应用管理。与其他容器化平台不一样,我们不提供Kubernetes原生的资源管理面板。因此在面对灵活的Helm应用包,我们没有很好的方式将所有Helm应用转化为Rainbond的应用规范。在OAM规范的实现模式中,有一个思路是把每一个Helm应用定义为自定义的组件类型,对其进行资源类型识别从而实现一些运维能力注入。我们认为这种模式精细化管理有优势,但用户落地成本很大。

当前版本我们采用一种新的模式来安装和管理Helm应用。我们将其定位为 Rainbond 原生应用的补充,作为部署一些中间件的载体,所以我们主要要考虑解决以下问题:

(1)Helm安装的应用如何接入Rainbond ServiceMesh微服务架构,实现原生应用可调用Helm应用。

(2)Helm安装的应用如何接入Rainbond 网关,实现外部访问及流量治理。

(3)Helm应用管理能力支持多少。

我们定义了Kubernets自定义资源HelmApp,实现HelmApp的多集群部署。用户对接上Helm应用商店后,即可选择应用进行安装。安装过程中完全支持Helm应用的配置规范进行应用配置,同时也支持 Rancher 定义的配置表单规范,实现配置表单自动生成。Helm应用部署后自动识别其Service资源,进行服务的注册,实现Rainbond微服务和网关体系的接入。

逐步适配OAM应用规范

从5.3.1版本开始,我们开始逐步适配OAM应用规范,提升Rainbond的可扩展性。在当前版本中我们基于OAM规范,重新实现第三方组件类型,定义了ThirdComponent 作为第一个 ComponentDefinition,并在产品中实现对ComponentDefinition的基础管理机制。接下来Rainbond中现有的两种内置组件类型逐步基于ComponentDefinition定义实现。然后开放用户自行扩展的能力,Rainbond中提供整个支撑体系,包括通用运维特征能力注入、配置UI化、通用的微服务治理和流量管理、应用打包交付等。

image-20210705114112352

详细变更点

新增功能

  • 【应用商店】支持Helm应用仓库对接;

  • 【应用管理】支持Helm应用安装和配置;

  • 【微服务治理】支持通过网关或内部组件依赖两种方式访问Helm安装的应用;

  • 【微服务治理】新增GRPC协议的服务治理能力;

  • 【微服务治理】新增对组件下容器启动顺序控制,实现mesh容器先于业务容器启动;

  • 【组件管理】新增基于kubernetes service服务发现类型的第三方组件;

  • 【源码构建】Go语言新增对Go 1.14、1.15、1.16 版本Runtime的支持;

  • 【源码构建】Go语言新增对构建模块和启动命令的配置;

  • 【源码构建】Java、Go、PHP等语言新增pre_build、post_build构建时shell hook的支持;

  • 【企业管理】用户管理中新增对用户所在团队及角色的批量管理能力;

  • 【企业管理】团队管理中新增开通集群的功能入口;

  • 【集群安装】支持RKE集群配置,实现集群节点配置的灵活调整;

优化功能

  • 【性能】应用升级体系优化,支持100+组件批量升级;

  • 【性能】从应用商店安装组件实现优化,支持100+组件批量安装;

  • 【性能】改进拓扑图加载逻辑,加速大应用下拓扑图加载速度;

  • 【性能】优化在大量组件情况下的应用级生命周期操作API的性能;

  • 【稳定性】应用网关优化,解决异常应用访问导致网关内存泄露的故障;

  • 【组件管理】支持空值的环境变量和配置组变量;

  • 【监控报警】移除错误的节点健康检测报警策略;

  • 【组件管理】重构组件本地存储类型的实现,支持使用本地存储组件复用集群的调度策略;

  • 【内部组件库】新增应用模型版本管理,支持在发布页展示版本介绍;

  • 【组件管理】支持组件设置自定义主机名解析记录;

BUG修复

  • 【源码构建】修复 .netcore 源码构建任务无法结束的故障;

  • 【控制台】修复网关策略搜索功能不可用故障;

  • 【源码构建】修复Maven 配置删除后源码构建无法执行的故障;

  • 【稳定性】修复错误的网关策略参数导致网关故障;

  • 【组件管理】修复组件实例数不一致的故障;

  • 【稳定性】修复rbd-worker系统组件由于etcd不稳定异常重启的故障;

  • 【应用管理】修复对接非HTTPS镜像仓库时应用备份不可用的故障;

  • 【集群安装】修复集群镜像仓库证书不一致的故障;

社区

如果您对Rainbond项目感兴趣,如果您有一些疑问,如果您对云原生、Kubernetes等技术感兴趣,欢迎加入Rainbond 社区钉钉群。

安装使用请参考文档:快速安装

从5.3.0升级到5.3.1: 升级参考文档

【GOTC 预告】王思宇:从 OpenKruise 看云原生应用负载发展趋势

alicloudnative阅读(1514)评论(0)

头图.png

2021 年 7 月 9 日至 10 日,GOTC 全球开源技术峰会(The Global Opensource Technology Conference)上海站即将拉开大幕。大会由开源中国和 Linux 软件基金会(The Linux Foundation)联合发起,全球头部开源公司和顶级开源项目将一起亮相,覆盖云原生、大数据、人工智能、物联网、区块链、DevOps、开源治理等多个技术领域,为开发者带来全球最新、最纯粹的开源技术,同时传播开源文化和理念,推动开源生态的建设和发展。

7 月 10 日, CNCF Sandbox 项目 OpenKruise 作者 & 社区负责人、阿里云技术专家王思宇将在 GOTC 上海站 「开源云原生计算时代论坛」专题论坛带来主题分享。OpenKruise 是由阿里开源的云原生应用自动化扩展套件,它在完全兼容标准的 Kubernetes 之上,围绕云原生应用场景提供多种丰富的自动化能力。目前 OpenKruise 在 Github 上已经有 2300+ star, 50+ 贡献者,已登记生产使用的用户包括来自国内外的阿里、蚂蚁、携程、苏宁、OPPO、有赞、斗鱼TV、申通、小红书、Lyft、Spectro Cloud 等 25+ 企业。

在本次论坛上,王思宇将分享哪些在社区中观察到的云原生趋势,又将介绍哪些值得关注的 OpenKruise 最新动态?让我们先睹为快。

GOTC 讲师王思宇:从 OpenKruise 看云原生应用负载发展趋势

1.png

讲师简介

王思宇,OpenKruise 作者&负责人,阿里云技术专家,Kubernetes、OAM 社区贡献者。长期从事云原生、容器、调度等领域研发;阿里巴巴百万容器调度系统核心研发成员,多年支撑阿里双十一超大规模容器集群经验。

议题介绍

云原生的应用负载从 Kubernetes 原生的 workloads(Deployment、StatefulSet)为人所熟知,但在另一方面,我们也看到从中小型的创业公司到大型互联网公司,越是大规模的应用场景下这些原生的 workloads 越是无法满足复杂的业务部署诉求。因此,不少公司都自研了适用于自身场景的自定义 workload,但其中真正在通用化、全面性、稳定性等多方面做到成熟的开源组件,只有阿里云开源的、已经成为 CNCF Sandbox 项目的 OpenKruise。

本次分享中,我们将从 Kubernetes 原生 workloads 开始介绍云原生应用负载的职责、实现基础,而后分析在超大规模业务场景下对应用负载的真实诉求,OpenKruise 是通过什么样的方式来满足这些需求,以及后续开源生态下的发展趋势,包括:

  1. 云原生应用部署的问题与挑战。
  2. K8s 原生应用负载的基本能力。
  3. OpenKruise 如何满足超大规模业务场景下的部署发布诉求。
  4. 以阿里巴巴应用场景为例,介绍使用 OpenKruise 做应用管理的实践。
  5. 未来云原生下,应用负载领域的发展趋势与方向。

如果对 OpenKruise 项目感兴趣,或有任何问题,欢迎大家通过钉钉扫码,加入 OpenKruise 社区交流群:
2.png

点击https://gotc.oschina.net/,查看 GOTC 大会详情

同程旅行基于 RocketMQ 高可用架构实践

alicloudnative阅读(1760)评论(0)

头图.jpeg

背景介绍

为何选择 RocketMQ

我们在几年前决定引入 MQ 时,市场上已经有不少成熟的解决方案,比如 RabbitMQ , ActiveMQ,NSQ,Kafka 等。考虑到稳定性、维护成本、公司技术栈等因素,我们选择了 RocketMQ :

  • 纯 Java 开发,无依赖,使用简单,出现问题能 hold ;
  • 经过阿里双十一考验,性能、稳定性可以保障;
  • 功能实用,发送端:同步、异步、单边、延时发送;消费端:消息重置,重试队列,死信队列;
  • 社区活跃,出问题能及时沟通解决。

使用情况

  • 主要用于削峰、解耦、异步处理;
  • 已在火车票、机票、酒店等核心业务广泛使用,扛住巨大的微信入口流量;
  • 在支付、订单、出票、数据同步等核心流程广泛使用;
  • 每天 1000+ 亿条消息周转。

下图是 MQ 接入框架图

由于公司技术栈原因,client sdk 我们提供了 java sdk ;对于其他语言,收敛到 http proxy ,屏蔽语言细节,节约维护成本。按照各大业务线,对后端存储节点进行了隔离,相互不影响。

1.png

MQ 双中心改造

之前单机房出现过网络故障,对业务影响较大。为保障业务高可用,同城双中心改造提上了日程。

为何做双中心

  • 单机房故障业务可用;​
  • 保证数据可靠:若所有数据都在一个机房,一旦机房故障,数据有丢失风险;
  • 横向扩容:单机房容量有限,多机房可分担流量。

双中心方案

做双中心之前,对同城双中心方案作了些调研,主要有冷(热)备份、双活两种。(当时社区 Dledger 版本还没出现,Dledger 版本完全可做为双中心的一种可选方案。)

1)同城冷(热)备份

两个独立的 MQ 集群, 用户流量写到一个主集群,数据实时同步到备用集群,社区有成熟的 RocketMQ Replicator 方案,需要定期同步元数据,比如主题,消费组,消费进度等。

2.png

2)同城双活

两个独立 MQ 集群,用户流量写到各自机房的 MQ 集群,数据相互不同步。

平时业务写入各自机房的 MQ 集群,若一个机房挂了,可以将用户请求流量全部切到另一个机房,消息也会生产到另一个机房。

3.png

对于双活方案,需要解决 MQ 集群域名。

1)若两个集群用一个域名,域名可以动态解析到各自机房。此方式要求生产、消费必须在同一个机房。假如生产在 idc1 ,消费在 idc2 ,这样生产、消费各自连接一个集群,没法消费数据。

2)若一个集群一个域名,业务方改动较大,我们之前对外服务的集群是单中心部署的,业务方已经大量接入,此方案推广较困难。

为尽可能减少业务方改动,域名只能继续使用之前的域名,最终我们采用一个 Global MQ 集群,跨双机房,无论业务是单中心部署还是双中心部署都不影响;而且只要升级客户端即可,无需改动任何代码。

双中心诉求

  • 就近原则:生产者在 A 机房,生产的消息存于 A 机房 broker ; 消费者在 A 机房,消费的消息来自 A 机房 broker 。
  • 单机房故障:生产正常,消息不丢。
  • broker 主节点故障:自动选主。

就近原则

简单说,就是确定两件事:

  • 节点(客户端节点,服务端节点)如何判断自己在哪个 idc;
  • 客户端节点如何判断服务端节点在哪个 idc。

如何判断自己在哪个 idc?

1) ip 查询
节点启动时可以获取自身 ip ,通过公司内部的组件查询所在的机房。

2)环境感知
需要与运维同学一起配合,在节点装机时,将自身的一些元数据,比如机房信息等写入本地配置文件,启动时直接读写配置文件即可。

我们采用了第二个方案,无组件依赖,配置文件中 logicIdcUK 的值为机房标志。

修改图.jpg
客户端节点如何识别在同一个机房的服务端节点?

客户端节点可以拿到服务端节点的 ip 以及 broker 名称的,因此:

  • ip 查询:通过公司内部组件查询 ip 所在机房信息;
  • broker 名称增加机房信息:在配置文件中,将机房信息添加到 broker 名称上;
  • 协议层增加机房标识:服务端节点向元数据系统注册时,将自身的机房信息一起注册。

相对于前两者,实现起来略复杂,改动了协议层, 我们采用了第二种与第三种结合的方式。

就近生产

基于上述分析,就近生产思路很清晰,默认优先本机房就近生产;

若本机房的服务节点不可用,可以尝试扩机房生产,业务可以根据实际需要具体配置。
5.png

就近消费

优先本机房消费,默认情况下又要保证所有消息能被消费。

队列分配算法采用按机房分配队列

  • 每个机房消息平均分给此机房消费端;
  • 此机房没消费端,平分给其他机房消费端。

伪代码如下:

Map<String, Set> mqs = classifyMQByIdc(mqAll);
Map<String, Set> cids = classifyCidByIdc(cidAll);
Set<> result = new HashSet<>;
for(element in mqs){
                     result.add(allocateMQAveragely(element, cids, cid)); //cid为当前客户端
}

消费场景主要是消费端单边部署与双边部署。

单边部署时,消费端默认会拉取每个机房的所有消息。
6.png

双边部署时,消费端只会消费自己所在机房的消息,要注意每个机房的实际生产量与消费端的数量,防止出现某一个机房消费端过少。
7.png

单机房故障

  • 每组 broker 配置

一主两从,一主一从在一机房,一从在另一机房;某一从同步完消息,消息即发送成功。

  • 单机房故障

消息生产跨机房;未消费消息在另一机房继续被消费。

故障切主

在某一组 broker 主节点出现故障时,为保障整个集群的可用性,需要在 slave 中选主并切换。要做到这一点,首先得有个broker 主故障的仲裁系统,即 nameserver(以下简称 ns )元数据系统(类似于 redis 中的哨兵)。

ns 元数据系统中的节点位于三个机房(有一个第三方的云机房,在云上部署 ns 节点,元数据量不大,延时可以接受),三个机房的 ns 节点通过 raft 协议选一个leader,broker 节点会将元数据同步给 leader, leader 在将元数据同步给 follower 。

客户端节点获取元数据时, 从 leader,follower 中均可读取数据。

8.png

切主流程

  • 若 nameserver leader 监控到 broker 主节点异常, 并要求其他 follower 确认;半数 follower 认为 broker 节点异常,则 leader 通知在 broker 从节点中选主,同步进度大的从节点选为主;
  • 新选举的 broker 主节点执行切换动作并注册到元数据系统;
  • 生产端无法向旧 broker 主节点发送消息。

流程图如下

9.pngimage.png

切中心演练

用户请求负载到双中心,下面的操作先将流量切到二中心—回归双中心—切到一中心。确保每个中心均可承担全量用户请求。

先将用户流量全部切到二中心

10.png
image.png
流量回归双中心,并切到一中心

11.png

回顾

  • 全局 Global 集群
  • 就近原则
  • 一主二从,写过半消息即及写入成功
  • 元数据系统 raft 选主
  • broker 主节点故障,自动选主

MQ 平台治理

即使系统高性能、高可用,倘若随便使用或使用不规范,也会带来各种各样的问题,增加了不必要的维护成本,因此必要的治理手段不可或缺。

目的

​让系统更稳定

  • 及时告警
  • 快速定位、止损

治理哪些方面

主题/消费组治理

  • 申请使用

生产环境 MQ 集群,我们关闭了自动创建主题与消费组,使用前需要先申请并记录主题与消费组的项目标识与使用人。一旦出现问题,我们能够立即找到主题与消费组的负责人,了解相关情况。若存在测试,灰度,生产等多套环境,可以一次申请多个集群同时生效的方式,避免逐个集群申请的麻烦。

  • 生产速度

为避免业务疏忽发送大量无用的消息,有必要在服务端对主题生产速度进行流控,避免这个主题挤占其他主题的处理资源。

  • 消息积压

对消息堆积敏感的消费组,使用方可设置消息堆积数量的阈值以及报警方式,超过这个阈值,立即通知使用方;亦可设置消息堆积时间的阈值,超过一段时间没被消费,立即通知使用方。

  • 消费节点掉线

消费节点下线或一段时间无响应,需要通知给使用方。

客户端治理

  • 发送、消费耗时检测

监控发送/消费一条消息的耗时,检测出性能过低的应用,通知使用方着手改造以提升性能;同时监控消息体大小,对消息体大小平均超过 10 KB 的项目,推动项目启用压缩或消息重构,将消息体控制在 10 KB 以内。

  • 消息链路追踪

一条消息由哪个 ip 、在哪个时间点发送,又由哪些 ip 、在哪个时间点消费,再加上服务端统计的消息接收、消息推送的信息,构成了一条简单的消息链路追踪,将消息的生命周期串联起来,使用方可通过查询msgId或事先设置的 key 查看消息、排查问题。

  • 过低或有隐患版本检测

随着功能的不断迭代,sdk 版本也会升级并可能引入风险。定时上报 sdk 版本,推动使用方升级有问题或过低的版本。

服务端治理

  • 集群健康巡检

如何判断一个集群是健康的?定时检测集群中节点数量、集群写入 tps 、消费 tps ,并模拟用户生产、消费消息。

  • 集群性能巡检

性能指标最终反映在处理消息生产与消费的时间上。服务端统计处理每个生产、消费请求的时间,一个统计周期内,若存在一定比例的消息处理时间过长,则认为这个节点性能有问题;引起性能问题的原因主要是系统物理瓶颈,比如磁盘 io util 使用率过高,cpu load 高等,这些硬件指标通过夜鹰监控系统自动报警。

  • 集群高可用

高可用主要针对 broker 中 master 节点由于软硬件故障无法正常工作,slave 节点自动被切换为 master ,适合消息顺序、集群完整性有要求的场景。

部分后台操作展示

主题与消费组申请

12.png

生产,消费,堆积实时统计

13.png

集群监控
修改图2.jpg

踩过的坑

社区对 MQ 系统经历了长时间的改进与沉淀,我们在使用过程中也到过一些问题,要求我们能从深入了解源码,做到出现问题心不慌,快速止损。

  • 新老消费端并存时,我们实现的队列分配算法不兼容,做到兼容即可;
  • 主题、消费组数量多,注册耗时过长,内存 oom ,通过压缩缩短注册时间,社区已修复;
  • topic 长度判断不一致,导致重启丢消息,社区已修复;
  • centos 6.6 版本中,broker 进程假死,升级 os 版本即可。

MQ 未来展望

目前消息保留时间较短,不方便对问题排查以及数据预测,我们接下来将对历史消息进行归档以及基于此的数据预测。

  • 历史数据归档
  • 底层存储剥离,计算与存储分离
  • 基于历史数据,完成更多数据预测
  • 服务端升级到 Dledger ,确保消息的严格一致

了解更多 RocketMQ 信息,可加入社区交流群,下面是钉钉群,欢迎大家加群留言。

15.jpg

深度解读畅捷通云原生架构转型实战历程

alicloudnative阅读(1450)评论(0)

新的头图.png
在信通院 2021 年云原生产业大会上,畅捷通获得 2021 年度云原生优秀案例。

畅捷通公司是用友集团旗下的成员企业,专注于服务国内小微企业的财务和管理服务。一方面,畅捷通将自己的产品、业务、技术架构互联网化;另一方面,畅捷通推出了畅捷通一站式云服务平台,面向小微企业提供以数智财税、数智商业为核心的平台服务、应用服务、业务服务及数据增值服务,致力于建立“小微企业服务生态体系”。

根据易观国际的报告,目前畅捷通在国内小微企业云服务市场的覆盖率保持第一。有超过 130 万家企业与机构通过使用畅捷通的软件及服务,实现降本提效、持续创新、数智化转型。

早期,畅捷通的业务应用都是构建在公司自主研发的 CSP 平台之上,包括客户管家、易代账、好会计和好生意等,每个企业分配一个独立的虚机,资源完全隔离,初期该平台方案极大简化了开发的复杂度,提高了开发效率,满足公司向云服务发展的最初需求。

新的需求

随着用户规模的增多,现有为每个用户分配一个独立虚机的方案产生的问题很突出。首先体现在虚机的数量日益庞大,在客户转换率不高的情况下,容易造成资源的浪费,同时运维成本也偏高;其次,不同的用户软件迭代的版本不尽相同,还需要专门设计维护多套版本的补丁更新系统;第三,产品需要停服上线,业务会出现短暂不可用的情况;最后,如果若干用户的业务出现高峰期,不能快速弹性扩容,资源的利用率不高。

在此背景下,畅捷通研发团队决定尝试当前业界比较通用的云原生架构设计方案,利用云上的基础设施,共享计算资源和存储资源,采用容器化方案,快速弹性扩容,利用微服务架构,按应用更新产品,彻底解决当前运维成本偏高、弹性不足、资源利用不均衡的问题。

小微企业的特点是数量多、单个企业业务量相对较小、企业 IT 能力有限。以畅捷通好生意产品为例,采用现有的云原生架构,能够方便畅捷通快速开发出一款应对大规模小型用户,支持弹性可扩展的 SaaS 应用。同时通过服务的编排,又能快速搭建出其他产品线,如智+。目前已经有超过 20 万的付费用户正在使用畅捷通提供的云原生架构企业云服务,每天产生的业务数据达百 G 以上。

云原生应用架构的驱动力

国内云计算产品快速发展,企业应用往云端迁移趋势明显,加上政府部门鼓励企业上云推出补贴政策,企业上云已成为大势所趋。

尤其在疫情阶段下,商业模式的变革,消费方式的转变,只有企业上云才能更有利于推动企业加快数字化、智能化的转型,更有效的帮助企业实现“客户在线、业务在线、人员在线、管理在线”。而现在的云原生技术带来的价值能更好的帮助企业进行数智转型。

1. 实时在线

采用智能接入网关,就近接入云企业网,在 VPC 间、VPC 与本地数据中心间搭建私网通信通道,通过自动路由分发及学习,提高网络的快速收敛和跨网络通信的质量和安全性,保证用户全球范围实时在线,加速实现整个客群的线上线下一体化。

2. 数智化

通过云端廉价的存储、强大的算力资源以及众多的算法模型,畅捷通用极低的成本存储海量数据并在线实时分析,为用户提供更多的商业决策。

3. 快速响应市场需求

采用 DDD 设计思想,利用微服务架构,快速开发高内聚、低耦合的应用。这些应用通过服务的编排,能快速组合更多的应用,满足不同行业和领域的客户群体,达到快速上线、迭代优化的效果。

4. 稳定高可靠

利用容器和微服务架构,可以快速构建和运行可弹性扩展的应用。系统出现故障或者性能瓶颈的时候,通过镜像可以秒级恢复受损应用,保障了系统的高可用性。利用云原生技术的红利,畅捷通可以只关注业务的开发,一方面加速构建新应用,另一方面也可以优化现有应用并在云原生架构中集成,达到奔跑中更换轮子的效果,去更方便地让历史存量的客户升级到云上。

云原生应用架构设计

云原生应用架构设计路线

原有产品是部署在物理 IDC 中,通过对 cloudfoundry 云平台的二开,实现每个租户间虚机的隔离。但由于每个租户独享容器+数据库,当用户量达到几十万级时,数据库的升级效率、容器部署成本、硬件运维复杂度都明显提升。通过应用的微服务化、上云来解决降本提效的问题迫在眉睫。

畅捷通通过基础设施上云、数据库上云、技术框架和业务框架的重构,实现了在多租户之间容器部署、应用共享、DB 共享,产品基于 EDAS 及集成在其上的阿里云容器服务 Kubernetes 版 ACK。希望通过云原生的技术红利,解决当前运维成本高、系统弹性不足、产品迭代交付周期长的问题。

应用架构的改造

1. 微服务架构

将复杂应用按照业务的视角切分为高内聚、低耦合模块,这些模块独立开发、独立发布。业务领域一共分为四层,即核心领域服务层、业务领域服务层、应用服务层和接口服务层。其中核心领域服务层包括授权、UOM、组织(Party)、产品、计价、促销和存量模型模块,主要提供核心领域知识、能力服务;业务领域服务层是提供好生意业务的业务功能,包括采购、库存管理和销售领域服务;应用服务层基于具体应用场景,调用领域服务,解决应用中具体的业务问题。每层服务都是一个单独的微服务,基于 EDAS 进行服务的全生命周期管理,通过引入 Spring Cloud 实现服务的注册发现以及治理。

此外,由于 EDAS 无缝集成了 ACK ,支持以容器的形式托管应用到阿里云 Kubernetes 集群或混合云集群(其他云域或IDC内自建集群),因此能够与畅捷通底层K8s集群打通,实现了 K8s 集群的定时弹性能力和基于 CPU/RT/Load 等某一监控指标的自动弹性能力。

2. 数据一致性

传统的单一服务架构,所有领域的功能都聚合在一个进程内运行,可以通过数据库的事务来保证业务强一致性。但是畅捷通现在按分布式微服务架构设计,不同领域模块会建成独立运行的微服务,微服务之间需要按照最终一致性方案来保证数据的一致。对于实时性要求高的场景,畅捷通采用 TCC 模型;对于实时性要求不高,可长过程处理的,畅捷通采用消息队列的方式进行服务的解耦,达到最终一致性。

技术架构的改造

1. 容器化管理

核心应用部署在容器中,通过 Kubernetes 进行统一编排和运行调度。对于秒杀场景或者耗算力的异步任务,通过函数计算来按需构建。

2. 服务治理

引入微服务架构后,服务管理尤为复杂,为此畅捷通引入 Spring Cloud 一站式解决方案,使得开发只需要专注业务的发展,不去关注技术细节。通过 Spring Cloud 完成服务发现注册、配置管理、限流降级、服务调用、数据监控等,实现降本提效,也降低了运维成本。

3. Gitops 流水线

基于 Gitlab、Jenkins、Rundeck、K8s,搭建自研的 DevOps 流水线,按照微应用独立构建、微服务自由组包、按照容器化进行发布部署,保证了研发、测试、线上各阶段的运行环境均保持不变。

4. 数据库层面的改造

所有租户共享数据库,按照应用、数据量等因素进行分库分表;引入 OLAP 数据库,分离交易库和分析库,避免大查询拖累用户的交易。

云原生应用技术框架


图片 1.png

系统分为前端展现层、业务中台、技术平台、运维中台以及基础设施层。

前端展现层:使用基于微前端应用集成思想形成的 H5 前端开发框架,使用 qiankun 实现同构应用和异构应用的集成,支持多端开发。

业务中台:采用微服务架构的设计思想,基于 EDAS 平台搭建而成,在应用服务层可以实现弹性伸缩、限流降级、流量监控等;业务模型通过元数据驱动并支持 GraphQL 查询;利用 RocketMQ 实现了业务的解耦。

技术中台:包括容器管理、DevOps 流水线、微服务治理、链路追踪等,是企业业务快速交付、系统稳定运行,业务快速创新的基石。

基础设施层:包括数据存储层以及中间件。数据基于关系型数据库存储,如MySQL、PolarDB 等,通过 Tenant 标识在数据库表级别实现多租户共享数据库;数据库也支持读写分离,查询操作只需要访问读数据库即可。其余中间件均由技术平台进行了二次封装,对业务屏蔽了具体的技术细节。

前台采用微前端框架

图片 2.png**

前端应用由于参与的人员增多、产品的应用模块随着需求不断增加,已经从一个普通单体应用演变成一个巨石应用,好生意、智+、微商城等产品前端开发工程都在一起开发,相互影响,导致功能开发、迭代上线无法按照应用单独部署。再加上前期业务和技术分层不清晰,导致公共组件抽象不够,业务和技术强耦合,技术想单独更新换代异常艰难。

畅捷通需要设计一套框架,确保畅捷通的业务代码能平滑的迁移,且还能确保畅捷通能在不影响业务在线的情况下,进行技术的迭代更新。

畅捷通只需要在主系统构造一个足够轻量的基座,再加上公共可复用的组件(包括基础技术组件、基础业务组件、公共业务服务等),然后让各子应用按照共同的协议去实现即可。这个协议包括,主应用应该如何加载子应用,以及子应用如何被主应用感知、调度,应用之间如何通信等。同时畅捷通这个系统还应该有提供统一的 build 工程,让各子应用不再关注配置、依赖、构建、发布等操作。这样,各个微应用之间就可以独立发布部署,不同应用之间的弱耦合,也降低了开发的复杂度。

技术平台-容器管理

快速发布环节:

基于从 git 的源码管理平台和配置管理平台的联动,实现了容器镜像的快速自动生成和管理,基于环境变量的区分和配置中心的统一管控,能实现一个容器镜像多环境部署模式,并且能对每次的代码进行安全和一致性扫描,保障代码环节的安全稳定。

闭环管理环节:

发布到线上后,基于阿里云 Prometheus 监控,异常信息发送到消息中心中,并且在消息中心数据汇聚和策略编排,形成了工单流的模式,实现有效数据的闭环管理。

业务保障环节:

在容器弹性伸缩方面,畅捷通借助 K8s 的 HPA 机制,基于阿里云容器服务 ACK 最大化利用资源的能力以及业务层自定义指标,实现面对直播、秒杀、在线考试等突发流量下微服务的快速扩缩容。

技术平台-DevOps 流水线

图片 3.png

  • 采用管道方式,将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排。自动化构建、测试和发布过程可轻松测试每次代码更改并捕获易于修复的错误。
  • 通过构建 DevOps 工具链,实现从需求下发、到代码提交与编译,测试与验证到部署与运维的全过程支撑,打通软件交付的完整路径,提供软件研发端到端支持。

技术平台-微服务治理

随着业务的快速发展,畅捷通对原有的IT系统进行了大量的微服务化改造,以适应互联网大型应用快速迭代以及频繁发布的需求。由于 SaaS 化企业管理云服务具备用户量大、业务复杂、调用链路长、与第三方应用系统深度集成等特点,给微服务化改造工作带来了非常大的挑战。畅捷通必须提升整体的微服务治理能力与监控能力,在频繁的版本迭代中才能确保系统的稳定健壮。

图片 4.png

最终畅捷通的微服务部署到阿里云提供的企业级分布式应用服务 EDAS 上,运行在 EDAS 上的 Spring Cloud 应用,可以享受到应用生命周期管理、无损下线、全链路流控等一系列针对微服务治理领域的能力增强。特别在应用发布的流程中,EDAS 所提供的平滑上下线以及灰度机制极大程度的提升了系统在版本更新期间的稳定性,降低了应用发布所带来的风险。

技术平台-全链路监控

海恩法则指出:每一起严重事故背后,必然有 29 次轻微事故和 300 起未遂先兆以及 1000 起事故隐患。

图片 5.png

尤其是畅捷通采用了微服务架构后,由于 SaaS 产品所涉及到的业务链路极为复杂,当用户反馈系统 Bug 或者性能存在问题之后,技术团队需要耗费非常长的时间在错综复杂的链路之间定位故障源以及分析性能瓶颈。畅捷通也使用了一些 APM 工具类的产品,包括应用性能监控,用户体验监控,链路追踪,问题诊断等,但是这类工具仅能定位框架级的问题,对于自定义函数以及进程中的异步处理均无法做到链路追踪,在分布式应用架构中这类 APM 发挥的作用就更少了。

由于畅捷通的应用是托管在阿里云的 EDAS 中,EDAS 集成了应用实时监控服务 ARMS,可以监控微服务的健康状态和关键指标,并针对监控指标设置告警,及时发现并处理可能存在的异常或故障,以保障应用的健康和可用性,所以畅捷通只需要将业务操作与系统日志、系统日志和 ARMS 打通,就可以实现从业务出发,贯穿整个业务的生命周期,快速定位应用的性能瓶颈以及异常故障的位置。

为此,畅捷通实现了一套机制,将业务操作按照 Timeline 进行抽象建模,并结合系统日志、阿里云 ARMS 系统形成三位一体的全链路跟踪机制。

  • 原则上,除了读操作之外的写权限点所对应的交互都应当视作一次 BI。所以畅捷通可以简单地认为每个写操作的权限点就是一个 BI。这就反过来要求后端提供的 REST api 必须是面向场景,每个API对应一个权限点。
  • BI 与 REST api 的 request_id 之间需要建立关联,以便追踪业务操作与系统日志之间的关系。
  • WebFilter 拦截所有 RESTful API的Request 和 Response ,获取到请求和应答信息,通过交互协议中的取值公式从拦截到的数据中解析出具体的特征、角色、关系等数据。在 Request 请求中增加 Create 操作将实体的原值自动记录下来,在 Response 返回时额增加 Complete 操作,负责把新值写入,并记录前后变化。两者在接口的调用开始和调用结束时配对使用。
  • 在后台日志里面,记录了 user_req_id 和阿里云的 arms 的 uber-trace-ID 的对照关系。

通过全链路跟踪机制,畅捷通将业务的交互操作与阿里云 ARMS 应用监控关联起来,尤其是业务中还存在一些通过消息队列进行解耦的操作,畅捷通都可以通过 BI 来进行追踪,为畅捷通的微服务体系更进一步的提供了监控能力。在接入 ARMS 之后,通过全链路信息排查以及应用实时诊断等工具,将定位系统故障源以及性能瓶颈的工作量降低到了之前的 50% 以下,极大程度的提升了 IT 团队的工作效率。

技术平台-灰度发布

灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行 A/B testing ,即让一部分用户继续用产品特性 A ,一部分用户开始用产品特性 B 。

灰度期:新特性在灰度环境发布一直到新特性部署到线上正式环境的这一段时间,称为灰度期。对于 2C 的应用,是以用户作为灰度的基本单位进行分流。而对于 2B 的应用,则是以租户为基本单位进行分流。

灰度环境包括数据库灰度和应用程序的灰度。若在数据库层面支持灰度,则需要新建一个灰度 DB ,把参与灰度的客户数据导入到灰度 DB 中,灰度结束后再把数据清洗合并到正式生产 DB 中。这个过程所需操作较多且成本较高,鉴于此,数据库层面不考虑灰度。基于这个设定,需要遵循以下几个约束:

  • 灰度客户的量控制在较小范围,以尽可能缩小数据修复的范围;
  • 模型设计严格遵从兼容原则,如:只增不减,字段避免复用;
  • 对于影响业务逻辑的系统数据需要有评估。比如增加了某个系统级的枚举值,只对灰度客户可见,可以考虑针对灰度客户复制一份系统数据,灰度后删除,系统后端代码逻辑优先取租户数据,取不到再获取系统级数据。

应用程序的灰度因为后端对外提供的服务都是 REST API ,可以采用支持调用外部服务的负载均衡或反向代理获取灰度用户名单后,对用户进行分流。后端接口的兼容性需要保证,当无法保证兼容性的情况下,需要强制前端一同升级。

图片 6.png

基础设施层-数据库读写分离

初期云服务上线的时候,用户规模小,数据量也小,所以线上环境 MySQL 没有实现读写分离方案。在平稳运行一段时间,随着用户规模导致 PV 和 UV 增加,给数据库带来了巨大压力,主要体现在如下三点:

  • 复杂业务,多表联查效率低下,导致业务功能响应不够及时甚至查询超时;
  • 业务高峰期传统的数据库无法快速升配,只能等待业务高峰过去或者做流控;
  • 业务代码对主从延迟敏感,无法充分利用从库。

初期采用 Sharding-JDBC 实现读写分离,但现有产品读写操作互绕,不易将读操作分离,且读库和写库的同步延迟较大,不太满足业务需要。调研 PolarDB 后发现,其集群地址直接支持读写分离,且与 MySQL 语法 100% 的兼容,对业务无影响。通过 PolarDB 的分钟级弹升能力,能快速升配;其特有的并行查询能力也可以降低复杂查询的响应时间,同时提升系统的并发能力。

所以畅捷通将数据库从 MySQL 迁移到 PolarDB,并采用了一写多读的模式,并对耗时的报表查询业务单独指定读节点,降低了耗时操作对其他业务的影响,保障了用户的正常交易操作。

运维中台-监控体系

传统的报警机制,以消息为载体,尽量简洁,更倾向于报警即故障模式,涵盖信息少,对后期判断往往起到一个提醒开始的状态。

随着运维模式的提高,大量高效工具的结合,自愈,时序事件等相关系统不断涌现,同时近年来兴起的云原生模式,将实现智能预警的老大难问题:信息标准化问题,给出了优质的解决方案,也加速了升级监控结构的步伐,报警的单一模式已不再适应需求,运维人员更希望报警有意义,有过程、有结论。

畅捷通长期致力于以客户体验为基础的立体化监控架构,从客户性能与体验损失的纬度,对监控信息分级,分权重,通过业务链中各环节的客户画像,特征模型,精准监控用户体验。可在用户未感知的情况下预警并进入处理流程,进入流程的事件在内部消息中心扭转,关联计算,分析根因以及自愈方案。并根据事件处理模型响应事件,发送报警,报警涵盖事件的关键信息与结论。在提高了预警有效行的同时,也避免的传统故障处理模型中的各种效率损失。

云原生应用服务特点

DevOps 工具-故障检修中心

众所周知,分布式系统遵从 CAP 理论,畅捷通通常选择满足 AP 而放弃 C(一致性)。按照墨菲定律,畅捷通最终是需要通过人工干预来保证数据的最终一致性。那么检查发现不一致的数据、区别错误场景并按规程修复,就需要有一套系统来辅助进行。

不同于系统运维,业务检修面向的是业务和服务,通过租户和租户数据的监控来发现和解决业务问题。系统运行中,除数据不一致之外,还有其他一些常见故障,也需要为支持和解决一些较为普遍有共性的客户问题提供便利,以及提供部分分析视图帮助研发或运维掌握了解租户状态和趋势,这些诉求的响应也都会因为生产环境的物理隔离和安全性要求,而变得低效,只有通过固化到系统中形成工具集才能更好地解决。

所以,畅捷通提供了一套业务检修系统,负责业务数据的监控、问题诊断,故障修复、态势预警等。整体设计如下:

图片 7.png

目前系统针对开通失败、定时任务失败、死信、数据稽核违规、导入超时、TCC 失败六类故障进行定时检查,并与运维的 Midas 消息系统集成,及时发现问题,并向具体负责人发出钉钉告警;负责人通过故障展示的看板和明细列表来查看具体原因;针对具体故障,还提供查看上下文日志、重新开通、死信重放、数量帐重算、重提交等辅助手段解决故障。

安全服务-内容安全

畅捷通的产品会涉及到协同中的聊天信息,电商类的商品评价,这些内容可能会被外人恶意使用,将一些非法广告、网络诈骗、恐怖信息等内容录入畅捷通的产品中并传播出去,所以畅捷通的运维安全部门需要增加内容安全的监控,保障公司产品的纯净、健康运行,避免网络犯罪行为发生在畅捷通公司的产品和用户之中。

图片 8.png

畅捷通借用系统的操作日志和消息队列,将内容安全检测和业务功能进行解耦,借助函数计算的能力,按需调用云服务厂商提供的安全检测能力(文字类、图片类等),去识别可分享的业务,内容是否符合安全法,检测结果还增加了人工审核及误判赦免。

数用分离

图片 9.png

统计分析类数据,畅捷通每天通过 DTS 将原始数据从关系型数据库增量同步到数据仓库里,在固定时间进行数据清洗、汇总统计分析后,再将这部分数据传输 Elasticsearch 。同时畅捷通也利用 MaxCompute 的多任务计算能力,进行业务数据的标签计算,计算结果也传递给 Elasticsearch ,业务系统通过 Elasticsearch 的 REST API 访问这些数据,并展现给最终用户。

全链路流量控制技术+端云联调

由于微服务间的依赖,开发人员无法在本地完成开发和测试,必须依赖一套测试开发环境。

研发效率方面:当上百人的开发人员共享一套环境,开发态的代码频繁变更,质量较低,服务之间相互影响,开发环境经常中断,调试困难,严重影响了开发效率,研发希望每个项目能提供一套环境,如图:

图片 10.png

研发成本方面:如果为每个项目提供全量环境,计算资源成本很高,运维成本也会激增。(在开发态有近百个微服务,按 20 个项目并行开发计算,需要近 2000 个POD/ECS 的计算资源成本和运维成本)产品在成长期,并行的项目和服务数都在增加。

综合效率和成本方面的因素,畅捷通引入网关、全链路流量精确控制技术、端云联调技术,用基线环境+增量修改的应用叠加出项目开发环境:在入口处根据企业 Id 进行流量打标(根据企业 ID 在请求中注入环境标识),如:https://cloud.chanjet.com/req?orgId =企业 ID ,环境标识随请求在整个执行流程中流转(http 请求,rpc 请求,定时任务,消息等),通过环境标识,对微服务调用/消息进行精确控制。如图:

图片 11.png

畅捷通在基线环境部署最新上线的代码,在项目环境中只部署涉及修改的应用:

(1)为项目研发提供稳定的独立项目环境,使研发能按“小规模,多批次”(DevOps 最佳实践)的方式快速协作;
(2)节省了资源、降低了运维成本。(按每个项目修改 5% 的应用,在开发态有近百个微服务,按 20 个项目并行开发计算,将节省 2000*95%=1900 个ECS/POD);
(3)使用端云联调技术,方便开发本地PC加入到项目环境调试。如图所示:

图片12.png

云原生带来的技术价值

高弹性可扩展

系统可以在应用服务层和数据库层支持横向扩展。

应用方面:通过微服务架构,容器化部署,技术平台可以感知服务器负载,弹性伸缩服务节点,实现集群扩/缩容,快速补充计算能力。

数据库方面:数据库支持快速扩容读节点,以支持更多并发请求,同时租户实现数据隔离,并可根据负载实现跨库迁移。

微服务化

在微服务架构和设计阶段:

  • 引入 MDD 领域驱动方法论进行应用架构设计和领域模型设计;
  • 按照微服务的设计原则进行服务的拆分和职责、关系定义,确定服务接口和领域模型;

作为一个复杂的 ToB 应用,畅捷通按照如下原则进行微服务的拆分:

  • 可用 – 拆分的模块能够满足应用的需要;
  • 好用 – 拆分的模块能够通过比较简单、清晰的方式组成应用,服务应用价值;
  • 美 – 用最简单的方式表达复杂问题;业务人员容易理解。

Back-end For Front-end

服务器端采用微服务架构之后,畅捷通需要在前后端之间增加 BFF 层,简化前后端人员的协同开发复杂度。BFF 层基于 Node 实现,畅捷通选择了 Egg.js ,并基于Egg.js 做了分层封装,在 Node 自身技术升级的情况下可以不影响业务开发。为了提高效率,畅捷通还在 Node 层接入了缓存中间件 Redis ,并利用 GraphQL 做聚合查询。

端云联调方案

采用微服务架构后,每套环境部署需要 30+ 的微服务容器,如果开发阶段存在多特性并行,为每个特性分支单独部署环境,资源消耗太大。为此,畅捷通引入了网关、全链路流量打标控制技术、端云联调技术,用基线环境+增量修改的应用叠加出项目开发环境,以最小代价快速拉起一套完整的开发调试环境,降低了开发成本。同时开发人员也可以将本地电脑直接注册到微服务环境中去,在本地完成上下游业务的验证和测试,极大地提高了开发人员的工作效率以及提交代码的质量。

离线数据分析

早期,畅捷通的数据库只有 OLTP ,数据存储在 MySQL 里,随着数据量的增多,统计分析类的查询不仅自身查询时间长,还消耗了在线交易库的资源,影响在线交易的响应时间。仅仅通过云原生数据库 PolarDB ,进行读写分离,只能缓解一部分查询压力。

针对系统中一些实时性要求不高的分析,畅捷通引入了数据仓库进行离线数据的处理,通过 DataWorks 工具进行 ETL 和统一调度,在 MaxCompute 中进行大数据指标计算和标签计算。

全链路灰度方案

畅捷通的系统,实现了从前端到后端服务、消息队列、定时任务的全链路灰度。
前端静态资源:区分灰度静态资源和正式环境静态资源。登录后由登录信息里的租户,确定应该路由到哪个环境。
消息队列:区分灰度消息队列和正式消息队列。根据环境变量进行消费
定时任务:定时任务的任务执行方,通过环境变量和租户名单,来决定是否执行。
后端 Rest 接口:在 Nginx 中通过 lua 脚本,解析 url 路径,根据灰度名单中的租户信息进行路由。

云原生带来的业务价值

容器化运维管理:

基于 EDAS+ACK 模式的容器化部署和管理,实现了应用发布的提升,并且在弹性伸缩和容量管理,也可以加快异常识别,故障自愈。并且在 2(异常识别)-5(快速定位)-10(自愈止损)的目标上不断攀升。

可观测性:

基于日志平台和数据中台的结合分析,实现用户画像和用户轨迹的展示,并且对数据的不断挖掘,可以实现用户体验百分数量化,产品质量百分数量化,从而在服务团队对接上提供数字化保障。

成本方面:

从 IDC 机房容器模式到云平台容器化的升级,不必要投入到硬件设备的大额投入和替换设备上,从而在设备成本和人力成本上大幅度节省了很多,并且新的需求和想法都可以按量投入,弹性伸缩。

DevOps:
对接云原生环境的 devops ,在研发需求支持上,节省人员 50%,构建及部署效率上提升了 4 倍。其中 2020 全年更新次数 12734 次,成功率 91.8%。

多环境资源:

通过多特性灰度方案的模式,7 套开发环境合并为1套。其中每套环境 90 个微服务,可以实现灵活性的动态调整和扩展。节省 450 个节点。

云原生容器化
容器化 EDAS+K8s 部署模式,可以实现节点的弹性伸缩,特别是支撑了畅捷通直播活动,秒杀场景。不仅仅节省了人力的部署等环节投入,也节省了秒级伸缩的成本。

多云平台:

通过 DevOps 和容器化模式的建立,满足了用户多云场景的需求,并且节省人力50%,实现多云环境的自动化运维模式。

数据库升级:

好生意从 MySQL 升级到 PolarDB 后,其中处理性能提升 20%~40% ,并成功的将无法支持的低端手持 pda 适配工作成功复活。抽样回访客户,满意度也大幅提升。

稳定性:

​基于云原生基础组建的对接,云产品可以实现 5 个 9 的 SLA 服务,这样避免了自身搭建开源组件的稳定性和安全风险。并且在组件功能上也能享受到各种业务需求功能的福利。对接云原生后,可以提出 2(告警)-5(定位)-10(止损)的稳定性保障目标,从而在用户满意度上不断提升。

RocketMQ 千锤百炼–哈啰在分布式消息治理和微服务治理中的实践

alicloudnative阅读(1667)评论(0)

头图.jpg

作者|梁勇

背景


哈啰已进化为包括两轮出行(哈啰单车、哈啰助力车、哈啰电动车、小哈换电)、四轮出行(哈啰顺风车、全网叫车、哈啰打车)等的综合化移动出行平台,并向酒店、到店团购等众多本地生活化生态探索。

随着公司业务的不断发展,流量也在不断增长。我们发现生产中的一些重大事故,往往是被突发的流量冲跨的,对流量的治理和防护,保障系统高可用就尤为重要。

本文就哈啰在消息流量和微服务调用的治理中踩过的坑、积累的经验进行分享。

作者介绍


梁勇 ( 老梁 ) ,《 RocketMQ 实战与进阶》专栏联合作者、参与了《 RocketMQ 技术内幕》审稿工作。ArchSummit 全球架构师大会讲师、QCon 案例研习社讲师。

当前主要在后端中间件方向,在公众号【瓜农老梁】已陆续发表百余篇源码实战类文章,涵盖 RocketMQ 系列、Kafka 系列、GRPC 系列、Nacosl 系列、Sentinel 系列、Java NIO 系列。目前就职于哈啰出行,任职高级技术专家。

聊聊治理这件事


开始之前先聊聊治理这件事情,下面是老梁个人理解:

治理在干一件什么事?

  • 让我们的环境变得美好一些

需要知道哪些地方还不够好?

  • 以往经验
  • 用户反馈
  • 业内对比

还需要知道是不是一直都是好的?

  • 监控跟踪
  • 告警通知

不好的时候如何再让其变好?

  • 治理措施
  • 应急方案

目录

  1. 打造分布式消息治理平台
  2. RocketMQ 实战踩坑和解决
  3. 打造微服务高可用治理平台

背景

裸奔的 RabbitMQ


公司之前使用 RabbitMQ ,下面在使用 RabbitMQ 时的痛点,其中很多事故由于 RabbitMQ 集群限流引起的。

  • 积压过多是清理还是不清理?这是个问题,我再想想。
  • 积压过多触发集群流控?那是真的影响业务了。
  • 想消费前两天的数据?请您重发一遍吧。
  • 要统计哪些服务接入了?您要多等等了,我得去捞IP看看。
  • 有没有使用风险比如大消息?这个我猜猜。

裸奔的服务

曾经有这么一个故障,多个业务共用一个数据库。在一次晚高峰流量陡增,把数据库打挂了。

  • 数据库单机升级到最高配依然无法解决
  • 重启后缓一缓,不一会就又被打挂了
  • 如此循环着、煎熬着、默默等待着高峰过去

思考:无论消息还是服务都需要完善的治理措施

打造分布式消息治理平台

设计指南


哪些是我们的关键指标,哪些是我们的次要指标,这是消息治理的首要问题。

设计目标

旨在屏蔽底层各个中间件( RocketMQ / Kafka )的复杂性,通过唯一标识动态路由消息。同时打造集资源管控、检索、监控、告警、巡检、容灾、可视化运维等一体化的消息治理平台,保障消息中间件平稳健康运行。

消息治理平台设计需要考虑的点

  • 提供简单易用 API
  • 有哪些关键点能衡量客户端的使用没有安全隐患
  • 有哪些关键指标能衡量集群健康不健康
  • 有哪些常用的用户/运维操作将其可视化
  • 有哪些措施应对这些不健康

尽可能简单易用

设计指南


把复杂的问题搞简单,那是能耐。

极简统一 API

提供统一的 SDK 封装了( Kafka / RocketMQ )两种消息中间件。

1.png

一次申请


主题消费组自动创建不适合生产环境,自动创建会导致失控,不利于整个生命周期管理和集群稳定。需要对申请流程进行控制,但是应尽可能简单。例如:一次申请各个环境均生效、生成关联告警规则等。

2.png

客户端治理

设计指南

监控客户端使用是否规范,找到合适的措施治理

场景回放

场景一 瞬时流量与集群的流控

假设现在集群 Tps 有 1 万,瞬时翻到 2 万甚至更多,这种过度陡增的流量极有可能引发集群流控。针对这类场景需监控客户端的发送速度,在满足速度和陡增幅度阈值后将发送变的平缓一些。

场景二 大消息与集群抖动

当客户端发送大消息时,例如:发送几百KB甚至几兆的消息,可能造成 IO 时间过长与集群抖动。针对这类场景治理需监控发送消息的大小,我们采取通过事后巡检的方式识别出大消息的服务,推动使用同学压缩或重构,消息控制在 10KB 以内。

场景三 过低客户端版本

随着功能的迭代 SDK 的版本也会升级,变更除了功能外还有可能引入风险。当使用过低的版本时一个是功能不能得到支持,另外一个是也可能存在安全隐患。为了解 SDK 使用情况,可以采取将 SDK 版本上报,通过巡检的方式推动使用同学升级。

场景四 消费流量摘除和恢复

消费流量摘除和恢复通常有以下使用场景,第一个是发布应用时需要先摘流量,另外一个是问题定位时希望先把流量摘除掉再去排查。为了支持这种场景,需要在客户端监听摘除/恢复事件,将消费暂停和恢复。

场景五 发送/消费耗时检测

发送/消费一条消息用了多久,通过监控耗时情况,巡检摸排出性能过低的应用,针对性推动改造达到提升性能的目的。

场景六 提升排查定位效率

在排查问题时,往往需要检索发了什么消息、存在哪里、什么时候消费的等消息生命周期相关的内容。这部分可以通过 msgId 在消息内部将生命周期串联起来。另外是通过在消息头部埋入 rpcId / traceId 类似链路标识,在一次请求中将消息串起来。

治理措施提炼

需要的监控信息

  • 发送/消费速度
  • 发送/消费耗时
  • 消息大小
  • 节点信息
  • 链路标识
  • 版本信息

常用治理措施

  • 定期巡检:有了埋点信息可以通过巡检将有风险的应用找出来。例如发送/消费耗时大于 800 ms、消息大小大于 10 KB、版本小于特定版本等。
  • 发送平滑:例如检测到瞬时流量满足 1 万而且陡增了 2 倍以上,可以通过预热的方式将瞬时流量变的平滑一些。
  • 消费限流:当第三方接口需要限流时,可以对消费的流量进行限流,这部分可以结合高可用框架实现。
  • 消费摘除:通过监听摘除事件将消费客户端关闭和恢复。

主题/消费组治理

设计指南


监控主题消费组资源使用情况

场景回放


场景一 消费积压对业务的影响

有些业务场景对消费堆积很敏感,有些业务对积压不敏感,只要后面追上来消费掉即可。例如单车开锁是秒级的事情,而信息汇总相关的批处理场景对积压不敏感。通过采集消费积压指标,对满足阈值的应用采取实时告警的方式通知到应用负责的同学,让他们实时掌握消费情况。

场景二 消费/发送速度的影响

发送/消费速度跌零告警?有些场景速度不能跌零,如果跌零意味着业务出现异常。通过采集速度指标,对满足阈值的应用实时告警。

场景三 消费节点掉线

消费节点掉线需要通知给应用负责的同学,这类需要采集注册节点信息,当掉线时能实时触发告警通知。

场景四 发送/消费不均衡

发送/消费的不均衡往往影响其性能。记得有一次咨询时有同学将发送消息的key设置成常量,默认按照 key 进行 hash 选择分区,所有的消息进入了一个分区里,这个性能是无论如何也上不来的。另外还要检测各个分区的消费积压情况,出现过度不均衡时触发实时告警通知。

治理措施提炼


需要的监控信息

  • 发送/消费速度
  • 发送分区详情
  • 消费各分区积压
  • 消费组积压
  • 注册节点信息

常用治理措施

  • 实时告警:对消费积压、发送/消费速度、节点掉线、分区不均衡进行实时告警通知。
  • 提升性能:对于有消费积压不能满足需求,可以通过增加拉取线程、消费线程、增加分区数量等措施加以提升。
  • 自助排查:提供多维度检索工具,例如通过时间范围、msgId 检索、链路系统等多维度检索消息生命周期。

集群健康治理

设计指南


度量集群健康的核心指标有哪些?

场景回放

场景一 集群健康检测

集群健康检测回答一个问题:这个集群是不是好的。通过检测集群节点数量、集群中每个节点心跳、集群写入Tps水位、集群消费Tps水位都是在解决这个问题。

场景二 集群的稳定性

集群流控往往体现出集群性能的不足,集群抖动也会引发客户端发送超时。通过采集集群中每个节点心跳耗时情况、集群写入Tps水位的变化率来掌握集群是否稳定。

场景三 集群的高可用

高可用主要针对极端场景中导致某个可用区不可用、或者集群上某些主题和消费组异常需要有一些针对性的措施。例如:MQ 可以通过同城跨可用区主从交叉部署、动态将主题和消费组迁移到灾备集群、多活等方式进行解决。

治理措施提炼


需要的监控信息

  • 集群节点数量采集
  • 集群节点心跳耗时
  • 集群写入 Tps 的水位
  • 集群消费 Tps 的水位
  • 集群写入 Tps 的变化率

常用治理措施

  • 定期巡检:对集群 Tps 水位、硬件水位定期巡检。
  • 容灾措施:同城跨可用区主从交叉部署、容灾动态迁移到灾备集群、异地多活。
  • 集群调优:系统版本/参数、集群参数调优。
  • 集群分类:按业务线分类、按核心/非核心服务分类。

最核心指标聚焦


如果说这些关键指标中哪一个最重要?我会选择集群中每个节点的心跳检测,即:响应时间( RT ),下面看看影响 RT 可能哪些原因。

3.png

关于告警

  • 监控指标大多是秒级探测
  • 触发阈值的告警推送到公司统一告警系统、实时通知
  • 巡检的风险通知推送到公司巡检系统、每周汇总通知

消息平台图示

架构图


4.png

看板图示

  • 多维度:集群维度、应用维度
  • 全聚合:关键指标全聚合

5.png

6.png

RocketMQ 实战中踩过的坑和解决方案

行动指南


我们总会遇到坑,遇到就把它填了。

1. RocketMQ 集群 CPU 毛刺

问题描述

**

RocketMQ 从节点、主节点频繁 CPU 飙高,很明显的毛刺,很多次从节点直接挂掉了。

7.png

只有系统日志有错误提示

2020-03-16T17:56:07.505715+08:00 VECS0xxxx kernel:[] ? __alloc_pages_nodemask+0x7e1/0x9602020-03-16T17:56:07.505717+08:00 VECS0xxxx kernel: java: page allocation failure. order:0, mode:0x202020-03-16T17:56:07.505719+08:00 VECS0xxxx kernel: Pid: 12845, comm: java Not tainted 2.6.32-754.17.1.el6.x86_64 #12020-03-16T17:56:07.505721+08:00 VECS0xxxx kernel: Call Trace:2020-03-16T17:56:07.505724+08:00 VECS0xxxx kernel:[] ? __alloc_pages_nodemask+0x7e1/0x9602020-03-16T17:56:07.505726+08:00 VECS0xxxx kernel: [] ? dev_queue_xmit+0xd0/0x3602020-03-16T17:56:07.505729+08:00 VECS0xxxx kernel: [] ? ip_finish_output+0x192/0x3802020-03-16T17:56:07.505732+08:00 VECS0xxxx kernel: [] ?

各种调试系统参数只能减缓但是不能根除,依然毛刺超过 50%
8.png

解决方案

将集群所有系统升级从 centos 6 升级到 centos 7 ,内核版本也从从 2.6 升级到 3.10 ,CPU 毛刺消失。

2. RocketMQ 集群线上延迟消息失效

问题描述

RocketMQ 社区版默认本支持 18 个延迟级别,每个级别在设定的时间都被会消费者准确消费到。为此也专门测试过消费的间隔是不是准确,测试结果显示很准确。然而,如此准确的特性居然出问题了,接到业务同学报告线上某个集群延迟消息消费不到,诡异!

解决方案

将” delayOffset.json “和” consumequeue / SCHEDULE_TOPIC_XXXX “移到其他目录,相当于删除;逐台重启 broker 节点。重启结束后,经过验证,延迟消息功能正常发送和消费。

打造微服务高可用治理平台

设计指南

哪些是我们的核心服务,哪些是我们的非核心服务,这是服务治理的首要问题

设计目标

服务能应对突如其来的陡增流量,尤其保障核心服务的平稳运行。

应用分级和分组部署

应用分级


根据用户和业务影响两个纬度来进行评估设定的,将应用分成了四个等级。

  • 业务影响:应用故障时影响的业务范围
  • 用户影响:应用故障时影响的用户数量

S1:核心产品,产生故障会引起外部用户无法使用或造成较大资损,比如主营业务核心链路,如单车、助力车开关锁、顺风车的发单和接单核心链路,以及其核心链路强依赖的应用。

S2: 不直接影响交易,但关系到前台业务重要配置的管理与维护或业务后台处理的功能。

S3: 服务故障对用户或核心产品逻辑影响非常小,且对主要业务没影响,或量较小的新业务;面向内部用户使用的重要工具,不直接影响业务,但相关管理功能对前台业务影响也较小。

S4: 面向内部用户使用,不直接影响业务,或后续需要推动下线的系统。

分组部署

S1 服务是公司的核心服务,是重点保障的对象,需保障其不被非核心服务流量意外冲击。

  • S1 服务分组部署,分为 Stable 和 Standalone 两套环境
  • 非核心服务调用 S1 服务流量路由到 Standalone 环境
  • S1 服务调用非核心服务需配置熔断策略

9.png

多种限流熔断能力建设

我们建设的高可用平台能力

10.png

部分限流效果图

**

  • 预热图示

11.png

  • 排队等待

12.png

  • 预热+排队

13.png

高可用平台图示

**

  • 中间件全部接入
  • 动态配置实时生效
  • 每个资源和 IP 节点详细流量

14.png

总结

  • 哪些是我们的关键指标,哪些是我们的次要指标,这是消息治理的首要问题
  • 哪些是我们的核心服务,哪些是我们的非核心服务,这是服务治理的首要问题
  • 源码&实战 是一种比较好的工作学习方法。

连续中标建信金科、浦发和杭州银行,云原生助推数字化转型

谐云阅读(1959)评论(0)

谐云近期连续中标建信金科、浦发银行、杭州银行等容器平台及服务项目,为其建设容器云平台,助力拥抱云原生落地。谐云将为其各类互联网化应用提供稳定、可靠、安全的技术支持,有效提升研发效能与研发管理水平,运用数字化技术,完善服务模式,快速应对市场变化,通过更加方便、人性化的客户服务来稳固客户,实现业务的持续发展,助推银行数字化转型。

数字经济加速金融行业转型升级

据前段时间发布的《中国数字经济发展白皮书》显示,2020年我国数字经济规模扩张到39.2万亿元,占GDP比重的38.6%,是国民经济的核心增长极之一。数字经济已经成为国民经济的重要推动力量,各行各业都在大力推进数字化转型。

金融是实体经济的血脉,经济社会各领域的发展都离不开金融的支撑。无论是更好解决中小企业融资难融资贵等老难题,还是积极应对新技术改变生产生活方式而催生出的新需求,金融机构都需要更灵活的组织架构、流程以及业务组织形式,这些都推动金融业通过数字化转型探索解决之道。

建信金科、浦发银行和杭州银行,包括越来越多的银行借力金融科技技术快速实现数字化产品创新,金融业已经成为云原生应用最为深入、数字化最为迫切的领域之一。

 金融行业的数字化转型之痛

当前金融行业正处在巨大的IT架构变革与紧迫的数字化转型时期,金融机构和金融科技服务企业迎来新的挑战,数字化转型呈现出新的趋势,服务将升级到以云+端+生态+场景为基础的运营,不仅需要全面数字化,还需要有灵活的底层架构来应对前端业务的快速创新。

数字化浪潮下,大部分金融企业都面临着改造传统集中式架构并转向更为敏捷的分布式架构的问题,以期实现底层资源的统一纳管、加速产品上线、业务的快速迭代,从而满足日新月异的用户需求。但转型之路注定不是坦途,银行等金融机构在转型过程中往往面临着诸多痛点:
1、过于具体的技术升级,往往与整体业务架构不兼容,需要耗费过多额外成本;
2、整体性的解决方案升级往往缺乏重点,不能捕捉每家机构具体的需求;
3、技术不自主可控,安全、稳定性低;
4、应用未标准化,可移植性低,受云服务商绑定,迁移成本和难度高;
5、依赖人肉运维,流量洪峰时扩缩容难度大,周期长;
6、产品上线周期长,交付环节出错几率高,加上生产环境及测试环境差异,易造成线上故障;
7、分布式场景下,难以动态识别云上组件状态极配置信息,故障诊断困难
针对以上问题,谐云基于容器、DevOps、云上监控等云原生技术打造了金融级容器云解决方案,提供金融混合云方案,融合异构IT资源、PaaS应用管理、微服务、DevOps和分布式核心中间件,结合头部金融客户合作的场景优势,在为金融行业提供互联网端敏态业务支撑的同时,也为业务开放和混合架构带来的安全容灾和监管需求提供有效手段,是针对银行实际需求定制的一套金融容器云解决方案,堪称行业内最懂金融的容器云。

谐云金融级容器云解决方案

01
金融级的多活容灾方案

金融业务,尤其以银行业务为代表,对软件系统的可靠性要求相当严格,PAAS作为面向业务应用的管理平台,其可靠性也非常重要,依据银行业监管要求,需要提供基于两地三中心部署架构的容器云基座,谐云根据各方要求,设计并研发了金融级的两地三中心容器底座,通过三个层面保证业务及平台的整体连续性。
其一:平台级保证,通过设计研发平台数据库的多实例同步,完成平台管理数据层面的高可用保障;
其二:kubernetes底座层保证,引入etcd同步机制,通过研究同步效率及性能,提升底座级的高可用级别;
其三:业务级保证,配合业务系统数据设计,配合Otter同步组件,实现底层业务的数据高可用保障

02
无感知秒级故障隔离机制

业务系统的故障总是讲究MTTR、PRO、RTO,这些数字对于大多数运维、保障部门都是KPI红线,因此,一旦出现业务系统的故障,第一时间想到的必定是恢复业务。谐云科技针对这一普适性的需求,提出了基于容器云业务场景下的解决方案。
谐云科技基于自研的NPD组件,实现秒级的业务故障隔离,故障系统可以在秒级自动隔离、自动拉起新业务实例并优雅切换流量;在保留故障现场的同时,优先解决线上业务恢复问题;同时针对项目实际经验,设计各类故障特征,帮助NPD组件快速理解并通过特征隔离实例。

03
云上业务运行可观测能力

运维保障团队能解决问题,但不代表希望遇到问题。古往今来,最好的解决方案就是“趋利避害”,但是在这之前,我们需要有一套云上环境的全链路监控系统,通过最轻量的旁路角度,以类穿戴型设备的方式,帮助业务系统实现日常监测,降低系统罹患“心脑血管”等重要疾病的概率。

金融类业务大多具备调用链路长,一次交易请求跨域跨集群跨数据中心更是相当常见,为解决“数智金融”时代的分布式系统可观测问题,谐云科技提出了后K8s时代的重要解决方案–基于EBPF的旁路式全链路业务监控(后简称“云监控”)。

谐云通过多年的Linux内核技术研究积累,自研“云监控”解决方案,通过极低功耗的旁路代价,抓取云上的各类东西向、南北向流量,并结合自动绘制的业务拓扑,快速展示业务健康状况,让用户对业务的“血脂血压血糖”了然于胸;对于运维上报的各类云上业务故障,可以结合链路分析,定位故障层级,完成业务系统的“门诊预检”;同时“云监控”搭载了谐云多年研发积累的APM产品探针,对于“门站预检”确定是代码故障的结论,可以自动化装载APM Agent,在不重启业务的情况下,对业务进行代码级“冠脉造影”,最终定位故障点。

谐云的金融级容器云解决方案,是云原生时代金融行业解决方案的集大成者,多活容灾确保金融业务的安全可靠,秒级故障隔离保证金融业务的连续性,云上监控则实现业务系统的日常监测,近期的连续中标是对谐云金融级容器云方案的极大肯定,也强化了谐云科技深耕云原生全栈解决方案的信心。
未来,谐云将不断完善后K8s时代,继续深化与建信金科、浦发银行和杭州银行的紧密合作,构建敏捷数字平台,并将持续致力于技术创新,与金融行业客户共同成长,加速数字化转型,共创行业新价值。

END
欢 迎 免 费 试 用
联系方式:hezuo@harmonycloud.cn

阿里云中间件首席架构师李小平:企业为什么需要云原生?

alicloudnative阅读(1669)评论(0)

1.png

作者|李小平

前天我参加了信通院的云原生产业大会,参加会议的企业非常多,并且来自于各行各业,我在会场上非常感慨。我想起 2019 年的时候,我在搜索引擎上搜索“云原生”这个词,那时的搜索频率还比较低,而 2019 年又是云原生在国内开始飞速发展的一年。而今年的云原生会场上,已经有非常多的企业来参加,这些企业在技术、产品、生态中都在应用云原生,所以说,整个云原生已经从最开始的技术变成了行业,现在发展成了比较大的产业,并且这个产业的规模每年以非常快的速度在增长。

在今天,可能有很多咨询机构、企业,或者是个人开发者都在解读云原生,也许很多人对云原生都有比较深入的认识了。大家都可以认同的是,云原生肯定与云有关,但是它改变了什么,为企业带来什么价值呢?最核心的点应该是可以改变企业的应用架构;还有一种可能是不改变应用架构,只是把整个运维体系基于云原生进行重塑。但所有的这些,背后的目的都是为了加速企业的价值创造过程,简单的说,和制造企业改良生产线是一样的,核心点就是改良我们作为软件企业的生产线。

阿里在云原生的实践从 2006 年就开始了。我们在做云原生的过程中积累了很多经验,我们认为,今天云原生对于企业数字创新主要提供了多个价值:

一是资源弹性。弹性这个词大家很容易理解,实际上弹性有不同的层面。比如说基于虚拟机的弹性,提供的弹性能力是分钟级的。如果基于这些技术的应用是毫秒级的,那么分钟级只解决了资源弹性问题,整个应用高可用问题还需要进一步解决。如果说弹性到了应用的层面,到了毫秒级,高可用问题也得到一定程度的解决。

除此以外,系统的稳定性也是大家非常关注的方面。云原生就是把整个软件构造过程中非功能性特性拉出来放到云原生产品上去,帮助应用开发从非功能性处理过程中解脱出来,更多的专注在功能性。同样的,云原生有很多工具理念,可以让我们变得更好,整个软件开发从代码到上线的时间大幅缩短。同样的,今天在基于云原生可观测性上面我们会积累非常多的数据,这些数据可以结合机器学习这些能力,帮助我们改善企业的用户体验。这些对于业务来讲会带来比较大的价值。

2.png

阿里云原生的实践历程


今天,云原生在 CNCF、国内相关的开源、还有三方组织的推动下,可以使得一家企业在做技术选型的时候有非常多的选项。大家通常会面临一个问题,在这么多选择里面,要真正达到生产可用的目的到底选谁?特别是当我们的业务需要在非常短的时间内就上线,在业务高速发展的阶段,我们应该选什么样的架构,选什么样的开源开放的产品,这个是摆在广大企业技术决策者以及架构师面前的难题。

在云原生领域中,阿里云是相对比较早开始做自研的。从 2006 年到 2009 年互联网的中间件开始发展,到阿里云正式成立,整个过程中我们通过云原生解决很多业务问题。通过应用云原生相关技术,从早期很好地支持了淘宝的高速发展,到了 2015 年以后很好地支持了阿里的中台建设,以及到今天随着阿里巴巴整个生产系统、核心系统全部 100% 上云,这个过程中我们运用的云原生技术,像容器技术、微服务技术支持的规模都是百万级以上。

相关调研显示,这样的云原生落地规模在全球范围内都是非常领先的。实际上,对于很多企业来讲,也许用不到这些规模,但是阿里通过解决这样的大规模下的性能、稳定性问题,积累了非常多的硬核技术,最终能够把这些技术转变成了产品,通过阿里云对外输出,服务于各行各业的广大客户。

3.png

我们认为,云原生对于整个软件的改变,或者对软件公司的开发流程的改变是非常深刻的。首先 K8s 已经变成了软件交付的标准界面,它改变的不止是运维,而是从 CICD 到后续发布上线整个生产链条。由于所有生产流程得到改变,以及很多企业通过云原生技术重塑了软件架构,使得软件架构从传统架构变成了新的、我们称之为现代化的应用架构,因此云原生可以通过这种生产工具的改良进一步改变企业的生产关系,最终影响企业,使得企业在软件开发过程中得到了极大的提速。

阿里云在云原生实践过程中,积累了很强的技术竞争力,体现在这些方面:

一、我们有非常多领先的技术解决云原生领域里面的稳定性问题、可靠性问题,大规模下的高并发问题等。同时,我们会把所有的这些技术通过开源开放的形式输出。我们知道,在云原生的世界,企业需要的是开源开放的技术,而不是被像阿里这样单独一个厂商所锁定的技术。这个过程中我们基于开源开放技术标准积累了很多产品的硬核能力。在产品上,除了大家看到的基于云原生应用架构里,还包括云原生数据库、云原生大数据等。

4.png

在云原生相关的领域有比较多的测评,在这些测评里,例如阿里云容器产品 ACK,在去年 Gartner 评测中拿到满分,全球厂商中只有两个厂商拿到满分,阿里云是其中之一。今年,阿里云再次入选 Gartner 容器竞争格局。在新兴的计算形态领域中,今年阿里云进入 Forrester FaaS 领导者象限,函数计算获得了全球 FaaS 产品最高分。

在可观测性里,阿里云代表国内云厂商进入 Gartner APM 象限。所有这些三方评估从另外一个层面反映了阿里云产品的能力。容器架构上,我们基于开源开放的 K8s 技术体系,基于阿里云的硬件做深度的优化,在比较多的领域和场景里为广大 K8s 应用提供服务。我们把在 K8s 集群里面超大规模集群管理的能力输出到 ACK 产品里面,使得阿里云的客户在管理集群的时候,可以摆脱大规模集群的管理复杂性问题。

比如完美日记,作为美妆行业的独角兽公司,他们的业务发展速度非常快,但在业务快速发展过程中,他们面临的问题就是在大促的场景中怎么更好地预留资源,以及在大促时怎么样比较好地解决新上线的功能,以及需求的稳定性问题。在这个过程中,他们利用 PTS 作为压测,所有应用跑在 ACK 平台上面,通过压测模拟大促的流量,从而能够把整个大促从需要投入较大的状态提升到具备可以常态化的做大促压测的能力,也通过这个能力使得系统稳定性相关问题得到快速收敛。

云原生中间件


从微服务、消息到各种应用工具以外,根据企业常见的 IT 场景,云原生中间件也提供了很多解决方案。阿里云中间件诞生于集团内的大规模调用场景,同时兼容开源,并且融入了更多产品能力,例如在整个大促过程中表现优异的可观测性、高可用能力等,都属于云原生中间件产品体系。

同样在中间件领域里,我们也和较多企业客户有相应的合作。畅捷通是一家做 SaaS 的企业,迄今已经为超过四百万的小微企业做了云管。ToB 类型的应用复杂度较高,最大的问题就是整个软件的发布频率是非常快的,怎么样在高频软件发布下面能够比较好的解决软件的各种 BUG,或者解决设计上的不足带来的稳定性的问题,这是在前期探讨过程中畅捷通提出来的关注点。通过应用云原生中间件,不仅解决了整个应用的可观测性问题,并且让应用具备 360 度无死角可观测能力,通过应用探测能够快速发现在整个压测过程中各种可能的不稳定风险,从而使得相应风险得到快速的收敛。

Serverless


很多学术机构在 Serverless 领域深入研究,我们预感 Serverless 极有可能会成为下一代主流技术趋势。阿里云在 Serverless 领域里做到业界领先的毫秒级计费,以及在整个阿里云底层做深度优化,使客户的应用真正达到了智能的弹性、极致的运维和大幅提升开发效率。阿里云也和许多企业客户达成深度合作,进行 Serverless 落地实践,通过帮助客户将应用迁到 Serverless 技术体系上,达到比较快的应用部署;同时,把应用的稳定性问题、运维都委托给 Serverless 这样的云产品去解决。

解决方案


云原生在快速发展过程中,只有通过不断的技术创新、产品创新,才有可能使得云原生技术更好的服务于广大的企业客户。今天,阿里云对外发布四大解决方案:全链路压测解决方案、异地多活解决方案、资源混部解决方案、可观测解决方案。这些解决方案可以高效地解决在传统领域里还没有很好解决的问题。比如全链路压测,大家都知道全链路压测是个好东西,比较大的问题是在应用压测过程中使应用改造最小,甚至不要做改造,所以这次阿里云升级的全链路压测就可以帮助企业应用解决这些问题。

今天企业在不断深入地使用云以后,不管公有云还是专有云上,都会碰到整体 CPU 利用率不高的问题,混部就使得各种离线任务和在线任务可以部署在一起,各自享用资源调度的优势,使得整体机房的 CPU 利用率得到比较大的提升。在这个过程中要解决混部之后带来的稳定性问题、资源占用问题。阿里是比较早地应用大规模的混部,像支撑电商双十一的云原生产品。今天我们也是把混部能力变成解决方案对外输出。

大家都知道,阿里是比较早实现了单元化的架构,通过单元化架构实现了多活。今天我们把单元化整体的架构能力作为多活的解决方案。同时,这样的多活不仅可以支持自有数据中心、私有云的场景,也能够支持公有云和混合云场景实现整个应用的多活。

可观测性一直都是大家特别关注的话题,因为通过可观测性使得我们可以主动发现在系统的运行过程中可能出现的各类风险。今天,阿里云升级的可观测性方案包括从拨测到各种前端的性能监控,一直延伸到后端应用,甚至延伸到云服务里。

产品升级


除了解决方案的创新以外,我们在相应的云原生产品上面也做了比较多的升级。容器 ACK 备份容灾中心全新发布,为容器用户提供集群、应用和数据的完整性保护:

1、支持自动分析应用依赖的元数据及存储,实现秒级创建应用+数据的一致性快照;
2、支持创建备份计划,自动按预设时间点创建备份;
3、完全兼容 Kubernetes,并支持多集群、多地域、跨数据中心进行备份和恢复。

容器镜像 ACR 发布企业级 Serverless 构建服务,大幅提升云原生制品的构建效率和体验:

1、支持多操作系统、多架构镜像的矩阵构建,支持大规模并发任务构建。
2、支持多级缓存的构建加速,平均构建提速 30%。
3、支持自动构建加速镜像,实现 AI 等大镜像秒级按需加载,平均启动时间减少 60 %。

5.png

在微服务领域,越来越多的应用考虑采用服务网格技术。用户希望服务网格在开源技术之上有更强的微服务治理能力,因此阿里云推出专业版 ASM Pro,具备增强多协议支持,提升动态扩展能力,精细化服务治理,完善零信任安全体系。专业版相比去年发布的普通版,在性能及规模上均有显著提升,与开源的差异化竞争力进一步增强,降低用户在生产环境落地服务网格的门槛。

6.png

Gartner 预测,未来事件驱动将成为业务开发的主流架构。企业客户上云过程中对于低代码、无服务器弹性应用架构,如何轻量集成众多异构云服务的数据流有着明确的痛点和诉求。基于此趋势,阿里云发布了事件总线 EventBridge 这款产品,其目标在于统一阿里云云服务、第三方 SaaS 厂商、用户自定义的事件标准,通过标准、弹性、轻量的核心能力帮助用户快速低成本获取并处理海量事件,驱动业务开发。

在过去的一段时间,我们对 EventBridge 的产品能力做了进一步的扩充和升级:

  • 在事件生态集成的规模方面,新增 60+ 云产品官方事件源接入,涵盖计算、存储、网络、数据库等主流云产品;
  • 在事件触达和处理方式上,内置了十多种过滤匹配转换逻辑,并且新增了跨网络、跨地域、跨账号等深度触达方式,方便企业大客户做深层次的安全、隔离等定制;
  • 在此基础上,阿里云 EventBridge 首次推出事件驱动应用中心,内置常见的事件驱动应用模板,用户无需代码和部署即可简单配置完成常见的事件 ETL 处理、数据同步等场景功能。

7.png

阿里云拥有最广泛的云原生客户群体。随着更多的企业客户上云,将有更多复杂的场景,对于云原生技术、产品以及云原生理念提出更高的要求。阿里云希望跟社会各界的朋友一起在云原生领域里面做更多的探索,希望通过云原生技术,真正为企业带来更多的业务价值,助力企业整体的业务创新。