目录
一、Helm概述
1.1 为什么需要Helm?
1.2 Helm 介绍
1.3 Helm v3 变化
1.4 Helm客户端
1.4.1、部署Helm客户端
1.4.2、Helm常用命令
1.4.3、配置国内Chart仓库
二、Helm基本使用
2.1 Helm基本使用
2.1.1、使用chart部署一个应用
2.1.2、安装前自定义chart配置选项
2.1.3、构建一个Helm Chart
2.1.4、升级、回滚和删除
2.2 Chart模板
2.2.1、模板和调试
2.2.2、内置对象
2.2.3、Values
2.2.4、管道与函数
2.2.5、流程控制
if
with
range
2.2.6、变量
2.2.7、命名模板
2.3 使用Harbor作为Chart仓库
一、Helm概述
1.1 为什么需要Helm?
K8S上的应用对象,都是由特定的资源描述组成,包括deployment、service等。都保存各自文件中或者集中写到一个配置文件。然后kubectl apply –f 部署。
如果应用只由一个或几个这样的服务组成,上面部署方式足够了。
而对于一个复杂的应用,会有很多类似上面的资源描述文件,例如微服务架构应用,组成应用的服务可能多达十个,几十个。如果有更新或回滚应用的需求,可能要修改和维护所涉及的大量资源文件,而这种组织和管理应用的方式就显得力不从心了。
且由于缺少对发布过的应用版本管理和控制,使Kubernetes上的应用维护和更新等面临诸多的挑战,主要面临以下问题:
-
如何将这些服务作为一个整体管理
-
这些资源文件如何高效复用
-
不支持应用级别的版本管理
1.2 Helm 介绍
Helm是一个Kubernetes的包管理工具,就像Linux下的包管理器,如yum/apt等,可以很方便的将之前打包好的yaml文件部署到kubernetes上。
Helm有两个重要概念:
-
helm:一个命令行客户端工具,主要用于Kubernetes应用chart的创建、打包、发布和管理。
-
Chart:应用描述,一系列用于描述 k8s 资源相关文件的集合。
-
Release:基于Chart的部署实体,一个 chart 被 Helm 运行后将会生成对应的一个 release;将在k8s中创建出真实运行的资源对象。
1.3 Helm v3 变化
2019年11月13日, Helm团队发布 Helm v3的第一个稳定版本。
该版本主要变化如下:
-
架构变化
最明显的变化是 Tiller的删除
-
Release名称可以在不同命名空间重用
-
支持将 Chart 推送至 Docker 镜像仓库中
-
使用JSONSchema验证chart values
-
其他
-
为了更好地协调其他包管理者的措辞 Helm CLI个别更名
helm delete` 更名为 `helm uninstall
helm inspect` 更名为 `helm show
helm fetch` 更名为 `helm pull
但以上旧的命令当前仍能使用。
2)移除了用于本地临时搭建 Chart Repository的 helm serve 命令。
3)自动创建名称空间
在不存在的命名空间中创建发行版时,Helm 2创建了命名空间。Helm 3遵循其他Kubernetes对象的行为,如果命名空间不存在则返回错误。
4) 不再需要requirements.yaml, 依赖关系是直接在chart.yaml中定义。
1.4 Helm客户端
1.4.1、部署Helm客户端
Helm客户端下载地址:https://github.com/helm/helm/releases
解压移动到/usr/bin/目录即可。
wget https://get.helm.sh/helm-v3.0.0-linux-amd64.tar.gz
tar zxvf helm-v3.0.0-linux-amd64.tar.gz
mv linux-amd64/helm /usr/bin/
1.4.2、Helm常用命令
命令 |
描述 |
create |
创建一个chart并指定名字 |
dependency |
管理chart依赖 |
get |
下载一个release。可用子命令:all、hooks、manifest、notes、values |
history |
获取release历史 |
install |
安装一个chart |
list |
列出release |
package |
将chart目录打包到chart存档文件中 |
pull |
从远程仓库中下载chart并解压到本地 # helm pull stable/mysql --untar |
repo |
添加,列出,移除,更新和索引chart仓库。可用子命令:add、index、list、remove、update |
rollback |
从之前版本回滚 |
search |
根据关键字搜索chart。可用子命令:hub、repo |
show |
查看chart详细信息。可用子命令:all、chart、readme、values |
status |
显示已命名版本的状态 |
template |
本地呈现模板 |
uninstall |
卸载一个release |
upgrade |
更新一个release |
version |
查看helm客户端版本 |
1.4.3、配置国内Chart仓库
-
微软仓库(Index of /kubernetes/charts/)这个仓库强烈推荐,基本上官网有的chart这里都有。
-
阿里云仓库(https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts )
-
官方仓库(Kubeapps | Home)官方chart仓库,国内有点不好使。
添加存储库:
helm repo add stable http://mirror.azure.cn/kubernetes/charts
helm repo add aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
helm repo update
## 查看配置的存储库
helm repo list
helm search repo stable
二、Helm基本使用
2.1 Helm基本使用
主要介绍三个命令:
-
chart install
-
chart update
-
chart rollback
2.1.1、使用chart部署一个应用
查找chart:
helm search repo
helm search repo mysql
helm show values stable/mysql
## 查看chart信息
helm show chart stable/mysql
安装包:
helm install db stable/mysql
查看发布状态:
helm status db
我们直接使用helm 安装MySQL 后 发现 MySQL pod 处于 Pending ,因为默认模板资源会要求创建 PV,我们这里使用 NFS。
创建 MySQL-PV 资源为8G ,mysql peding 状态变为 Running
[root@k8s-master1 helm]# cat mysql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
spec:
capacity:
storage: 8Gi
accessModes:
- ReadWriteOnce
nfs:
path: /ifs/kubernetes/db
server: 192.168.2.118
进入MySQL容器内部, MySQL可以正常访问
2.1.2、安装前自定义chart配置选项
上面部署的mysql并没有成功,这是因为并不是所有的chart都能按照默认配置运行成功,可能会需要一些环境依赖,例如PV。
所以我们需要自定义chart配置选项,安装过程中有两种方法可以传递配置数据:
-
--values(或-f):指定带有覆盖的YAML文件。这可以多次指定,最右边的文件优先
-
--set:在命令行上指定替代。如果两者都用,--set优先级高
--values使用,先将修改的变量写到一个文件中
## 将修改的变量写到一个文件中
helm show values stable/mysql > config.yaml
## 新建数据库,和初始化密码
cat config.yaml
persistence:
enabled: true
storageClass: "managed-nfs-storage"
accessMode: ReadWriteOnce
size: 8Gi
mysqlUser: "k8s"
mysqlPassword: "123456"
mysqlDatabase: "k8s"
# 重新 install db2
helm install db2 -f config.yaml stable/mysql
db2-mysql pod 部署成功
进入 db2 容器 可以看到 数据库和用户成功创建。
以上成功创建MySQL用户k8s,并授予此用户访问新创建的k8s数据库权限,并接受该图表的所有其余默认值。
##命令行替代变量:
helm install db --set persistence.storageClass="managed-nfs-storage" stable/mysql
##也可以把chart包下载下来查看详情:
helm pull stable/mysql
该helm install命令可以从多个来源安装:
-
chart存储库
-
本地chart存档(helm install foo-0.1.1.tgz)
-
chart目录(helm install path/to/foo)
-
完整的URL(helm install https://example.com/charts/foo-1.2.3.tgz)
2.1.3、构建一个Helm Chart
创建目录和各个文件。
## 创建 mychart
helm create mychart
[root@k8s-master1 helm]# tree mychart/
mychart/
├── charts
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── ingress.yaml
│ ├── NOTES.txt
│ ├── serviceaccount.yaml
│ ├── service.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml
-
Chart.yaml:用于描述这个 Chart的基本信息,包括名字、描述信息以及版本等。
-
values.yaml :用于存储 templates 目录中模板文件中用到变量的值。
-
Templates: 目录里面存放所有yaml模板文件。
-
charts:目录里存放这个chart依赖的所有子chart。
-
NOTES.txt :用于介绍Chart帮助信息, helm install 部署后展示给用户。例如:如何使用这个 Chart、列出缺省的设置等。
-
_helpers.tpl:放置模板助手的地方,可以在整个 chart 中重复使用
创建Chart后,接下来就是将其部署:
## 导出 deployment 模板 ,自行定义value变量
kubectl create deployment mychart --image=nginx:1.16 -o yaml --dry-run > deployment.yaml
[root@k8s-master1 mychart]# tree .
.
├── charts
├── Chart.yaml
├── templates
│ └── deployment.yaml
└── values.yaml
2 directories, 3 files
[root@k8s-master1 mychart]# cat templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.name }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: mychart
template:
metadata:
labels:
app: mychart
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: nginx
cat mychart/values.yaml
name: hello
replicas: 3
image: nginx
imageTag: 1.17
## 部署 hello 应用
helm install hello mychart/
创建了3副本的 hello pod ,查看渲染后的 deployement 成功获取到 values中定义的变量。
2.1.4、升级、回滚和删除
发布新版本的chart时,或者当您要更改发布的配置时,可以使用该helm upgrade 命令
helm upgrade --set imageTag=1.16 hello mychart
helm upgrade -f values.yaml hello mychart
如果在发布后没有达到预期的效果,则可以使用helm rollback回滚到之前的版本。
例如将应用回滚到第一个版本:
## 回滚到上一个版本
helm rollback hello
## 回滚到 指定版本
helm rollback hello 2
## 查看历史记录
helm history hello
## 打包
helm package mychart
## 卸载发行版,请使用以下helm uninstall命令:
helm uninstall web
2.2 Chart模板
Helm最核心的就是模板,即模板化的K8S manifests文件。
它本质上就是一个Go的template模板。Helm在Go template模板的基础上,还会增加很多东西。如一些自定义的元数据信息、扩展的库以及一些类似于编程形式的工作流,例如条件语句、管道等等。这些东西都会使得我们的模板变得更加丰富。
2.2.1、模板和调试
有了模板,我们怎么把我们的配置融入进去呢?用的就是这个values文件。这两部分内容其实就是chart的核心功能。
Helm也提供了--dry-run --debug调试参数,帮助你验证模板正确性。在执行helm install时候带上这两个参数就可以把对应的values值和渲染的资源清单打印出来,而不会真正的去部署一个release。
比如我们来调试上面创建的 chart 包:
接下来,部署nginx应用,熟悉模板使用,先把templates 目录下面所有文件全部删除掉,这里我们自己来创建模板文件:
rm -rf mychart/templates/*
vim templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.16
name: nginx
实际上,这已经是一个可安装的Chart包了,通过 helm install命令来进行安装:
helm install web mychart
这样部署,其实与直接apply没什么两样。
然后使用如下命令可以看到实际的模板被渲染过后的资源文件:
helm get manifest web
可以看到,这与刚开始写的内容是一样的,包括名字、镜像等,我们希望能在一个地方统一定义这些会经常变换的字段,这就需要用到Chart的模板了。如下 定义 deployment.yaml 和service模板。
[root@k8s-master1 helm]# cat nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
chart: {{ .Chart.Name }}
app: {{ .Release.Name }}
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: {{ .Values.label }}
template:
metadata:
labels:
app: {{ .Values.label }}
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: {{ .Release.Name }}
[root@k8s-master1 helm]#
[root@k8s-master1 helm]#
[root@k8s-master1 helm]# cat nginx/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
chart: {{ .Chart.Name }}
app: {{ .Release.Name }}
name: {{ .Release.Name }}
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
run: {{ .Values.label }}
[root@k8s-master1 helm]# cat nginx/values.yaml
replicas: 3
image: nginx
imageTag: 1.17
label: nginx
这个deployment就是一个Go template的模板,这里定义的Release模板对象属于Helm内置的一种对象,是从values文件中读取出来的。这样一来,我们可以将需要变化的地方都定义变量。
再执行helm install chart 可以看到现在生成的名称变成了web-XXX,证明已经生效了。也可以使用命令helm get manifest查看最终生成的文件内容。
2.2.2、内置对象
刚刚我们使用 {{.Release.Name}}将 release 的名称插入到模板中。这里的 Release 就是 Helm 的内置对象,下面是一些常用的内置对象:
Release.Name |
release 名称 |
Release.Name |
release 名字 |
Release.Namespace |
release 命名空间 |
Release.Service |
release 服务的名称 |
Release.Revision |
release 修订版本号,从1开始累加 |
2.2.3、Values
Values对象是为Chart模板提供值,这个对象的值有4个来源:
-
chart 包中的 values.yaml 文件
-
父 chart 包的 values.yaml 文件
-
通过 helm install 或者 helm upgrade 的 -f或者 --values参数传入的自定义的 yaml 文件
-
通过 --set 参数传入的值
chart 的 values.yaml 提供的值可以被用户提供的 values 文件覆盖,而该文件同样可以被 --set提供的参数所覆盖。
[root@k8s-master1 helm]# cat nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
chart: {{ .Chart.Name }}
app: {{ .Release.Name }}
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: {{ .Values.label }}
template:
metadata:
labels:
app: {{ .Values.label }}
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: {{ .Release.Name }}
[root@k8s-master1 helm]#
[root@k8s-master1 helm]#
[root@k8s-master1 helm]# cat nginx/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
chart: {{ .Chart.Name }}
app: {{ .Release.Name }}
name: {{ .Release.Name }}
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
run: {{ .Values.label }}
[root@k8s-master1 helm]# cat nginx/values.yaml
replicas: 3
image: nginx
imageTag: 1.17
label: nginx
2.2.4、管道与函数
前面讲的模块,其实就是将值传给模板引擎进行渲染,模板引擎还支持对拿到数据进行二次处理。
例如从.Values中读取的值变成字符串,可以使用quote函数实现:
[root@k8s-master1 helm]# cat nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
chart: {{ .Chart.Name }}
app: {{ .Release.Name }}
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: {{ .Values.label }}
template:
metadata:
labels:
app: {{ .Values.label | quote }}
test: {{ default "kangll" .Values.test }}
test1: {{ default "kangll" .Values.test | indent 8 }}
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: {{ .Release.Name }}
函数生效,包括双引号、默认值和缩进函数
2.2.5、流程控制
流程控制是为模板提供了一种能力,满足更复杂的数据逻辑处理。
Helm模板语言提供以下流程控制语句:
-
if/else 条件块
-
with 指定范围
-
range 循环块
if
if/else块是用于在模板中有条件地包含文本块的方法,条件块的基本结构如下:
{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
# Do something else
{{ else }}
# Default case
{{ end }}
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
chart: {{ .Chart.Name }}
app: {{ .Release.Name }}
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: {{ .Values.label }}
template:
metadata:
labels:
app: {{ .Values.label | quote }}
{{- if eq .Values.hadoop "hbase" }} ## 判断
hadoop: regionServer
{{- else }}
hadoop: dataNode
{{- end }}
test: {{ default "kangll" .Values.test }}
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.imageTag }}
name: {{ .Release.Name }}
在上面条件语句使用了eq运算符判断是否相等,除此之外,还支持ne、 lt、 gt、 and、 or等运算符。
通过模板引擎来渲染一下,会得到如下结果:
可以看到渲染出来会有多余的空行,这是因为当模板引擎运行时,会将控制指令删除,所有之前占的位置也就空白了,需要使用{{- if ...}} 的方式消除此空行:
{{- if eq .Values.hadoop "hbase" }} ## 判断
hadoop: regionServer
{{- else }}
hadoop: dataNode
{{- end }}
test: {{ default "kangll" .Values.test }}
条件判断就是判断条件是否为真,如果值为以下几种情况则为false:
-
一个布尔类型的 假
-
一个数字 零
-
一个 空的字符串
-
一个 nil(空或 null)
-
一个空的集合( map、 slice、 tuple、 dict、 array)
除了上面的这些情况外,其他所有条件都为 真。
[root@k8s-master1 helm]# cat nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
...
spec:
containers:
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
name: {{ .Release.Name }}
{{- if .Values.resources.enable }}
resources:
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
{{- else }}
resources: {}
{{- end }}
values.yaml
[root@k8s-master1 helm]# cat nginx/values.yaml
# Declare variables to be passed into your templates.
replicaCount: 3
image:
repository: nginx
tag: 1.17
pullPolicy: IfNotPresent
hadoop: namenode
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
resources:
enable: false
limits:
cpu: 100m
memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
with
with :控制变量作用域。
还记得之前我们的 {{.Release.xxx}}或者 {{.Values.xxx}}吗?其中的 . 就是表示对当前范围的引用, .Values就是告诉模板在当前范围中查找 Values对象的值。而 with语句就可以来控制变量的作用域范围,其语法和一个简单的 if语句比较类似:
[root@k8s-master1 helm]# cat nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
...
spec:
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }} # toYaml 打印每一行,"." 取值 ,nindent换行缩进
{{- end }}
containers:
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
name: {{ .Release.Name }}
{{- if .Values.resources.enable }}
resources:
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
{{- else }}
resources: {}
{{- end }}
values.yaml
# Declare variables to be passed into your templates.
replicaCount: 3
image:
repository: nginx
tag: 1.17
pullPolicy: IfNotPresent
hadoop: namenode
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
...
nodeSelector:
team: a
gpu: long
我们打印下遍历出的结果
range
在 Helm 模板语言中,使用 range关键字来进行循环操作。
我们在 values.yaml文件中添加上一个变量列表:
# cat values.yaml
test:
- 1
- 2
- 3
configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
data:
test: |
{{- range .Values.test }}
{{ . }}
{{- end }}
循环内部我们使用的是一个" . ",这是因为当前的作用域就在当前循环内,这个 "."引用的当前读取的元素。
2.2.6、变量
在模板中,使用变量的场合不多,但我们将看到如何使用它来简化代码,并更好地利用with和range。
问题1:with中不能使用内置对象
with语句块内不能再 .Release.Name对象,否则报错。
我们可以将该对象赋值给一个变量可以来解决这个问题:
apiVersion: apps/v1
kind: Deployment
metadata:
...
spec:
{{- with .Values.nodeSelector }}
nodeSelector:
app: {{ .Release.Name }} ##出错
{{- toYaml . | nindent 8 }} # toYaml 打印每一行,"." 取值 ,nindent换行缩进
{{- end }}
containers:
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
name: {{ .Release.Name }}
{{- if .Values.resources.enable }}
resources:
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
{{- else }}
resources: {}
{{- end }}
使用变量赋值
spec:
{{- $releaseName := .Release.Name -}}
{{- with .Values.nodeSelector }}
nodeSelector:
app: {{ $releaseName }}
{{- toYaml . | nindent 8 }} # toYaml 打印每一行,"." 取值 ,nindent换行缩进
{{- end }}
可以看到在 with语句上面增加了一句 {{-$releaseName:=.Release.Name-}},其中 $releaseName就是后面的对象的一个引用变量,它的形式就是 $name,赋值操作使用 :=,这样 with语句块内部的 $releaseName变量仍然指向的是 .Release.Name。
问题2:获取列表键值
# cat ../values.yaml
env:
JAVA_OPTS: "-Xmx1G"
NAME: "JAVA"
# cat deployment.yaml
...
spec:
{{- $releaseName := .Release.Name -}}
{{- with .Values.nodeSelector }}
nodeSelector:
app: {{ $releaseName }}
{{- toYaml . | nindent 8 }} # toYaml 打印每一行,"." 取值 ,nindent换行缩进
{{- end }}
containers:
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
name: {{ .Release.Name }}
env:
{{- range $key,$val := .Values.env }}
- name: {{ $key }}
value: {{ $val }}
{{- end }}
结果如下:
上面在 range循环中使用 $key和 $value两个变量来接收后面列表循环的键和值。
2.2.7、命名模板
命名模板:使用define定义,template引入,在templates目录中默认下划线开头的文件为公共模板(_helpers.tpl)
[root@k8s-master1 templates]# cat _helpers.tpl
{{- define "name" -}}
{{ .Release.Name }}
{{- end -}}
{{- define "labels" -}}
app: {{ template "name" . }}
chart: {{ .Chart.Name }}-{{ .Chart.Name }}
release: {{ .Release.Name }}
{{- end -}}
查看渲染后的结果
2.3 使用Harbor作为Chart仓库
-
启用Harbor的Chart仓库服务
./install.sh --with-chartmuseum
启用后,默认创建的项目就带有helm charts功能了。
-
安装push插件
helm plugin install https://github.com/chartmuseum/helm-push
https://github.com/chartmuseum/helm-push/releases/download/v0.10.3/helm-push_0.10.3_linux_amd64.tar.gz
可以离线 下载 在该路径下创建helm-push文件夹,并将安装包拷贝到该文件夹下解压即可。
mkdir /root/.local/share/helm/plugins/helm-push
cp helm-push_0.10.3_linux_amd64.tar.gz /root/.local/share/helm/plugins/helm-push
tar zxvf /root/.local/share/helm/plugins/helm-push/helm-push_0.10.3_linux_amd64.tar.gz
插件安装好后,使用方式从:helm push 变为 helm cm-push
3、添加repo
helm repo add --username admin --password Harbor12345 myrepo http://harbor.winnerinf.com/chartrepo/library
# 查看仓库
helm repo list
# 更新仓库
helm repo update
4、推送与安装Chart
helm cm-push mychart-0.1.0.tgz myrepo
helm install web --version 0.1.0 myrepo/mychart
安装文章来源:https://www.toymoban.com/news/detail-716638.html
😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅文章来源地址https://www.toymoban.com/news/detail-716638.html
到了这里,关于【云原生】Kubernetes应用包管理器Helm的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!