目录

如何使用 helm

开始制作

搜索下载就行·版本使用 3.12.3

首先,通过

shell

helm create  helm_learn

来创建一个名字叫做 helm_learn 的项目目录,创建好后,你会看到一个名为 helm_learn 的文件夹,进入到该文件夹中,你会看到以下的内容

这个文件夹用来存放依赖的chart。如果你的chart依赖其他chart,可以将那些chart打包放在这里或者通过 Chart.yaml 文件定义依赖。 如果你的 chart 包,依赖于其他的 chart 包,那么你在使用

shell

helm dependency update
helm dependency build

在使用依赖成功后,会在 charts 文件夹中产生依赖的 chart 包文件

这个文件夹包含了所有 Kubernetes 资源的模板文件。这些模板文件定义了如何生成有效的 Kubernetes 配置文件,可以包括部署 (deployments)、服务 (services)、配置映射 (configmaps) 等。

这是一个模板文件,包含了可复用的模板片段,用于在多个模板文件中保持一致性。在其中可以定义比如 app.name app.label 等多个 pod 中相同的

定义了一个 Kubernetes 部署 (Deployment)。这通常是你的应用程序的主要组件,包括应用的容器和相关的规格。

定义了一个水平自动扩缩 (Horizontal Pod Autoscaler),用于根据 CPU 使用率或其他选择的指标自动扩缩你的应用。

定义了一个 Kubernetes Ingress,用于从外部网络路由流量到你的服务。

定义了一个 Kubernetes 服务 (Service),用于暴露你的应用程序或其中的某些部分到你的网络中。

定义了一个 Kubernetes 服务账号 (ServiceAccount),用于给 Pod 分配身份。

这个文件类似于 .gitignore,定义了 Helm 打包时应该忽略的文件和路径。

这是一个重要的文件,包含了 chart 的描述信息,如chart 的版本、名称和描述。

包含了chart中所有配置选项的默认值。这些值可以在安装或升级chart时被覆盖。

  • 定义模板函数,在 _helpers.tpl文件中定义

tpl

{{- define "helm_learn.fullname" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

这里定义了 app.name,以便其他文件可以使用 include 来使用这个值,保证 chart 包中这个值的统一

使用 defalut 函数,如果.Values.nameOverride不是空,就用.Values.nameOverride,如果是空就用.Chart.Name,

trunc 63 表示如果长度超过 63,就截断

trimSuffix “-” 确保最后的内容不会以 - 结尾

最终以 end 结束这个模板函数的定义

  • 使用模板函数

yaml

{{ include "helm_learn.fullname" . }}

使用 _helpers.tpl中定义的模板

  • 使用函数

有两种使用函数的方式

第一种,通过管道传递参数

yaml

.val | quote

第二种,直接使用函数

yaml

quote .val

以上两种方式都可以

  • quote 函数

yaml

.Values.hostPort | quote

这种使用方式代表将 values.yaml 文件中的 hostPort 配置,添加双引号

  • nindent 函数

yaml

{{- include "helm_learn.labels" . | nindent 4 }}

该函数表示在结果前边加 4 个空格,防止破坏 yaml 的格式

  • 获取 Values 中的内容

yaml

{{ .Values.images.web.repository }}

表示获取 values.yaml 文件下 images.web.repository 的值

同理还可以通过该种方式获取 chart 包下的内容

如下

yaml

{{ .Chart.Name }}
  • 以 yaml 模式嵌入 values 中的内容

yaml

{{- toYaml .Values.securityContext | nindent 12 }} 

如果你的 values 中有如下配置

yaml

securityContext:
  runAsUser: 1000
  runAsGroup: 3000
  fsGroup: 2000

则此处将这个配置以 yaml 的模式插入到deployment 文件中,并且前边都带有 12 个空格

  • 头部 - 以及尾部 -

yaml

{{- .Value.port -}}

表示去除表达式前边和后边的空白字符

  • 通过 with 引用 Value 中的内容

yaml

{{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
{{- end }}

with 是一个作用域改变语句。它将模板的上下文 (context) 临时改变为 .Values.nodeSelector

这意味着在这个 with 块内部,你可以直接使用 . 来引用 .Values.nodeSelector 对象。

如果 .Values.nodeSelector 不存在或为空,整个 with 块内的内容将被忽略,也就是说不会渲染 nodeSelector

这里以 redis 为例

打开 templates 文件夹下边的 deployment 文件,可见如下内容

deployment 中的大致部分分为 pod 级别和 容器级别

在 spec.template.spec 下边的都属于pod 级别,在spec.template.spec.containers 下边的都属于容器级别,容器之间彼此可以通过 containerPort 和 172.17.0.1 来进行访问,对 pod 之间是彼此隔离的

这里不清楚如何选择是否要有这些值的话,可以到 如何选择你需要的值 章节下,自行翻阅

yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "helm_learn.fullname" . }} # 名称
  labels:
    {{- include "helm_learn.labels" . | nindent 4 }} # pod 的标签
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }} # 实例的数量
  {{- end }}
  selector:
    matchLabels:
      {{- include "helm_learn.selectorLabels" . | nindent 6 }} # 控制的label,这里自己定义,要和你的目标 lable 相同
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }} # 自定义标识,可以忽略
      {{- end }}
      labels:
        {{- include "helm_learn.selectorLabels" . | nindent 8 }} # label,这里的 label 要和上边的 matchLabels 的 label 相同
    spec:
      spec:
        dnsPolicy: "None"  # 可以设置为 "ClusterFirstWithHostNet" 或 "Default"
        dnsConfig:  # 定义 DNS 配置
         nameservers:
           - "1.1.1.1"  # 示例 DNS 服务器地址
           - "8.8.8.8"
         searches:
           - "example.com"
           - "example.org"
         options:
           - name: "ndots"
             value: "2"
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }} # 拉取镜像的密钥,如果是从 pkg 或者 pkg-ihw 拉取则可以忽略
      {{- end }}
      serviceAccountName: {{ include "helm_learn.serviceAccountName" . }} # 从 helepers.tql中获取,这里是否要有 serviceAccountName 取决于你的应用是否需要与 k8s api 交互
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }} # 这里取决于你对容器在运行的时候,是否需要规划特定的执行用户,或者文件系统是否可读等问题,没有就不用管
      containers:
        - name: {{ .Chart.Name }} # 这个容器的名称,在一个 pod 中可以有多个容器存在,他们会被一起调用,如果要有多个容器,则增加 - name 即可
          securityContext: # 同上,此处的定义只会影响到当前的容器,不会影响到整个 pod
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" #镜像名称
          imagePullPolicy: {{ .Values.image.pullPolicy }} # 拉取策略 有 IfNotPresent (有就不拉) 和 Always(每次都拉)
          ports:
            - name: http # 给端口一个名字,在多端口的时候很有用,
              containerPort: {{ .Values.service.port }} # 容器自己的端口,比如 redis 就是 6379
              protocol: TCP # 端口要使用的协议
              hostPort: {{ .Values.service.hostPort }}  # 宿主机的端口,此选项会将宿主机的端口映射到配置的 containerPort,如果不通过该种方式暴露,则只能通过 service 的方式暴露
          livenessProbe: # 存活检查,如果这个检查不可用,k8s 会将容器重启
            httpGet:
              path: / # 请求路径
              port: http # 请求端口,即前边设置的 port
          readinessProbe: # 可用性检查,如果这个检查不可用,k8s 会将次容器视为不可用状态.
            httpGet:
              path: / 
              port: http
          resources: # 该容器占用的资源,limit 代表 超过 k8s 会将其重启,request 代表 k8s 至少有这么多的资源才会启动
            {{- toYaml .Values.resources | nindent 12 }}
            
            
      # 以下配置为pod 调度的选项,在 pod 运行时,会根据以下选项来进行调度
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

以上 deployment 文件中涉及到了许多的helm 的模板语法,如果有更多要查阅的内容可以去这里查阅

在这个文件中,会定义一些模板,这些模板可以在整个 chart 包中使用,以保证该值全局统一,比如 应用名称,应用标签等.

使用 helm 的 create 命令创建的文件如下,可以不用做改动

在默认的 _helpers.tpl 文件中,定义了如下内容

yaml

helm_learn.name
helm_learn.fullname
helm_learn.chart
helm_learn.labels
helm_learn.selectorLabels
helm_learn.serviceAccountName

这些值的定义用到了 Values 中的一些属性,因此要确保 values 中有对应的属性存在

在其他文件中可以直接通过一下方式使用 _helpers.tpl 文件定义的值

yaml

{{ include "helm_learn.fullname" . }}

以下为文件

yaml


{{- define "helm_learn.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "helm_learn.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "helm_learn.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "helm_learn.labels" -}}
helm.sh/chart: {{ include "helm_learn.chart" . }}
{{ include "helm_learn.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "helm_learn.selectorLabels" -}}
app.kubernetes.io/name: {{ include "helm_learn.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "helm_learn.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "helm_learn.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

在 configmap 中,可以有一些基础的配置文件,以及环境变量,或者其他任意的配置文件

在此处使用 redis 的默认配置并且将其挂载到 redis 的配置文件地址

以下是文件

yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ template "redis.fullname" . }} # 定义了configmap 的 name,等下会在 deployment 文件中根据这个名称进行匹配
  labels:
    app: {{ template "redis.fullname" . }}
    chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
    release: "{{ .Release.Name }}"
    heritage: "{{ .Release.Service }}"
data:
  default.conf: |-
    bind 0.0.0.0
    port 6379
    tcp-backlog 511
    timeout 0
  user.conf: |-  # | 代表了以下的配置将会包含完整的格式,包括回车,换行符等,- 代表了删去结尾的换行符
  {{- if .Values.config }} # 如果你的 values 文件中有 config 配置项,那么这里就将其放入
{{ tpl .Values.config . | indent 4 }} # tpl 函数代表,将会以模板函数的方式进行解析,也就是你的配置项中可以包含 helm 自己的模板语法
  {{- end }}

这个文件中用到了 values 文件中的配置,如果你的 values 中有以下配置

yaml

config: |
  name: {{ .Release.Name }}
  environment: {{ .Values.environment }}

那么该项配置将会被加载到 user.conf 中去,并且由于使用了 tpl 函数,在这个配置项中的语法也会被解析

定义好 configmap 后,我们可以通过 volumes 和 volumesMounts 使用这个配置

在 deployment 文件中,spec.template.spec下边

yaml

spec:
  template:
    spec:
      volumes: # 这个配置项必须定义到 pod 级别,而不是容器级别,它代表了,有哪些外部文件可以被容器挂载,通过 name 进行使用
        - name: data
          hostPath:
            type: DirectoryOrCreate
            path: /wls/appsystems/{{ .Chart.Name }}/{{ .Release.Namespace }}/data
        - name: configs # 声明了这个挂载的名称,这里使用了 data 中的 default.conf 文件
          configMap:
            name: {{ template "redis.fullname" . }}
        - name: configs-all # 这里没有指定使用configmap 下边的的那个文件,因此是 data 下边的所有
          configMap:
          	name: {{ template "redis.fullname" . }}
      containers:
        - name: {{ .Chart.Name }}
          volumeMounts:  # 这个定义需要在 容器级别,他可以使用 volumes 中定义的外部文件,将其挂载到容器内部中
            - name: configs-all # 使用了名为 configs-all 的挂载文件,也就是我们定义的 configmap,如果没有明确声明,他将会被放入到mountPath 中设置的值
              mountPath: /tmp/conf # 目标挂载点,在容器中
            - name: configs # 要使用的 configmap 名称
              mountPath: /data/redis/default.conf #要替换的目标
              subPath: default.conf # 具体使用的那个值,这里使用了 default.conf

通过以上配置,我们就可以在外部定义好 configmap,然后在 deployment 中,将其挂载到容器中使用

在涉及到数据库密钥等内容时,通过明文在 chart 包中保存就非常不安全了,此时就需要 secret 的方式来进行使用,保证密钥安全

使用 secret 的方式与configmap 类似

通过 helm create 命令创建的目录下边没有 secret 文件,因此需要自己创建文件,在 templates 目录下边创建 secrets.yaml

内容如下,其中 REDIS_PASSWORD 的值使用 1234567890 做 base64 编码后获得

yaml

# templates/secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: edge-secret
type: Opaque
data:
  REDIS_PASSWORD: MTIzNDU2Nzg5MA==

在 deployment 文件中要使用的地方,例如容器的环境变量中.

yaml

env:
  - name: REDIS_PASSWORD
    valueFrom:
      secretKeyRef:
        name: edge-secret # secret 文件的 metadata 中的 name
        key: REDIS_PASSWORD # 代表使用 data 下边的 REDIS_PASSWORD 

关于为什么使用 base64 就可以保证安全,可以查阅 这里

servuceAccount 主要应用于程序需要和k8s接口做交互的场景

示例的 serviceaccount 文件如下

yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "app.serviceAccountName" . }}

然后在 deployment 文件中,spec.template.spec.serviceAccountName处使用即可

yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "chart.fullname" . }}
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    metadata:
      labels:
        {{- include "chart.labels" . | nindent 8 }}
    spec:
      serviceAccountName: {{ include "chart.serviceAccountName" . }} # 该处使用配置的 serviceaccount 名称
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: {{ .Values.service.port }}

这里创建的就是 k8s 所自带的服务,样例配置如下

yaml

apiVersion: v1
kind: Service
metadata:
  name: {{ include "chart.fullname" . }}
  labels:
    {{- include "chart.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}
      protocol: TCP
  selector:
    {{- include "chart.selectorLabels" . | nindent 4 }}

然后再 values 文件中配置对应的参数

yaml

service:
  type: ClusterIP
  port: 80
  targetPort: 9376

上述的配置文件创建了 ClustetIP 类型的 service,他将k8s 集群内部的 80 端口流量传递给了这个 pod 的 9376 端口,其他 pod 可以通过 172.17.0.1:80 来访问到这个 pod 的 9376 端口

通过以上方法就可以创建你所需要的服务,k8s 提供的服务类型如下

  • ClusterIP(默认):提供一个内部的集群 IP 地址供集群内部通讯使用,外部无法直接访问。
  • NodePort:除了内部 IP 外,Service 也会在所有节点的一个静态端口上对外提供服务。外部可以通过 <NodeIP>:<NodePort> 访问 Service。
  • LoadBalancer:在支持负载均衡器的云平台上,Service 会获取一个外部 IP 地址,通过云提供商的负载均衡器对外提供服务。
  • ExternalName:通过返回一个名字(不是通过 IP 地址),该类型允许 Service 指向外部 DNS 名称,而不是内部的 Pod。

挂载容器的路径到宿主机主要用到了 Volumes和volumeMounts,和 docker 的路径映射类似

其中 volumes 只可以存在于 pod 范围,不能到容器范围,Volumes 指明了该pod 会使用到那些资源,包括宿主机路径、configmap、secret 等

volumes 的类型如下,一般常用 hostPath 、configMap、secret

  • emptyDir: 一个临时目录,仅在 Pod 生命周期内持续存在。
  • hostPath: 将节点(Node)的文件系统挂载到 Pod 中,用于特定情况,如运行需要访问特定系统文件的 DaemonSet。
  • persistentVolumeClaim (PVC): 引用一个持久卷声明,允许用户使用预先或动态创建的持久存储。
  • configMapsecret: 用于存放配置数据或敏感数据,容器可以通过 volume 挂载来访问这些数据。

以下是样例配置

yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: example
    spec:
      containers:
      - name: example-container
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts: # 在容器层面 使用在 pod 层面定义的卷,将其挂载到容器内部的路径中
        - name: temp-storage # 要使用的挂载卷名称
          mountPath: /tmp # 要挂载到的容器路径
        - name: node-logs
          mountPath: /host/var/log
        - name: persistent-storage
          mountPath: /data
        - name: config
          mountPath: /etc/config
        - name: secret-volume
          mountPath: /etc/secret
        - name: nfs-volume
          mountPath: /mnt/nfs
      volumes:
      - name: temp-storage
        emptyDir: {}
      - name: node-logs
        hostPath:
          path: /var/log
          type: Directory
      - name: config
        configMap:
          name: app-config # 这里的名称就是 configmap 的名称
      - name: secret-volume
        secret:
          secretName: my-secret # 这里的名称就是 secret 的名称

通过以上方式就可以将容器内部的路径挂载到外部

其中 hostpath 的 type 选项有

  1. "" (空字符串):默认值,不对挂载的路径进行任何检查。
  2. Directory:此路径必须是一个已存在的目录。
  3. DirectoryOrCreate:如果路径不存在,Kubernetes 将创建一个目录。
  4. File:此路径必须是一个已存在的文件。
  5. FileOrCreate:如果路径上的文件不存在,Kubernetes 将创建一个空文件。
  6. Socket:此路径必须是一个 UNIX 套接字。
  7. CharDevice:此路径必须是一个字符设备。
  8. BlockDevice:此路径必须是一个块设备

在编写完成 helm 的配置文件后,就进入到了打包的流程

首先通过

shell

helm template helm_learn --debug

来检查你的配置文件编写的是否有问题

这个命令会在控制台输出完整的 deployment 文件,如果有一些 helm 的语法错误,他会为你指出

再检查完毕后,通过

shell

helm package helm_learn

该命令会生成一份 tar 文件,这个就是打包的 helm 包,在接下来你可以通过

shell

helm install helm_learn-****.tar.gz

注意事项

  • 在 deployment 文件中 {{ .Values.service.port }}这类的语法不允许有 - ,会导致 helm template 错误,因此在values 文件中定义变量的时候不要带有- ,防止在后续的使用中带来不便.