Kubernetes开发指南 3 - Helm Chart 完全 指导 手册

425 阅读11分钟

日常工作大部分涉及创建、修改和部署 Helm 图表来管理应用程序的部署。Helm 是 Kubernetes 的应用程序包管理器,负责协调应用程序的下载、安装和部署。Helm 图表是我们将应用程序定义为相关 Kubernetes 资源集合的方式。

那么为什么有人会使用 Helm 呢?Helm 通过模板化方法使管理 Kubernetes 内的应用程序部署变得更加容易。所有 Helm 图表都遵循相同的结构,同时仍然具有足够灵活的结构来表示可以在 Kubernetes 上运行的任何类型的应用程序。Helm 还支持版本控制,因为部署需求肯定会随着时间而变化。另一种方法是使用手动应用于 Kubernetes 集群的多个配置文件来启动应用程序。手动流程不可避免地会导致错误。Helm 图表让我们有机会将同样的经验应用到 Kubernetes 世界。

在此示例中,我们将逐步使用 Helm 和 minikube(Kubernetes 的单节点测试环境)。我们将制作一个小型 Nginx Web 服务器应用程序。对于此示例,我在 Linux 笔记本电脑上安装了 minikube 版本 1.9.2 和 Helm 版本 3.0.0。要进行设置,请执行以下操作。

image.png

创建 Helm 图表

首先确认我们已经安装了helm和minikube:

$ which helm ## this can be in any folder as long as it returns in the path
/usr/local/bin/helm 
$ minikube status ## if it shows Stopped, run `minikube start`
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

新建 Helm 图表只需要一个简单的命令:

$ helm create mychartname

出于本教程的目的,将图表命名为buildachart

$ helm create buildachart
Creating buildachart
$ ls buildachart/
Chart.yaml   charts/      templates/   values.yaml

检查图表的结构

现在您已经创建了图表,请查看其结构以了解内部内容。您看到的前两个文件——Chart.yamlvalues.yaml定义了图表是什么以及部署时图表中包含哪些值。

查看 Chart.yaml,您可以看到 Helm 图表的结构轮廓:

apiVersion: v2
name: buildachart
description: A Helm chart for Kubernetes

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
version: 0.1.0

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application.
appVersion: 1.16.0

第一部分包括图表正在使用的 API 版本(这是必需的)、图表的名称以及图表的描述。下一部分描述图表的类型(默认情况下为应用程序)、您将部署的图表版本以及应用程序版本(应在您进行更改时递增)。

图表中最重要的部分是模板目录。它保存将部署到集群中的应用程序的所有配置。如下所示,该应用程序具有基本的部署、入口、服务帐户和服务。此目录还包括一个测试目录,其中包括对应用程序连接的测试。每个应用程序功能在**templates/**下都有自己的模板文件:

$ ls templates/
NOTES.txt            _helpers.tpl         deployment.yaml      ingress.yaml         service.yaml         serviceaccount.yaml  tests/

还有另一个名为Charts 的目录,它是空的。它允许您添加部署应用程序所需的依赖图表。某些应用程序的 Helm 图表最多有四个额外图表,需要与主应用程序一起部署。发生这种情况时,文件将使用每个图表的值进行更新,以便同时配置和部署应用程序。这是一个更高级的配置(我不会在这篇介绍性文章中介绍),因此请将图表文件夹留空。

理解和编辑values.yaml

模板文件设置为从values.yaml文件收集部署信息的格式。因此,要自定义 Helm 图表,您需要编辑值文件。默认情况下,values.yaml文件如下所示:

# Default values for buildachart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

image:
  repository: nginx
  pullPolicy: IfNotPresent

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  # Specifies whether a service account should be created
  create: true
  # Annotations to add to the service account
  annotations: {}
  # The name of the service account to use.
  # If not set and create is true, a name is generated using the fullname template
  name:

podSecurityContext: {}
  # fsGroup: 2000

securityContext: {}
  # capabilities:
  #   drop:
  #   - ALL
  # readOnlyRootFilesystem: true
  # runAsNonRoot: true
  # runAsUser: 1000

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: chart-example.local
      paths: []
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
  # limits:
  #   cpu: 100m
  #   memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi

nodeSelector: {}

tolerations: []

affinity: {}

基本配置

从顶部开始,您可以看到replicaCount自动设置为1,这意味着只会出现一个pod。在本示例中,您只需要一个 pod,但您可以看到告诉 Kubernetes 运行多个 pod 来实现冗余是多么容易。

镜像部分有两件事您需要查看:您拉取镜像的存储库和pullPolicy。pullPolicy 设置为IfNotPresent;这意味着如果集群中尚不存在该映像,该映像将下载该映像的新版本。还有其他两个选项:Always,这意味着它将在每次部署或重新启动时拉取映像(我总是建议在映像失败的情况下这样做),以及Latest,这将始终拉取最新版本的图像可用。如果您相信您的映像存储库与您的部署环境兼容,那么最新版本可能会很有用,但情况并非总是如此。

将值更改为Always

修改前:

image:
  repository: nginx
  pullPolicy: IfNotPresent

修改后:

image:
  repository: nginx
  pullPolicy: Always

Naming & Secrets

接下来,看看图表中的覆盖。第一个覆盖是imagePullSecrets,它是提取Secrets的设置,例如您为私有注册表生成的密码或 API 密钥。接下来是nameOverridefullnameOverride。从运行helm create 的那一刻起,它的名称 (buildachart) 就被添加到许多配置文件中 - 从上面的 YAML 到 templates /helper.tpl文件。如果您需要在创建图表后重命名图表,此部分是执行此操作的最佳位置,这样您就不会错过任何配置文件。

使用覆盖更改图表的名称。

修改前:

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

修改后:

imagePullSecrets: []
nameOverride: "cherry-awesome-app"
fullnameOverride: "cherry-chart"

Accounts

服务帐户提供在集群内的 Pod 中运行的用户身份。如果留空,则将使用helpers.tpl文件根据全名生成名称。我建议始终设置一个服务帐户,以便应用程序直接与图表中控制的用户关联。

作为管理员,如果您使用默认服务帐户,您的权限要么太少,要么太多,因此请更改此设置。

修改前:

serviceAccount:
  # Specifies whether a service account should be created
  create: true
  # Annotations to add to the service account
  annotations: {}
  # The name of the service account to use.
  # If not set and create is true, a name is generated using the fullname template
  Name:

修改后:

serviceAccount:
  # Specifies whether a service account should be created
  create: true
  # Annotations to add to the service account
  annotations: {}
  # The name of the service account to use.
  # If not set and create is true, a name is generated using the fullname template
  Name: cherrybomb

Security

您可以配置 pod 安全性来设置要使用的文件系统组类型或可以使用和不能使用的用户的限制。了解这些选项对于保护 Kubernetes Pod 的安全非常重要,但对于本示例,我将不考虑这些。

podSecurityContext: {}
  # fsGroup: 2000

securityContext: {}
  # capabilities:
  #   drop:
  #   - ALL
  # readOnlyRootFilesystem: true
  # runAsNonRoot: true
  # runAsUser: 1000

Netoworking

此图表中有两种不同类型的网络选项。一种使用具有ClusterIP地址的本地服务网络,该网络在集群内部 IP 上公开服务。选择此值使得与您的应用程序关联的服务只能从集群内部访问(并且通过ingress,默认情况下设置为false)。另一个网络选项是NodePort,它在静态分配的端口上公开每个 Kubernetes 节点的 IP 地址上的服务。建议使用此选项来运行minikube,因此请在本操作指南中使用它。

修改前:

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false

修改后:

service:
  type: NodePort
  port: 80

ingress:
  enabled: false

Resources

Helm 允许您显式分配硬件资源。您可以配置 Helm 图表可以请求的最大资源量以及它可以接收的最高限制。由于我在笔记本电脑上使用 Minikube,因此我将通过删除花括号和散列来设置一些限制,以将注释转换为命令。

修改前:

resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
  # limits:
  #   cpu: 100m
  #   memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi

修改后:

resources:
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
   limits:
     cpu: 100m
     memory: 128Mi
   requests:
     cpu: 100m
     memory: 128Mi

Tolerations、Node Selectors和affinities

最后三个值基于节点配置。尽管我无法在本地配置中使用它们中的任何一个,但我仍然会解释它们的用途。

当您想要将应用程序的一部分分配给 Kubernetes 集群中的特定节点时,nodeSelector会派上用场。如果您有特定于基础设施的应用程序,则可以设置节点选择器名称并在 Helm 图表中匹配该名称。然后,当部署应用程序时,它将与与选择器匹配的节点关联。

TolerationstaintingAffinities一起工作以确保 Pod 在不同的节点上运行。节点亲和性是_pod_的一个属性,它将它们创建到一组节点(作为偏好或硬性要求)。污点则相反——它们允许一个节点排斥一组 pod

在实践中,如果一个节点被污染,则意味着它无法正常工作或者可能没有足够的资源来容纳应用程序部署。容忍设置为由调度程序监视的键/值对,以确认节点将与部署配合使用。

节点亲和性在概念上类似于nodeSelector:它允许您根据节点上的标签来限制您的 pod 可以被调度的节点。但是,标签有所不同,因为它们与应用于调度的规则相匹配。

nodeSelector: {}

tolerations: []

affinity: {}

动手试一试,部署已准备好的chart

现在您已经进行了必要的修改来创建 Helm 图表,您可以使用 Helm 命令部署它,向图表添加名称点,添加值文件,并将其发送到命名空间:

$ helm install my-cherry-chart buildachart/ --values buildachart/values.yaml 
Release “my-cherry-chart” has been upgraded. Happy Helming!

该命令的输出将为您提供连接到应用程序的后续步骤,包括设置端口转发,以便您可以从本地主机访问应用程序。要按照这些说明连接到 Nginx 负载均衡器:

$ export POD_NAME=$(kubectl get pods -l "app.kubernetes.io/name=buildachart,app.kubernetes.io/instance=my-cherry-chart" -o jsonpath="{.items[0].metadata.name}")
$ echo "Visit http://127.0.0.1:8080 to use your application"
Visit http://127.0.0.1:8080 to use your application
$ kubectl port-forward $POD_NAME 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

查看已部署的应用程序

要查看您的应用程序,请打开网络浏览器:

Nginx 欢迎屏幕

恭喜!您已经使用 Helm 图表部署了 Nginx Web 服务器!

上面通过一步步创建一个Helm Chart并部署,体验了Helm Chart的完整流程,但是如果在产品中使用Helm Chart,还需要更多的内容,如详细的测试,打包与部署,接下来详细介绍这部分内容.

测试 helm chart

您可以使用 CLI 轻松测试新创建的模板helm。为此,只需在存储库根目录中执行以下命令即可。因此,您将看到根据我们的示例模板创建的 YAML 清单。

$ helm template buildachart

---
# Source: buildachart/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: cherry-chart
  labels:
    helm.sh/chart: buildachart-0.1.0
    app.kubernetes.io/name: cherry-awesome-app
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
---
# Source: buildachart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: cherry-chart
  labels:
    helm.sh/chart: buildachart-0.1.0
    app.kubernetes.io/name: cherry-awesome-app
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: cherry-awesome-app
    app.kubernetes.io/instance: release-name
---
# Source: buildachart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cherry-chart
  labels:
    helm.sh/chart: buildachart-0.1.0
    app.kubernetes.io/name: cherry-awesome-app
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: cherry-awesome-app
      app.kubernetes.io/instance: release-name
  template:
    metadata:
      labels:
        app.kubernetes.io/name: cherry-awesome-app
        app.kubernetes.io/instance: release-name
    spec:
      serviceAccountName: cherry-chart
      securityContext:
        {}
      containers:
        - name: buildachart
          securityContext:
            {}
          image: "nginx:1.16.0"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {}
---
# Source: buildachart/templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "cherry-chart-test-connection"
  labels:
    helm.sh/chart: buildachart-0.1.0
    app.kubernetes.io/name: cherry-awesome-app
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['cherry-chart:80']
  restartPolicy: Never

这样的测试方法很好……但只是在图表开发期间在本地运行它。单个 Helm 图表可以由多个应用程序使用,因此我们应该尽一切努力仔细测试它。幸运的是,有一些专用于 Helm 图表测试的工具。我的选择落在了helm-unittest上。它允许我们用纯 YAML 编写单元测试文件。我们可以将其作为 Helm 插件安装或在 Docker 容器内运行。让我们在本地验证我们的测试工作,然后再将其推送到 Git 存储库:

$ helm plugin install https://github.com/helm-unittest/helm-unittest

我们应该将单元测试放在test图表的目录中。这是我们的图表存储库的结构:

image.png

第一步,我们创建单元测试文件。如前所述,我们可以使用 YAML 表示法创建测试。这是非常直观的。我们需要传递值文件的位置 (1) 和测试的 Helm 模板的位置 (2) 。在测试部分,我们必须定义一个断言列表 (3) 。我不会详细介绍该helm-unittest工具 - 有关更多信息,请参阅其文档。重要的是我可以轻松测试 YAML 清单的每个路径。它可以是精确比较或正则表达式。它还支持用于映射和数组的 JsonPath。这是我们的测试deployment_test.yaml

suite: test deployment
values:
  - ./values/test.yaml                  ------------------ (1)
templates:
  - templates/deployment.yaml           ------------------ (2)
chart:
  version: 0.3.5+test
  appVersion: 1.0.0
tests:
  - it: should pass all kinds of assertion
    template: templates/deployment.yaml
    documentIndex: 0
    asserts:                            ------------------ (3)
      - equal:
          path: spec.replicas
          value: 1
      - equal:
          path: spec.template.spec.containers[?(@.name == "buildachart")].image
          value: helm-demo:1.1

现在,我们可以通过从项目根目录执行以下命令来在本地验证测试:

$ helm unittest buildachart/

 PASS  test deployment  buildachart/tests/deployment_test.yaml

Charts:      1 passed, 1 total
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshot:    0 passed, 0 total
Time:        9.561393ms

目前,我们的目录中只有一个图表charts。假设我们有更多,它会运行所有图表的测试。

如果Chart中有错误,将显示类似下面的结果:

$ helm unittest buildachart/

### Chart [ buildachart ] buildachart/

 FAIL  test deployment  buildachart/tests/deployment_test.yaml
        - should pass all kinds of assertion

                - asserts[1] `equal` fail
                        Template:       buildachart/templates/deployment.yaml
                        DocumentIndex:  0
                        Path:   spec.template.spec.containers[?(@.name == "buildachart")].image
                        Expected to equal:
                                helm-demo2:1.1
                        Actual:
                                helm-demo:1.1
                        Diff:
                                --- Expected
                                +++ Actual
                                @@ -1,2 +1,2 @@
                                -helm-demo2:1.1
                                +helm-demo:1.1


Charts:      1 failed, 0 passed, 1 total
Test Suites: 1 failed, 0 passed, 1 total
Tests:       1 failed, 0 passed, 1 total
Snapshot:    0 passed, 0 total
Time:        8.242399ms

Package与Depoyment

到目前为止,在本教程中,我们一直在使用helm install命令来安装本地未打包的图表。但是,如果您希望与您的团队或社区共享图表,您的消费者通常会从 tar 包安装图表。我们可以用来helm package创建 tar 包:

helm package buildachart
Successfully packaged chart and saved it to: buildachart-0.1.0.tgz

buildachart-0.1.0.tgz Helm 将使用文件中定义的元数据中的名称和版本在我们的工作目录中创建一个包。用户可以通过将该包作为参数传递给此包而不是本地目录进行安装helm install

$ helm install buildachart-0.1.0.tgz --generate-name


NAME: buildachart-0-1697011990
LAST DEPLOYED: Wed Oct 11 04:13:10 2023
NAMESPACE: webhookdemo
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace webhookdemo -l "app.kubernetes.io/name=cherry-awesome-app,app.kubernetes.io/instance=buildachart-0-1697011990" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace webhookdemo $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace webhookdemo port-forward $POD_NAME 8080:$CONTAINER_PORT