如何理解Helm

你将在本文学到什么?

  • Helm是什么?
  • 如何使用以及调试Helm?
  • helm原理相关技术分析

涵盖的技术概念

  • kubernetes
  • Chart
  • go/template库

为什么需要使用helm

要弄懂这个问题,我们需要了解kubernetes上如何部署一个应用,如下图所示:如何理解Helm当用户要求 Kubernetes 使用 kubectl 创建部署时,如下所示,

  1. 该过程是这样的kubectl apply -f k8s-deployment.yml;
  2. kubectl 将 yaml 资源转换为 JSON 并将其发送到主节点或控制平面内的 API Server;
  3. API Server 接收请求并将部署详细信息或对象定义保存到数据库中(etcd)
  4. etcd 是 Kubernetes 用于存储数据的高可用键值存储。
  5. etcd 是 Kubernetes 的重要组件,因为它存储集群的整个状态:其配置、规格和正在运行的工作负载的状态。
  6. 当新的deployment资源存储在 etcd 存储中时,**控制器管理器(contoller manager)**会收到通知,并创建足够的 Pod 资源来匹配 Deployment 中的副本。
  7. 此时etcd中部署(deployment)的状态为Pending状态。
  8. Scheduler 查看 Pending 状态并检查基础设施的状态,对节点进行过滤和排序,以决定将 Pod 调度到哪里,然后执行其业务逻辑,用可调度节点的名称填写 Pod 规范中的 nodeName 字段,将调度的 Pods 对象以 Scheduled 状态持久保存在 etcd 中;
  9. 在此阶段,尚未创建 Pod,只是创建、更新对象并将其存储在 etcd 中。
  10. 在每个工作节点上,都有一个 kubelet 负责与主节点或控制平面通信。它提供了 Kubernetes 控制平面和集群中每台服务器上的容器运行时之间的接口
  11. kubelet 确保容器在 Pod 中运行。当控制平面需要在节点中发生某些事情时,kubelet 就会执行该操作。如果我们的部署规范有创建 3 个副本的说明。在此阶段,每一项都将按计划在 etcd 中进行标记。Kubelet 检索 Pod 的模板并将创建容器委托给 Docker 守护进程。
  12. 当 Docker 守护进程完成任务时,kubelet 会检查就绪情况和活性探测器,Pod 最终被标记为正在运行。

在这个过程中,最为关键的就是manifest文件,即告诉k8s,如何编排我们的部署单元。 一开始这么使用,当然是最快,最方便的,但是在开发整个生命周期的,除了部署,还需要升级,回滚,历史版本记录,配置修改等操作,那么原生的kubectl就无法支撑整个周期。这时候就祭出了helm。

介绍

helm官方定义:kubernetes包管理器,在学习过程中,可以与现有的技术进行类比,如:centos的yum, Python->pip, go->go mod,rust -> cargo等。目的是为了在某个位置引用已有的包来使用。那么对应,helm即是在kubernetes中,引用我们所需的包,通过helm install即可安装。 这里的包:可以是中间件(prometheus),可以是服务程序等。 helm包含了三大概念,详情可查看:使用Helm, 这里我说说我的理解:Chart:英文:图表,谱。这里我更喜欢谱这个解释, 告诉kubernetes如何演奏这个乐谱,而不离谱。在谱中,我们可以加入一些根据演奏环境而变化的歌词,但不修改谱。也就是说:**在chart中,提供一组manifests的模板文件,根据环境的不同,可以自定义一些变量。 **如果之前使用过python,可以用jinja来理解。 go的话,则是template。当然helm使用的是golang,那么在这猜也能猜得到,他使用了template库。Repository:有了chart,肯定需要存放的位置,repository则是存在charts的地方。Release:英文:发布;则是chart的部署状态,我们真正使用到的,也是kubernetes使用的到的。一个chart可以部署成多个release。根据变量的不同,release内部的字段也有不同的展示形式。

快速使用

前置条件:

  1. 安装kubernets;(可以使用kind快速部署)
  2. 安装helm二进制文件, 直接进入 https://github.com/helm/helm/releases 下载最新版本即可。
  3. 将helm二进制文件拷贝到所有kubernetes操作权限的主机上(默认master主机)。

升级操作步骤如下:

  1. 添加对应仓库:告诉helm客户端,去哪里可以找到这个包。
helm repo add <别名> <地址>

注意点:当需要访问私有仓库时,使用helm repo add –help,查看用户名/密码如何使用; 如果私有仓库为自签名https时,需要添加 –insecure-skip-tls-verify 来忽略证书校验,当然也可以将自签名证书拷贝到当前机器下。

  1. 执行仓库更新操作:更新对应仓库的索引文件,告诉helm客户端,有哪些版本可以升级。
helm repo update  
# 通过以上命令完成对所有仓库索引的更新
  1. 安装对应包;
helm install <releaseName> <repoName>/<packageName> --version <版本号> 
  1. 等待安装完成。

详细步骤可参考官方文档,已经很细节了:快速入门指南详细原因以及实战在原理章节说明。

如何开发一个helm包

首先,通过以下命令创建一个helm文件:

helm create mychart

helm包具体包含了以下目录:

mychart/
├── charts  // 子chart目录
├── Chart.yaml  // chart申明文件
├── templates   // 模板文件
│   ├── deployment.yaml  
│   ├── _helpers.tpl
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml  // 动态值的声明文件,这个文件包含template文件下模板字段的默认值。

我们要理解helm包具体是什么?既然helm包是为了完成kubernetes一组manifests的部署,那么这即是它的最终状态—-一组manifests文件,即使用values.yaml覆盖templates中所有文件后产生的一组文件。具体其中文件该怎么写,可以查看从这里开始吧。

实战中使用到的命令

安装并更新:

# 等待资源全部执行成功,并设置超时时间
helm upgrade --install <release> <repo>/<chart> --wait --timeout 10m

回滚操作:

helm rollback <Release> <revision> 

这里注意: revision为helm自管理版本号,非chart版本号;如何测试chart:

helm tempalte --debug ./mychart 

如何模拟安装,发现错误:

helm install --dry-run <releaseName> ./mychart 

Helm的FAQ

another operation (install/upgrade/rollback) is in progress

英文原意:当前对应的Release下已经有一个正在进行中的操作了。 产生这种情况的原因: 在先前执行helm 操作时,没有等待操作完成,即关闭了进程,如当使用helm install –wait时,未完成的情况下,直接Ctrl+C 。 那么在下一次操作就会产生这个错误; 如果使用helm list ,发现没有看见对应的release, 其原因是:release的状态已变成 pending-install 或者pending-upgrade了。 而helm  list 源码,在初始化的时候,只会展示 deployd 以及 faild。 所以需要使用helm list -a 来查看当前release的状态。 解决方式: 将前一个执行的secret给删除, 命令如下:

kubectl get secret |grep <release>
# 找到对应最后一个版本删除
kubectl delete secret <release>_<版本号>

Error: rendered manifests contain a resource that already exists. Unable to continue with install: existing resource conflict

helm 在首次安装过程中,会去判断chart中的资源类型是否存在,如果存在则报错。有以下两种方式解决:删除现有资源通过kubectl delete循环删除冲突资源选项。 缺点: 如果资源涉及到service,deployment等关键资源时,会造成短时间内系统的崩溃。

欺骗helm,声明资源被helm纳管原理:使用helm部署的服务,分别在资源annotation以及label中新增加了字段:

annotation:
    meta.helm.sh/release-name: <relaseName>
    meta.helm.sh/release-namespace: <namespace>
label: 
    app.kubernetes.io/managed-by: Helm

那么,就简单了,可以使用命令完成欺骗:

helm template --debug  <repoName>/<chartName> -n <namespace> --version <version> | kubectl annotate -f - meta.helm.sh/release-name=<releaseName>  meta.helm.sh/release-namespace=<namespace>  -o yaml  --field-selector=metadata.namespace=<namespace> || true
helm template --debug  <repoName>/<chartName> -n <namespace> --version <version> | kubectl label -f - app.kubernetes.io/managed-by=Helm -o yaml  --field-selector=metadata.namespace=<namespace>

helm-SDK的使用

官方实例如下:

package main

import (
    "log"
    "os"

    "helm.sh/helm/v3/pkg/action"
    "helm.sh/helm/v3/pkg/cli"
)

func main() {
    // 创建客户端配置
    settings := cli.New()
 // 创建action配置, action为各种helm可执行操作的配置,这里是创建基础配置,
    // 所有的action内部配置都会引用这个对象
    actionConfig := new(action.Configuration)
    if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), log.Printf); err != nil {
        log.Printf("%+v", err)
        os.Exit(1)
    }
 // 创建list action 对象,来执行helm list中相关的操作
    client := action.NewList(actionConfig)
    // Only list deployed
    client.Deployed = true
    results, err := client.Run()
    if err != nil {
        log.Printf("%+v", err)
        os.Exit(1)
    }

    for _, rel := range results {
        log.Printf("%+v", rel)
    }
}

Helm原理分析

helm repo具体做了什么

repo的功能:

  • 告诉helm客户端,我有哪些仓库可以连接(仓库url,证书,账号密码,别名)。
  • 仓库中有哪些chart包可以获取,对应的版本号是什么。

在settings中存在两个字段:RepositoryCache, RepositoryConfig。RepositoryConfig:解决上述第一个问题,设置一个文件,用于管理配置。RepositoryCache:解决第二个问题,设置一个目录,用来缓存数据。缓存数据包括:

  • 安装时,下载的chart.tgz包;
  • 仓库的索引文件,以index.yaml结尾。

具体可查看自己集群,helm的存储文件。

问题: helm客户端的数据文件使用的是文件进行管理的,并发时势必会产生数据竞争,如何处理?在源码中,helm做的比较简单,在每次操作文件时,先进行文件锁定。这里可以学习到一个新库:github.com/gofrs/flock 。

helm install 具体做了什么

在helm v3时,helm操作的执行全都改成直接操作k8s完成。install的流程如下:

-->i.cfg.KubeClient.IsReachable() // 判断是否能够访问到k8s集群。
 --> client, err := c.getKubeClient()  // 获取kube client
 --> client.ServerVersion()   // 通过获取k8s服务端版本来判断是否能够连接

-->i.availableName()   // 判断ReleaseName是否有效
 --> chartutil.ValidateReleaseName()  // 判断字符是否有效
 --> i.cfg.Releases.History(start)    // 判断是否存在发布历史,!!!这里通过secret进行判断
     --> 存在即报错:cannot re-use a name that is still in use

-->chartutil.ProcessDependencies() // 检查chart依赖项。
-->crds := chrt.CRDObjects()    // 检查crd对象。

-->i.cfg.getCapabilities()   // 获取当前k8s中的GVK,这里的获取,在安装时会产生一个熟悉的报错:
-->chartutil.ToRenderValues() // 通过values 来渲染,得到Values对象,
         //后面渲染资源会用到,并且在这会将chart内部的values.yaml与传入的map[string]interface{}进行合并。

-->i.createRelease()    // 创建Release对象。
-->i.cfg.renderResources() // 渲染资源,会返回manifest对象
  ...
 --> releaseutil.SortManifests() // 对资源类型进行排序, 所以在安装时候,会按照这个顺序执行。
-->rel.SetStatus(release.StatusPendingInstall, "Initial install underway"// 设置当前状态
-->resources, err := i.cfg.KubeClient.Build() // 创建k8s中的资源对象列表
-->resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace, true)) // 循环遍历,对资源对象进行label,以及annotation的赋值。
-->existingResourceConflict()  //是否存在冲突,这里就是上面FAQ中的资源声明错误
-->i.CreateNamespace --> i.cfg.KubeClient.Create() // 创建命名空间,就算存在也不会报错。
-->i.cfg.Releases.Create(rel)   // 创建secret, sh.helm.release.v1.releasename.version
 ...
 -->newSecretsObject(key, rls, lbs) // 当我们查看secrets时,base64后也无法查看,是因为这里的压缩设置。
                                       // 会将整个release对象进行json后,再压缩保存。
rChan := make(chan resultMessage)   
ctxChan := make(chan resultMessage)
doneChan := make(chan struct{})
defer close(doneChan)
go i.performInstall(rChan, rel, toBeAdopted, resources)   // 执行安装协程
go i.handleContext(ctx, ctxChan, doneChan, rel)         // 如果超时,则进行结束操作。
select {
case result := <-rChan:
    return result.r, result.e
case result := <-ctxChan:
    return result.r, result.e
}

i.performInstall执行流程:

-->i.cfg.execHook() // 执行pre-install hook
--> i.cfg.KubeClient.Create() // 执行创建操作
--> i.Wait  
 --> i.cfg.KubeClient.WaitWithJobs()
 --> i.cfg.KubeClient.Wait()
    // 以上两个最终调用的是 helm.sh/helm/v3/pkg/kube waiter
        -->  wait.PollImmediateUntil(2*time.Second, func() (bool, error) {
            for _, v := range created {
                ready, err := w.c.IsReady(ctx, v)
                if !ready || err != nil {
                    return false, err
                }
            }
            return truenil
        }, ctx.Done())
--> 设置状态
--> i.recordRelease(rel) // 更新secret中的状态
--> i.reportToRun(c, rel, nil// 报告结束

HELM的不足

helm在使用实际情况下,还是无法适配整个开发周期流程,有以下2个点:

  1. 无法通过chart的版本号进行版本的回滚。
  2. 无法使用chart包中其他的values.yaml文件,对于配置管理是十分致命的。
  3. 每次上传新的chart包,都需要支持helm repo update操作。

所以在此继续上,结合helm进行了2次开发,并运用到部门的升级平台中。


原文始发于微信公众号(小唐云原生):如何理解Helm

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/247535.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!