一、概述
1、我们此前在使用kubernetes中,无论我们使用无状态的应用程序,比如myapp,nginx。以及有状态的tomcat,redis,etcd,...等等,他们部署在k8s之上会有这样的问题。首先对无状态应用我们首先使用deployment控制器来实现控制以后其规模伸缩极其容易。这也是k8s带给我们的最重要的需要。但是如果是有状态的应用,若是有状态的单一实例的应用,我们使用deployment控制它并限制他不能够进行规模的伸缩是没有任何问题的,照样能够使用持久存储来完成数据的持久存储能力。但是一旦我们需要将有状态应用扩展至多个副本这么一来就会导致不同的应用程序他的扩展方式是不尽相同的。比如tomcat,我们扩展至多个实例,如果我们需要去追踪用户状态而且允许用户上传数据的话就没有问题。同样的,redis,如果我们配置的是redis主从和配置的是redis cluster很显然他们就是不同的分布方式,同样的etcd等等也是一样的。这就会导致大量用户在配置和部署时面对这种有状态应用面对着极大的困难。所以我们说过没有足够实力想把有状态的应用部署在k8s之上真的不是一件轻松的事。不过阿里或蚂蚁金服这种大佬公司除外。我们也说过如果要去部署有状态应用我们可以可以看到比如prometheus他们就可以借助于opertor来实现,我们讲过运维工程师本身就可以理解为opertor,因此opertor也就是封装了对应的分布式系统的运维需求到一个相应的k8s组件中,让这个组件代为执行我们日常的运维工作,但你要充分调试出所有问题来确保没有问题。这俨然就是一种开发。只不过对有状态应用来讲我们的开发去支持他能够灵活的适配到k8s系统上的方式有两三种。我们用哪一种?到底是改k8s源代码,改应用程序源代码,还是我们在k8s上去自定义资源应该有不同的解决思路。但无论如何这势必会组织一大堆人不敢向k8s靠近,当然k8s肯定也不会面对这种现状。
2、虽然我们管理我们应用程序有直接命令式,有命令式配置文件,有声明式配置文件多种方式,特别是最后一种方式我们借助于本地配置的配置文件,并借助于代码管理工具来完成管理,也能很大程度上减轻我们前期的开发,但确是一个非常头疼的事情。在linux我们也面临过这种问题,程序包的维护代价非常大,我们要去安装程序就需要去编译安装,而编译本身就有门槛,并且在编译的时候就更不容易判定在当前版本linux上使用哪个版本的应用程序。这种现象最终的结果就是从入门到放弃。所以便有了rpm包管理,但是有了rpm包后还是有很多困扰,比如依赖关系,后来便有了yum这样的解决方案。同样的逻辑,我们k8s能否也能完成类似的应用程序的部署和维护效果。之上在应用安装上能满足客户需求呢?这就是Helm。
二、Helm
1、对helm来讲,他也一样和我们的yum工作方式很相像,假设网络中有一台被称为仓库的服务器,这个仓库定义了我们去配置部署k8s的应用程序所需要的清单文件,他和yum不一样的地方在于yum除了负责解决各安装包的依赖关系他自己也得去负责提供应用程序包的内容。但是k8s中helm不需要提供程序包,因为程序包已经有人提供了。因为在k8s上运行的是容器,容器运行靠镜像仓库中的镜像提供的。因此helm这个仓库提供配置清单即可,他主要定义了一个应用程序在部署时所需要的清单文件。比如我们要部署nginx,他应该有一个deployment,还需要一个service的定义,如果有必要还需要有一个hpa的定义,我们把这三个文件打包在一起就形成了一个应用程序包,里面可能会用到很多镜像但是镜像不需要自己提供,用户可能会有自己的私有仓库的镜像或者其它来源的镜像。他的包不大,他只是包含了我们试图部署到k8s之上的应用程序的配置文件。这就叫一个应用。在helm中这个不叫应用程序而叫chart。当然这个chart部署起来肯定没有那么轻松了,因为不同用户部署nginx部署需求就是不一样的,比如配置文件,secret等等。所以chart在制作时会支持一些模板机制,即允许用户在启动时允许传递相应的参数。
2、于是我们在helm仓库中就配置了很多chart,我们也称之为chart仓库,他允许官方提供也允许用户自己提供。
3、我们chart是怎么部署应用程序的呢,我们需要先有一个kube cluster,helm是一个工作在kube cluster之外类似于我们此前使用的一个叫做kubectl的 客户端工具。只不过我们kubectl是直接与apiserver交互而我们的helm不是直接与apiserver交互的,他还有一个中间层。假如我们中间有一主机是我们的工作站,他远程去管理我们的kube cluster,于是在这个主机上我们应该有一个helm,他和我们Kubectl很相像,他是一个命令行客户端工具。那么当我们需要去利用helm在kube cluster去安装应用程序时他不直接操作apiserver而是我们需要有一个中间组件叫tiller,tiller本身是一个server,而helm是tilller的客户端,而不是api server客户端。tiller运行在k8s之上运行为一个守护进程,helm主要与tiller完成交互,每次部署请求时需要helm提交给tiller再由tiller提交给apiserver。再由apiserver负责完成部署。在helm上他的管理方式和我们的docker 很像,每一个chart在运行的时候必须先下载到helm这个主机所在的本地,一般是当前运行helm命令用户的家目录下。即安装一个charm需要首先由helm 到远程仓库中去下载一个并把它存储在本地,我们其实也可以自己开发chart然后存放在本地,因为他是在本地获取的。当试图部署应用程序时helm先试图去联系tiller,由tiller联系apiserver来完成部署和应用。我们如果想运行多个nginx pod也是可以的,同样的逻辑,helm 也可以把一个chart在kube cluster中部署多次,当然,对应的参数肯定也需要修改的。部署到kube cluster上就不再叫chart了,chart只是定义我们需要把我们给的值赋值到模板中以后生成很多信息他就有了具体的实例,我们通常称之为对象。chart更像是某一种部署的类型,一种类,我们把这种类赋值以后他就不叫对象了,而称为release。即同一个chart给其赋予不同的属性值以后完全部署出多个release来这就是我们对应的helm的工作逻辑和架构形式。
4、假设有一个chart仓库,helm如果自己不想定义,就从仓库中拖下来定义,将其属相赋值以后创建提交给tiller,由tiller认证到apiserver上完成在本地对应资源的管理,每一个资源对象叫一个release,从这个角度来讲helm把k8s的资源打包到一个chart中,制作并测试完成各个chart和chart本身的依赖关系,并利用chart仓库对外实现分发,而helm还实现了可配置的发布,我们通过values这样的文件来完成配置和发布,同时还支持应用程序的配置的版本管理,而且还能简化k8s部署的版本控制,打包,发布,删除,更新的操作,也就意味着说如果你的chart版本更新了,helm自动支持滚动更新机制并且还支持一键回滚。所以从此以后在kube cluster上部署应用程序就和我们在linux上使用yum部署应用程序这么简单了。
5、核心术语:
a、Chart:一个helm程序包
b、Repository: Charts仓库,https/http服务器
c、Release: 特定的Chart部署于目标集群上的一个实例
d、chart -> Config ->Release
6、程序架构
a、helm:客户端,管理本地的Chart仓库,管理Chart,与Tiller服务器交换,发送chart,实现安装,查询,卸载等操作
b、Tiller:服务端,接收helm发来的Charts与Config,合并生成relase;
三、安装使用Helm。对应git链接https://github.com/helm/helm 。此处我们以2.9.1为例。
1、首先我们将linux包下载到本地。解压后直接使用即可
[root@k8smaster application]# tar -zxvf helm-v2.9.1-linux-amd64.tar.gz && ls
linux-amd64/
linux-amd64/README.md
linux-amd64/helm
linux-amd64/LICENSE
helm-v2.9.1-linux-amd64.tar.gz linux-amd64
[root@k8smaster application]# cd linux-amd64/ && ls
helm LICENSE README.md
2、接下来我们去部署tiller,我们使用helm init 就能让其自动生成tiller,你只需要指定好k8s集群即可,另外,k8s本身去init的时候他需要联系到apiserver,让apiserver指挥着去安装部署tiller pod,这就意味着我们的helm需要联系apiserver并进行认证,而且得有管理员权限才行。所以从这个角度来讲,运行helm时helm会获取当前系统上的kubectl的配置文件,叫kubeconfig,所以无论放在哪个节点上要确保你的Kubeconfig文件可用,否则helm在第一次初始化时没办法联系至apiserver。因此当前我们节点下kubeconfig虽然是kubectl的配置文件但是他也能被我们的helm所获取到。helm可用拿着他把自己扮演成kubectl客户端,去连接至k8s集群之上进行应用程序的部署和安装。我们把tiller部署到k8s集群时我们的tiller服务器需要自己能获取到整个集群的管理权限,否则他没办法完成应用程序的安装卸载等管理操作。所以部署完我们的helm后我们再去部署我们tiller后,在启用了rbac的k8s集群上去设置他的rbac的配置,通常他依赖的用户名就叫tiller,是一个serveraccount,我们希望这个tiller拥有很大的管理权限的话我们一般使用clusterolebinding去绑定在clusteradmin这么一个clusterrole之上。
a、创建rbac sa账号对应的rbac的配置文件为https://github.com/helm/helm/blob/v2.9.1/docs/rbac.md
[root@k8smaster helm]# cat tiller-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: tiller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: tiller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: tiller
namespace: kube-system
[root@k8smaster helm]# kubectl apply -f tiller-rbac.yaml
serviceaccount/tiller created
clusterrolebinding.rbac.authorization.k8s.io/tiller created
[root@k8smaster helm]# kubectl get sa -n kube-system |grep till
tiller 1 19s
b、接下来开始初始化
[root@k8smaster helm]# kubectl get sa -n kube-system |grep till
tiller 1 19s
[root@k8smaster helm]# history ^C
[root@k8smaster helm]# helm init --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: https://kubernetes-charts.storage.googleapis.com
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.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!
[root@k8smaster helm]# kubectl get pods -n kube-system |grep till
tiller-deploy-759cb9df9-ndfp7 1/1 Running 0 20m
查看helm版本
[root@k8smaster helm]# helm version
Client: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
3、接下来我们使用helm
a、helm repo updata 更新仓库中所使用的镜像仓库
b、官方可用的cahrt列表: https://hub.kubeapps.com/ 他的仓库分了两个,一个stable(稳定)版还有一个incubator(预发)版本
c、更新helm仓库:helm repo update
d、查看仓库列表:helm repo list
[root@k8smaster ~]# helm repo list #可以看到有两个仓库
NAME URL
stable https://kubernetes-charts.storage.googleapis.com
local http://127.0.0.1:8879/charts
e、查看是否有jenkins应用
[root@k8smaster ~]# helm search jenkins
NAME CHART VERSION APP VERSION DESCRIPTION
stable/jenkins 1.6.0 lts Open source continuous integration server. It s...
f、查看chart中的信息
[root@k8smaster ~]# helm inspect stable/jenkins
4、对一个简单的应用程序来讲如果我们找到所有相关的配置信息并且做出相应的修改之后我们生成一个values文件对象就可以应用了,有些helm chart是很复杂的,他的依赖是强依赖的,我们必须给他明确定义各种参数配置以后才能运行起来,而有些纯粹使用默认值都是直接可以部署的。
a、接下来我们安装一个memcached
[root@k8smaster ~]# helm install --name meml stable/memcached
NAME: meml
LAST DEPLOYED: Mon Sep 9 16:33:48 2019
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
meml-memcached ClusterIP None <none> 11211/TCP 0s
==> v1beta1/StatefulSet
NAME DESIRED CURRENT AGE
meml-memcached 3 0 0s
==> v1beta1/PodDisruptionBudget
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
meml-memcached 2 N/A 0 0s
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
meml-memcached-0 0/1 Pending 0 0s
NOTES:
Memcached can be accessed via port 11211 on the following DNS name from within your cluster:
meml-memcached.default.svc.cluster.local
If you'd like to test your instance, forward the port locally:
export POD_NAME=$(kubectl get pods --namespace default -l "app=meml-memcached" -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward $POD_NAME 11211
In another tab, attempt to set a key:
$ echo -e 'set mykey 0 60 5\r\nhello\r' | nc localhost 11211
You should see:
STORED
b、然后我们将它删除
[root@k8smaster ~]# helm delete meml
release "meml" deleted
5、helm常用命令
a、release管理:
install:增
delete:删
upgrade/rollback:更新
list:查
history:查看历史
status:获取release状态信息
b、chart管理
create:创建
fetch:从远程下载一个压缩包到本地
get:获取
inspect:查看一个chart的详细信息
package:本地自己开发的文件打包
verify:做校验
6、创建一个redis集群
[root@k8smaster ~]# helm install --name redis1 stable/redis
NAME: redis1
LAST DEPLOYED: Mon Sep 9 17:04:37 2019
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/Secret
NAME TYPE DATA AGE
redis1 Opaque 1 0s
==> v1/ConfigMap
NAME DATA AGE
redis1 3 0s
redis1-health 6 0s
==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis1-headless ClusterIP None <none> 6379/TCP 0s
redis1-master ClusterIP 10.107.226.20 <none> 6379/TCP 0s
redis1-slave ClusterIP 10.107.20.68 <none> 6379/TCP 0s
==> v1beta2/StatefulSet
NAME DESIRED CURRENT AGE
redis1-master 1 1 0s
redis1-slave 2 1 0s
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
redis1-master-0 0/1 Pending 0 0s
redis1-slave-0 0/1 Pending 0 0s
NOTES:
** Please be patient while the chart is being deployed **
Redis can be accessed via port 6379 on the following DNS names from within your cluster:
redis1-master.default.svc.cluster.local for read/write operations
redis1-slave.default.svc.cluster.local for read-only operations
To get your password run: #获取密码的方式
export REDIS_PASSWORD=$(kubectl get secret --namespace default redis1 -o jsonpath="{.data.redis-password}" | base64 --decode)
To connect to your Redis server:
1. Run a Redis pod that you can use as a client:
kubectl run --namespace default redis1-client --rm --tty -i --restart='Never' \
--env REDIS_PASSWORD=$REDIS_PASSWORD \
--image docker.io/bitnami/redis:5.0.5-debian-9-r124 -- bash
2. Connect using the Redis CLI:
redis-cli -h redis1-master -a $REDIS_PASSWORD
redis-cli -h redis1-slave -a $REDIS_PASSWORD
To connect to your database from outside the cluster execute the following commands:
kubectl port-forward --namespace default svc/redis1 6379:6379 &
redis-cli -h 127.0.0.1 -p 6379 -a $REDIS_PASSWORD
7、其实对我们整个helm来讲其内部是有着模板和对应的值文件来定义的,对整个helm chart而言,我们首先可以来看他的结构
[root@k8smaster archive]# pwd && ls
/root/.helm/cache/archive
jenkins-1.6.0.tgz memcached-2.9.0.tgz redis-9.1.7.tgz
我们可以看到我们此前安装的应用都在这个目录下。
a、我们解压jenkins包查看文件
[root@k8smaster archive]# pwd && tree redis
/root/.helm/cache/archive
redis
├── Chart.yaml
├── ci
│ ├── default-values.yaml
│ ├── dev-values.yaml
│ ├── extra-flags-values.yaml
│ ├── production-sentinel-values.yaml
│ ├── production-values.yaml
│ ├── redisgraph-module-values.yaml
│ └── redis-lib-values.yaml
├── README.md
├── templates
│ ├── configmap.yaml
│ ├── headless-svc.yaml
│ ├── health-configmap.yaml
│ ├── _helpers.tpl
│ ├── metrics-prometheus.yaml
│ ├── metrics-svc.yaml
│ ├── networkpolicy.yaml
│ ├── NOTES.txt
│ ├── redis-master-statefulset.yaml
│ ├── redis-master-svc.yaml
│ ├── redis-rolebinding.yaml
│ ├── redis-role.yaml
│ ├── redis-serviceaccount.yaml
│ ├── redis-slave-statefulset.yaml
│ ├── redis-slave-svc.yaml
│ ├── redis-with-sentinel-svc.yaml
│ └── secret.yaml
├── values-production.yaml
└── values.yaml
b、我们可以将values.yaml修改成我们期望的值然后进行安装
[root@k8smaster helm]# helm install --name redis1 -f /root/manifests/helm/values.yaml stable/redis