Amazing Archdocker. k8sKubernetes

用 Helm3 构建多层微服务

2019-11-29  本文已影响0人  天天向上卡索

Intro

Helm是一款非常流行的k8s包管理工具。以前就一直想用它,但看到它产生的文件比k8s要复杂许多,就一直犹豫,不知道它的好处能不能抵消掉它的复杂度。但如果不用,而是用Kubectl来进行调式真的很麻烦。正好最近Helm3正式版出来了,比原来的Helm2 简单了不少,就决定还是试用一下。结果证明确实很复杂,它的好处和坏处大致相当。有了它确实能大大简化对k8s的调式,但也需要花费比较多的时间来学习,而且产生的配置文件要复杂许多。但是事实是现在没有什么很方便的帮助调式k8s的工具,在没有更好的方案之前,我还是建议用它,只是前期需要花些功夫学习和掌握它。

Helm3和Helm2的语法差不太多,只是使用起来更方便,不用安装Tiller。一个比较明显的变化是不再需要“requirements.yaml”, 依赖关系是直接在“chart.yaml”中定义。有关Helm3和Helm2的区别,详情请参见CHANGES SINCE HELM 2

网上有不少讲述Helm的文章,但大部分都是主要讲解安装和举一个简单的例子。但Helm使用起来还是比较复杂的,一定要有一个复杂的例子才能把它的功能讲清楚,里面有不少设计方面的问题需要思考。我刚开始接触的时候就觉得头绪繁多,不知从哪下手。本文就通过一个相对复杂的例子来讲解用Helm3来设计配置文件的思路,使上手更容易。

这里不讲Helm3的安装,它比较很容易。也不讲解Helm的基本语法,你可以自己去看其他文档。即使你不懂Helm,应该也能猜出七八成,剩下的就要读文档了(Charts)。Helm的语法还是比较复杂的,要想搞懂可能要花一两天时间。

本文假设你对helm有一个大概的了解,想要构建一个复杂的微服务,但有不知如何下手;或者你想了解一下构建Helm的最佳实践,那就请你继续读下去。

Helm文件结构

chart里一个很重要的概念就是模板(template),它就是Go语言模板,它是里面加入了编程逻辑的k8s文件。这些模板文件在使用时都要先进行模板解析,把其中的程序逻辑转化成对应的编码,最终生成k8s配置文件。

以上就是Helm自动生成的chart目录结构,在Helm里每个项目叫一个chart,它由下面几个组成部分:

Helm的基本元素

Helm有四个基本元素,值,常量,变量和共享常量(这个后面会讲)

值(literal)

Helm在k8s的基础之上增加了模板功能,使k8s的配置文件更加灵活。里面的主要概念就是模板(Template),也就是在k8s的配置文件里增加了常量和变量以及编程逻辑。如果你不用这些新增功能,那么就是普通的YAML文件(k8s配置文件),里面用到的基本元素就是值。

常量

节点定位(Node Anchor):

如果你想复用重复的值,能把它定义成常量吗?YAML有一个功能叫节点定位(Node Anchor),类似于定义一个常量,然后引用。但它有一些限制,定义的必须是一个节点,因此不如真正的常量灵活。
例如如下文件中,用“&”定义了一个常量“&k8sdemoDatabaseService”,然后用“*k8sdemoDatabaseService”引用它。

global:
  k8sdemoDatabaseService: &k8sdemoDatabaseService k8sdemo-database-service
  mysqlHost: *k8sdemoDatabaseService

这时,“k8sdemoDatabaseService:”是YAML文件节点的键名,“&k8sdemoDatabaseService”是节点定位的名字,相当于常量名,“k8sdemo-database-service”是YAML节点的键值。在上述代码中,k8sdemoDatabaseService和mysqlHost的值都是“k8sdemo-database-service”。

有关节点定位(Node Anchor)的详细内容,请参见 YAML

常量:

由于节点定位的局限性,Helm引入了真正的常量,也就是在"values.yaml"里定义的内容,它可以定义是任何东西,不只限于节点。

在"values.yaml"里定义常量:

replicaCount: 1

在部署模板里引用:

replicas: {{ .Values.replicaCount }}

那么什么时候用常量,什么时候用值(Literal)呢?如果一个值在模板中出现多次,就要定义常量,避免重复。例如“accessModes”,既要在存储卷里出现,又要在存储卷申请里出现。另外如果值有可能变化(不论是随部署环境变化,还是随时间变化),那么就定义成常量,这样在修改时就只用改"values.yaml",而不必修改模板文件。例如“replicas”的值(也就是集群的个数)是可能变化的,就要定义成常量。在模板里可以引用常量的,但在"values.yaml"里不行,因为它只是普通YAML文件,没有模板解析功能,因此不支持常量,这里就只能用节点定位(来代替常量)。

有关Helm常量的详细内容,请参见 Use placeholders in yamlUse YAML with variables

变量

节点定位的功能是有限的,例如你想利用已有的节点定位,对它进行转换,定义一个新的节点定位,这在"values.yaml"里就不行了。
例如你已有节点定位“name”,你想在这个基础上定义一个新的节点定位“serviceName”,这个"values.yaml"就不支持了,你必须要用模板。

如下所示,这在"values.yaml"里是不支持的。

name: &name k8sdemo-backend
serviceName:*name-service

这就引出了变量的概念,但它只能在模板里才行。 换句话说,模板既支持常量,也支持变量。但如果把变量的定义逻辑放在Helm每个模板里,就显得很乱。因此一般的做法是把这些逻辑放在一个单独的模板文件里,这个就是前面讲到的"_helpers.tpl"文件。当你需要对常量进行转换,生成新的常量,你就在定义变量,这部分代码就放在"_helpers.tpl"里。

下面就是"_helpers.tpl"中定义"k8sdemo.name"的代码。

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

在以上这些元素中,常量(也就是在"values.yaml"中定义的)是最灵活的,能用它时尽量用它。而且因为它是定义在普通YAML文件中("values.yaml"),应用程序可以直接访问它,这样可以实现应用程序和k8s之间的数据共享。但如果你需要对常量进行编程转换,那就没办法了,只能定义变量,把它放在"_helpers.tpl"中。

ConfigMap和Secret

在k8s中ConfigMap和Secret是用来存储共享配置参数和保密参数的,但在Helm中,由于有了上面讲的Helm基本元素,它们完全可以代替ConfigMap的功能,因此ConfigMap就不需要了,但Secret还是需要的,因为要存储加密信息。下面会讲解。

有关ConfigMap的设计局限性,请参见把应用程序迁移到k8s需要修改什么?

Chart设计

现在我们就用一个具体的例子来展示Helm的chart设计。这个例子是一个微服务应用程序,它共有三层: 前端,后端和数据库,只有这样才能让Helm的一些设计问题付出水面,如果只有一层的话,就太简单了,没有参考价值。

在k8s中,每一层就是一个单独的服务,它里面有各种配置文件。Helm的优势是把这些不同的服务组成一个Chart来共同管理和调式,方便了许多。

file

上面就是最终的chart目录结构图。“chart”是总目录,里面有三个子目录“k8sdemo”,“k8sdemo-backend”,“k8sdemo-database”, 每一个对应一个服务,每个服务都是一个独立的chart,能单独调式部署,chart之间也可以有依赖关系。其中“k8sdemo”是父chart,同时也是前端服务,它的“charts”目录里有它依赖的另外两个服务。“k8sdemo-backend”是后端服务,“k8sdemo-database”是数据库服务。

处理Chart的依赖关系有两种方式:

  1. 嵌入式:就是直接把依赖的chart放在“charts”子目录里,这样子chart是父chart的一部分。它是一种紧耦合的关系,好处是比较简单,但不够灵活。
  2. 依赖导入式:就是各个chart是并列关系,各自单独调试部署,互相独立,需要合并时再把子chart导入父chart里,它是一种松耦合的关系,好处是比较灵活,但设计更复杂。 在这种结构下,各个chart可以单独工作也可以联合工作,不过你需要更好的设计。

这里采用的是依赖导入式方式,主要原因是我原来认为嵌入式需要一起调试,复杂度太高,如果你觉得这不是问题,这也是个不错的办法。用依赖导入式方式,可以单独调试各个chart,简单了很多。后来发现其实采用嵌入式也可以单独调试子chart,只是父chart不能单独调试而已。

调试顺序:

当你采用依赖导入式方式时,调试顺序关系不大,因为各个chart是各自独立的,可以单独调试。举个例子,虽然“k8sdemo-backend”需要“k8sdemo-database”才能正常运行,但当没有数据库服务时,你的程序也可以运行,只不过输出的是错误信息,但这并不影响你调试chart。

我先调试“k8sdemo”,它虽然依赖另外两个chart,但没有它们也能单独工作。然后再调试“k8sdemo-backend”和“k8sdemo-database”,最后再把它们导入到“k8sdemo”中去再进行联调。

调式“k8sdemo”

它的调试是最容易的,由于它里面没有真正的前端代码,只要把Nginx调试成功了就可以了。只要在生成的文件基础上做些修改就行了。

键入如下命令创建chart,其中“k8sdemo”是chart的名字,这个名字很重要,服务的名字和label都是由它产生的。

helm create k8sdemo

这之后,系统会自动创建前面讲到的chart目录结构。让后就是对已经生成的文件进行修改。

修改"values.yaml":
以下是"values.yaml"主要修改的地方

image:
  repository: nginx:1.17.6
  pullPolicy: Never

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

service:
  type: NodePort
  port: 80
  nodePort: 31080

另外,由于"ingress.yaml"和"serviceaccount.yaml"暂时没用,就把它们都设成了“false”

ingress:
  enabled: false

serviceAccount:
  # Specifies whether a service account should be created
  create: false

修改"service.yaml":

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

修改"deployment.yaml":

。。。
containers:
  - name: {{ .Chart.Name }}
    securityContext:
      {{- toYaml .Values.securityContext | nindent 12 }}
    image: {{ .Values.image.repository }}
    imagePullPolicy: {{ .Values.image.pullPolicy }}
。。。

以上都是简单的修改,不涉及到设计问题。由于篇幅的关系,这里没有列出全部源码,如果有兴趣请在本文末尾找到源码地址。

共享常量

在进行下面的调试之前,先要讲一个重要概念。 前面介绍Helm的基本元素时讲的都是在一个chart里共享值,如果要在不同chart之间共享值(例如k8s服务名,数据库用户名和端口),那么这些还不够,你需要共享常量. 通常情况下子chart和父chart之间的常量是不能共享的,如果需要共享,需要有一种特殊的方法来定义常量,这就是共享常量。它必须是定义在父chart中。

共享常量

例如,你在“k8sdemo”的“values.yaml”加入下面代码,注意节点的名字必须是子chart名(例如“k8sdemo-backend”)

k8sdemo-backend:
  replicaCount: 2
k8sdemo-database:
  replicaCount: 2

在“k8sdemo”的模板里就可以通过“{{ .Values.k8sdemo-backend.replicaCount }}” 来访问。当Helm发现节点名是子chart名时,它会自动拷贝这个常量到子chart的“values.yaml”中,因此,在“k8sdemo-backend”中,你也可以通过“{{ .Values.replicaCount }}” 来访问这个常量。注意这里并没有包含子chart名(“k8sdemo-backend”),而是只有常量名,因为子chart名只是一个标识,而不是常量名的一部分。

全局常量

共享常量只能把常量共享给一个字chart,如果你需要多个子chart之间共享,就需要创建全局常量,它用“global”来标识,下面是示例。

在“k8sdemo-backend”的"values.yaml"中定义:

global:
  k8sdemoDatabaseService: &k8sdemoDatabaseService k8sdemo-database-service
  mysqlUserName: dbuser
  mysqlUserPassword: dbuser
  mysqlPort: 3306
  mysqlHost: *k8sdemoDatabaseService
  mysqlDatabase: service_config

在“k8sdemo-backend”的“deployment.yaml”中引用。

env:
  - name: MYSQL_USER_NAME
    value: {{ .Values.global.mysqlUserName }}
  - name: MYSQL_USER_PASSWORD
    value: {{ .Values.global.mysqlUserPassword }}
  - name: MYSQL_HOST
    value: {{ .Values.global.mysqlHost }}
  - name: MYSQL_PORT
    value: "{{ .Values.global.mysqlPort }}"
  - name: MYSQL_DATABASE
    value: {{ .Values.global.mysqlDatabase }}

在“k8sdemo-database”的"values.yaml"中定义:

global:
  k8sdemoDatabaseService: k8sdemo-database-service
  mysqlUserName: dbuser
  mysqlUserPassword: dbuser
  mysqlRootPassword: root
  mysqlDatabase: service_config

在“k8sdemo-database”的“deployment.yaml”中引用。

env:
  - name: MYSQL_ROOT_PASSWORD
    value: {{ .Values.global.mysqlRootPassword }}
  - name: MYSQL_USER_NAME
    value: {{ .Values.global.mysqlUserName }}
  - name: MYSQL_USER_PASSWORD
    value: {{ .Values.global.mysqlUserPassword }}
  - name: MYSQL_DATABASE
    value: {{ .Values.global.mysqlDatabase }}

当把“k8sdemo-backend”和“k8sdemo-database”导入"k8sdemo"后进行联调时, 就要把上面提到的全局常量写入"k8sdemo"的"values.yaml"文件中,这样就能让各个子chart共享这些常量。如下所示:

global:
  k8sdemoBackendService: k8sdemo-backend-service
  k8sdemoDatabaseService: &k8sdemoDatabaseService k8sdemo-database-service
  mysqlUserName: dbuser
  mysqlUserPassword: dbuser
  mysqlRootPassword: root
  mysqlPort: 3306
  mysqlHost: *k8sdemoDatabaseService
  mysqlDatabase: service_config

如果父chart和子chart有重复的全局常量,这时父chart("k8sdemo")的全局常量值就会覆盖子chart的全局常量。

它的使用原则就是如果只是子chart独有的常量就在子chart的"values.yaml"中定义,如果是共享的常量就在父chart中定义。但如果采用的是依赖导入方式,由于子chart也要单独调试,这时你在子chart里也要定义这些全局常量。这样在进行chart总调试时,就会使用父chart的中的值。

详情请参见 Subcharts and Global Values

调试“k8sdemo-backend”

“k8sdemo-backend”的chart需要取(与“k8ssdemo”)不同的名字,
创建:

helm create k8sdemo-backend
file

上面就是“k8sdemo-backend”的目录图。由于它需要建持久卷,因此这里增加了两个文件“persistentvolume.yaml”和“persistentvolumeclaim.yaml” ( 不是自动生成的)。

值得一提的是k8s对象的命名。一般情况下,如果不需要对其进行引用,用chart的全名就行了。例如部署的名称,如下所示。

name: {{ include "k8sdemo.fullname" . }}

如果是服务名(Service Name),它需要在应用程序和k8s之间共享,也需要在父chart和子chart之间共享,这时最好单独定义一个全局共享常量。

在“values.yaml”中定义:

global:
  k8sdemoBackendService: k8sdemo-backend-service

在“service.yaml”中引用:

name: {{.Values.global.k8sdemoBackendService}}

调试“k8sdemo-database”

它的调试方式与“k8sdemo-backend”大同小异,就不详细讲解了。

联合调试:

上面各个chart都单独调试成功之后,就要把它们合在一起进行联合调试。
在“k8sdemo”(父chart)中加入依赖关系(Chart.yaml)。

dependencies:
  - name: k8sdemo-backend
    repository: file://../k8sdemo-backend
    version: 0.1.0
  - name: k8sdemo-database
    repository: file://../k8sdemo-database
    version: 0.1.0

这里为了简单起见,没有用到chart库(Chart Repository),使用了本地目录。这里的“file://”是针对chart的根的相对路径,“file://..”就是“k8sdemo”的上级目录。

详情请参见How to refer to a helm chart in the same repository

修改全局常量("values.yaml"):

global:
  k8sdemoBackendService: k8sdemo-backend-service
  k8sdemoDatabaseService: &k8sdemoDatabaseService k8sdemo-database-service
  mysqlUserName: dbuser
  mysqlUserPassword: dbuser
  mysqlRootPassword: root
  mysqlPort: 3306
  mysqlHost: *k8sdemoDatabaseService
  mysqlDatabase: service_config

只有需要在chart之间共享的常量才需要在父chart里的"values.yaml"定义,其余的在各自子chart里的"values.yaml"定义就可以了。

键入如下命令“helm dependency update k8sdemo”,更新依赖关系

~ # vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/chart$ helm dependency update k8sdemo
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 2 charts
Deleting outdated charts

完成之后,生成的图如下所示,这时在“charts”目录下就导入了新的依赖关系“k8sdemo-backend”和“k8sdemo-database”的chart。

file

有一点需要注意的是,单独调试和联合调试时,生成的k8s配置文件大部分都是一样的,但有一个地方不同

下面是联合调试时“k8sdemo-database”的部署文件,最后一行“app.kubernetes.io/instance: ”的值是“k8sdemo”。

# Source: k8sdemo/charts/k8sdemo-database/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: k8sdemo-database
  labels:
    helm.sh/chart: k8sdemo-database-0.1.0
    app.kubernetes.io/name: k8sdemo-database
    app.kubernetes.io/instance: k8sdemo
。。。

下面是单独调试时“k8sdemo-database”的部署文件,最后一行“app.kubernetes.io/instance: ”的值是“”k8sdemo-database”。

# Source: k8sdemo/charts/k8sdemo-database/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: k8sdemo-database
  labels:
    helm.sh/chart: k8sdemo-database-0.1.0
    app.kubernetes.io/name: k8sdemo-database
    app.kubernetes.io/instance: k8sdemo-database
。。。

因为“instance”的名字是“{{ .Release.Name }}”,而单独调试和联合调试时给的“release”名字不同。而其他的值都是由配置文件决定的,因此不会有意外。

安装k8sdemo:

vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/chart$ helm upgrade k8sdemo ./k8sdemo
Release "k8sdemo" has been upgraded. Happy Helming!
NAME: k8sdemo
LAST DEPLOYED: Fri Nov 29 01:28:55 2019
NAMESPACE: default
STATUS: deployed
REVISION: 2
NOTES:
1\. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services k8sdemo)
  export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT

获取Pod名称:

vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/chart$ kubectl get pod
NAME                                          READY   STATUS    RESTARTS   AGE
k8sdemo-74cb7b997c-pgcj4                      1/1     Running   0          33s
k8sdemo-backend-5cd9d79856-dqlmz              1/1     Running   0          33s
k8sdemo-database-85855485c6-jtksb             1/1     Running   0          33s
k8sdemo-jenkins-deployment-675dd574cb-r57sb   1/1     Running   3          23d

运行程序进行测设:

vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/chart$ kubectl exec -ti k8sdemo-backend-5cd9d79856-dqlmz -- /bin/sh
~ # ./main.exe
time="2019-11-27T07:03:03Z" level=debug msg="connect to database "
time="2019-11-27T07:03:03Z" level=debug msg="dataSourceName:dbuser:dbuser@tcp(k8sdemo-database-service:3306)/service_config?charset=utf8"
time="2019-11-27T07:03:03Z" level=debug msg="FindAll()"
time="2019-11-27T07:03:03Z" level=debug msg="created=2019-10-21"
time="2019-11-27T07:03:03Z" level=debug msg="find user:{1 Tony IT 2019-10-21}"
time="2019-11-27T07:03:03Z" level=debug msg="find user list:[{1 Tony IT 2019-10-21}]"
time="2019-11-27T07:03:03Z" level=debug msg="user lst:[{1 Tony IT 2019-10-21}]"
~ #

其他问题:

由于篇幅有限,本文不可能把所有的问题都讲清楚,还有两个比较重要的问题,这里简单的提一下。

1.Secret:
本文用的都是明码,如果需要加密的话有两种方式,一种是 helm-secrets,另一种是Vault,请阅读相关文档。

2.为不同环境设置不同的常量:
本文只创建了针对一种环境的文件 ,如果你需要针对不同环境(例如DEV,QA,PROD)配置不同的参数的话,你可以在“k8sdemo”的chart里给不同的环境创建不同的"values.yaml",例如“values-dev.yaml”给DEV环境。但在子chart里,就不能这样做,因为系统要求"values.yaml"。这时,你可以在父chart的“values-dev.yaml”里为不同的子chart创建常量,这样这些常量就能覆盖子chart里定义的常量。

在“values-dev.yaml”加入下面代码。

k8sdemo-backend:
    replicaCount: 2
k8sdemo-database:
    replicaCount: 2

键入如下命令试运行:

vagrant@ubuntu-xenial:~$ cd /home/vagrant/jfeng45/k8sdemo/script/kubernetes/chart
vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/chart$ helm install --dry-run --values ./k8sdemo/values-dev.yaml --debug k8sdemo ./k8sdemo

查看结果,子chart中的相应参数已被覆盖。

详情请参阅How to set environment related values.yaml in Helm subcharts?

常见错误:

在调试过程中还是遇到了不少问题,但大多数都是与语法有关的问题,因为Helm和k8s都用的是YAML文件,而它对文件格式有着严格的要求,如果不满足要求就会报错。幸好它报错时包含了错误代码行号,这样查找起来比较容易。

  1. Pod的状态是CrashLoopBackOff

它的症状是在用“helm install --dry-run --debug”调试时没有问题,但正式运行时出了问题,用下面命令检查,Pod的状态是“CrashLoopBackOff”。

vagrant@ubuntu-xenial:~$ kubectl get pod
NAME                                           READY   STATUS             RESTARTS   AGE
k8sdemo-74cb7b997c-gn5v2                       1/1     Running            1          47h
k8sdemo-backend-6cdbb96964-tb2xd               0/1     CrashLoopBackOff   129        9h
k8sdemo-database-deployment-578fc88c88-mm6x8   1/1     Running            12         37d
k8sdemo-jenkins-deployment-675dd574cb-r57sb    1/1     Running            3          19d

这个问题我以前调试k8s时也碰到过,主要是与Docker镜像有关,但这次明明镜像是 好的。试了很多组合,最后终于发现是自动生成的代码出了问题。
在“deployment.yaml”里有下面代码,这是Helm自动生成用来测试部署的。

livenessProbe:
  httpGet:
    path: /
    port: http
readinessProbe:
  httpGet:
    path: /
    port: http

把它去掉之后就没有问题了。而且它只在特定的chart(“k8sedemo-backend”)里会出错,在“k8sdemo”里就没有问题。我现在也不是特别清楚问题在哪,只是把它暂时删除掉了。

  1. 持久卷未能绑定到持久卷申请

它的症状是宿主机的持久卷未能绑定到持久卷申请,导致持久卷申请又另外创建了一个持久卷。你用“kubectl get pv”就能看到新创建的持久卷,但实际上它是不必要的,只要把持久卷申请绑定到已有的PV上就行了。这个错误并不是每次都发生,而是随机的。大部分时间绑定正确,少数时候绑定错误。我开始想是不是因为执行k8s文件的顺序问题,但k8s文件是按照文件类别(kind)来执行的,按理来说顺序应该是正确的。再有一个可能就是时间延迟,因为创建持久卷需要时间,而如果持久卷申请没有检测到这个持久卷,那么它就会另外创建一个。如果真是这样的话,就要在创建时设定一个延迟。但它暂时来讲对我影响不大,因此就偷了一下懒,以后有时间再来调试。

源码库

完整源码的github链接:
k8sdemo

索引:

  1. CHANGES SINCE HELM 2
  2. Charts
  3. YAML
  4. Use placeholders in yaml
  5. Use YAML with variables
  6. 把应用程序迁移到k8s需要修改什么?
  7. Subcharts and Global Values
  8. How to refer to a helm chart in the same repository
  9. helm-secrets
  10. Vault
  11. How to set environment related values.yaml in Helm subcharts?

不堆砌术语,不罗列架构,不迷信权威,不盲从流行,坚持独立思考

Memo

本文转载至:https://www.cnblogs.com/code-craftsman/p/11958281.html

上一篇下一篇

猜你喜欢

热点阅读