如果你问我:在使用k8s的过程中,最痛苦的是什么? 我可能会说:最痛苦的莫过于编写各种各样的yaml资源文件,并且稍有不慎就会出错。 如果,你也遇到了这个问题,那么helm将拯救你。
认识helm
Helm 是 Kubernetes 应用程序的包管理器,属于 Kubernetes 的一个子项目,主要用来对 Kubernetes 进行包管理,类似Linux系统常用的 apt、yum等包管理工具。它可以帮助您在 Kubernetes 中无缝安装、配置和管理复杂的应用程序。
Helm Chart 是描述一组 Kubernetes 资源(如部署、服务和配置映射)以及必要配置值的文件集合。
三大概念
它包含在 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管理名为chart的Kubernetes包的工具。Helm可以做以下的事情:
从头开始创建新的chart
将chart打包成归档(tgz)文件
与存储chart的仓库进行交互
在现有的Kubernetes集群中安装和卸载chart
管理与Helm一起安装的chart的发布周期
组件 Helm是一个可执行文件,执行时分成两个不同的部分。Helm客户端
是终端用户的命令行客户端。负责以下内容:
本地chart开发
管理仓库
管理发布
与Helm库建立接口
发送安装的chart
发送升级或卸载现有发布的请求
Helm库
提供执行所有Helm操作的逻辑。与Kubernetes API服务交互并提供以下功能:
结合chart和配置来构建版本
将chart安装到Kubernetes中,并提供后续发布对象
与Kubernetes交互升级和卸载chart
独立的Helm库封装了Helm逻辑以便不同的客户端可以使用它。
特点
操作示例
简化部署
helm install myapp ./myapp-chart
自定义配置
helm install myapp ./myapp-chart --set environment=production helm install myapp ./myapp-chart --set environment=development
简化更新
helm upgrade myapp ./myapp-chart
查询与快速部署
helm search hub mysql helm install mymysql stable/mysql
执行 Helm客户端和库是使用Go编程语言编写的,这个库使用Kubernetes客户端库(k8s.io/client-go)与Kubernetes通信。现在,这个库使用REST+JSON。它将信息存储在Kubernetes的密钥中。 不需要自己的数据库。(如果可能,配置文件是用YAML编写的)
源码与文档 github地址: https://github.com/helm/helm 官方文档:https://helm.sh/zh/docs/intro/quickstart/
源码主要的目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 ➜ tree -L 3 helm helm ├── cmd │ ├── helm # 目录中几乎每一个文件都是helm的一个子命令 │ │ ├── completion.go │ │ ├── create.go │ │ ├── dependency.go │ │ ├── dependency_build.go │ │ ├── dependency_update.go │ │ ├── docs.go │ │ ├── env.go │ │ ├── flags.go │ │ ├── get.go │ │ ├── get_all.go │ │ ├── get_hooks.go │ │ ├── get_manifest.go │ │ ├── get_metadata.go │ │ ├── get_notes.go │ │ ├── get_values.go │ │ ├── helm.go │ │ ├── history.go │ │ ├── install.go │ │ ├── lint.go │ │ ├── list.go │ │ ├── load_plugins.go │ │ ├── package.go │ │ ├── plugin.go │ │ ├── plugin_install.go │ │ ├── plugin_list.go │ │ ├── plugin_uninstall.go │ │ ├── plugin_update.go │ │ ├── printer.go │ │ ├── pull.go │ │ ├── push.go │ │ ├── registry.go │ │ ├── registry_login.go │ │ ├── registry_logout.go │ │ ├── repo.go │ │ ├── repo_add.go │ │ ├── repo_index.go │ │ ├── repo_list.go │ │ ├── repo_remove.go │ │ ├── repo_update.go │ │ ├── require │ │ ├── rollback.go │ │ ├── root.go │ │ ├── root_unix.go │ │ ├── root_windows.go │ │ ├── search │ │ ├── search.go │ │ ├── search_hub.go │ │ ├── search_repo.go │ │ ├── show.go │ │ ├── status.go │ │ ├── template.go │ │ ├── testdata │ │ ├── uninstall.go │ │ ├── upgrade.go │ │ ├── verify.go │ └── version.go ├── go.mod ├── go.sum ├── internal ├── pkg ├── scripts └── testdata
helm 使用前注意事项 先决条件 想成功和正确地使用Helm,需要以下前置条件。
一个 Kubernetes 集群
确定你安装版本的安全配置
安装和配置Helm
安装或使用现有Kubernetes集群
使用Helm,需要一个Kubernetes集群。对于Helm的最新版本,我们建议使用Kubernetes的最新稳定版, 在大多数情况下,它是倒数第二个次版本。
应该有一个本地的 kubectl
helm的安装与使用 安装可以参考官方文档 https://helm.sh/zh/docs/intro/install/
,笔者这里使用 gox 对helm源码进行编译安装:
1 2 3 4 5 6 7 8 9 10 11 ➜ git clone https://github.com/helm/helm.git ➜ cd helm/cmd/helm # 这里使用的是mac,所以编译为 darwin/amd64。得到可执行文件 helm_darwin_amd64 ➜ gox -osarch="darwin/amd64" ./ ➜ mv helm_darwin_amd64 helm ➜ cp helm /usr/local/bin/ # 查看安装的helm的版本号 ➜ helm version version.BuildInfo{Version:"v3.15.2", GitCommit:"1a500d5625419a524fdae4b33de351cc4f58ec35", GitTreeState:"clean", GoVersion:"go1.22.4"}
使用 helm 创建 helm-demo chart 模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ➜ helm create helm-demo Creating helm-demo ➜ tree helm-demo helm-demo ├── Chart.yaml - chart定义,可以定义chart的名字,版本号信息。 ├── charts - 依赖的子包目录,里面可以包含多个依赖的chart包 ├── templates - k8s配置模版目录,除NOTES.txt和下划线开头命名的文件,其他可任意命名 │ ├── NOTES.txt - chart 包的帮助信息文件,执行helm install命令安装成功后会输出这个文件的内容 │ ├── _helpers.tpl - 下划线开头的文件被视为公共库定义文件,定义通用的子模版、函数等,不会将这些文件的渲染结果提交给k8s处理 │ ├── deployment.yaml │ ├── hpa.yaml │ ├── ingress.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ └── tests │ └── test-connection.yaml └── values.yaml - 包的参数配置文件,模版可引用这里参数 4 directories, 10 files
以 模板 templates/service.yaml 文件为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: Service metadata: name: {{ include "helm-demo.fullname" . }} labels: {{- include "helm-demo.labels" . | nindent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: {{- include "helm-demo.selectorLabels" . | nindent 4 }}
可以看到被 {{}}
包裹的都是在渲染时需要填充的数据,其中 include 意味着此处嵌入其他模板,其他的则为 渲染时填充的字段值,.Values 开头的值在 values.yaml 文件中。例如.Values.service.port为values.yaml文件中 service下port 的值,其他的类似(当然用法跟 go 的 text/template package的用法一样)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 replicaCount: 1 image: repository: nginx pullPolicy: IfNotPresent tag: "" imagePullSecrets: []nameOverride: "" fullnameOverride: "" serviceAccount: create: true automount: true annotations: {} name: "" podAnnotations: {}podLabels: {}podSecurityContext: {} securityContext: {} service: type: ClusterIP port: 80 ingress: enabled: false className: "" annotations: {} hosts: - host: chart-example.local paths: - path: / pathType: ImplementationSpecific tls: [] resources: {} livenessProbe: httpGet: path: / port: http readinessProbe: httpGet: path: / port: http autoscaling: enabled: false minReplicas: 1 maxReplicas: 100 targetCPUUtilizationPercentage: 80 volumes: []volumeMounts: []nodeSelector: {}tolerations: []affinity: {}
在helm-demo chart各个文件中,所引用或使用的顶层变量大致为:
1 2 3 4 5 6 7 example-helm/templates/tests/test-connection.yaml -> [Chart] [Files] [Release] [Capabilities] [Values] [Subcharts] [Template] example-helm/templates/serviceaccount.yaml -> [Chart] [Files] [Release] [Capabilities] [Values] [Subcharts] [Template] example-helm/templates/service.yaml -> [Files] [Release] [Capabilities] [Values] [Subcharts] [Template] [Chart] example-helm/templates/ingress.yaml -> [Template] [Chart] [Files] [Release] [Capabilities] [Values] [Subcharts] example-helm/templates/hpa.yaml -> [Values] [Subcharts] [Template] [Chart] [Files] [Release] [Capabilities] example-helm/templates/deployment.yaml -> [Files] [Release] [Capabilities] [Values] [Subcharts] [Template] [Chart] example-helm/templates/NOTES.txt -> [Chart] [Files] [Release] [Capabilities] [Values] [Subcharts] [Template]
Helm install命令执行流程 helm的大部分子命令执行逻辑相似,这里以 helm 的子命令install为例,简单追溯其执行过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 cmd/helm/install.go newInstallCmd() runInstall() vals, err := valueOpts.MergeValues(p) RunWithContext() i.cfg.KubeClient.IsReachable() i.cfg.renderResources() e.Render() e.render() t := template.New("gotpl" ) t.ExecuteTemplate() i.cfg.KubeClient.Build() i.cfg.KubeClient.Create()
可以看出,在执行install的过程中会检查k8s集群是否可用,这也是使用前注意事项的先决条件
中要求一个Kubernetes集群的原因。
假设没有可用k8s集群,则在执行helm指令时可能会报错,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 ➜ helm install demo ./helm-demo --debug --dry-run install.go:222: [debug] Original chart version: "" install.go:239: [debug] CHART PATH: /Users/xxxxxx/code/k8s/helm-demo Error: INSTALLATION FAILED: Kubernetes cluster unreachable: Get "https://127.0.0.1:26443/version": dial tcp 127.0.0.1:26443: connect: connection refused helm.go:84: [debug] Get "https://127.0.0.1:26443/version": dial tcp 127.0.0.1:26443: connect: connection refused Kubernetes cluster unreachable helm.sh/helm/v3/pkg/kube.(*Client).IsReachable helm.sh/helm/v3/pkg/kube/client.go:135 helm.sh/helm/v3/pkg/action.(*Install).RunWithContext helm.sh/helm/v3/pkg/action/install.go:231 main.runInstall helm.sh/helm/v3/cmd/helm/install.go:314 main.newInstallCmd.func2 helm.sh/helm/v3/cmd/helm/install.go:156 github.com/spf13/cobra.(*Command).execute github.com/spf13/cobra@v1.8.0/command.go:983 github.com/spf13/cobra.(*Command).ExecuteC github.com/spf13/cobra@v1.8.0/command.go:1115 github.com/spf13/cobra.(*Command).Execute github.com/spf13/cobra@v1.8.0/command.go:1039 main.main helm.sh/helm/v3/cmd/helm/helm.go:83 runtime.main runtime/proc.go:271 runtime.goexit runtime/asm_amd64.s:1695 INSTALLATION FAILED main.newInstallCmd.func2 helm.sh/helm/v3/cmd/helm/install.go:158 github.com/spf13/cobra.(*Command).execute github.com/spf13/cobra@v1.8.0/command.go:983 github.com/spf13/cobra.(*Command).ExecuteC github.com/spf13/cobra@v1.8.0/command.go:1115 github.com/spf13/cobra.(*Command).Execute github.com/spf13/cobra@v1.8.0/command.go:1039 main.main helm.sh/helm/v3/cmd/helm/helm.go:83 runtime.main runtime/proc.go:271 runtime.goexit runtime/asm_amd64.s:1695
调试模式下渲染模板 helm命令加上–debug –dry-run 两个参数,只输出模版渲染结果,不交给k8s处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 ➜ k8s helm install demo ./helm-demo --debug --dry-run install.go:222: [debug] Original chart version: "" install.go:239: [debug] CHART PATH: /Users/ivanli/code/k8s/helm-demo # === chart信息,STATUS为执行install后当前chart的状态 === NAME: demo LAST DEPLOYED: Mon Jul 8 11:40:27 2024 NAMESPACE: default STATUS: pending-install REVISION: 1 USER-SUPPLIED VALUES: {} # === 渲染模板时使用的数据 === COMPUTED VALUES: affinity: {} autoscaling: enabled: false maxReplicas: 100 minReplicas: 1 targetCPUUtilizationPercentage: 80 fullnameOverride: "" image: pullPolicy: IfNotPresent repository: nginx tag: "" imagePullSecrets: [] ingress: annotations: {} className: "" enabled: false hosts: - host: chart-example.local paths: - path: / pathType: ImplementationSpecific tls: [] livenessProbe: httpGet: path: / port: http nameOverride: "" nodeSelector: {} podAnnotations: {} podLabels: {} podSecurityContext: {} readinessProbe: httpGet: path: / port: http replicaCount: 1 resources: {} securityContext: {} service: port: 80 type: ClusterIP serviceAccount: annotations: {} automount: true create: true name: "" tolerations: [] volumeMounts: [] volumes: [] HOOKS: --- # Source: helm-demo/templates/tests/test-connection.yaml apiVersion: v1 kind: Pod metadata: name: "demo-helm-demo-test-connection" labels: helm.sh/chart: helm-demo-0.1.0 app.kubernetes.io/name: helm-demo app.kubernetes.io/instance: demo 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: ['demo-helm-demo:80'] restartPolicy: Never # === 渲染后所有的清单 === MANIFEST: --- # Source: helm-demo/templates/serviceaccount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: demo-helm-demo labels: helm.sh/chart: helm-demo-0.1.0 app.kubernetes.io/name: helm-demo app.kubernetes.io/instance: demo app.kubernetes.io/version: "1.16.0" app.kubernetes.io/managed-by: Helm automountServiceAccountToken: true --- # Source: helm-demo/templates/service.yaml apiVersion: v1 kind: Service metadata: name: demo-helm-demo labels: helm.sh/chart: helm-demo-0.1.0 app.kubernetes.io/name: helm-demo app.kubernetes.io/instance: demo 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: helm-demo app.kubernetes.io/instance: demo --- # Source: helm-demo/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: demo-helm-demo labels: helm.sh/chart: helm-demo-0.1.0 app.kubernetes.io/name: helm-demo app.kubernetes.io/instance: demo app.kubernetes.io/version: "1.16.0" app.kubernetes.io/managed-by: Helm spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: helm-demo app.kubernetes.io/instance: demo template: metadata: labels: helm.sh/chart: helm-demo-0.1.0 app.kubernetes.io/name: helm-demo app.kubernetes.io/instance: demo app.kubernetes.io/version: "1.16.0" app.kubernetes.io/managed-by: Helm spec: serviceAccountName: demo-helm-demo securityContext: {} containers: - name: helm-demo 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: {} # === template/NOTES.txt 文件内容 === NOTES: 1. Get the application URL by running these commands: export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=helm-demo,app.kubernetes.io/instance=demo" -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
总之,helm简化对 k8s 的操作与管理,非常值得一试。