TOC

Helm使用场景

    应用程序运行在Kubernetes之上,那么它首先要被组织为Pod,如果需要持久存储,我们还需要选择一个合适的Volume存储卷,另外,Pod创建之后,我们肯定不能让Pod单独的自我管理,一般而言我们需要给它施加一个Pod控制器来进行管理,那么因此我们应该是创建一个Pod控制器;
    通常对于无状态应用是一个Deployment,对于有状态应用应该是一个是一个StateFulSet,对于守护进程类型的我们一般十一DaemonSet等,但无论哪一种,我们使用Pod控制器去控制Pod时,我们还需要定义所谓的标签选择器,那么除了存储卷之外,考虑到Pod是由Pod控制器所控制的,因此,他们表现出足够的动态,所以为了确保客户端能通过统一的接口来访问它们,我们还需要给它做一个Service或者Ingress作为统一访问入口,等等,这一切的一切都有着莫名其妙复杂的关系,这些也只是最基础的核心组成部分;
    为了配置Pod应用程序,我们通常还是借助于另外一个资源类别,比如ConfigMap、Secret,且不说我们要做什么安全控制,那么在正常情况下,这每一个组件都有可能用到的;
    那么这就给我们带来了一个麻烦,我们正常情况下,要配置一个应用,在Kubernetes用到的每一个资源类别下,我们要创建出具体的对象实例,我们就得给它通过所谓的配置清单,即便有很多资源,我们能够在命令行中创建,但是命令行选项所能够给我们提供的可配置的项目实在是少之又少,它的完整的配置我们必须通过配置清单来实现,而且通过配置清单我们也能够去跟踪过去,对某一资源的变化状态;
    当然这个时候,可能我们借助于git这样的代码仓库,来保存我们过去的每一次的配置变化,写配置清单也不算是一件容易的活,那么就对很多人来讲,能够用好Kubernetes其实也挺麻烦的,这就和我们在Linux要想安装应用程序一样,如果每一个应用程序我们都不得不自己下载下来,自己去构建、编译、配置然后运行,我相信Linux不达到今天这样的流行度;
    一样的来讲,对于Kubernetes来讲,如果它需要这么高的入门门槛的话,很显然,有很大一部分用户对其望而却步,它至少在很大程度上也会阻碍其流行度,事实上到今天为止,依然没能很好的被解决,因此就有很多人在视图突破,寻找一些解决方案,比如通过一些图形管理界面,能够帮用户动态的去生成配置清单的框架,用户只需要给定一些关键性的数据,剩余的可以由这个所谓的框架自动生成;
Helm基础简介
    从某种意义上来讲,如果我们可以把通过配置清单这种方式来在Kubernetes之上部署应用程序类比为Linux上手动编译安装应用程序,那么Helm就有点类似于YUM,它能够让我们一键安装部署Kubernetes之上的应用的一个解决方案;
    如果我们要想部署一个Jenkins在Kubernetes之上,我们只需要helm install jenkins那么接下来,helm就会把jenkins需要的各种各样的配合清单,包括Volume、Service、Ingress之类的,通通的在网络仓库上下载到本地Kubernetes集群之上,展开之后把这样的清单应用在你的集群之上,完成一键部署,因此这就大大的降低了,我们自己去写配置清单的难度和必要性,甚至不用去学习如何去写配置清单了;
    从这个角度来讲,Helm就是我们的Kubernetes的应用程序管理工具,只不过有别于我们过去的应用程序管理,但是我们的Helm只包含我们的YAML格式的配置清单,并不包含YAML配置清单中需要用到的镜像;
Helm基础工作逻辑
    对于Helm而言,它其实就是一个命令行工具,简单来讲,我可以把整个Helm的工作架构来进行这样的描述,首先我们期望在Kubernetes集群上运行有一个特殊的Pod,这个Pod我们通常把它称为Tiller,它才是真正的在Kubernetes集群上,完成相应的部署任务执行的程序,它自身是一个守护进行,以Pod的形式在运行,而后Helm这个命令,是Tiller的客户端,它于Tiller进行交互,因此Helm可以运行在Kubernetes集群之外的任何位置,它主要能够连如这个Tiller这个Pod的服务就行了;
    Helm本身其实就是Tiller的一个命令行客户端组件,它能够指挥者Tiller做相应的操作,比如Helm,告诉我们说要安装部署一个Jenkins,接下来Tiller这么一个Pod可以实现,把Jenkins相关的打包格式的配置清单拖到Tiller内部来,然后由Tiller向Kubernetes的ApiServer请求创建资源;
    同样的逻辑,如果我们要去卸载一个应用程序,无非的Helm告诉Tiller,把此前部署的应用程序给它Delete就可以了,这就是Helm和Tiller的组合方式,Tiller一般部署在Kubernetes之上,以Pod的形式在运行,那么我们还需要确保Tiller这个Pod能够与集群至之外的Helm进行连接,那这个时候我们可能需要做一些Service之类的方式实现;
Chart
    从某种意义上来讲,真正做应用操作的可以理解为,是Helm负责下载清单,Tiller负责应用清单,于是这就有了第三个概念Repository,Repository当中存储有我们要部署应用的配置清单,而且所有的同类的清单要打包为一个打包文件,这个打包后的文件我们不再称其为清单,Helm官方称之为Chart;
Release
    那么于是Helm真正去部署应用到我们的Kubernetes集群中时,我们可以理解为Helm自身本地有Chart,它把这个Chart提交给Tiller,于是由Tiller负责将Chart应用到ApiServer,所以真正下载、维护和使用Chart的是Helm,而一旦提交到我们的Kubernetes集群之后由Tiller进行维护和监视,这个Chart就不再称之为Chart,基于Chart部署后的应用程序也有一个称呼,我们称之为Release
    Release,我们可以理解为,Chart的一个发行版,或者说Chart的一次部署,所以从某种意义上来讲,Chart只是一个定义,只是一个Template,而Release,更像是Template构造出来是实体,它是一个运行的副本;
    所以Tiller负责生成和管理Release,而Helm负责维护和管理Chart;
Repository
    对于用户来讲,写一个配置清单,不是一个容易的事,那如果我们要在遵循Chart的格式,去写一个配置清单,恐怕更加麻烦,因此互联网上有很多人,对那么些非常流行的应用程序,比如Jenkins、Redis等,他们直接已经组织和打包成为了Chart,并且放在一个统一的位子,让用户可以直接加载使用,因此这里的Repository就是我们的Chart Repository里面存储了各种各样的Charts;
Config
    还有一个问题,那就是,当我们使用一个Chart部署在Kubernetes之上,继而生成了多个Release时,这多个Release之间又如何去区别呢,因为Chart是同一个Chart,那YAML格式的配置也都是一样的,而且更重要的是,如果我们部署一个deployment,有时候我们需要部署三个Pod副本,有时候我们要运行八个Pod副本,如果我们要都来自一个Chart,那么这个副本数量,按常理来说,应该是Chart写死的,固定的;
    因此这显然不合适,所以从这个角度来讲,如果我们可以以一对多的话,比如说用一个Chart来生成多个Release的话,那我们就必须做到能够向Chart传递一些自定义的配置参数,以确保Release是可以被定制的,这个定制是通过我们传递的参数,来完成的;
    因此在Helm内部还有一个非常重要的术语,叫做Config,有点类似于ConfigMap,Config需要结合Chart生产Release,Chart生成Release的时候,可以使用默认值,如果部署两个Release都是使用的默认值的话,那我们就之能使用Chart的随机生成名称来区别他们了,否则我们就应该在Config中,对第一个参数设置一个不同值,最起码,你的Release的名字得不一样,因此在Helm部署Chart为Release的时候,用户所能够自我定义配置信息的接口,就称之为Config;
    Config实际就是一个文本配置文件,它就是一个YAML格式的配置文件,里面大体上有一些指定的键值数据,或者映射型数据、列表型数据,所提供的内容无非是告诉Chart如何去替换,Chart当中的定义的,说白了就是用Config来生成YAML,所以说Chart内部的YAML格式的配置清单,通常不是原始格式,而是内嵌了很多遵循模版语法的模版代码;
    而这些所谓的变量的值,是来自于Config的,当然Config自身也是一个YAML格式的配置文件, 所以Config里面的配置信息,或者变量赋的值,可以被对应的YAML模版的模版引擎在实现模版渲染时,把Config当中的变量值替换到模版文件当中,并且,生成真正意义上的YAML格式的配置清单,当然,多熟情况下,如果我们要以默认参数进行配置,Config当中的大多数参数,也是有默认值的,很多时候,我们甚至不用给任何一个参数设定默认值,都能完成部署,有一些复杂的应用就要求我们必须在Config当中给定值,因为它没有默认值,或者默认值是不适用的 ;
Helm内部逻辑
    因此Helm真正去部署一个应用程序时,用户只需要告诉Helm,我们要install一个Jenkins,那么Helm会首先连入你配合给它的Chart仓库上,去检索一个叫Jenkins的Chart来,把Chart检索到了以后,下载的Helm本地,因为它是打包格式的,我们还要将其展开,展开以后是一个目录,目录当中有各种各样的YAML清单 ;
    因此Helm就把这些清单提交给了Tiller,由Tiller负责联系ApiServer,把它创建位一个Release,大概就是这么一个过程;
    因此Chart一般由第三方组织去维护,维护完之后去分发,为了让所有用户都能够用到,我们可以放在一个公共的Chart仓库上,Helm既能够时候也远程仓库当中的Chart,也能使用本地仓库中的Chart,所谓本地就是Helm所在的主机有一个目录,在这个目录之下,每一个Chart通常就是一个子目录,子目录名称就是我们的Chart名称,因此当我们去部署一个Chart时,我们也可以直接配置为本地仓库,只要能从本地找到,那么就直接进行部署了,如果找不到就去互联网上搜;
    所以有了Helm,那我们去管理一个复杂应用,就变,便捷多了,因此通过Chart的各种机制,和Chart仓库的分发功能,我们就能够实现,让众多用户定义那些比较普片的主流的应用部署在Kubernetes之上的部署配置了;
    另外应用程序也需要升级,过去我们经常会使用Deployment的滚动更新来完成更新,有了Helm以后Helm也能够调其内部所依赖的控制器,来完成所谓的滚动更新等功能,几乎能做到一键升级,并且,在必要时间,我们还能够完成应用程序的回滚操作,因为Helm只是外壳,内部真正负责工作的,还应该是Pod控制器,或者其他相关的控制器等功能;
    Helm与Tiller通信是使用GRPC协议进行的,是Google的RPC协议,它在分布式系统当中,有更高的性能和更好的通信逻辑,以至于早些时候,在分布式系统当中,通用的协作协议是http,它API风格是Restful风格,这也是2000年左右发布到产品,到今天已经近20年了,Google最近发布是GRPC协议变得越来越重要,越来越主流,而Tiller与ApiServer之间的通信,ApiServer本身是http协议的,或者说是https协议,所以我们的Tiller与ApiServer是直接通过https协议通信的,而Helm是不能够直接与ApiServer直接通信的;
总结
Helm:主要负责本地的Chart开发、模版渲染并且与Tiller服务器交互;
Tiller:监听来自Helm客户端的请求,必要时合并Chart和Config来生成一个Release,并且把这个Release真正的去部署在Kubernetes之上,当然升级、回滚、卸载Release也是Tiller的功能;
Config:YAML格式,主要提供新版本的Release的变量值,用来进行变量替换;
Chart:主要是一些基于模版语法编写的一堆模版YAML文件的打包文件;

部署

    部署Helm大体分为两个步骤,第一我们得在Kubernetes部署Tiller,然后我们得在本地部署完Helm,好在我们在本地部署完Helm以后,可以让Helm先与ApiServer进行交互,让Helm自己请求ApiServer创建出Tiller,接下来Helm就可以与Tiller交互,来管理Release,因此我们不需要手动去部署Tiller,我们只需要找一个客户端,部署好Helm,而后由Helm向ApiServer发起请求部署Tiller就可以了;
    Helm所在的这个主机,一定要能正常运行kubectl这个命令,因为Helm自己需要借助于我们的真正能够连接到Kubernetes集群之上拥有管理员角色权限的账号,才能通过ApiServer做操作,所以我们可以理解为Helm会读取kubectl的配置,也就是家目录下的.kube/config这个文件的配置,那么Helm的后命令才能正常执行;
    在部署完了Tiller以后,Tiller自身并没有Kubernetes集群相关的操作权限,因此为了便于让Tiller可以访问ApiServer并具有相应的权限,我们还要给Tiller定义一个ServiceAccount,并赋予ServiceAccount一个ClusterRole,而这个ClusterRole应该是具有一个管理权限的Role,Tiller是需要整个集群的部署和操作权限的,因此我们需要将其绑定在cluster-admin这个角色上;
官方网址:https://helm.sh
官方Github:https://github.com/helm/helm
[root@node1 ~]# echo "DNS1=223.5.5.5" >> /etc/sysconfig/network-scripts/ifcfg-eth0
[root@node1 ~]# systemctl restart network
[root@node1 ~]# tar xf helm-v2.15.2-linux-amd64.tar.gz
[root@node1 ~]# mv linux-amd64/helm /usr/local/bin/
[root@node1 ~]# helm version
Client: &version.Version{SemVer:"v2.15.2", GitCommit:"8dce272473e5f2a7bf58ce79bb5c3691db54c96b", GitTreeState:"clean"}
Error: could not find tiller  # 提示找不到tiller
# 自动补齐
[root@node1 ~]# helm completion bash > /usr/share/bash-completion/completions/helm
[root@node1 ~]# chmod +x /usr/share/bash-completion/completions/helm
[root@node1 ~]# source /usr/share/bash-completion/completions/helm
# 创建ServiceAccount
[root@node1 ~]# kubectl create serviceaccount tiller -n kube-system
# 创建ClusterRolebingding
[root@node1 ~]# kubectl create clusterrolebinding tiller --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
# 初始化helm
[root@node1 ~]# helm init -i registry.aliyuncs.com/google_containers/tiller:v2.15.2 --stable-repo-url http://mirror.azure.cn/kubernetes/charts/ --service-account tiller
Creating /root/.helm 
Creating /root/.helm/repository 
Creating /root/.helm/repository/cache 
Creating /root/.helm/repository/local 
Creating /root/.helm/plugins 
Creating /root/.helm/starters 
Creating /root/.helm/cache/archive 
Creating /root/.helm/repository/repositories.yaml 
Adding stable repo with URL: http://mirror.azure.cn/kubernetes/charts/ 
Adding local repo with URL: http://127.0.0.1:8879/charts 
$HELM_HOME has been configured at /root/.helm.

Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.

Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
To prevent this, run `helm init` with the --tiller-tls-verify flag.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
# 更新仓库本地缓存
[root@node1 ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "stable" chart repository
Update Complete.
# 查看指定Chart的readme,当我们查看的时候实际上我们的helm已经把这个Chart下载到本地了,存储在/root/.helm/cache/archive/
[root@node1 ~]# helm inspect readme stable/grafana
# 测试部署一个redis
[root@node1 ~]# helm install stable/redis -n redis
# 删除redis
[root@node1 ~]# helm delete redis
Chart文件组织结构
Chart.yaml:当前Chart的描述信息;
LICENSE:许可证;
README.md:使用信息;
requirements.yaml:描述当前Chart所依赖的其他Chart;
values.yaml:为当前Chart提供配置的默认值;
chars/:所有被当前Chart所依赖的其他Chart的打包格式的Chart;
templates/:当前Chart的所有的模版;
templates/NOTES.txt:部署完成之后会被模版引擎渲染,并将渲染以后的结果显示出来,一般在helm install之后给予显示的信息;
创建一个Chart项目
    Helm给我们考虑到了这一点,如果想创建一个Chart,Helm可以直接帮我们快速生成一个项目框架,就和我们各种各样的web框架一样,以Python为例,比如我们写一个Django项目,直接Django初始化一下,那么就快速生成了一个Web框架,你往里面写内容就行,对于helm而言,去开发Chart也一样;
# 创建一个Chart
[root@node1 ~]# helm create myapp
Creating myapp
[root@node1 ~]# tree myapp/
├── charts
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   ├── _helpers.tpl
│   ├── ingress.yaml
│   ├── NOTES.txt
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml
# 修改Chart信息
[root@node1 ~]# cat myapp/Chart.yaml    
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for myapp web Services
name: myapp
version: 0.1.0
# 修改基本的values.yaml的文件
[root@node1 ~]# vim myapp/values.yaml
replicaCount: 3

image:
  repository: ikubernetes/myapp
  tag: v1
  pullPolicy: IfNotPresent
# 检查语法是否有错误
[root@node1 ~]# helm lint myapp/
==> Linting myapp/
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, no failures
# 部署myapp
[root@node1 ~]# helm install -n myapp ./myapp
NAME:   myapp
LAST DEPLOYED: Thu Mar  5 17:54:04 2020
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Deployment
NAME   READY  UP-TO-DATE  AVAILABLE  AGE
myapp  0/3    0           0          0s

==> v1/Pod(related)
NAME                   READY  STATUS             RESTARTS  AGE
myapp-676fd5cbc-22bzt  0/1    ContainerCreating  0         0s
myapp-676fd5cbc-gnn4v  0/1    ContainerCreating  0         0s
myapp-676fd5cbc-nfqjg  0/1    ContainerCreating  0         0s

==> v1/Service
NAME   TYPE       CLUSTER-IP   EXTERNAL-IP  PORT(S)  AGE
myapp  ClusterIP  10.99.39.74  <none>       80/TCP   0s

==> v1/ServiceAccount
NAME   SECRETS  AGE
myapp  1        0s


NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=myapp,app.kubernetes.io/instance=myapp" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl port-forward $POD_NAME 8080:80
# 查看创建出的资源
[root@node1 ~]# kubectl get pods,svc -l app.kubernetes.io/name=myapp
NAME                        READY   STATUS    RESTARTS   AGE
pod/myapp-676fd5cbc-82tw5   1/1     Running   0          7m44s
pod/myapp-676fd5cbc-ppt6x   1/1     Running   0          7m44s
pod/myapp-676fd5cbc-vbrvh   1/1     Running   0          7m44s

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/myapp   ClusterIP   10.109.166.136   <none>        80/TCP    7m44s
# 测试请求
[root@node1 ~]# curl 10.109.166.136
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

# 版本升级
# 修改Chart版本
[root@node1 ~]# vim myapp/Chart.yaml 
apiVersion: v1
appVersion: "1.1"
description: A Helm chart for myapp web Services
name: myapp
version: 0.1.0
# 修改镜像版本
[root@node1 ~]# vim myapp/values.yaml 
image:
  repository: ikubernetes/myapp
  tag: v2
  pullPolicy: IfNotPresent
# 升级
[root@node1 ~]# helm upgrade myapp ./myapp/
Release "myapp" has been upgraded.
...
[root@node1 ~]# curl 10.109.166.136
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>  # 版本升级成功


# 回滚
[root@node1 ~]# helm rollback myapp 1
Rollback was a success.
# 回滚成功
[root@node1 ~]# curl 10.109.166.136
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
# 查看状态
[root@node1 ~]# helm status myapp 

# 打包Chart,打包完成之后会自动存放在本地仓库
[root@node1 ~]# helm package myapp/ 
Successfully packaged chart and saved it to: /root/myapp-0.1.0.tgz
# 将打包好的Chart放在指定的目录
[root@node1 ~]# mv myapp-0.1.0.tgz /data/repo/
# 启动本地仓库
[root@node1 ~]# helm serve --repo-path /data/repo/
Regenerating index. This may take a moment.
Now serving you on 127.0.0.1:8879
# 删除本地的仓库
[root@node1 ~]# helm repo remove local
"local" has been removed from your repositories
# 添加本地仓库
[root@node1 ~]# helm repo add local http://127.0.0.1:8879/
# 测试在仓库里面搜索我们的myapp
[root@node1 ~]# helm search myapp
NAME            CHART VERSION   APP VERSION     DESCRIPTION                        
local/myapp     0.1.0           1.1             A Helm chart for myapp web Services  # 测试搜索成功
# 测试安装
[root@node1 ~]# helm install -n myapp-cce-v2 local/myapp
NAME:   myapp-cce-v2
LAST DEPLOYED: Thu Mar  5 18:27:34 2020
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Deployment
NAME          READY  UP-TO-DATE  AVAILABLE  AGE
myapp-cce-v2  0/3    3           0          0s

==> v1/Pod(related)
NAME                           READY  STATUS             RESTARTS  AGE
myapp-cce-v2-6fcfdfd885-2hs46  0/1    ContainerCreating  0         0s
myapp-cce-v2-6fcfdfd885-vfqjv  0/1    ContainerCreating  0         0s
myapp-cce-v2-6fcfdfd885-whsrt  0/1    ContainerCreating  0         0s

==> v1/Service
NAME          TYPE       CLUSTER-IP     EXTERNAL-IP  PORT(S)  AGE
myapp-cce-v2  ClusterIP  10.106.56.139  <none>       80/TCP   0s

==> v1/ServiceAccount
NAME          SECRETS  AGE
myapp-cce-v2  1        0s


NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=myapp,app.kubernetes.io/instance=myapp-cce-v2" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl port-forward $POD_NAME 8080:80
# 查看创建出来的资源
[root@node1 ~]# kubectl get pods,svc -l app.kubernetes.io/instance=myapp-cce-v2
NAME                                READY   STATUS    RESTARTS   AGE
pod/myapp-cce-v2-6fcfdfd885-2hs46   1/1     Running   0          35s
pod/myapp-cce-v2-6fcfdfd885-vfqjv   1/1     Running   0          35s
pod/myapp-cce-v2-6fcfdfd885-whsrt   1/1     Running   0          35s

NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/myapp-cce-v2   ClusterIP   10.106.56.139   <none>        80/TCP    35s

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注