你真的会使用Helm 么?
你有看过官方文档中的最佳实践么 ?
你在编写yaml 的时候是否在使用空格而不是tab?
你是否也发现你写的helm别人很难阅读?
如果是,请看一下下面的文章吧
本文复制粘贴于Helm官方文档(抄的抄的):
https://helm.sh/zh/docs/chart_best_practices/
一般惯例
最佳实践的这部分阐述了一般惯例。
Chart名称
chart名称必须是小写字母和数字。单词之间 可以 使用横杠分隔(-):
示例:
drupal nginx-lego aws-cluster-autoscaler
chart名称中不能用大写字母也不能用下划线。点 . 符号也不行。
版本号
Helm尽可能使用 SemVer 2来表示版本号。(注意Docker镜像的tag不需要遵循SemVer, 因此被认为是一个不幸的例外规则。
当SemVer版本存储在Kubernetes标签中,我们通常把+字符改成_,因为标签不允许使用+作为值进行签名。
values
最佳实践的该部分包括了values的使用。这部分指南中,我们提供了关于你如何构建和使用values的建议,以及专注于设计chart的 values.yaml文件。
命名规范
变量名称以小写字母开头,单词按驼峰区分:
正确的:
chicken: true
chickenNoodleSoup: true
错误的:
Chicken: true # initial caps may conflict with built-ins
chicken-noodle-soup: true # do not use hyphens in the name
注意所有的Helm内置变量以大写字母开头,以便与用户定义的value进行区分:.Release.Name,.Capabilities.KubeVersion。
扁平或嵌套的Value
YAML是一种灵活格式,值可以嵌套得很深,也可以是扁平的。
嵌套的:
server:
name: nginx
port: 80
扁平的
serverName: nginx
serverPort: 80
大多数场景中,扁平的优于嵌套的。因为对模板开发者和用户来说更加简单。
为了最佳的安全性,嵌套值的每一层都必须检查:
{{ if .Values.server }}
{{ default "none" .Values.server.name }}
{{ end }}对于每个嵌套层,都必须进行存在性检查。但对于扁平的配置,使得模板更易于阅读和使用,这个检查可以跳过。
{{ default "none" .Values.serverName }}当有大量的相关变量时,其中至少有一个是非选择性的,嵌套的值可以改善可读性。
搞清楚类型
YAML的类型强制规则有时候是很反常的。比如,foo: false 和 foo: "false" 是不一样的。大整型数如:foo: 12345678 有时会被转换成科学计数法。
避免类型强制规则错误最简单的方式是字符串明确定义,其他都是不明确的。或者,简单来讲, 给所有字符串打引号。
通常,为了避免整数转换问题,将整型存储为字符串更好,并用 {{ int $value }} 在模板中将字符串转回整型。
在大多数场景中,显示的类型标记更好,所以 foo: !!string 1234 会将1234作为字符串对待。但是,YAML解析器会消耗标记,因此类型数据在一次解析后会丢失。
考虑用户如何使用你的value
有三种潜在的value来源:
chart的values.yaml文件
由helm install -f 或 helm upgrade -f提供的values文件
在执行helm install 或 helm upgrade 时传递给--set 或 --set-string 参数的values
当设计values的结构时,记得你的chart用户可能会通过-f 参数或--set选项覆盖他们。
由于--set在表现上更有限,编写你values.yaml文件的第一指导原则是 确保它容易被--set覆盖。
因此使用map构建values文件更好。
很难与--set一起使用:
servers:
- name: foo
port: 80
- name: bar
port: 81
上述在Helm <=2.4的版本中无法和--set一起表达。在Helm 2.5中,访问foo上的端口是 --set servers[0].port=80。用户不仅更难理解,而且以后更改servers顺序之后更易出错。
易于使用:
servers:
foo:
port: 80
bar:
port: 81
这样访问foo的port更加明显: --set servers.foo.port=80。
给values.yaml写文档
values.yaml中每个定义的属性都应该文档化。文档字符串应该以它要描述的属性开头,并至少给出一句描述。
不正确的:
# the host name for the webserver
serverHost: example
serverPort: 9191
正确的:
# serverHost is the host name for the webserver
serverHost: example
# serverPort is the HTTP listener port for the webserver
serverPort: 9191
以它描述的参数名称开始每个注释可以很容易整理文档,并使文档工具能可靠地将文档字符串与其描述的参数关联起来。
模板
最佳实践指南的这部分聚焦于模板。
templates/结构
templates/目录结构应该如下:
如果生成YAML输出,模板文件应该有扩展名.yaml。扩展名是.tpl可用于生成非格式化内容的模板文件。
模板文件名称应该使用横杠符号(my-example-configmap.yaml),不用驼峰记法。
每个资源的定义应该在它自己的模板文件中。
模板文件的名称应该反映名称中的资源类型。比如:foo-pod.yaml, bar-svc.yaml
定义模板的名称
定义的模板(在{{ define }}命令中定义的模板)是可全局访问的。这就意味着chart和所有的子chart都可以访问用{{ define }}创建的所有模板。
因此, 所有定义的模板名称应该被命名空间化。
正确的:
{{- define "nginx.fullname" }}
{{/* ... */}}
{{ end -}}不正确的:
{{- define "fullname" -}}
{{/* ... */}}
{{ end -}}强烈建议通过helm create命令创建新chart,因为模板名称是根据此最佳实践自动定义的。
格式化模板
模板应该使用两个 空格 缩进(永远不要用tab)。
模板命令的大括号前后应该使用空格:
正确的:
{{ .foo }}
{{ print "foo" }}
{{- print "bar" -}}不正确的:
{{.foo}}
{{print "foo"}}
{{-print "bar"-}}模板应该尽可能多地使用空格:
foo:
{{- range .Values.items }}
{{ . }}
{{ end -}}
块(例如控制结构) 可以缩进表示模板代码流。
{{ if $foo -}}
{{- with .Bar }}Hello{{ end -}}
{{- end -}}然而,因为YAML是面向空格的语言,代码缩进通常不可能遵守规范。
生成模板中的空格
最好在生成的模板中将空格量保持在最小值。尤其是大量的空行不应该相邻出现。但偶尔有空行(尤其在逻辑块之间)是没问题的。
这样是最好的:
apiVersion: batch/v1
kind: Job
metadata:
name: example
labels:
first: first
second: second
这样也OK:
apiVersion: batch/v1
kind: Job
metadata:
name: example
labels:
first: first
second: second
但避免这样:
apiVersion: batch/v1
kind: Job
metadata:
name: example
labels:
first: first
second: second
注释 (YAML注释 vs. 模板注释)
YAML和Helm模板都有注释标记符。
YAML注释:
# This is a comment
type: sprocket
模板注释:
{{- *
This is a comment.
*/}}
type: frobnitz描述模板的特性时应当使用模板注释,比如解释一个定义的模板:
{{- *
mychart.shortname provides a 6 char truncated version of the release name.
*/}}
{{ define "mychart.shortname" -}}
{{ .Release.Name | trunc 6 }}
{{- end -}}在模板中,当有益于Helm用户(可能)在调试时查看注释,可以使用YAML注释。
# This may cause problems if the value is more than 100Gi
memory: {{ .Values.maxMem | quote }}
以上注释在用户执行helm install --debug时是可见的,而在{{- * */}}部分指定注释不会显示。
在模板和模板输出中使用JSON
YAML是JSON的超集。在某些情况下,使用JSON语法比其他YAML表示更具可读性。
比如,这个YAML更接近表示列表的普通YAML方法:
arguments:
- "--dirname"
- "/foo"
但是折叠成JSON列表样式时会更易阅读:
arguments: ["--dirname", "/foo"]
使用JSON可以很好地提高易读性。然而,JSON语法不应用于表示更复杂的结构。
在处理嵌入到YAML中的纯JSON时(比如初始化容器配置),使用JSON格式当然是最合适的。
依赖
最佳实践的这部分阐述Chart.yaml中声明的dependencies。
版本
如果有可能的话,使用版本范围而不是某个固定的版本。建议的默认设置时使用补丁级别版本的匹配:
version: ~1.2.3
这样会匹配 1.2.3以及该版本的任何补丁,也就是说,~1.2.3相当于>= 1.2.3, < 1.3.0
关于完整的版本匹配语法,请参照 语义版本文档。
仓库URL
如果可能的话,使用 https:// 仓库URL,而不是http:// URL。
如果这个仓库已经被添加到仓库索引文件中,仓库名称可以作为URL的别名。使用alias: 或 @ 后跟仓库名称。
文件URL(file://...) 被认为是一种有固定部署管道组装的chart的“特例”。官方Helm仓库不允许chart使用file://。
条件和标签
条件和标签可以被添加到任意 可选的 依赖中。
条件的首先格式是:
condition: somechart.enabled
somechart是依赖的chart名称。
当多个子chart(依赖)一起提供可选或可交换的特性时,这些chart应该共享相同的标签。
比如,如果nginx 和 memcached在chart中一起提供性能优化,且需要在使用该功能时同时存在,则都应该有如下的标签部分:
tags:
- webaccelerator
这允许用户使用一个标签打开和关闭该功能。
标签和注释
最佳实践的这部分讨论关于在chart中使用标签和注释的最佳方式。
是标签还是注释?
在以下条件下,元数据项应该是标签:
Kubernetes使用它来识别这种资源
为了查询系统,暴露给操作员会很有用
比如,我们建议使用 helm.sh/chart: NAME-VERSION 作为标签,以便操作员可以找到特定chart的所有实例。
如果元数据项不是用于查询,就应该设置为注释。
Helm钩子都是注释。
标准标签
以下表格定义了Helm chart使用的通用标签。Helm本身从不要求出现特定标签。标记为REC的是推荐标签。且 应该 放置在chart之上保持全局一致性。标记为OPT的是可选的。这些是惯用的和常用的,但操作时并不经常依赖。
名称 | 状态 | 描述 |
app.kubernetes.io/name | REC | app名称,反应整个app。{{ template "name" . }}经常用于此。很多Kubernetes清单会使用这个,但不是Helm指定的。 |
helm.sh/chart | REC | chart的名称和版本: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}。 |
app.kubernetes.io/managed-by | REC | 此值应始终设置为 {{ .Release.Service }}。用来查找被Helm管理的所有内容。 |
app.kubernetes.io/instance | REC | 这个应该是{{ .Release.Name }}。有助于在同一应用程序中区分不同的实例。 |
app.kubernetes.io/version | OPT | app的版本,且被设置为 {{ .Chart.AppVersion }}. |
app.kubernetes.io/component | OPT | 这是通用标签,用于标记块在应用程序中可能扮演的不同角色。比如 app.kubernetes.io/component: frontend。 |
app.kubernetes.io/part-of | OPT | 当多个chart或块用于构建一个应用程序时。比如,应用软件和数据库生成一个网站。这可以设置为受支持的顶级应用程序。 |
你可以在 Kubernetes documentation找到更多 app.kubernetes.io作为前缀的Kubernetes标签。
pod和pod模板
最佳实践的这部分讨论在chart清单中格式化Pod和Pod模板部分。
以下(非详尽的)资源列表使用Pod模板:
Deployment
ReplicationController
ReplicaSet
DaemonSet
StatefulSet
镜像
容器镜像应该使用固定的tag或镜像SHA。不应该使用latest, head, canary等标签或其他被设计为“浮动的”标签。
镜像 可以 被定义在 values.yaml 文件中是的切换镜像更加容易。
image: {{ .Values.redisImage | quote }}镜像和tag 可以 在 values.yaml中定义为两个独立的字段:
image: "{{ .Values.redisImage }}:{{ .Values.redisTag }}"镜像拉取策略
helm create 通过以下方式在deployment.yaml中将 imagePullPolicy 默认设置为 IfNotPresent:
imagePullPolicy: {{ .Values.image.pullPolicy }}以及values.yaml:
image:
pullPolicy: IfNotPresent
类似地,如果Kubernetes根本没有定义,默认会将 imagePullPolicy 设置为 IfNotPresent。如果想设置一个值而不是 IfNotPresent,只需在 values.yaml 中更新为需要的值即可。
Pod模板应该声明选择器
所有的Pod模板部分应该指定一个selector。比如:
selector:
matchLabels:
app.kubernetes.io/name: MyName
template:
metadata:
labels:
app.kubernetes.io/name: MyName
这是一个很好的实践,因为它建立了集合和pod之间的关系。
但这一点对于像工作负载这样的集合来说更加重要。如果没有,标签的 所有 集合会选择匹配pod,如果你使用了改变的标签,比如版本和发布日期,这个功能会失效。
基于角色的访问控制
最佳实践的这部分讨论在chart清单中创建和格式化RBAC资源。
RBAC 资源有:
ServiceAccount (namespaced)
Role (namespaced)
ClusterRole
RoleBinding (namespaced)
ClusterRoleBinding
YAML 配置
RBAC和服务账户配置应该使用独立的key。它们是独立的内容。在YAML中将这两个概念分开可以消除歧义使其更加清晰。
rbac:
# Specifies whether RBAC resources should be created
create: true
serviceAccount:
# Specifies whether a ServiceAccount should be created
create: true
# The name of the ServiceAccount to use.
# If not set and create is true, a name is generated using the fullname template
name:
这个结构可以在更加复杂的需要多个服务账户的chart中扩展。
someComponent:
serviceAccount:
create: true
name:
anotherComponent:
serviceAccount:
create: true
name:
RBAC 资源应该默认创建
rbac.create 应该是布尔值,用于控制RBAC资源是否被创建。默认是 true。用户想自己管理RBAC访问控制时可以设置为false (示例如下)。
使用RBAC资源
serviceAccount.name 要设置为由chart创建的访问控制资源的ServiceAccount的名称。如果serviceAccount.create是true,则使用该名称的ServiceAccount会被创建。如果没有设置名称, 则会使用fullname模板生成一个名称。如果serviceAccount.create是false,则不会被创建,但仍然会与相同的资源关联, 以便后续手动创建的引用它的RBAC资源可以正常工作。如果serviceAccount.create是false且没有指定名称, 会使用默认的ServiceAccount。
以下辅助模板应用于ServiceAccount。
{{/*
Create the name of the service account to use
*/}}
{{- define "mychart.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "mychart.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}



