使用Helm为Kubernetes构建软件包:图表结构和模板



上一篇文章中,我们讨论了Helm并“总体上”使用了Helm。 现在,让我们从另一侧着手进行练习-从图表创建者的角度(即Helm的软件包)的角度出发。 尽管这篇文章来自剥削世界,但事实证明它更像是关于编程语言的材料,这就是图表作者的命运。 因此,图表是文件的集合...

图表文件可分为两组:

  1. 生成Kubernetes资源清单所需的文件。 其中包括模板目录中的templates和带有值的文件(默认值存储在values.yaml )。 该组中还包括requirements.yaml文件和charts目录-所有这些都用于组织嵌套的图表。
  2. 随附的文件包含的信息可能对查找图表,了解它们和使用它们很有用。 该组中的大多数文件是可选的。

有关这两个组的文件的更多信息:

  • Chart.yaml包含有关图表信息的文件;
  • LICENSE -具有图表许可的可选文本文件;
  • README.md带有文档的可选文件;
  • requirements.yaml带有相关性图表列表的可选文件;
  • values.yaml具有模板默认值的文件;
  • charts/ -带有嵌套图表的可选目录;
  • templates/ --具有Kubernetes资源清单模板的目录;
  • templates/NOTES.txt带有注释的可选文本文件,在安装和更新过程中会显示给用户。

为了更好地理解这些文件的内容,您可以参考官方图表开发人员指南或在官方存储库中查找相关示例。

总体而言,创建图表归结为组织设计正确的文件集。 这种“设计”的主要困难是使用相当先进的模板系统来获得所需的结果。 为了渲染Kubernetes资源清单,使用了标准的Go模板引擎 ,并通过Helm函数进行了扩展

提醒 :Helm开发人员宣布,在该项目的下一个主要版本-Helm 3中,将支持Lua脚本,该脚本可与Go模板同时使用。 我不会在这一点上更详细地介绍-这(以及Helm 3中的其他更改)可以在此处阅读。

例如,以下是上一篇文章的Helm 2看起来像WordPress博客的Deployment的Kubernetes清单模板的样子:

deployment.yaml
 apiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ template "fullname" . }} labels: app: {{ template "fullname" . }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" spec: replicas: {{ .Values.replicaCount }} template: metadata: labels: app: {{ template "fullname" . }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" spec: {{- if .Values.image.pullSecrets }} imagePullSecrets: {{- range .Values.image.pullSecrets }} - name: {{ . }} {{- end}} {{- end }} containers: - name: {{ template "fullname" . }} image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy | quote }} env: - name: ALLOW_EMPTY_PASSWORD {{- if .Values.allowEmptyPassword }} value: "yes" {{- else }} value: "no" {{- end }} - name: MARIADB_HOST {{- if .Values.mariadb.enabled }} value: {{ template "mariadb.fullname" . }} {{- else }} value: {{ .Values.externalDatabase.host | quote }} {{- end }} - name: MARIADB_PORT_NUMBER {{- if .Values.mariadb.enabled }} value: "3306" {{- else }} value: {{ .Values.externalDatabase.port | quote }} {{- end }} - name: WORDPRESS_DATABASE_NAME {{- if .Values.mariadb.enabled }} value: {{ .Values.mariadb.db.name | quote }} {{- else }} value: {{ .Values.externalDatabase.database | quote }} {{- end }} - name: WORDPRESS_DATABASE_USER {{- if .Values.mariadb.enabled }} value: {{ .Values.mariadb.db.user | quote }} {{- else }} value: {{ .Values.externalDatabase.user | quote }} {{- end }} - name: WORDPRESS_DATABASE_PASSWORD valueFrom: secretKeyRef: {{- if .Values.mariadb.enabled }} name: {{ template "mariadb.fullname" . }} key: mariadb-password {{- else }} name: {{ printf "%s-%s" .Release.Name "externaldb" }} key: db-password {{- end }} - name: WORDPRESS_USERNAME value: {{ .Values.wordpressUsername | quote }} - name: WORDPRESS_PASSWORD valueFrom: secretKeyRef: name: {{ template "fullname" . }} key: wordpress-password - name: WORDPRESS_EMAIL value: {{ .Values.wordpressEmail | quote }} - name: WORDPRESS_FIRST_NAME value: {{ .Values.wordpressFirstName | quote }} - name: WORDPRESS_LAST_NAME value: {{ .Values.wordpressLastName | quote }} - name: WORDPRESS_BLOG_NAME value: {{ .Values.wordpressBlogName | quote }} - name: WORDPRESS_TABLE_PREFIX value: {{ .Values.wordpressTablePrefix | quote }} - name: SMTP_HOST value: {{ .Values.smtpHost | quote }} - name: SMTP_PORT value: {{ .Values.smtpPort | quote }} - name: SMTP_USER value: {{ .Values.smtpUser | quote }} - name: SMTP_PASSWORD valueFrom: secretKeyRef: name: {{ template "fullname" . }} key: smtp-password - name: SMTP_USERNAME value: {{ .Values.smtpUsername | quote }} - name: SMTP_PROTOCOL value: {{ .Values.smtpProtocol | quote }} ports: - name: http containerPort: 80 - name: https containerPort: 443 livenessProbe: httpGet: path: /wp-login.php {{- if not .Values.healthcheckHttps }} port: http {{- else }} port: https scheme: HTTPS {{- end }} {{ toYaml .Values.livenessProbe | indent 10 }} readinessProbe: httpGet: path: /wp-login.php {{- if not .Values.healthcheckHttps }} port: http {{- else }} port: https scheme: HTTPS {{- end }} {{ toYaml .Values.readinessProbe | indent 10 }} volumeMounts: - mountPath: /bitnami/apache name: wordpress-data subPath: apache - mountPath: /bitnami/wordpress name: wordpress-data subPath: wordpress - mountPath: /bitnami/php name: wordpress-data subPath: php resources: {{ toYaml .Values.resources | indent 10 }} volumes: - name: wordpress-data {{- if .Values.persistence.enabled }} persistentVolumeClaim: claimName: {{ .Values.persistence.existingClaim | default (include "fullname" .) }} {{- else }} emptyDir: {} {{ end }} {{- if .Values.nodeSelector }} nodeSelector: {{ toYaml .Values.nodeSelector | indent 8 }} {{- end -}} {{- with .Values.affinity }} affinity: {{ toYaml . | indent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{ toYaml . | indent 8 }} {{- end }} 

现在-关于Helm标准化的基本原理和功能。 下面的大多数示例均摘自官方资料库的图表。

模板化


模板: {{ }}


与模板相关的所有内容都用大括号括起来。 大括号外的文本在渲染过程中保持不变。

上下文值:


呈现文件或部分文件时(有关重用模板的更多信息,请参见本文的下一部分) ,该值可通过上下文变量在内部访问-抛出该点。 当作为参数传递给结构时,该点用于访问此结构的字段和方法。

在渲染过程中,变量的值会根据使用它的上下文而变化。 大多数块语句会覆盖主块内的上下文变量。 熟悉基本的Helm结构后,下面将讨论主要运算符及其功能。

头盔的基本结构


渲染清单时,将具有以下字段的结构放入模板中:

  • 字段.Values用于访问在安装和更新发行版期间确定的参数。 这些包括选项--set ,-- --set-string--set-file ,以及带有值的文件, values.yaml文件和与选项--values对应的文件的--values

     containers: - name: main image: "{{ .Values.image }}:{{ .Values.imageTag }}" imagePullPolicy: {{ .Values.imagePullPolicy }} 
  • .Release使用有关发布 ,安装或更新的发行版数据 ,发行版名称,名称空间以及在生成清单时可能有用的其他几个字段的值:

     metadata: labels: heritage: "{{ .Release.Service }}" release: "{{ .Release.Name }}" subjects: - namespace: {{ .Release.Namespace }} 
  • .Chart访问图表信息 。 这些字段对应于Chart.yaml文件的内容:

     labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 
  • 结构.Files用于处理存储在图表目录的文件 ; 其结构和可用方法可以在这里找到。 范例:

     data: openssl.conf: | {{ .Files.Get "config/openssl.conf" | indent 4 }} 

     data: {{ (.Files.Glob "files/docker-entrypoint-initdb.d/*").AsConfig | indent 2 }} 
  • .Capabilities -访问有关在其中执行.Capabilities 的群集的信息

     {{- if .Capabilities.APIVersions.Has "apps/v1beta2" }} apiVersion: apps/v1beta2 {{- else }} apiVersion: extensions/v1beta1 {{- end }} 

     {{- if semverCompare "^1.9-0" .Capabilities.KubeVersion.GitVersion }} apiVersion: apps/v1 {{- else }} 

经营者


当然,我们从ifelse ifelse

 {{- if .Values.agent.image.tag }} image: "{{ .Values.agent.image.repository }}:{{ .Values.agent.image.tag }}" {{- else }} image: "{{ .Values.agent.image.repository }}:v{{ .Chart.AppVersion }}" {{- end }} 

range运算符旨在与数组和映射一起使用。 如果将数组作为参数传递并包含元素,则对于每个元素,将依次执行一个块(在这种情况下,可通过上下文变量使用该块内的值):

 {{- range .Values.ports }} - name: {{ .name }} port: {{ .containerPort }} targetPort: {{ .containerPort}} {{- else }} ... {{- end}} 

 {{ range .Values.tolerations -}} - {{ toYaml . | indent 8 | trim }} {{ end }} 

要使用地图,提供了带有变量的语法:

 {{- range $key, $value := .Values.credentials.secretContents }} {{ $key }}: {{ $value | b64enc | quote }} {{- end }} 

with :运算符的行为类似:如果传递的参数存在,则执行该块,并且该块中的上下文变量对应于该参数的值。 例如:

 {{- with .config }} config: {{- with .region }} region: {{ . }} {{- end }} {{- with .s3ForcePathStyle }} s3ForcePathStyle: {{ . }} {{- end }} {{- with .s3Url }} s3Url: {{ . }} {{- end }} {{- with .kmsKeyId }} kmsKeyId: {{ . }} {{- end }} {{- end }} 

要重新使用模板,可以使用来自define [name]template [name] [variable]的捆绑包,其中通过define块中的context变量使传递的值可用:

 apiVersion: v1 kind: ServiceAccount metadata: name: {{ template "kiam.serviceAccountName.agent" . }} ... {{- define "kiam.serviceAccountName.agent" -}} {{- if .Values.serviceAccounts.agent.create -}} {{ default (include "kiam.agent.fullname" .) .Values.serviceAccounts.agent.name }} {{- else -}} {{ default "default" .Values.serviceAccounts.agent.name }} {{- end -}} {{- end -}} 

使用define或更简单地说,partial'ov时要考虑的几个功能:

  • 声明的partial'y是全局的,可以在templates目录的所有文件中使用。
  • 主图表与相关图表一起进行编译,因此,如果有两个相同类型的部分部分名称,则将使用最后一个加载的部分名称。 命名局部图时,通常会添加图表名称以避免此类冲突: define "chart_name.partial_name"

变量: $


除了使用上下文,您还可以使用变量来存储,修改和重用数据:

 {{ $provider := .Values.configuration.backupStorageProvider.name }} ... {{ if eq $provider "azure" }} envFrom: - secretRef: name: {{ template "ark.secretName" . }} {{ end }} 

呈现文件或部分文件时, $与点的含义相同。 但是与上下文变量(点)不同, $的值在block语句的上下文中不会改变 ,这使您可以同时使用block语句的上下文值和基本的Helm结构(或传递给partial的值,如果我们谈论在partial'a中使用$话) 。 差异图:

 context: {{ . }} dollar: {{ $ }} with: {{- with .Chart }} context: {{ . }} dollar: {{ $ }} {{- end }} template: {{- template "flant" .Chart -}} {{ define "flant" }} context: {{ . }} dollar: {{ $ }} with: {{- with .Name }} context: {{ . }} dollar: {{ $ }} {{- end }} {{- end -}} 

作为处理此模板的结果,将显示以下内容(为清楚起见,在结构的输出中将其替换为相应的伪名称):

 context: #  helm dollar: #  helm with: context: #.Chart dollar: #  helm template: context: #.Chart dollar: #.Chart with: context: habr dollar: #.Chart 

这是使用此功能的真实示例:

 {{- if .Values.ingress.enabled -}} {{- range .Values.ingress.hosts }} apiVersion: extensions/v1beta1 kind: Ingress metadata: name: {{ template "nats.fullname" $ }}-monitoring labels: app: "{{ template "nats.name" $ }}" chart: "{{ template "nats.chart" $ }}" release: {{ $.Release.Name | quote }} heritage: {{ $.Release.Service | quote }} annotations: {{- if .tls }} ingress.kubernetes.io/secure-backends: "true" {{- end }} {{- range $key, $value := .annotations }} {{ $key }}: {{ $value | quote }} {{- end }} spec: rules: - host: {{ .name }} http: paths: - path: {{ default "/" .path }} backend: serviceName: {{ template "nats.fullname" $ }}-monitoring servicePort: monitoring {{- if .tls }} tls: - hosts: - {{ .name }} secretName: {{ .tlsSecret }} {{- end }} --- {{- end }} {{- end }} 

压痕


开发模板时,可能会保留额外的边距:空格,制表符,换行符。 有了它们,文件看起来更具可读性。 您可以放弃它们,也可以使用特殊语法删除使用的模式周围的缩进:

  • {{- variable }}截断前面的空格;
  • {{ variable -}}截断后续空格;
  • {{- variable -}}都是两个选项。

一个文件示例,其处理将为habr flant helm

 habr {{- " flant " -}} helm 

内建功能


模板中内置的所有功能都可以在以下链接中找到。 在这里,我只会讲一些。

index函数旨在访问数组或映射的元素:

 definitions.json: | { "users": [ { "name": "{{ index .Values "rabbitmq-ha" "rabbitmqUsername" }}", "password": "{{ index .Values "rabbitmq-ha" "rabbitmqPassword" }}", "tags": "administrator" } ] } 

该函数接受任意数量的参数,这使您可以使用嵌套元素:

$map["key1"]["key2"]["key3"] => index $map "key1" "key2" "key3"

例如:

 httpGet: {{- if (index .Values "pushgateway" "extraArgs" "web.route-prefix") }} path: /{{ index .Values "pushgateway" "extraArgs" "web.route-prefix" }}/#/status {{- end }} 

布尔运算在模板引擎中作为函数( 而非运算符)实现。 通过时评估它们的所有参数:

 {{ if and (index .Values field) (eq (len .Values.field) 10) }} ... {{ end }} 

如果没有field field模板呈现将失败( error calling len: len of untyped nil ):检查第二个条件,尽管第一个条件尚未满足。 值得记录一下,并通过分成几部分检查来解决此类查询:

 {{ if index . field }} {{ if eq (len .field) 10 }} ... {{ end }} {{ end }} 

管道是Go模板的一项独特功能,它使您可以声明像shell中的管道一样执行的表达式。 形式上,管道是由符号|分隔的命令链。 。 命令可以是简单值或函数调用 。 每个命令的结果作为最后一个参数传递给下一个命令 ,管道中最终命令的结果是整个管道的值。 范例:

 data: openssl.conf: | {{ .Files.Get "config/openssl.conf" | indent 4 }} 

 data: db-password: {{ .Values.externalDatabase.password | b64enc | quote }} 

附加功能


Sprig是一个包含70个有用功能的库,用于解决各种任务。 出于安全原因,Helm排除了可访问Tiller环境变量的envexpandenv函数。

与标准template功能类似, include函数用于重用模板。 与template不同,该功能可以在管道中使用,即 将结果传递给另一个函数:

 metadata: labels: {{ include "labels.standard" . | indent 4 }} {{- define "labels.standard" -}} app: {{ include "hlf-couchdb.name" . }} heritage: {{ .Release.Service | quote }} release: {{ .Release.Name | quote }} chart: {{ include "hlf-couchdb.chart" . }} {{- end -}} 

required函数使开发人员有机会声明渲染模板所需的必需值:如果该值存在,则在渲染模板时使用它,否则渲染以开发人员指示的错误消息结束:

 sftp-user: {{ required "Please specify the SFTP user name at .Values.sftp.user" .Values.sftp.user | b64enc | quote }} sftp-password: {{ required "Please specify the SFTP user password at .Values.sftp.password" .Values.sftp.password | b64enc | quote }} {{- end }} {{- if .Values.svn.enabled }} svn-user: {{ required "Please specify the SVN user name at .Values.svn.user" .Values.svn.user | b64enc | quote }} svn-password: {{ required "Please specify the SVN user password at .Values.svn.password" .Values.svn.password | b64enc | quote }} {{- end }} {{- if .Values.webdav.enabled }} webdav-user: {{ required "Please specify the WebDAV user name at .Values.webdav.user" .Values.webdav.user | b64enc | quote }} webdav-password: {{ required "Please specify the WebDAV user password at .Values.webdav.password" .Values.webdav.password | b64enc | quote }} {{- end }} 

使用tpl函数可以将字符串呈现为模板。 与templateinclude不同,该函数允许您执行在变量中传递的模板以及不仅存储在templates目录中的渲染模板。 看起来像什么?

从变量运行模板:

 containers: {{- with .Values.keycloak.extraContainers }} {{ tpl . $ | indent 2 }} {{- end }} 

...,并在values.yaml具有以下值:

 keycloak: extraContainers: | - name: cloudsql-proxy image: gcr.io/cloudsql-docker/gce-proxy:1.11 command: - /cloud_sql_proxy args: - -instances={{ .Values.cloudsql.project }}:{{ .Values.cloudsql.region }}:{{ .Values.cloudsql.instance }}=tcp:5432 - -credential_file=/secrets/cloudsql/credentials.json volumeMounts: - name: cloudsql-creds mountPath: /secrets/cloudsql readOnly: true 

呈现存储在templates目录之外的文件:

 apiVersion: batch/v1 kind: Job metadata: name: {{ template "mysqldump.fullname" . }} labels: app: {{ template "mysqldump.name" . }} chart: {{ template "mysqldump.chart" . }} release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" spec: backoffLimit: 1 template: {{ $file := .Files.Get "files/job.tpl" }} {{ tpl $file . | indent 4 }} 

...在图表上,沿着路径files/job.tpl ,有以下模板:

 spec: containers: - name: xtrabackup image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy | quote }} command: ["/bin/bash", "/scripts/backup.sh"] envFrom: - configMapRef: name: "{{ template "mysqldump.fullname" . }}" - secretRef: name: "{{ template "mysqldump.fullname" . }}" volumeMounts: - name: backups mountPath: /backup - name: xtrabackup-script mountPath: /scripts restartPolicy: Never volumes: - name: backups {{- if .Values.persistentVolumeClaim }} persistentVolumeClaim: claimName: {{ .Values.persistentVolumeClaim }} {{- else -}} {{- if .Values.persistence.enabled }} persistentVolumeClaim: claimName: {{ template "mysqldump.fullname" . }} {{- else }} emptyDir: {} {{- end }} {{- end }} - name: xtrabackup-script configMap: name: {{ template "mysqldump.fullname" . }}-script 

Helm标准化基础入门到此结束...

结论


本文介绍了Helm图表的结构,并详细检查了其创建过程中的主要困难-模板:基本原理,语法,函数和Go-template运算符以及其他函数。

如何开始使用所有这些? 由于Helm已经是一个完整的生态系统,因此您始终可以查看类似软件包的图表示例。 例如,如果您要打包新的消息队列,请查看RabbitMQ公共图表 。 当然,没有人会向您承诺现有程序包中的理想实现,但是作为起点,它们是完美的。 其余内容随实践一起提供,其中helm templatehelm lint 调试命令将为您提供帮助,并使用--dry-run选项开始安装。

为了更广泛地了解Helm图表的开发,最佳实践和使用的技术,建议您通过以下链接(全部为英文)熟悉相关材料:


在下一份Helm材料的结尾处,我将附上一份调查问卷,这将有助于更好地理解Habr读者还在等待(或不等待?)的其他有关Helm的文章。 感谢您的关注!

聚苯乙烯


另请参阅我们的博客:

Source: https://habr.com/ru/post/zh-CN423239/


All Articles