[云原生微服务架构](九)入门HELM

实际生产中,微服务项目可能有十几个模块,若还需要进行安全访问和控制,那么需要创建诸如Role、ServiceAccount等资源。部署和版本升级时也往往需要修改或添加配置文件中的一些参数(例如:服务占用的CPU、内存、副本数、端口等),维护大量的yaml文件极不方便。

那么在CNCF的体系中是否存在这样的强力“工具”,能够简化我们部署安装过程呢?答案是存在的,Helm就是这样一款工具。

二、什么是Helm

作为CNCF的毕业项目。它的官方的定义是:Helm是一个为K8s进行包管理的工具。Helm将yaml作为一个整体管理并实现了这些yaml的高效复用,就像Linux中的yum或apt-get,它使我们能够在K8s中方便快捷的安装、管理、卸载K8s应用。

Helm基于go模板语言,用户只要提供规定的目录结构和模板文件。在真正部署时Helm模板引擎便可以将其渲染成真正的K8s资源配置文件,并按照正确的顺序将它们部署到节点上。

Helm中有三个重要概念,分别为Chart、Repository和Release。

Chart代表中Helm包。它包含在K8s集群内部运行应用程序,工具或服务所需的所有资源定义。可以类比成yum中的RPM。

Repository就是用来存放和共享Chart的地方,可以类比成Maven仓库。

Release是运行在K8s集群中的Chart的实例,一个Chart可以在同一个集群中安装多次。Chart就像流水线中初始化好的模板,Release就是这个“模板”所生产出来的各个产品。

Helm作为K8s的包管理软件,每次安装Charts 到K8s集群时,都会创建一个新的 release。你可以在Helm 的Repository中寻找需要的Chart。Helm对于部署过程的优化的点在于简化了原先完成配置文件编写后还需使用一串kubectl命令进行的操作、统一管理了部署时的可配置项以及方便了部署完成后的升级和维护。

三、Helm的架构

最新版本Helm的整体架构大致如下:

Helm客户端使用REST+JSON的方式与K8s中的apiserver进行交互,进而管理deployment、service等资源,并且客户端本身并不需要数据库,它会把相关的信息储存在K8s集群内的Secrets中。

Helm的目录结构

假设我们的Chart名称叫做myChart,我们可以使用命令:

$ Helm create myChart

创建一个初始模板工程,那么在名为myChart的目录下包含了以下目录和文件:

图 1-1 Helm安装包起始目录结构

其中关键的目录和文件作用如下:

★ templates/ 目录包含了模板文件。Helm会通过模板渲染引擎渲染所有该目录下的文件来生成Chart,之后将收集到的模板渲染结果发送给K8s。

★ values.yaml 文件对于模板也非常重要。这个文件包含了对于一个Chart的默认值 。这些值可以在用户执行Helm install 或 Helm upgrade时指定新的值来进行覆盖。

★ Chart.yaml 文件包含对于该Chart元数据描述。这些描述信息可以在模板中被引用。

★ _helper.tpl 包含了一些可以在Chart中进行复用的模板定义。

★ 其他诸如deployment.yaml、service.yaml、ingress.yaml文件,就是我们用于生成K8s配置文件的模板,Helm默认会按照如下的顺序将生成资源配置发送给K8s:

Namespace -> NetworkPolicy -> ResourceQuota -> LimitRange -> PodSecurityPolicy --> PodDisruptionBudget -> ServiceAccount -> Secret -> SecretList -> ConfigMap -> StorageClass -> PersistentVolume -> PersistentVolumeClaim -> CustomResourceDefinition -> ClusterRole -> ClusterRoleList -> ClusterRoleBinding -> ClusterRoleBindingList -> Role -> RoleList -> RoleBinding -> RoleBindingList -> Service -> DaemonSet -> Pod -> ReplicationController -> ReplicaSet -> Deployment -> HorizontalPodAutoscaler -> StatefulSet -> Job -> CronJob -> Ingress -> APIService

四、Helm的命令

通过在Helm的客户端中键入helm –help,我们可以看到如下截图:

其中比较常用的命令如下:

★helm create 创建一个Helm Chart初始安装包工程

★helm search 在Helm仓库中查找应用

★helm install 安装Helm

★helm list 罗列K8s集群中的部署的Release列表

★helm lint 对一个Helm Chart进行语法检查和校验

五、Helm Chart实战

准备工作和目标

首先我们准备一个基于spring-boot工程的镜像,我们无需关注工程本身的业务功能,重点介绍下这个镜像在K8s集群下资源配置。假设这个镜像已经存在于K8s的镜像仓库中,镜像名称为cnp-order,标签为1.0.0,如下图所示

图1-2 演示镜像情况截图

镜像在K8s下的deployment.yaml配置文件内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cnp-order
  namespace: paas-cnp
spec:
  # 此处为程序将要启动的 pod 副本数量
  # 当副本为 1 时,pod 启动在 K8S 的 master 节点上
  replicas: 1
  selector:
    # 此处为 K8S 的 selector label
    matchLabels:
      app: cnp-order
  template:
    metadata:
      labels:
        app: cnp-order
    spec:
      volumes:
        - name: order-application
          configMap:
            # 指定该 volume 对应到 K8S 集群中相应的configMap name
            name: order-application
      containers:
        - name: cnp-order
          image: cnp-order:1.0.0
          imagePullPolicy: IfNotPresent
          # 声明 volume 到 container 的卷加载信息
          volumeMounts:
             # 此处对应 volume name 为 order-application 的卷加载信息
            - mountPath: /apps/conf/cnp-order/application.yml
              name: order-application
              subPath: application.yml

从上面的配置中,我们可以看到这个deployment存在于名为paas-cnp的namespace下,并且spring-boot依赖的配置文件application.yml被设置成了一个名为order-application的configmap,deployment挂载了这个configmap。同时,我们发现配置中一些配置值存在着对应关系(如volumes第一个的name和volumeMounts第一个的name), replicaCount被固定设置成了1(但它应该根据不同的环境在安装时被修改)。

我们在K8s中部署安装该应用过程是:首先创建namespace,再创建application.yaml文件并修改其内容,生成创建configmap,最后再创建deployment。

我们的目标是通过Helm,把上述安装过程简化,同时更好的管理配置中存在关联关系的配置值。最后,deployment的后期维护和升级也能通过Helm命令进行。

附上应用的application.yaml内容:

server:
  port: 9094
  servlet:
    context-path: /api/order/v1
spring:
  application:
    # 服务的实例名, 服务间的调用通过此名字调用
    name: cnp-order
  datasource:
    url: jdbc:mysql:// {ip}/cnp-order?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: ********

备注:置中username和password只是用作示例说明

初始化工程语法分析

初始化工程中deployment.yaml的内容涵盖了我们在制作Chart时会使用到的大部分语法,在开始创作我们的Chart前,我们有必要对其进行分析,了解基本的语法使用。deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myChart.fullname" . }}
  labels:
    {{- include "myChart.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "myChart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "myChart.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "myChart.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

图1-3红框部分,在常规K8s配置文件中containers的image值被替换成了模板变量{{ .Values.image.repository }},这是go模板指令语法,其中Values是Helm的内置对象,此处“.Values”可理解为工程内的values.yaml文件,image.repository表示访问其中image片段下的repository值。我们可以根据以上含义在values.yaml内找到图1-4的片段,Helm在渲染时会把该变量替换成nginx。

图1-3 示例配置片段1

图1-4 values.yaml配置片段1

图1-5红框部分,我们观察到其中的变量写法变得更加的复杂,我们首先在values.yaml中找到resources的片段,如图1-6所示。表达式前面还有一个指令toYaml,这是Helm中函数的用法,语法为“函数名 变量”,此处toYaml .values.resources表示把获取到的变量以yaml的形式原封不动进行回显(更多函数可以在官网查找到),渲染后的结果应该为:

resources:
  limits:
    cpu: 100m
    memory: 128Mi
  requests:
    cpu: 100m
    memory: 128Mi

这明显不符合我们的期望,Helm中支持类似linux中管道的语法,即表达式中 | nindent 12部分,nindent 12是函数作用是在内容前面增加12个空格,toYaml .Values.resources | nindent 12表示把获取到内容通过管道传给nindent 12函数处理。所以整个表达式综合起来渲染后的结果类似:

resources:
  limits:
    cpu: 100m
    memory: 128Mi
  requests:
    cpu: 100m
    memory: 128Mi

而最前面的“-”表示去除表达式之前的空行。

图1-5 实例配置片段2

图1-6 values.yaml配置片段2

图1-7红框部分,include “myChart.selectorLabels”表示获取_helper.tpl中myChart.selectorLabels模板定义的内容,通过图1-8我们可以看到它返回的是一个类似:

app.kubernetes.io/name: {{ include "myChart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}

的格式,第二句中{{ .Release.Name }}表示获取内置对象Release的名称,这个值是在我们安装Chart的时候命令行输入的。第一句{{ include “myChart.name” . }}又是一个类似于1-7的语句,我们同样可以在_helper.tpl中找到”myChart.name”的定义,如图1-9所示。它的语句意思是首先使用values.yaml中的nameOverride的值作为返回值,若配置的nameOverride为空,则把当前Chart的Name属性当做返回值。

图1-7 实例配置片段3

图1-8 _helper.tpl配置片段1

图1-9 _helper.tpl配置片段2

开始创作

通过上一节的内容介绍,我们已经了解了Helm模板的一些基本语法,回顾我们在“准备工作和目标”章节中的目标:简化安装过程,同时更好的管理配置中存在关联关系的配置值。最后,deployment的后期维护和升级也能通过Helm命令进行。既是把“准备工作和目标”章节中所列的K8s配置标红部分,通过模板语法进行替换,再增加namespace.yaml模板配置,我们的Chart安装包就制作完成了。

具体步骤:1. 首先删除myChart工程下template/目录下的所有目录和文件;2. 在template目录下创建名为namesapce.yaml、configmap.yaml、deployment.yaml模板配置文件,内容如下:

namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: {{ .Values.namespace }}

configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Values.volumes.application }}
  namespace: {{ .Values.namespace }}
data:
  application.yml: |
    server:
      port: {{ .Values.service.targetPort }}
      servlet:
        context-path: /api/order/v1
    spring:
      application:
        # 服务的实例名, 服务间的调用通过此名字调用
        name: {{ .Values.nameOverride }}
      datasource:
        url: jdbc:mysql://{{ .Values.config.mysql.server }}/cnp-order?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: {{ .Values.config.mysql.user }}
        password: {{ .Values.config.mysql.password }}

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.nameOverride }}
  namespace: {{ .Values.namespace }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      app: cnp-order 
  template:
    metadata:
      labels:
        app: cnp-order
    spec:
      volumes:
        - name: {{ .Values.volumes.application }}
          configMap:
            name: {{ .Values.volumes.application }}
      containers:
        - name: {{ .Values.nameOverride }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          volumeMounts:
            - mountPath: /apps/conf/cnp-order/application.yml
              name: {{ .Values.volumes.application }}
              subPath: application.yml

3. 编辑工程下的values.yaml文件,内容如下:

replicaCount: 1
namespace: paas-cnp
volumes:
  application: order-application
image:
  repository: cnp-order
  # Overrides the image tag whose default is the Chart appVersion.
  tag: 1.0.0
  pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: cnp-order
fullnameOverride: ""
autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 5
  targetCPUUtilizationPercentage: 80
  # targetMemoryUtilizationPercentage: 80

nodeSelector:
  app: cnp-order

config:
  mysql:
    server: {ip}
    user: root
    password: root

调试校验

我们可以在myChart/同级目录,使用命令:

$ Helm lint myChart/

进行语法校验,若我们使用上述的所有配置,执行后结果如图1-10所示:

图1-10 语法校验结果

我们在values.yaml中增加如下配置:

service:
  targetPort: 9094

再次执行Helm lint myChart/ 校验通过,结果如图1-11所示:

图1-11 语法校验通过图

我们也可以通过命令:

$ Helm template myChart/ --output-dir ./result

该命令会在myChart同级目录创建一个result目录,该目录内会存在模板渲染之后的配置文件,我们可以通过检查这些文件内容结合命令行的提示信息判断错误发生位置。

接着,可以进行一个预安装校验,命令如下:

$ Helm install --dry-run --debug cnp-order ./myChart

预安装会模拟正式的安装流程,但不会在环境上真正部署安装包。

安装和后续升级

通过预安装之后,当我们想要部署我们的工程时,只需要使用命令:

$ Helm install [release-name] myChart

即可完成应用的安装了。

但有个遗留问题,values.yaml 中包含了默认的安装参数,但是诸如数据库的ip、username、password,若我们不想去修改安装包,如何在安装的时候进行覆盖呢?我们只要在 install 时使用 set 选项,设置想要覆盖的参数值即可。

$ Helm install myChart-test myChart--set config.mysql.server=100.71.32.11

用户也可以在安装时指定自己的values.yaml配置。例如用户在升级的时候用 upgrade 命令,指定新的参数配置文件,即可实现在原有部署好的应用的基础上变更配置。命令如下:

$ Helm install myChart-test02 myChart -f my-values.yaml
$ Helm upgrade myChart-test02 myChart -f my-new-values.yaml

六、总结

我们可以看出Helm作为K8s的“yum工具”,确实可以解决前言背景所提到的用户和实施在部署和升级应用时所遇到的问题,并且研发人员在K8s配置文件已经存在的基础上再进行Helm安装包的制作,并不会有太多的改造量。通过Helm把一个应用所包含的yaml进行成套的管理,为我们后续安装维护带来了巨大的便利性。

本作品采用《CC 协议》,转载必须注明作者和本文链接
你还差得远呐!
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!