helm实战小攻略
Why Helm日常操作kubernetes 的编排,一般都是以下几个步骤:创建namespace->创建对应的rs(deployment,statefulset)->创建对应的service->创建对应的Ingress可能有些app还需要pv/pvc或者secretmap之类。纯靠手工管理简直反人类,所以一般的云平台都会直接调用client-go进行各类resource的创建,
Why Helm
日常操作kubernetes 的编排,一般都是以下几个步骤:
创建namespace->创建对应的rs(deployment,statefulset)->创建对应的service->创建对应的Ingress
可能有些app还需要pv/pvc或者secretmap之类。纯靠手工管理简直反人类,所以一般的云平台都会直接调用client-go进行各类resource的创建,通过提前预置的信息作为模板调用K8S API,实现统一管理,该做法比较正规,但是对于云平台的开发者来说,需要在云平台设计之初就规划好各类resource对象的模板,一旦遇到特殊化的需求,会比较被动。
那么有人会想到,能不能像docker 一样,让一个app的所有需要使用到的K8S资源全部打包在一起,然后云平台只要管理这个包就可以了,就像docker 的镜像仓库一样,开发想部署哪个应用,直接把包拉下来即可部署,啥service,configmap全给整上,想升级也就一起升级,想回退也可以一起回退,省的每次发布都要提心吊胆的看着revision 逐个资源进行回退,于是就有了helm,如果说docker是应用环境的打包,那么helm就是基于k8s的资源打包,在更高的维度解决了上述问题。
名词解释
- chart:包含了创建Kubernetes的一个应用实例的必要资源
- tiller:包含了应用发布配置信息
- release:是一个chart的运行实例
- helm: cli,通过 gRPC 协议与 tiller 进行交互,主要提供了增删查改 chart, release 和 repository 相关的功能
- repo: 存放chart的源
结构图:
找一个网上比较清晰的图:
日常操作
安装
helm 是直接二进制安装的,直接基于cobra编写的,所以下载下来放在$PATH下即可
tiller 同样也可以二进制安装,但还是推荐使用容器化部署,不过在初始化前需要创建对应的rbac权限
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
创建rbac
kubectl create -f tiller_rbac.yml
然后安装tiller
helm init
该操作会首先创建一个tillerr的deployment,同时会在~/.helm 下生成对应目录,里面存放了对应的repo源的信息以及cache等,如果产线无法访问外网,一般会失败,所以一般可以直接到处yml,然后自己修改下,再create 一下即可
helm init --service-account tiller -o yaml > tiller_deployment.yml
#修改yml的image,指向本地docker镜像
kubectl create -f tiller_deployment.yml
查看是否完成安装
helm version
添加本地repo,repo其实就是个简单的http 服务器,根目录有个index.yaml用于记录repo的各类版本,chart信息,可以通过helm 直接生成并启动,产线可以helm 生成后,自己启个nginx 作为repo的http服务。
helm serve &
helm repo add local http://127.0.0.1:8879/charts
如果要使用外部源,直接修改repo add的url即可
chart编写
helm 解决了K8S单应用多资源的整合,我们可以create 一个chart来看下目录结构
helm create mychart
tree mychart
mychart - chart 包目录名
├── charts - 依赖的子包目录,里面可以包含多个依赖的chart包
├── Chart.yaml - chart定义,可以定义chart的名字,版本号信息。
├── templates - k8s配置模版目录, 我们编写的k8s配置都在这个目录, 除了NOTES.txt和下划线开头命名的文件,其他文件可以随意命名。
│ ├── deployment.yaml
│ ├── _helpers.tpl - 下划线开头的文件,helm视为公共库定义文件,主要用于定义通用的子模版、函数等,helm不会将这些公共库文件的渲染结果提交给k8s处理。
│ ├── ingress.yaml
│ ├── NOTES.txt - chart包的帮助信息文件,执行helm install命令安装成功后会输出这个文件的内容。
│ └── service.yaml
└── values.yaml - chart包的参数配置文件,模版可以引用这里参数。
可以看到template 里有很多资源文件,这些只是个demo,比如里面的deployment.yml完全可以删除,改成自己需要的即可
假设我的这个app只需要service, deployment, ingress 这3种资源,那么删除掉template里的其他yml,生成对应的mychart_deployment.yml, mychart_service.yml, mychart_ingress.yml即可
tree mychart
mychart
├── charts
├── Chart.yaml
├── templates
│ ├── mychart_deployment.yml
│ ├── _helpers.tpl
│ ├── mychart_ingress.yml
│ ├── NOTES.txt
│ └── mychart_service.yml
└── values.yaml
一般的想法是这时候是不是只要把之前写的一些yml丢进去就可以了?如果仅仅是这样的话,那每次变更不是还是需要把这些yml 重写一遍?注意到外面那个values.yaml 了么?可以理解为values.yml 是template下所有资源的配置文件,templates下的资源为文件均支持模板语言,什么是模板语言?玩过ansible的playbook或者写过django的同学还记得jinja2 么?go也有类似的, “text/template” 就是干这活的。
举个栗子
cat mychart_deployment.yml:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ .Values.depname }}
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
spec:
replicas: {{ .Values.replicaCount }}
template:
metadata:
labels:
app: {{ .Values.depname }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.internalPort }}
livenessProbe:
httpGet:
path: /
port: {{ .Values.service.internalPort }}
readinessProbe:
httpGet:
path: /
port: {{ .Values.service.internalPort }}
resources:
{{ toyaml .Values.resources | indent 12 }}
不用猜,花括号里的就是对应的变量,这些变量有些是内置变量,有些是写在values.yaml里的,有些是写在chart.yaml里的
比如.Values.replicaCount
在values.yaml里就有,.Values.service.internalPort 这一类同理,只是深度不同而已。
depname: mychartdep
replicaCount: 1
image:
repository: nginx
tag: stable
pullPolicy: IfNotPresent
service:
name: nginx
type: ClusterIP
externalPort: 80
internalPort: 80
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
除此之外,还有一些内置的变量,如:
Release.Name release 名称,就是通过helm install -n lamp-server ./lamp-chart 指定的的release 名字 “lamp-server”
Release.Time release 创建的时间
Release.Revision release 版本号
Release.IsUpgrade 返回boolen,true表示该release发布或者回滚了
Release.IsInstall 返回boolen,true表示该release已安装了
内置的变量还有很多,可以自己查阅一下,细心的同学会发下最后2个是2个布尔值,难道还能用来判断?当然可以,类似puppet的pp一样,helm可以在资源文件里做一些简单的判断,甚至可以在表达式里赋予变量
#变量名以$开始命名, 赋值运算符是 := (冒号+等号)
{{- $relname := .Release.Name -}}
引用自定义变量:
#不需要 . 引用
{{ $relname }}
#简单的判断方法
{{ if 条件表达式 }}
# Do something
{{ else if 条件表达式 }}
# Do something else
{{ else }}
# Default case
{{ end }}
另外还有一些内置的函数以及管道符,比如上述文件的{{ .Chart.Name }}-{{ .Chart.Version | replace “+” “_” }}
其实就是一个很简单的管理符处理,然后将+ 替换为-号。
当然还有很多其他的用法,比如with 限定作用域,range 遍历数组等等,可以去helm的中文文档上进行查阅
http://www.coderdocument.com/docs/helm/v2/developing_templates/intro.html
最后就是子模板,上面有提到_开头的均是子模板,那么子模板是干啥的呢?
我们日常写yaml的时候经常遇到一个很苦恼的问题,就是很多东西需要重复写,在deployment里写了,还要再service里写,比如namespaces, label里的各类标签等,而这些在多个资源文件里需要重复利用的,可以写在子模板里
#定义子模板_mylabel.tpl
{{- define "mychart.app" -}} #定义子模板名称
app_name: {{ .Chart.Name }} #定义模板内容
app_version: "{{ .Chart.Version }}+{{ .Release.Time.Seconds }}"
{{- end -}}
#资源模板文件 mychart_configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{ include "mychart.app" . | nindent 4 }} #引用mychart.app模版内容,并对输出结果缩进4个空格
data:
myvalue: "good"
调试上线
写完chart 后,我们如何检查自己写的是否正确呢
helm lint #可以检查语法
确认语法没问题后,可以进行调试
helm install . --dry-run --debug ./mychart # --debug --dry-run 两个参数,让helm输出模版结果,但是不把模版输出结果交给k8s处理
注意,新chart用的是install,更新用的是upgrade
确认无误后,打包到repo
helm package ./mychart
repo 内可以看到
helm search
查看release
helm ls
更新k8s的release
helm upgrade $release ./mychart
更多推荐
所有评论(0)