Kubernetes 包管理工具 Helm 初体验

Published: 2023-12-19

Tags: k8s helm

本文总阅读量

在了解 Helm 之前,我使用 Yaml 文件 + yq 来生成最终配置文件,通过 kubectl 应用配置,使用方式虽简单粗暴但不够健壮和主流,本文是对 Kubernetes 包管理工具 Helm 的入门使用记录。

Helm 概念

Chart 代表着 Helm 包。它包含在 Kubernetes 集群内部运行应用程序,工具或服务所需的所有资源定义。你可以把它看作是 Homebrew formula,Apt dpkg,或 Yum RPM 在Kubernetes 中的等价物。

Repository(仓库) 是用来存放和共享 charts 的地方。它就像 Perl 的 CPAN 档案库网络 或是 Fedora 的 软件包仓库,只不过它是供 Kubernetes 包所使用的。

Release 是运行在 Kubernetes 集群中的 chart 的实例。一个 chart 通常可以在同一个集群中安装多次。每一次安装都会创建一个新的 release。以 MySQL chart为例,如果你想在你的集群中运行两个数据库,你可以安装该chart两次。每一个数据库都会拥有它自己的 release 和 release name。

在了解了上述这些概念以后,我们就可以这样来解释 Helm:

Helm 安装 charts 到 Kubernetes 集群中,每次安装都会创建一个新的 release。你可以在 Helm 的 chart repositories 中寻找新的 chart。

安装 Helm 客户端

macOS 系统

$ brew install helm

如果是 AWS EKS,需要先安装 aws-cli(重要‼️需要是 2.x 版本) ,并通过 aws 命令获取 kubeconfig 文件

其它系统参考:https://helm.sh/zh/docs/intro/install/

Helm 基本命令

查看仓库

$ helm repo list

因为 helm 仓库信息保存在本地,所以在新安装工具的环境需要重新配置仓库。

添加仓库

$ helm repo add helm-stable https://charts.helm.sh/stable
$ helm repo add truecharts https://charts.truecharts.org
$ helm repo add bitnami https://charts.bitnami.com/bitnami

以下是一些仓库列表:

  • https://charts.helm.sh/stable 官方的 Helm Chart 仓库
  • https://charts.truecharts.org TrueCharts 维护
  • https://charts.bitnami.com/bitnami Bitnami 团队
  • https://charts.rancher.io Rancher Charts
  • https://hub.kubeapps.com 社区驱动
  • https://brigadecore.github.io/charts Brigade 社区

更新/删除仓库

# 更新
$ helm repo update 

# 删除
$ helm repo remove helm-charts

查找 Chart

$ helm search repo <关键词>

支持模糊搜索

Helm 安装软件

接下来通过仓库安装软件,先添加 Example 示例仓库

$ helm repo add examples https://helm.github.io/examples

安装 Chart

$ helm install ahoy examples/hello-world -n default

输出

NAME: ahoy
LAST DEPLOYED: Tue Dec 19 06:45:09 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=hello-world,app.kubernetes.io/instance=ahoy" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT

依次执行上述命令,最后可以通过 kubectl 命令的 port-forward 在本地环境访问到容器

$ kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

打开另外的终端,访问本地的 127.0.0.1:8080,如果是在服务器上执行 kubectl,也可以使用 socat TCP-LISTEN:8123,fork TCP:127.0.0.1:8080 暴露 8123 端口,供本地电脑访问。

# 本地电脑可以使用浏览器打开
$ curl 127.0.0.1:8080

命令行执行返回

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

另外查看 Pod 信息,已创建名为 ahoy-hello-world-5bfd6d648-nnkss 的 Pod

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
ahoy-hello-world-5bfd6d648-nnkss    1/1     Running   0          136m
nginx-deployment-549c78f58f-tmnkb   1/1     Running   0          28h

可以看到 examples/hello-world 已经由 helm 部署并可访问。

Helm 的运行环境

Helm 需要运行在已具备集群访问权限的环境中,即可以正常执行 kubectl 命令的环境。

使用以下命令查看当前的 Kubernetes 上下文信息:

$ kubectl config current-context

Kubernetes 上下文是一组描述如何与 Kubernetes 集群进行通信的参数,包括集群的地址、认证凭据等。Helm 会使用当前的上下文中指定的集群信息来确定要将应用程序安装到哪个 Kubernetes 集群中。

拆解 Hello World!

上文部署的 Example Chart 的代码地址:https://github.com/helm/examples

$ git clone https://github.com/helm/examples

Chart 的结构

$ cd examples/charts
$ tree
.
└── hello-world
    ├── Chart.yaml
    ├── README.md
    ├── templates
    │   ├── NOTES.txt
    │   ├── _helpers.tpl
    │   ├── deployment.yaml
    │   ├── service.yaml
    │   └── serviceaccount.yaml
    └── values.yaml

3 directories, 8 files

Chart.yaml

apiVersion: v2
name: hello-world
description: A Helm chart for Kubernetes

# Chart 可以是 'application' 或 'library' 类型
#
# Application charts 是一组模板,可以打包成版本化的存档文件进行部署。
#
# Library charts 提供有用的实用程序或函数给图表开发者使用。它们作为应用程序图表的依赖项,被注入到渲染流程中的实用程序和函数。库图表不定义任何模板,因此不能被部署。
type: application

# 版本. 每当您对 Chart 及其模板进行更改,包括应用程序版本时,都应该递增该版本号。版本号应遵循语义化版本规范 (https://semver.org/)
version: 0.1.0

# 这是正在部署的应用程序的版本号。每当您对应用程序进行更改时,都应该递增该版本号。版本号不需要遵循语义化版本规范。它们应该反映应用程序正在使用的版本。建议用引号将其括起来使用。
appVersion: "1.16.0"

values.yaml

# 默认值文件
# 声明要传递到模板中的变量

replicaCount: 1

image:
  repository: nginx
  pullPolicy: IfNotPresent
  # 覆盖默认为 chart appVersion 的镜像标签
  tag: ""

nameOverride: ""
fullnameOverride: ""

serviceAccount:
  # 指定是否应创建服务帐户 
  create: true
  # 要添加到服务帐户的注解
  annotations: {}
  # 要使用的服务帐户的名称 
  # 如果创建但未指定名称, 则使用 fullname 模版生成
  name: ""

service:
  type: ClusterIP
  port: 80

templates/service.yaml

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

暂且先看使用 Values 定义的默认值,在如上的 Service 配置中:{{ .Values.service.type }} 中的 .Values 是一个对象,它包含了我们在 Chart 的 values.yaml 文件中定义的所有值。

{{ .Chart.Name }} 中的 .Chart 是另一个对象,包含了定义在 Chart.yaml 文件中的元数据。

除此之外,还有 include 语法需要介绍,这是一个典型的示例:{{- include "hello-world.labels" . | nindent 4 }}

它自动引入了同目录下的 _helpers.tpl 文件,这是一个特殊文件,用于保存可复用配置和函数。

值得注意的是 .(点)用来引用定义的模板函数和操作符,并将当前上下文作为参数传递给它们,例如 {{ include "mytemplate" . }} 表示使用当前上下文作为参数调用名为 mytemplate 的模板函数。

模版段中的 nindent 4 表示引入配置片段到当前位置时,需要在前方添加四个空格。

_helpers.tpl

{{/*
展开 Chart 的名称
*/}}
{{- define "hello-world.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
创建默认的完全限定的应用程序名称
截断为63个字符,因为一些 Kubernetes 名称字段仅限于此(根据DNS命名规范)。
如果发布名称包含 Chart 名称,它将用作全名。
*/}}
{{- define "hello-world.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 }}

{{/*
使用 Chart 名称及版本作为标签使用
*/}}
{{- define "hello-world.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
通用标签
*/}}
{{- define "hello-world.labels" -}}
helm.sh/chart: {{ include "hello-world.chart" . }}
{{ include "hello-world.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
选择器标签
*/}}
{{- define "hello-world.selectorLabels" -}}
app.kubernetes.io/name: {{ include "hello-world.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
创建 service account 的名称
*/}}
{{- define "hello-world.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "hello-world.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

余下的 serviceaccount.yamldeployment.yaml 大同小异,不再逐个说明。

生成全量 Yaml 文件

如果仅使用 Helm 的模版功能,用于生成最终配置文件,可以使用如下命令:

$ helm template hello-world > hello.yaml

hello.yaml

---
# Source: hello-world/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: release-name-hello-world
  labels:
    helm.sh/chart: hello-world-0.1.0
    app.kubernetes.io/name: hello-world
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
---
# Source: hello-world/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: release-name-hello-world
  labels:
    helm.sh/chart: hello-world-0.1.0
    app.kubernetes.io/name: hello-world
    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: hello-world
    app.kubernetes.io/instance: release-name
---
# Source: hello-world/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: release-name-hello-world
  labels:
    helm.sh/chart: hello-world-0.1.0
    app.kubernetes.io/name: hello-world
    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: hello-world
      app.kubernetes.io/instance: release-name
  template:
    metadata:
      labels:
        app.kubernetes.io/name: hello-world
        app.kubernetes.io/instance: release-name
    spec:
      serviceAccountName: release-name-hello-world
      containers:
        - name: hello-world
          image: "nginx:1.16.0"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http

Chart 安装与打包方式

有六种不同的方式来标识需要安装的 Chart:

  • 通过 chart 引用: helm install mymaria example/mariadb
  • 通过 chart 包: helm install mynginx ./nginx-1.2.3.tgz
  • 通过未打包 chart 目录的路径: helm install mynginx ./nginx
  • 通过 URL 绝对路径: helm install mynginx https://example.com/charts/nginx-1.2.3.tgz
  • 通过 chart 引用和仓库 URL: helm install --repo https://example.com/charts/ mynginx nginx
  • 通过 OCI 注册中心: helm install mynginx --version 1.2.3 oci://example.com/charts/nginx

所以我们也可以以不同的方式发布 Chart

制作 tgz 包

$ helm package hello-world

这会在当前目录下生成名为 hello-world-0.1.0.tgz 的压缩包。

tgz 格式的 Chart 适合存储于 S3,另外也可以不打包,将文件夹格式的 Chart 存储于 Git 仓库。

其余发布方式暂不做发散。

参考