第23章:Helm应用包管理器
3.1 为什么需要Helm
K8S上的应用对象,都是由特定的资源描述组成,包括deployment、service等。都保存各自文件中或者集中写到一个配置文件。然后kubectl apply –f 部署。
如果应用只由一个或几个这样的服务组成,上面部署方式足够了。 而对于一个复杂的应用,会有很多类似上面的资源描述文件,例如微服务架构应用,组成应用的服务可能多达十个,几十个。如果有更新或回滚应用的需求,可能要修改和维护所涉及的大量资源文件,而这种组织和管理应用的方式就显得力不从心了。 且由于缺少对发布过的应用版本管理和控制,使Kubernetes上的应用维护和更新等面临诸多的挑战,主要面临以下问题:
(1) 如何将这些服务作为一个整体管理 (2) 这些资源文件如何高效复用 (3) 不支持应用级别的版本管理
3.2 Helm介绍
Helm是一个Kubernetes的包管理工具,就像Linux下的包管理器,如yum/apt等,可以很方便的将之前打包好的yaml文件部署到kubernetes上。Helm有3个重要概念如下: (1) helm # 一个命令行客户端工具,主要用于Kubernetes应用chart的创建、打包、发布和管理。 (2) Chart # 应用描述,一系列用于描述 k8s 资源相关文件的集合。 (3) Release # 基于Chart的部署实体,一个 chart 被 Helm 运行后将会生成对应的一个 release;将在k8s中创建出真实运行的资源对象。
3.3 Helm v3 变化
Helm目前有两个大版本:v2和v3,2019年11月,Helm团队发布 v3 版本,v3版本相比v2版本最主要变化如下。
1 架构变化
最明显的变化是 Tiller
的删除,并大部分代码重构。
2 Release
名称可以在不同命名空间重用
3 支持将 Chart 推送至 Docker 镜像仓库中
4 使用JSONSchema验证chart values
5 其他
(1) 为了更好地协调其他包管理者的措施 Helm CLI
个别更名
helm delete 更名为 helm uninstall
helm inspect 更名为 helm show
helm fetch 更名为 helm pull
但以上旧的命令当前仍能使用。
(2) 移除了用于本地临时搭建 Chart Repository
的 helm serve
命令。
(3) 自动创建名称空间
在不存在的命名空间中创建发行版时,Helm 2创建了命名空间。Helm 3遵循其他Kubernetes对象的行为,如果命名空间不存在则返回错误。
(4) 不再需要requirements.yaml
, 依赖关系是直接在chart.yaml
中定义。
3.4 Helm客户端
1 部署Helm客户端
使用helm很简单,你只需要下载一个二进制客户端包即可,会通过kubeconfig配置(通常$HOME/.kube/config)来连接Kubernetes。 Helm客户端下载地址:https://github.com/helm/helm/releases/
# 下载后解压移动到/usr/bin/目录即可
# wget https://get.helm.sh/helm-v3.7.1-linux-amd64.tar.gz
# tar -zxf helm-v3.7.1-linux-amd64.tar.gz
# mv linux-amd64/helm /usr/bin/
2 Helm常用命令
命令 | 描述 |
---|---|
completion | 命令补全,source <(helm completion bash) |
create | 创建一个chart并指定名字 |
dependency | 管理chart依赖 |
get | 下载一个release。可用子命令:all、hooks、manifest、notes、values |
history | 获取release历史 |
install | 安装一个chart |
list | 列出release |
package | 将chart目录打包到chart存档文件中 |
pull | 从远程仓库中下载chart并解压到本地 # helm pull stable/mysql --untar |
repo | 添加,列出,移除,更新和索引chart仓库。可用子命令:add、index、list、remove、update |
rollback | 从之前版本回滚 |
search | 根据关键字搜索chart。可用子命令:hub、repo |
show | 查看chart详细信息。可用子命令:all、chart、readme、values |
status | 显示已命名版本的状态 |
template | 本地呈现模板 |
uninstall | 卸载一个release |
upgrade | 更新一个release |
version | 查看helm客户端版本 |
3 配置Chart仓库
(1) 常用仓库 1)官方仓库网址: https://hub.kubeapps.com/ 2)其它仓库网址: https://artifacthub.io/
(2) 添加存储库并更新
# 添加阿里云chart存储仓库,版本比较老
# helm repo add aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
# 更新仓库
# helm repo update
(3) 查看配置的存储库
# helm repo list
(4) 查找chart应用
helm search repo <应用名>
(5) 删除存储库
# helm repo remove <仓库名>
3.5 Helm基本使用
1 Helm管理应用生命周期
(1) 创建Chart应用示例
# helm create
(2) 部署
# helm install
(3) 更新
# helm upgrade
(4) 回滚
# helm rollback
(5) 卸载
# helm uninstall
2 使用chart部署一个应用
(1) 查找chart
# helm search repo mysql
NAME CHART VERSIONAPP VERSIONDESCRIPTION
aliyun/mysql 0.3.5 Fast, reliable, scalable, and easy to use open-...
aliyun/percona 0.3.0 free, fully compatible, enhanced, open source d...
aliyun/percona-xtradb-cluster0.0.2 5.7.19 free, fully compatible, enhanced, open source d...
aliyun/gcloud-sqlproxy 0.2.3 Google Cloud SQL Proxy
aliyun/mariadb 2.1.6 10.1.31 Fast, reliable, scalable, and easy to use open-...
# 为什么mariadb也在列表中,因为他和mysql有关。
(2) 查看chart的基本信息
# helm show chart aliyun/mysql
(3) 安装chart应用
# helm install db aliyun/mysql -n default
(4) 安装后查看chart帮助信息
# helm status db -n default
3 安装前自定义chart配置选项
(1) chart
上面部署的mysql并没有成功,这是因为并不是所有的 chart 都能按照默认配置运行成功,可能会需要一些环境依赖,例如PV。
所以我们需要自定义 chart 配置选项(覆盖values.yaml),安装过程中有两种方法可以传递配置数据:
--values 或 -f # 指定带有覆盖的YAML文件,这可以多次指定,最右边的文件优先。
--set # 在命令行上指定替代,如果两者都用,--set优先级高
。
# 把chart包下载下来查看详情
# helm pull aliyun/mysql --untar
# tree mysql/
mysql/
├── Chart.yaml
├── README.md
├── templates
│ ├── configmap.yaml
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── NOTES.txt
│ ├── pvc.yaml
│ ├── secrets.yaml
│ └── svc.yaml
└── values.yaml
1 directory, 10 files
# helm show values ./mysql <=> # cat mysql/values.yaml # 查看yaml模板变量
# helm show chart ./mysql/ <=> # cat mysql/Chart.yaml # 查看chart基本信息
# 由于我这里使用的是k8s版本为1.20,Deployment yaml配置文件需要进行修改,否则无法部署
# vim mysql/templates/deployment.yaml
# cat > config.yaml << EOF
persistence:
enabled: true
accessMode: ReadWriteOnce
size: 8Gi
mysqlUser: "k8s"
mysqlPassword: "123456"
mysqlDatabase: "k8s"
EOF
# kubectl create namespace mysql-db
# 创建MySQL用户k8s,并授予此用户访问新创建的k8s数据库的权限,并指定资源所在的名称空间,
# --set使用命令行替代变量,未指定的使用默认值。
# helm install db -f config.yaml -n mysql-db --set persistence.storageClass="managed-nfs-storage" ./mysql/
# kubectl get pod -n mysql-db
NAME READY STATUS RESTARTS AGE
db-mysql-5c695fcdfb-g6jt6 1/1 Running 0 77s
# helm list -n mysql-db
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
db mysql-db 1 2021-10-22 17:17:32.401870823 +0800 CST deployed mysql-0.3.5
# kubectl get svc -n mysql-db
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
db-mysql ClusterIP 172.28.104.81 <none> 3306/TCP 2m38s
# yum install mysql -y
# mysql -uk8s -p123456 -h172.28.104.81 k8s
MySQL [k8s]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| k8s |
+--------------------+
2 rows in set (0.00 sec)
# 卸载helm应用(pvc,pv,存储资源被删除)
# helm uninstall db -n mysql-db
(2) values.yaml参数格式对应set变量格式表
helm install命令可以从多个来源安装:chart存储库、本地chart存档(helm install mysql-0.3.5.tgz)、chart目录(helm install path/to/mysql)、完整的URL(helm install https://example.com/charts/mysql-0.3.5.tgz)
4 构建一个Helm Chart
(1) 自动生成目录
# helm create mychart
# tree mychart/
mychart/
├── charts
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── NOTES.txt
│ ├── serviceaccount.yaml
│ ├── service.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml
3 directories, 10 files
1)charts # 目录里存放这个chart依赖的所有子chart。
2)Chart.yaml # 用于描述这个Chart的基本信息,包括名字、描述信息以及版本等。
3)Templates # 目录里面存放所有yaml模板文件。
4)NOTES.txt # 用于介绍Chart帮助信息,helm install 部署后展示给用户。例如:如何使用这个 Chart、列出缺省的设置等。
5)_helpers.tpl # 放置模板助手的地方,可以在整个 chart 中重复使用。
6)values.yaml # 用于存储 templates 目录中模板文件中用到变量的值。
(2) 创建Chart后,接下来就是将其部署
# 修改service的type为NodePort,方便实验
# vim mychart/values.yaml
service:
type: NodePort
port: 80
# helm install web -n default mychart/
(3) 查看Release
# helm list -n default
NAMENAMESPACEREVISIONUPDATED STATUS CHART APP VERSION
web default 1 2021-10-23 23:11:12.33497844 +0800 CSTdeployedmychart-0.1.01.16.0
(4) 查看部署的pod
# kubectl get pod,svc -n default
NAME READY STATUS RESTARTS AGE
pod/nfs-client-provisioner-666969576d-k2pgw 1/1 Running 1 31h
pod/web-mychart-5b58c47cd6-nsk4r 1/1 Running 0 57s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 172.28.0.1 <none> 443/TCP 33d
service/web-mychart NodePort 172.28.48.236 <none> 80:31644/TCP 57s
(5) 打包推送到chart仓库共享给别人使用
# helm package mychart/
Successfully packaged chart and saved it to: /root/helm_dir/mychart-0.1.0.tgz
5 升级、回滚和删除
(1) 升级
1)使用Chart升级应用有两种方法:
--values,-f # 指定yaml文件覆盖values.yaml文件中的值,没有覆盖的保持默认
--set # 在命令行上指定覆盖values.yaml文件中的值,没有覆盖的保持默认
注: 如果两种方法一起使用,--set优先级高
2)发布新版本的chart时,或者当您要更改发布的配置时,可以使用该helm upgrade
命令
# 第一种方式,将nginx服务升级到1.18版本
# vim values.yaml
image:
tag: "1.18"
# helm upgrade -f values.yaml web mychart/ -n default
# 第二种方式,将nginx服务升级到1.19版本
# helm upgrade --set image.tag=1.19 web mychart/ -n default
(2) 回滚
如果在发布后没有达到预期的效果,则可以使用helm rollback
回滚到之前的版本。
1)查看历史版本
# helm history web -n default
REVISIONUPDATED STATUS CHART APP VERSIONDESCRIPTION
1 Sat Oct 23 23:28:45 2021supersededmychart-0.1.01.16.0 Install complete
2 Sat Oct 23 23:30:22 2021supersededmychart-0.1.01.16.0 Upgrade complete
3 Sat Oct 23 23:33:15 2021deployed mychart-0.1.01.16.0 Upgrade complete
注:
REVISION 1 表示的是nginx:1.16版本
REVISION 2 表示的是nginx:1.18版本
REVISION 3 表示的是nginx:1.19版本
2)将应用回滚到指定版本
# helm rollback web 2 -n default
Rollback was a success! Happy Helming!
注: 回滚到上一个版本使用如下命令
helm rollback web -n default
(3) 卸载chart应用
# helm uninstall web -n default
release "web" uninstalled
6 Helm工作流程
3.6 Chart模板
1 介绍
Helm最核心的就是模板,即模板化的K8S YAML文件,它本质上就是一个Go的template模板,Helm在Go template模板的基础上,还会增加
很多东西,如一些自定义的元数据信息、扩展的库以及一些类似于编程形式的工作流,例如条件语句、管道等等,这些东西都会使得我们的
模板变得更加丰富。
通过模板实现Chart的高效复用,当部署多个应用时,可以将差异化的字段进行模板化,在部署时使用 -f 或者 --set 动态覆盖默认值,从而适配多个应用。
# cd /root/helm_dir/mychart/
# cat > values.yaml << EOF
replicas: 1
label:
project: ms
app: nginx
image: nginx
imageTag: "1.20"
EOF
# rm -rf templates/*
# cat > templates/deployment.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-deployment
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
project: {{ .Values.label.project }}
app: {{ .Values.label.app }}
template:
metadata:
labels:
project: {{ .Values.label.project }}
app: {{ .Values.label.app }}
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: nginx
EOF
2 内置对象
Release对象获取发布记录信息。
在上面示例中,模板文件中.Release
、.Values
是Helm内置对象,顶级开头写。
(1) Release对象
使用 {{.Release.Name}}
将 release 的名称插入到模板中,下面是一些常用的内置对象。
Release.Name | release 名称 |
---|---|
Release.Name | release 的名称 |
Release.Time | release 的时间 |
Release.Namespace | release 的命名空间 |
Release.Service | release 的服务名称 |
Release.Revision | release 的修订版本号,从1开始累加 |
(2) Values对象
1)Values对象是为Chart模板提供值,这个对象的值有3个来源:
A、chart 包中的 values.yaml 文件
B、通过 helm install 或者 helm upgrade 的 -f
或者 --values
参数传入的自定义的 yaml 文件
C、通过 --set
参数传入的值
2)chart 的 values.yaml 提供的值可以被用户提供的 values 文件覆盖,而该文件同样可以被 --set
提供的参数所覆盖。
(3) Chart对象
可以通过Chart对象访问Chart.yaml文件的内容,例如: {{ .Chart.AppVersion }}
3 调试
Helm也提供了--dry-run --debug
调试参数,帮助你验证模板正确性,在执行helm install
时候带上这两个参数就可以把对应的
values值和渲染的资源清单打印出来,而不会真正的去部署一个release。
比如我们来调试上面创建的 chart 包:
# helm install --dry-run --debug web ./mychart/ -n default
install.go:178: [debug] Original chart version: ""
install.go:199: [debug] CHART PATH: /root/helm_dir/mychart
NAME: web
LAST DEPLOYED: Sun Oct 24 19:46:42 2021
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
USER-SUPPLIED VALUES:
{}
COMPUTED VALUES:
image: nginx
imageTag: "1.20"
label:
app: nginx
project: ms
replicas: 1
HOOKS:
MANIFEST:
---
# Source: mychart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
spec:
replicas: 1
selector:
matchLabels:
project: ms
app: nginx
template:
metadata:
labels:
project: ms
app: nginx
spec:
containers:
- image: nginx:1.20
name: nginx
4 函数与管道
前面讲的模块,其实就是将变量值传给模板引擎进行渲染,模板引擎还支持对拿到数据进行二次处理。 (1) 常用函数
quote # 将值转换为字符串,即加双引号
default # 设置默认值,如果获取的值为空则为默认值
indent、nindent# 缩进字符串
toYaml # 引用一块YAML内容
其他函数:
upper、title等
(2) quote,将值转换为字符串,即加双引号
例如:从 .Values 中读取的值变成字符串,可以使用quote
函数实现。
nodeSelector标签的值用了true正常使用会报错,这是因为它是关键字,需要加引号才可以。
# vim values.yaml
nodeSelector:
gpu: true
# vim templates/deployment.yaml
......
nodeSelector:
disktype: {{ quote .Values.nodeSelector.gpu }}
(3) default,设置默认值,如果获取的值为空则为默认值 示例:以防止忘记定义而导致模板文件缺少字段无法创建资源,这时可以为字段定义一个默认值。
# vim templates/deployment.yaml
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}
注:
这里用到了管道符"|",前面的值传递后函数验证是否为空。
(4) indent和nindent函数都是缩进字符串,主要区别在于nindent会在缩进前多添加一个换行符 示例:
# vim templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: {{ .Release.Name | indent 0 }}
......
(5) toYaml,引用一块YAML内容
示例:在values.yaml里写结构化数据,引用内容块
# vim values.yaml
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
# vim templates/deployment.yaml
......
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: nginx
resources: {{ toYaml .Values.resources | nindent 10 }}
(6) 其他函数
# vim templates/deployment.yaml
......
{{ upper .Values.resources }}# 大写
{{ title .Values.resources }}# 首字母大写
5 流程控制
流程控制是为模板提供了一种能力,满足更复杂的数据逻辑处理,Helm模板语言提供的控制语句有,if/else
条件判断、range
循环、with
指定变量作用域。
(1) 流程控制之 if/else 1)if/else 块是用于在模板中有条件的包含文本块的方法
条件判断,根据不同的条件做不同的行为,语法如下:
{{ if <表达式> }}
# 做某事
{{ else if <表达式> }}
# 做某事
{{ else }}
# 默认
{{ end }}
2)示例 部署一个应用,在没明确启用ingress时,默认情况下不启用。
# vim values.yaml
ingress:
enabled: false
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
hosts:
host: www.lc.com
path: /
tls:
secretName: chart-example-tls
hosts: www.lc.com
# vim templates/ingress.yaml
{{ if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
labels: {{ include "labels" . | nindent 4 }}
name: {{ include "fullname" . }}
annotations: {{ toYaml .Values.ingress.annotations | nindent 4 }}
spec:
{{- if .Values.ingress.tls }}
tls:
- hosts:
- {{ .Values.ingress.tls.hosts }}
secretName: {{ .Values.ingress.tls.secretName }}
{{- end }}
rules:
- host: {{ .Values.ingress.hosts.host }}
http:
paths:
- path: {{ .Values.ingress.hosts.path }}
pathType: Prefix
backend:
service:
name: {{ include "fullname" . }}
port:
number: {{ .Values.service.port }}
{{ end }}
# 测试
# helm install test --set ingress.enabled=true --dry-run ./mychart/
# 如果值为以下几种情况则为false
一个布尔类型 false
一个数字 0
一个空的字符串
一个 nil(空或 null)
一个空的集合( map、 slice、 tuple、 dict、 array)
除了上面的这些情况外,其他所有条件都为真。
# 条件表达式也支持操作符
eq 等于
ne 不等于
lt 小于
gt 大于
and 逻辑与
or 逻辑或
3)消除判断空格
示例:如果是一个空的集合则不启用资源配额
# vim values.yaml
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
# vim templates/deployment.yaml
......
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: nginx
{{ if .Values.resources }}
resources: {{ toYaml .Values.resources | nindent 10 }}
{{ end }}
验证渲染结果:
# helm install test --dry-run ./mychart/
渲染结果会发现有多余的空行,这是因为模板渲染时会将指令删除,所以原有的位置就空白了,可以使用横杠"-"消除空行。
"{{-" 表示删除左边的所有空格,直到非空格字符,"-}}" 表示删除右边的所有空格,还包括换行符、TAB字符。
示例:
# vim templates/deployment.yaml
......
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: nginx
{{- if .Values.resources }}
resources: {{ toYaml .Values.resources | nindent 10 }}
{{- end }}
4)判断一个空的数组
# vim values.yaml
resources: {}
# vim templates/deployment.yaml
......
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: nginx
{{- if .Values.resources }}
resources:
{{ toYaml .Values.resources | indent 10 }}
{{- end }}
(2) range
循环: 一般用于遍历序列结构的数据,例如序列、键值。
语法:
{{ range <值> }}
# 引用内容
{{ end }}
示例: 遍历数据
在 Helm 模板语言中,使用 range 关键字来进行循环操作,我们在 values.yaml 文件中添加上一个变量列表
# cat values.yaml
env:
- 1
- 2
- 3
# vim templates/deployment.yaml
......
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: nginx
env:
{{- range $k,$v := .Values.env }}
- name: {{ $k }}
value: {{ quote $v }}
{{- end }}
# 循环打印该列表
# vim templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
data:
test: |
{{- range .Values.env }}
{{ . }}
{{- end }}
注:
循环内部我们使用的是一个".",这是因为当前的作用域就在当前循环内,这个"."引用的是当前读取的元素。
(3) with
with: 控制变量作用域
之前我们的 {{ .Release.xxx }}
或者 {{ .Values.xxx }}
,其中的 .
就是表示对当前范围的引用, .Values
就是告诉模板在当
前范围中查找 Values
对象的值,而 with
语句就可以来控制变量的作用域范围。
语法:
{{ with <值> }}
# 限制范围
{{ end }}
with
语句可以允许将当前范围 .
设置为特定的对象,比如使用.Values.nodeSelecotr
,我们可以使用 with
来将.
范围指向
.Values.nodeSelecotr
。
# vim values.yaml
nodeSelector:
team: a
gpu: yes
# vim templates/deployment.yaml
......
spec:
{{- $ReleaseName := .Release.Name -}}
{{- with .Values.nodeSelector }}
nodeSelector:
team: {{ .team }}
gpu: {{ .gpu }}
test: {{ $ReleaseName }}
{{- end }}
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
......
注:
上面增加了一个"{{- with .Values.label }} xxx {{- end }}"的一个块,这样的话就可以在当前的块里面直接引用".team"
和".gpu"了。"with"是一个循环构造,使用".Values.nodeSelector"中的值,将其转换为Yaml。
with 块限制了变量作用域,也就是无法直接引用模板对象,例如".Values"、".Release",with 语句块内不能再使用
".Release.Name"对象,否则报错,我们可以将该对象赋值给一个变量来解决这个问题,可以在 with 语句上面增加一句
"{{- $ReleaseName := .Release.Name -}}",其中"$ReleaseName"就是后面的对象的一个引用变量,它的形式就
是"$name",赋值操作使用":=",这样 with 语句块内部的"$ReleaseName"变量仍然指向的是".Release.Name"。
# 优化一下
# vim templates/deployment.yaml
......
spec:
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
......
注: toYaml之后的点是循环中".Values.nodeSelector"的当前值
6 变量
变量在实际应用中不多,但有时候结合with
、range
能更好的处理数据。
获取列表键值
# vim values.yaml
env:
NAME: "gateway"
JAVA_OPTS: "-Xmx1G"
hello: world
# vim templates/deployment.yaml
......
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: nginx
env:
{{- range $k,$v := .Values.env }}
- name: {{ $k }}
value: {{ quote $v }}
{{- end }}
注: 上面在 range 循环中使用 "$key" 和 "$value" 两个变量来接收后面列表循环的键和值。
# 输出结果
env:
- name: JAVA_OPTS
value: "-Xmx1G"
- name: NAME
value: "gateway"
# vim templates/deployment.yaml
......
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: nginx
env:
- name: NAME
{{- if eq .Values.env.hello "world" }}
value: true
{{- else }}
value: false
{{- end }}
# 输出结果
env:
- name: NAME
value: true
7 命名模板 命名模板类似于开发语言中的函数,指一段可以直接被另一段程序或代码引用的程序或代码。在编写chart时,可以将一些重复使用的内容 写在命名模板文件中供公共使用,这样可减少重复编写程序段和简化代码结构。命名模块使用define字段定义,模板中使用template字段 或include字段引入,在templates目录中默认下划线开头的文件为公共模板(_helpers.tpl)。
示例: 将资源名称生成指令放到公共模板文件中作为所有资源名称
# vim templates/_helpers.tpl
{{- define "fullname" -}}
{{- .Chart.Name }}-{{ .Release.Name }}
{{- end }}
{{- define "labels" -}}
app: {{ template "fullname" . }}
chart: {{ .Chart.Name }}-{{ .Chart.Version }}
release: {{ .Release.Name }}
{{- end }}
# vim templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "fullname" . }}
labels:
{{- include "labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicas }}
......
# 得到的结果为
apiVersion: apps/v1
kind: Deployment
metadata:
name: mychart-test
labels:
app: mychart-test
chart: mychart-0.1.0
release: test
spec:
replicas: 1
......
template指令是将一个模板包含在另一个模板中的方法,但是template函数不能用于Go模板管道,为了解决该问题,引入了 include 指令。
上面包含一个名为 labels 的模板,然后将值 "." 传递给模板,最后将该模板的输出传递给 nindent 函数。
3.7 写一个通用的Chart
1 创建步骤
(1) 先创建模板示例 helm create demo
(2) 修改Chart.yaml,Values.yaml,参考示例预留变动的字段值
(3) 在templates目录下准备部署应用所需的yaml文件,并添加指令引用Values.yaml字段
(4) 将重复使用的内容放到命名模板文件中
(5) 使用Chart结合参数部署多个同类服务
2 查看Chart的目录结构
# tree demo
demo
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── ingress.yaml
│ ├── NOTES.txt
│ └── service.yaml
└── values.yaml
1 directory, 7 files
3 Chart.yaml
# cat demo/Chart.yaml
apiVersion: v2
appVersion: 0.1.0
description: java demo
name: demo
type: application
version: 0.1.0
创建Chart时的默认配置:
# egrep -v "^$|^#" mychart/Chart.yaml
apiVersion: v2
# Chart的api版本
name: mychart
# Chart的名称
description: A Helm chart for Kubernetes
# Chart的描述信息
type: application
# Chart可以是"应用程序"或"库"Chart。
# 应用程序Chart是一组模板,可以打包到要部署的版本化归档中。
# 库为Chart开发人员提供了有用的实用程序或函数。
# 它们作为应用程序Chart的依赖项包含在其中,用于将这些实用程序和函数注入渲染管道。
# 库Chart不定义任何模板,因此无法部署。
version: 0.1.0
# 这是Chart版本。
# 每次更改Chart及其模板(包括应用程序版本)时,此版本号应增加。
# 版本应遵循语义版本控制(https://semver.org/)。
appVersion: "1.16.0"
# 这是正在部署的应用程序的版本号。
# 每次更改应用程序时,此版本号都应递增。
# 版本不应遵循语义版本控制。
# 它们应该反映应用程序正在使用的版本。
# 建议将其与引号一起使用。
4 values.yaml
# cat demo/values.yaml
image:
pullPolicy: IfNotPresent
repository: lizhenliang/java-demo
tag: latest
imagePullSecrets: []
ingress:
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 100m
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
enabled: true
host: example.lc.com
tls:
secretName: example-lc-com-tls
nodeSelector: {}
replicaCount: 3
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 100m
memory: 128Mi
service:
port: 80
type: ClusterIP
tolerations: []
5 templates
(1) deployment.yaml
# cat demo/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "demo.fullname" . }}
labels:
{{- include "demo.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "demo.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "demo.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
(2) _helpers.tpl
# cat demo/templates/_helpers.tpl
{{- define "demo.fullname" -}}
{{- .Chart.Name -}}-{{ .Release.Name }}
{{- end -}}
{{/*
公用标签
*/}}
{{- define "demo.labels" -}}
app: {{ template "demo.fullname" . }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
{{- end -}}
{{/*
标签选择器
*/}}
{{- define "demo.selectorLabels" -}}
app: {{ template "demo.fullname" . }}
release: "{{ .Release.Name }}"
{{- end -}}
(3) ingress.yaml
# cat demo/templates/ingress.yaml
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: {{ include "demo.fullname" . }}
labels:
{{- include "demo.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
- hosts:
- {{ .Values.ingress.host }}
secretName: {{ .Values.ingress.tls.secretName }}
{{- end }}
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /
backend:
serviceName: {{ include "demo.fullname" . }}
servicePort: {{ .Values.service.port }}
{{- end }}
(4) NOTES.txt
# cat demo/templates/NOTES.txt
访问地址:
{{- if .Values.ingress.enabled }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .Values.ingress.host }}
{{- end }}
{{- if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "demo.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- end }}
(5) service.yaml
# cat demo/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "demo.fullname" . }}
labels:
{{- include "demo.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "demo.selectorLabels" . | nindent 4 }}