本文作者:Rainbond 项目维护者:黄润豪
Rainbond 是一个完全开源,简单易用的云原生应用管理平台。除了支持内置的本地组件库
, 云原生应用商店, 还支持 Helm 应用商店
. 用户把常用的 Helm 仓库对接到 Rainbond 后, 可以简单, 方便地对Helm应用进行配置和安装。
Rainbond Helm 应用商店的功能
当前版本支持基于Helm v3协议添加外部应用商店。
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 应用列表
-
建立 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
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
-
What is cache penetration, cache breakdown and cache avalanche?: https://www.pixelstech.net/article/1586522853-What-is-cache-penetration-cache-breakdown-and-cache-avalanche
-
singleflight: https://pkg.go.dev/golang.org/x/sync/singleflight
社区
如果您对Rainbond项目感兴趣,如果您有一些疑问,如果您对云原生、Kubernetes等技术感兴趣,欢迎加入Rainbond 社区钉钉群。
登录后评论
立即登录 注册