3-16-1-应用管理

熊 0 阅读 容器

1. Kustomize声明式应用管理

在Kubernetes系统上部署容器化应用时需要事先手动编写资源配置清单文件以定义资源对象 , 而且每一次定义的配置基本都是硬编码 , 基本无法实现复用。对于较大规模的应用场景 , 应用程序的配置、分发、版本控制、查找、回滚甚至是查看都将是用户的噩梦。

Kubernetes v1.14版本以原生方式引入的Kustomize实现了一种非模板式的应用管理能力 , 它允许用户以一个应用描述文件为基础 , 而后将保存了具体需求的" 补丁" 文件以" 叠加" 的方式在基础文件之上添加、删除或更新应用配置 , 进而生成最终的应用版本。因而 , Kustomize是一种完全基于YAML补丁系统的纯声明式配置管理工具 , 而非模板引擎 , 在使用体验上基本与Kubernetes API相契合。

Kustomize程序自身既能够集成到kubectl命令之中并以-k选项调用 , 也能够以独立的二进制文件运行。前者在帮助用户管理、变更和部署海量应用的路径上前进了一大步 , 而后者则让这个应用管理机制非常适合于扩展和集成到其他服务中 , 例如CI/CD管道等。

1.1. 声明式应用管理基本用法

Kustomize的核心目标在于为管理的应用生成资源配置 , 而这些资源配置中定义了资源的期望状态 , 在具体实现上 , 它通过 kustomization.yaml文件组合和 ( 或 ) 叠加多种不同来源的资源配置来生成。

Kustomize将一个特定应用的配置保存在专用的目录中 , 且该目录中必须有一个名为kustomization.yaml的文件作为该应用的核心控制文件。由以下kustomization.yaml文件的格式说明可以大体看出 , Kustomize可以直接组合由resources字段指定的资源文件作为最终配置 , 也可在它们的基础上进行修订 , 例如添加通用标签和通用注解、为各个资源添加统一的名称前缀或名称后缀、改动Pod模板中的镜像文件及向容器传递变量等。

apiVersion: kustomize.config.k8s.io/v1beta1 
kind: Kustomization 
resources <[]string>                      # 待定制的原始资源配置文件列表 , 将由Kustomize按顺序处理 
namespace <string>                        # 设定所有名称空间级别资源所属的目标名称空间 
commonLabels <map[string]string>          # 添加到所有资源的通用标签 , 包括Pod模板及相关的标签选择器 
commonAnnotations <map[string]string>     # 添加到所有资源的通用注解 
namePrefix <string>      # 统一给所有资源添加的名称前缀 
nameSuffix <string>      # 统一给所有资源添加的名称后缀 
images <[]Image>         # 将所有Pod模板中符合name字段条件的镜像文件修改为指定的镜像
- name <String>          # 资源清单中原有的镜像名称 , 即待替换的镜像
  newName <String>       # 要使用的新镜像名称
  newTag <String>        # 要使用的新镜像的标签
  digest <String>        # 要使用的新镜像的SHA256校验码
vars <[]Var>             # 指定可替换Pod容器参数中变量的值或容器环境变量
- name <String>          # 变量的名称 , 支持以$(name)格式进行引用
  objref <String>        # 包含了要引用的目标字段的对象的名称
  fieldref <String>      # 引用的字段名称 , 默认为metadata.name

Kustomize示例 :

假如部署一个demoapp应用 , 但是希望将来版本升级或在其它名称空间中能够复用相关的配置。

  1. 创建一个目录 , 用于保存该应用的所有配置文件。

    # mkdir kustomize-demo
  2. 编写创建应用的资源清单文件 , 这里定义一个deployment/demoapp资源和一个service资源 , 用于服务发布。

    • 编写deploy-demoapp.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: demoapp
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: demoapp
        template:
          metadata:
            labels:
              app: demoapp
          spec:
            containers:
            - name: demoapp
              image: vvoo/demoapp:v1.0
              imagePullPolicy: IfNotPresent
              ports:
              - containerPort: 80
                name: http
    • 编写service-demoapp.yaml

      apiVersion: v1
      kind: Service
      metadata:
        name: demoapp
      spec:
        selector:
          app: demoapp
        ports:
        - name: http
          protocol: TCP
          port: 80
          targetPort: 80
  3. 编写Kustomize资源清单文件kustomization.yaml , 依次引用上面创建的deployment/demoapp和service/demoapp资源。

    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    # 要组合的原始资源配置文件列表 , 它们将会按次序由kustomize来处理
    resources:
    - deploy-demoapp.yaml
    - service-demoapp.yaml
    # 统一添加资源标签 , 对于不支持修改资源标签的场景 , 该变动仅能执行资源的重新创建来完成
    commonLabels:
      generated-by: kustomize
  4. 查看最终的目录结构

    # tree kustomize-demo/
    kustomize-demo/
    ├── deploy-demoapp.yaml
    ├── kustomization.yaml
    └── service-demoapp.yaml
    ​
    0 directories, 3 files
  5. 通过kubectl apply将其应用在集群之上 , 通过-k命令指定该资源目录 , 也可以通过--dry-run选项只查看生成的配置文件而不真正进行资源的创建 , 当配置文件没问题时再直接创建。

    # kubectl apply -k ./kustomize-demo --dry-run=client -o yaml

1.2. 应用配置分解

Kustomize建议将公共或共享的资源配置放置在base子目录中 , 而将适用于特定环境或目的的配置以叠加或自定义的形式放置在各自子目录中 , 各项目可导入基础配置 ( base目录 ) , 而后进行各自所需的配置定义 , 这些子目录处于应用程序目录下的同一层级。事实上 , 若某自定义配置仍可作为公共或共享配置提供给其他更高一级的自定义配置 , 它能以同样的结构再次分割子目录 , 从而将应用目录分解为多个维度的多个层级。如下图的示例中 , 公共的基础配置放置在 base目录 , prod、test和staging从base载入配置后添加或修订适应于当前环境的设定 , 并分别放置在下级的base目录中作为各地域 ( region ) 集群上的共享配置。

kustomization.yaml支持使用bases字段装载指定路径下Kustomize格式的资源配置并以之为当前配置的基础结构 , 当前配置将叠加在基础配置之上 , 以类似Docker镜像分层的方式向上暴露最终数据。下面我们仍然以demoapp为例来说明其构建方式。

  1. 创建demoapp/base目录 , 将上面示例中的deploy-demoapp.yaml和service-demoapp.yaml资源清单 , 以及kustomization.yaml文件的副本各一份放置到该目录中作为公共的资源配置。

    # mkdir -p demoapp/base
    ​
    # cp kustomize-demo/* demoapp/base/
  2. 以demoapp/base为基础配置 , 为测试环境添加专用的资源配置并放置在demoapp/test目录下 , 之后将各个资源对象部署到test名称空间之中。编辑namespace.yaml文件。

    # mkdir demoapp/test
    
    # vim demoapp/test/namespace.yaml
    apiVersion: v1
    kind: Namespace
    metadata:
      name: test
  3. 在demoapp/test目录下创建一个kustomization.yaml配置文件 , 它使用bases字段从名为base的目录装载基础配置 , 同时添加namespace.yaml资源文件 , 并指定了如下面内容所示的其他配置。

    # vim demoapp/test/kustomization.yaml
    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    bases:                     # 从指定的目录或仓库载入基础配置
    - ../base/                 # 上级目录中的base目录
    resources:                 # 添加其他的资源清单
    - namespace.yaml
    namespace: test            # 为资源指定目标名称空间
    commonLabels:              # 统一添加指定的标签
      environment: test
    commonAnnotations:         # 统一添加指定的注解信息
      ik8s.io/app: "demoapp"
    images:                    # 设定镜像信息
    - name: "vvoo/demoapp"
      newTag: "v1.1"           # 指定新的镜像标签
  4. 通过创建该资源 , 验证其相关的信息。同样可以通过kubectl apply -k命令的--dry-run和-o yaml选项确认最终的配置 , 无误后再进行创建。如下面结果所示

    # kubectl apply -k demoapp/test --dry-run=client -o yaml
    ......
    - apiVersion: apps/v1
      kind: Deployment
      metadata:
        annotations:                    # 由test添加
          ik8s.io/app: demoapp
        labels:
          environment: test             # 由test添加
          generated-by: kustomize
        name: demoapp
        namespace: test
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: demoapp
            environment: test           # 由test添加
            generated-by: kustomize     # 由base添加
        template:
          metadata:
            annotations:
              ik8s.io/app: demoapp      # 由test添加
            labels:
              app: demoapp
              environment: test
              generated-by: kustomize
          spec:
            containers:
            - image: vvoo/demoapp:v1.1  # 由test修改
    ......

确认无误后 , 将最终的资源配置仍使用上面命令创建到集群上即可完成资源管理。当需要额外配置prod和staging等其他环境时 , 配置方式与此类似。进一步地 , 在为特定环境生成适用于不同集群的配置也可以使用类似上面的配置机制完成。

1.3. 配置生成器

在Kustomize的语境中 , 能够生成完整资源配置的指令都称为配置生成器 ( generator ) , 例如上面使用的resources就是一种从指定的资源清单直接生成配置的方式 , 而bases则是基于已有Kustomize项目载入基础配置 , 它们都是生成器类型的指令。另外两个常用的生成器是configmapGenerator和secretGenerator , 它们能够根据指定的直接值或者文件生成相应类型的资源 , 而且generatorOptions字段还可以为生成资源统一添加标签和注解信息 , 具体的使用格式及简要功能说明如下所示。

configMapGenerator <[]ConfigMapGeneratorArgs>   # ConfigMap资源生成器列表 
- name <String>                                 # ConfigMap资源的名称 , 会受到namePrefix和nameSuffix的影响 
  namespace <String>                            # 资源所在的名称空间 , 会覆盖Kustomize级别的名称空间设定
  behavior <String>                             # 与上级同名资源的合并策略 , 可用取值为create/replace/merge
  files <[]String>                              # 从指定的路径加载文件生成ConfigMap , 要使用当前项目的相对路径
  literals <[]String>                           # 从指定的key=value格式的直接值生成ConfigMap
  env <String>                                  # 从指定的环境变量文件中加载key=value格式的环境变量作为资源数据
secretGenerator <[]secretGeneratorArgs>         # Secret资源生成器列表
- name <String>                                 # Secret资源的名称 , 会受到namePrefix和nameSuffix的影响
  namespace <String>                            # 资源所在的名称空间 , 会覆盖Kustomize级别的名称空间设定
  behavior <String>                             # 与上级同名资源的合并策略 , 可用取值为 create/replace/merge
  files <[]String>                              # 从指定的路径加载文件生成Secret , 加载起始于当前项目的相对路径
  literals <[]String>                           # 从指定的key=value格式的直接值生成Secret
  type <String>                                 # Secret资源的类型 , 且kubernetes.io/tls有特殊的键名要求
generatorOptions <GeneratorOptions>             # 当前kustomization.yaml中的ConfigMap
  labels <map[String]String>                    # 为当前kustomization.yaml中所有生成的资源添加的标签
  annotations <map[String]String>               # 为生成的所有资源添加的注解
  disableNameSuffixHash <Boolean>               # 是否禁用hash名称后缀 , 默认为启用

这里示例在demoapp应用的demoapp/staging目录下准备专用于staging环境的应用配置。它与此前的test环境几乎完全相似 , 而我们希望在此基础上为该环境添加专用的ConfigMap和Secret资源 , 配置示例如下。

  1. 首先准备tsl.crt和tls.key文件。

    # mkdir demoapp/staging/secrets -p
    
    # openssl genrsa -out demoapp/staging/secrets/tls.key 2048
    
    # openssl req -new -x509 -key demoapp/staging/secrets/tls.key -out demoapp/staging/secrets/tls.crt -subj /C=CN/ST=ShenZhen/L=ShenZhen/O=DevOps/CN=demo.ikubernetes.io -days 3650
  2. 编写创建名称空间的资源清单

    # vim demoapp/staging/namespace.yaml
    apiVersion: v1
    kind: Namespace
    metadata:
      name: staging
  3. 编写kustomization.yaml

    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    bases:                     # 从指定的目录或仓库载入基础配置
    - ../base/                 # 上级目录中的base目录
    resources:                 # 添加其他的资源清单
    - namespace.yaml
    namespace: staging         # 为资源指定目标名称空间
    commonLabels:              # 统一添加指定的标签
      environment: staging
    commonAnnotations:         # 统一添加指定的注解信息
      ik8s.io/app: "demoapp"
    configMapGenerator:        # 根据指定键值生成ConfigMap资源
    - name: demoapp-conf
      literals:                # 直接值列表 , 它们将组合在同一个ConfigMap资源中
      - host="0.0.0.0"
      - port="8080"
    secretGenerator:           # 根据文件生成Secret资源
    - name: demoapp-ssl
      files:                   # 文件列表
      - secrets/tls.crt        # 证书文件的健名必须为tls.crt , 因而准备的文件名也得为tls.crt
      - secrets/tls.key        # 证书文件的健名必须为tls.key , 因而准备的文件名也得为tls.key
      type: "kubernetes.io/tls"
    generatorOptions:          # 生成器专用选项 , 禁止为生成的资源自动添加随机的hash后缀
      disableNameSuffixHash: true
    images:                    # 设定镜像信息
    - name: "vvoo/demoapp"
      newTag: "v1.1"           # 指定新的镜像标签
  4. 通过创建该资源 , 验证其相关的信息。同样可以通过kubectl apply -k命令的--dry-run和-o yaml选项确认最终的配置 , 无误后再进行创建。如下面结果所示

    # kubectl apply -k demoapp/staging --dry-run=client -o yaml
    ......
    - apiVersion: v1
      data:
        host: 0.0.0.0
        port: "8080"
      kind: ConfigMap
      metadata:
        annotations:
          ik8s.io/app: demoapp
        labels:
          environment: staging
        name: demoapp-conf
        namespace: staging
    - apiVersion: v1
      data:
        tls.crt: |
          LS0tLS1CRUdJTiBDRVJUSUZJ......
        tls.key: |
          LS0tLS1CRUdJTiBSU0EgUFJJ......
      kind: Secret
      metadata:
        annotations:
          ik8s.io/app: demoapp
        labels:
          environment: staging
        name: demoapp-ssl
        namespace: staging
      type: kubernetes.io/tls
    ......

生成的配置还要demoapp应用能够正确载入及使用 , deployment/demoapp资源在Pod模板中默认不以任何形式调用ConfigMap和Secret资源 , 我们可以通过覆盖该资源定义或者为其添加补丁来自定义其他的配置信息。

1.4. 资源补丁

除了资源配置的整体覆盖或替换 , Kustomize还提供了两种补丁机制以完善基础资源配置的定义 , 补丁机制同样以叠加的方式增加、修改或删除资源或资源属性的配置。相关的配置格式及简要功能如下。

patchesJson6902 <[]Json6902>       # 由各待补对象及其补丁文件所组成的列表
  path <String>                    # 补丁文件 , 不含有目标资源对象的信息 , 支持JSON或YAML格式
  target <Target>                  # 待补资源对象
    group <String>                   # 资源所属的群组
    version <String>                 # API版本
    kind <String>                    # 资源类型
    name <String>                    # 资源对象的名称
    namespace <string>               # 资源对象所属的名称空间
patchesStrategicMerge <[]string>   # 将补丁补到匹配的资源之上 , 匹配的方式是根据资源Group/Version/Kind+Name/Namespace判断

同样 , 这里以上面demoapp在demoapp/prod目录下准备了专用于production环境的应用配置 , 它与此前的staging环境几乎完全相似 , 而我们希望在此基础为该环境添加专用的ConfigMap和Secret资源 , 下面的kustomization.yaml示例文件就给出了所需的相关配置项 , 其中依赖的补丁文件需要事先准备妥当。

  1. 准备补丁文件

    # mkdir -p demoapp/prod/patches/
    
    # vim demoapp/prod/patches/patch-service-demoapp-targetport-8080.yaml 
    - op: replace          # 替换式补丁
      path: /spec/ports/0/targetPort   # .spec.ports[0].targetPort字段
      value: 8080          # 将上面字段的值修改为此处指定的值
    - op: add              # 添加新的配置项
      path: /spec/ports/1  # .spec.ports[1]列表项
      value:               # 添加的内容为以下几个字段
        name: https
        protocol: TCP
        port: 443
        targetPort: 8443
    
    
    # vim demoapp/prod/patches/demoapp-add-configmap-add-secret.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: demoapp
    spec:
      template:
        spec:
          containers:
          - name: demoapp                 # 补丁适配的目标位置 , 即Pod目标中名为demoapp的容器
            env:                          # 此处起至尾部都是补丁的内容
            - name: PORT
              valueFrom:
                configMapKeyRef:
                  name: demoapp-conf
                  key: demoapp.port
                  optional: false
            - name: HOST
              valueFrom:
                configMapKeyRef:
                  name: demoapp-conf
                  key: demoapp.host
                  optional: true
            volumeMounts:
            - name: demoappcerts
              mountPath: /etc/demoapp/certs/
              readOnly: true
          volumes:
          - name: demoappcerts
            secret:
              secretName: demoapp-ssl

  2. 编写创建名称空间的资源清单

    # vim demoapp/prod/namespace.yaml
    apiVersion: v1
    kind: Namespace
    metadata:
      name: prod
  3. 编写kustomization.yaml

    # vim demoapp/prod/kustomization.yaml 
    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    bases:                     # 从指定的目录或仓库载入基础配置
    - ../base/                 # 上级目录中的base目录
    resources:                 # 添加其他的资源清单
    - namespace.yaml
    namespace: prod            # 为资源指定目标名称空间
    commonLabels:              # 统一添加指定的标签
      environment: prod
    commonAnnotations:         # 统一添加指定的注解信息
      ik8s.io/app: "demoapp"
    configMapGenerator:        # 根据指定键值生成ConfigMap资源
    - name: demoapp-conf
      literals:                # 直接值列表 , 它们将组合在同一个ConfigMap资源中
      - host="0.0.0.0"
      - port="8080"
    secretGenerator:           # 根据文件生成Secret资源
    - name: demoapp-ssl
      files:                   # 文件列表
      - secrets/tls.crt        # 证书文件的健名必须为tls.crt , 因而准备的文件名也得为tls.crt
      - secrets/tls.key        # 证书文件的健名必须为tls.key , 因而准备的文件名也得为tls.key
      type: "kubernetes.io/tls"
    generatorOptions:          # 生成器专用选项 , 禁止为生成的资源自动添加随机的hash后缀
      disableNameSuffixHash: true
    patchesStrategicMerge:     # 向匹配的资源打上指定文件中包含的补丁
    - patches/demoapp-add-configmap-add-secret.yaml   # 补丁文件路径
    patchesJson6902:           # 将Json6902格式的补丁补到指定的资源之上
    - path: patches/patch-service-demoapp-targetport-8080.yaml  # 补丁文件路径 
      target:
        version: v1
        kind: Service
        name: demoapp
    images:                    # 设定镜像信息
    - name: "vvoo/demoapp"
      newTag: "v1.1"           # 指定新的镜像标签

    整体目录结构如下 :

    # tree demoapp/prod/
    demoapp/prod/
    ├── kustomization.yaml
    ├── namespace.yaml
    ├── patches
    │   ├── demoapp-add-configmap-add-secret.yaml
    │   └── patch-service-demoapp-targetport-8080.yaml
    └── secrets
        ├── tls.crt
        └── tls.key
    
    2 directories, 6 files

  4. 通过创建该资源 , 验证其相关的信息。同样可以通过kubectl apply -k命令的--dry-run和-o yaml选项确认最终的配置 , 无误后再进行创建。如下面结果所示

    # kubectl apply -k demoapp/prod --dry-run=client -o yaml
    ......
    - apiVersion: apps/v1
      kind: Deployment
      metadata:
        annotations:
          ik8s.io/app: demoapp
        labels:
          environment: prod
          generated-by: kustomize
        name: demoapp
        namespace: prod
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: demoapp
            environment: prod
            generated-by: kustomize
        template:
          metadata:
            annotations:
              ik8s.io/app: demoapp
            labels:
              app: demoapp
              environment: prod
              generated-by: kustomize
          spec:
            containers:
            - env:
              - name: PORT
                valueFrom:
                  configMapKeyRef:
                    key: demoapp.port
                    name: demoapp-conf
                    optional: false
              - name: HOST
                valueFrom:
                  configMapKeyRef:
                    key: demoapp.host
                    name: demoapp-conf
                    optional: true
              image: vvoo/demoapp:v1.1
              imagePullPolicy: IfNotPresent
              name: demoapp
              ports:
              - containerPort: 80
                name: http
              volumeMounts:
              - mountPath: /etc/demoapp/certs/
                name: demoappcerts
                readOnly: true
            volumes:
            - name: demoappcerts
              secret:
                secretName: demoapp-ssl

JSON Patch的格式考站点http://jsonpatch.com/上的说明。

上面的示例步骤仅是为了循序渐进地说明如何构建分层的Kustomize应用 , 具体应用时 , 共享的配置部分通常都应该归置到基础库(base) 中 , 仅个别需要定制的内容由最上级应用来提供 , 而且每一个变更都可以纳入Git仓库中 , 以便在需要时进行跟踪和溯源。

2. Helm介绍

Helm是一款将Kubernetes应用打包为" 图表" 格式 , 并基于该格式完成应用管理的工具。 Helm最初由Deis公司 ( 后被Microsoft公司收购 ) 创建并于2015年年底发布 , 后将该项目加入CNCF社区。 与Kustomize使用JSON ( 或YAML ) 与叠加机制生成最终配置有所不同的是 , Helm是一个模板引擎 , 它使用模板与值文件 ( value.yaml ) 来构建最终的配置清单。换个角度来说 , Helm类似于Linux系统上的yum或 apt-get等包管理器 , 可以帮助用户查找、分享及管理Kubernetes应用程序。

2.1 为何需要Helm

kubernetes上的应用对象 , 都是由特定的资源描述组成 , 包括deployment、service等 , 都保存各自文件中或集中写到一个配置文件中 , 然后通过kubectl apply -f ...进行部署。

kubernetes-helm-base-1

如果应用只是一个或几个这样的服务组成 , 上面的部署方式足够了。而对于一个复杂的应用 , 会有很多类似上面的资源文件 , 例如为服务架构应用 , 组成应用的服务可能多达十个 , 几十个。如果有更新或回滚应用的需求 , 可能要修改和维护所涉及的大量资源文件 , 而这种组织和管理应用的方式就显得力不从心。且由于缺少对发布过的应用版本管理和空置 , 使Kubernetes上的应用和更新等面临诸多的挑战。主要面临以下几个问题 :

  • 如何管理、编辑和更新这些分散的kubernetes应用配置文件。

  • 如何把一套相关的配置文件作为一个应用进行管理。

  • 如何分发和重用kubernetes的应用配置。

2.2. Helm基础

Helm把Kubernetes的资源打包到一个Chart中 , 并将制作、测试完成的各个Chart保存到仓库进行存储和分发。Helm还实现了可配置的发布 , 它支持应用配置的版本管理 , 简化了Kubernetes部署应用的版本控制、打包、发布、删除、更新等操作 , 它有如下几个关键概念。

  • Chart : 即一个Helm程序包 , 它包含了运行一个Kubernetes应用所需要的镜像、依赖关系和资源定义等 , 它类似于APT的dpkg文件或者yum的rpm文件。

  • Repoistory : 集中存储和分发Chart的仓库 , 类似于Perl的CPAN , 或者Python的PyPI等。

  • Config : Chart实例化安装运行时使用的配置信息。

  • Release : Chart实例化配置后运行于Kubernetes集群中的一个应用实例;在同一个集群上 , 一个Chart可以使用不同的Config重复安装多次 , 每次安装都会创建一个新的Release ( 发布 ) 。

因此 , Chart更像是存储在Kubernetes集群之外的程序 , 它的每次安装是指在集群中使用专用配置运行一个实例 , 执行过程有点类似于在操作系统上基于程序启动一个进程。

通常 , 用户在Helm客户端本地遵循其格式编写Chart文件 , 而后即可部署在Kubernetes集群上 , 运行为一个特定的Release。仅在有分发需求时 , 才应该把同一应用的Chart文件打包成归档压缩格式提交到特定的Chart仓库。仓库可以运行为公共托管平台 , 也可以是用户自建的服务器 , 仅供特定的组织或个人使用。

目前 , Helm的主流可用版本主要有v2和v3两个。版本v2中 , Helm主要由与用户交互的客户端、与 Kubernetes API交互的服务端Tiller和Chart仓库 ( repository ) 组成 , 如下图所示。Helm的客户端是一个命令行工具 , 采用Go语言开发 , 它主要负责本地Chart开发、管理Chart仓库 , 以及基于gRPC协议与 Tiller交互 , 从而完成应用部署、查询等管理任务。而Tiller服务器则托管运行在Kubernetes上 , 负责接收Helm客户端请求、将Chart转换为最终配置以生成一个Release , 随后部署、跟踪以及管理各Release等功能。

2.2.1. Helm v2工作原理

下图描述了 Helm 的几个关键组件 Helm ( 客户端 ) 、Tiller ( 服务器 ) 、Repository ( Chart软件仓库 ) 、Chart ( 软件包 ) 之间的关系。

kubernetes-helm-base-2

Chart Install 过程

  • Helm 从指定的目录或者 TAR 文件中解析出 Chart 结构信息。

  • Heml 将指定的 Chart 结构和 Values 信息通过 gRPC 传递给 Tiller。

  • Tiller 根据 Chart 和 Values 生成一个 Release。

  • Tiller 根据 Release 发送给 Kubernetes 用于生成 Release。

Chart Update 过程

  • Helm 从指定的目录或者 TAR 文件中解析出 Chart 结构信息。

  • Helm 将需要更新的 Release 的名称、Chart结构和 Values 信息传递给 Tiller。

  • Tiller 生成 Release 并更新指定名称的 Release 和 History。

  • Tiller 将 Release 发送给 Kubernetes 用于更新 Release。

Chart Rollback 过程

  • Helm 将要回滚的 Release 的名称传递给 Tiller。

  • Tiller 根据 Release 的名称查找 History。

  • Tiller 从 History 中获取上一个 Release。

  • Tiller 将上一个 Release 发送给 Kubernetes 用于替换当前 Release。

Chart 处理依赖说明

Tiller 在处理 Chart 时 , 直接将 Chart 以及其依赖的所有 Charts 合并为一个 Release , 同时传递给 Kubernetes。因此 Tiller 并不负责管理依赖之间的启动顺序。Chart 中的应用需要能够自行处理依赖关系。

2.2.2. Helm v3

Helm2时期 , 添加了Tiller组件和GRPC来处理Helm chart的安装和管理 , 呈现chart并将它们推送到Kubernetes API服务器 , 2019年11月13日 , Helm 团队发布了 Helm v3 的第一个稳定版本

架构变化

很明显从helm v3移除了 Tiller

kubernetes-helm-base-3

Helm 3新特性

  • Release名称可以在不同命名空间重用

  • 支持将 Chart 推送至 Docker 镜像仓库中

  • 使用JSONSchema验证chart values

  • 其他特性如下

  • 为了更好地协调其他包管理者的措辞 Helm CLI个别更名

    • helm delete 更名为 helm uninstall

    • helm inspect 更名为 helm show

    • helm fetch 更名为 `helm pull

但以上旧的命令当前仍能使用

  • 移除了用于本地临时搭建 Chart Repository的 helm serve 命令。

  • 自动创建名称空间

    在不存在的命名空间中创建发行版时 , Helm 2创建了命名空间。Helm 3遵循其他Kubernetes对象的行为 , 如果命名空间不存在则返回错误。

  • 不再需要requirements.yaml, 依赖关系是直接在chart.yaml中定义。

版本v2进化到版本v3的过程中 , Helm客户端基本保持了原貌 , 但肩负重要任务的服务端组件Tiller被移除 , 取而代之是专用的CRD资源。换句话说 , 版本3时代的Helm使用CRD将Release直接保存到Kubernetes之上 , 且无须再跟踪各Release的状态 , 而将Chart渲染成Release的功能也移往Helm客户端 , 从而不必再用到Tiller组件。

事实上 , 由于Tiller拥有管理Kubernetes集群的密钥 , 在集群内公开了无须身份验证的gRPC端点且又无法在客户端用户上实现RBAC方式的授权管理功能 , 从而为Kubernetes集群引入了许多不确定的安全风险。因而 , 移除Tiller也是势在必行。好在 , Helm的优势都基本保持未变 , 例如下面的这些优势依然存在。

  • 管理复杂应用 : Chart能够描述哪怕是最复杂的程序结构 , 提供了可重复使用的应用安装的定义。

  • 易于升级 : 使用就地升级和自定义钩子来解决更新的难题。

  • 简单分享 : Chart易于通过公共或私有服务完成版本化、共享及主机构建 , 且目前有众多成熟的Chart可供使用。

  • 回滚 : 使用helm rollback命令轻松实现快速回滚。

1.3. Helm 部署

helm 部署方式有很多 , 可以参考官方文档

1.3.1. Helm v2部署

  1. 下载helm

    # wget https://mirrors.huaweicloud.com/helm/v2.16.9/helm-v2.16.9-linux-amd64.tar.gz
  2. 安装配置

    # tar xvzf helm-v2.16.9-linux-amd64.tar.gz
    # cp -av linux-amd64/helm /usr/bin/
    # cp -av linux-amd64/tiller /usr/bin/
  3. 指定配置文件 ( 此步骤视情况而定 , 比如rke部署的k8s集群则需要;kubeadm部署的集群默认在master节点则不需要 )

    # vim /root/.bashrc
    alias helm='helm --kubeconfig /home/rancher/kube_config_cluster.yml'
    # source /root/.bashrc
  4. 在kube-system命名空间中创建serviceaccount

    # kubectl create serviceaccount --namespace kube-system tiller
  5. 创建ClousterRoleBinding以授予tiller账户对集群的访问权限

    # kubectl create clusterrolebinding tiller-cluster-admin --clusterrole=cluster-admin --group=system:serviceaccounts --namespace=kube-system:tiller
  6. 安装helm server ( tiller ) ,注意保持版本号相同

     helm init --service-account tiller --upgrade -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.16.9  --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts --force-upgrade
  7. 查看tiller服务是否正常运行

    # kubectl get pod -n kube-system -l app=helm
    NAME                             READY   STATUS    RESTARTS   AGE
    tiller-deploy-79f8998f84-85lwq   1/1     Running   0          45s
  8. 查看版本 ( 注意 : client和server版本必须要保持一致 )

    # helm version
    Client: &version.Version{SemVer:"v2.16.9", GitCommit:"0e7f3b6637f7af8fcfddb3d2941fcc7cbebb0085", GitTreeState:"clean"}
    Server: &version.Version{SemVer:"v2.16.9", GitCommit:"0e7f3b6637f7af8fcfddb3d2941fcc7cbebb0085", GitTreeState:"clean"}

    补充 : 如果需要在kubernetes中卸载已部署的 Tiller , 可使用如下命令进行卸载

    # helm reset

1.3.2. helm v3部署

helm v3已经移除了tiller , 所以只需要部署helm客户端即可 , 即解压软件移动命令即可

# wget https://get.helm.sh/helm-v3.9.0-linux-amd64.tar.gz

# tar xvzf helm-v3.9.0-linux-amd64.tar.gz

# cp -av linux-amd64/helm /usr/bin/

# helm version
version.BuildInfo{Version:"v3.9.0", GitCommit:"7ceeda6c585217a19a1131663d8cd1f7d641b2a7", GitTreeState:"clean", GoVersion:"go1.17.5"}

Helm的各种管理功能均通过其子命令完成 , 例如要获取其简要使用帮助 , 直接使用如下所示的help子命令即可。但需要注意的是 , Helm与Kubernetes API Server通信依赖于本地安装并配置的kubectl , 因此运行Helm的节点也应该是可以正常使用kubectl命令的主机 , 或者至少是有着可用kubeconfig配置文件的主机。

2. Helm 3快速入门

介于轻量、安全性等特性 , 这里以v3版本进行使用示例。

2.1 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

push

将本地chart推送到远端仓库中

registry

登陆远端仓库

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客户端版本

2.2 添加Chart仓库

Helm可基于本地自行开发的Chart或经由公开仓库获取到的Chart完成应用管理。Helm项目维护的stable和incubator仓库中保存了一系列精心制作与维护的Chart , 因而使用Helm完成应用管理的第一步通常是将它们添加为helm命令可以使用的本地仓库 , 从而能够使用内部的Chart。

常用仓库如下 :

  1. 添加仓库

    # helm repo add bitnami https://charts.bitnami.com/bitnami
    "bitnami" has been added to your repositories
    
    # helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
    "prometheus-community" has been added to your repositories

  2. 更新元数据

    # helm repo update 
    Hang tight while we grab the latest from your chart repositories...
    ...Successfully got an update from the "prometheus-community" chart repository
    ...Successfully got an update from the "bitnami" chart repository
    Update Complete. ⎈Happy Helming!⎈
  3. 查看本地配置的Chart仓库

    # helm repo list
    NAME                    URL                                               
    bitnami                 https://charts.bitnami.com/bitnami                
    prometheus-community    https://prometheus-community.github.io/helm-charts
  4. 在Chart仓库搜索软件包

    // 在本地配置的所有Chart仓库中的搜索软件包
    # helm search repo etcd 
    NAME            CHART VERSION   APP VERSION     DESCRIPTION                                       
    bitnami/etcd    8.3.3           3.5.4           etcd is a distributed key-value store designed ...
    
    
    // 在整个Helm hub中进行搜索 , 意味着在线搜索Hub中存在的所有仓库。
    # helm search hub etcd 
    URL                                                     CHART VERSION   APP VERSION     DESCRIPTION                                       
    https://artifacthub.io/packages/helm/bitnami-ak...      8.3.3           3.5.4           etcd is a distributed key-value store designed ...
    https://artifacthub.io/packages/helm/bitnami/etcd       8.3.3           3.5.4           etcd is a distributed key-value store designed ...
    https://artifacthub.io/packages/helm/cloudnativ...      2.2.3           3.3.13          etcd is a distributed key value store that prov...
    https://artifacthub.io/packages/helm/riftbit/etcd       6.8.4           3.5.0           etcd is a distributed key value store that prov...
    https://artifacthub.io/packages/helm/openstack-...      0.1.2           v3.4.3          OpenStack-Helm etcd                
    ......

2.3 部署应用示例

如果要查看某个Chart的详细信息 , 可以使用helm show命令 , 它能通过all子命令显示chart的所有信息 , 或者通过chart、readme和values子命令分别显示Chart的定义、README信息及默认的值文件 ( values.yaml ) 内容。如下面的命令结果所示

// 查看Chart的详细信息
# helm show all bitnami/etcd
annotations:
  category: Database
apiVersion: v2
appVersion: 3.5.4
dependencies:
- name: common
  repository: https://charts.bitnami.com/bitnami
  tags:
  - bitnami-common
  version: 1.x.x
description: etcd is a distributed key-value store designed to securely store data
  across a cluster. etcd is widely used in production on account of its reliability,
  fault-tolerance and ease of use.
......


// 查看readme信息
# helm show readme bitnami/etcd

// 查看values.yaml的内容
# helm show values bitnami/etcd

helm部署etcd示例 :

将Chart中定义的应用部署到Kubernetes集群上生成的部署实例称为Release , 一个集群上可能会存在某一Chart的多个实例 , 而Release名称便成了区分这些实例的关键标识 。 因此 , 部署命令helm install便要求为各Release指定一个专有的名称 , 如下面的命令格式所示。

helm install [NAME] [CHART] [flags]

Helm是一个模板引擎 , 它使用值文件渲染模板生成具体的资源配置 , 但每个Chart通常都要为其模板提供一个具有通用目的的默认值文件 , 因而多数情况下 , 用户无须提供这类文件也能够直接部署生成一个实例。例如 , 下面的命令尝试在test名称空间中基于指定的Chart ( bitnami/etcd ) 及其默认的值文件部署出一个名为test-etcd的实例来。

# helm install test-etcd bitnami/etcd --dry-run -n test

由于上面使用了--dry-run选项 , 该命令仅会打印生成的最终配置 , 并会给出部署后的使用提示 , 若无错误信息返回且各配置项符合需要 , 移除选项即可进入安装流程。

# kubectl create namespace test

# helm install test-etcd bitnami/etcd -n test
NAME: test-etcd                           # Relase名称
LAST DEPLOYED: Tue Jul  5 14:34:54 2022   # 部署时间
NAMESPACE: test           # 名称空间
STATUS: deployed          # 部署状态
REVISION: 1               # 历史版本
TEST SUITE: None          # 测试套件状态 , 拥有测试套件的Release可由helm test命令测试
NOTES:                    # 简要使用信息
CHART NAME: etcd
CHART VERSION: 8.3.3
APP VERSION: 3.5.4

** Please be patient while the chart is being deployed **

etcd can be accessed via port 2379 on the following DNS name from within your cluster:

    test-etcd.test.svc.cluster.local

To create a pod that you can use as a etcd client run the following command:

    kubectl run test-etcd-client --restart='Never' --image docker.io/bitnami/etcd:3.5.4-debian-11-r9 --env ROOT_PASSWORD=$(kubectl get secret --namespace test test-etcd -o jsonpath="{.data.etcd-root-password}" | base64 -d) --env ETCDCTL_ENDPOINTS="test-etcd.test.svc.cluster.local:2379" --namespace test --command -- sleep infinity

Then, you can set/get a key using the commands below:

    kubectl exec --namespace test -it test-etcd-client -- bash
    etcdctl --user root:$ROOT_PASSWORD put /message Hello
    etcdctl --user root:$ROOT_PASSWORD get /message

To connect to your etcd server from outside the cluster execute the following commands:

    kubectl port-forward --namespace test svc/test-etcd 2379:2379 &
    echo "etcd URL: http://127.0.0.1:2379"

 * As rbac is enabled you should add the flag `--user root:$ETCD_ROOT_PASSWORD` to the etcdctl commands. Use the command below to export the password:

    export ETCD_ROOT_PASSWORD=$(kubectl get secret --namespace test test-etcd -o jsonpath="{.data.etcd-root-password}" | base64 -d)

命令返回的结果称为Release的状态信息 , 这些信息随后还可以通过helm status命令再次获取。另外 , helm install命令支持基于多种安装源进行应用部署 , 这包括Chart仓库、本地的Chart压缩包、本地 Chart目录 , 甚至是指定一个Chart的URL等。

使用Helm客户端列出的已经安装生成的Release的helm list命令 , 来了解该应用的部署状况。

# helm list -n test
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
test-etcd       test            1               2022-07-05 14:34:54.328687399 +0800 CST deployed        etcd-8.3.3      3.5.4

查看该Release的创建的资源对象状态 , 这里处于Peding是因为bitnami/etcd下的etcd默认创建会使用存储卷 , 这里没有存储卷所以一直处于Pending状态。

# kubectl get pods -n test
NAME          READY   STATUS    RESTARTS   AGE
test-etcd-0   0/1     Pending   0          6m32s

这里为了测试 , 将values.yaml文件保存本地 , 修改不使用PVC , 使用emptyDir作为存储卷

// 查看values.yaml文件 , 并保存在本地
# helm show values bitnami/etcd > etcd_values.yaml

// 进行修改
# vim etcd_values.yaml
......
persistence:
  ## @param persistence.enabled If true, use a Persistent Volume Claim. If false, use emptyDir.
  ##
  enabled: false   # 这里修改true为false则表示使用emptyDir作为存储卷
  ## @param persistence.storageClass Persistent Volume Storage Class
  ## If defined, storageClassName: <storageClass>
  ## If set to "-", storageClassName: "", which disables dynamic provisioning
  ## If undefined (the default) or set to null, no storageClassName spec is
  ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
  ##   GKE, AWS & OpenStack)
  ##
  storageClass: ""
......

// 修改完成 , 指定本地的values.yaml文件进行重新创建
# helm uninstall test-etcd -n test
release "test-etcd" uninstalled

# helm install test-etcd bitnami/etcd -f etcd_values.yaml -n test

待部署完成 , 再次查看其状态信息

# kubectl get pods -n test
NAME          READY   STATUS    RESTARTS   AGE
test-etcd-0   1/1     Running   0          1m3s

对于运行中的Release , 我们可以使用helm get命令获取相关的钩子、最终资源配置清单、注意事项和用户自定义的模板参数值信息 , 相关信息的获取各自依赖于相关的子命令 , 或者直接使用子命令all一次性地列出所有这些信息。

# helm get values test-etcd -n test
USER-SUPPLIED VALUES:
affinity: {}
args: []
auth:
  client:
    caFilename: ""
    certFilename: cert.pem
    certKeyFilename: key.pem
    enableAuthentication: false
    existingSecret: ""
    secureTransport: false
    useAutoTLS: false
......

升级或回滚应用 , 要分别使用helm upgrade和helm rollback命令 , 还可以使用helm history命令获取指定Release的变更历史。若各可用仓库中均不存在某应用相关的可用Chart , 用户可以自行编写Chart。

3. Helm Chart

Chart本质是描述一组Kubernetes资源配置的文件集合 , 它既能部署小到单个资源的简单应用 , 例如一个memcached Pod , 又能部署大型的复杂应用 , 例如由HTTP服务器、数据库服务器、缓存服务器和应用程序服务器等共同组成的Web应用栈 , 甚至是Istio这样的微服务网络系统等。从物理的角度来描述 , Chart就是一个遵循特定规范的目录结构 , 它能够打包成一个可用于部署的版本化归档文件。

3.1. Chart包结构与描述文件

包是多数编程语言中都存在的概念 , 常用于创建独立的名称空间以及模块化应用程序。类似地 , Chart就是Helm封装应用的格式 , 目录名即为包名或Chart名。例如 , demoapp应用Chart的目录结构应该类似如下所示。

demoapp
├── charts/                           # 当前Chart包含的其他Chart ( 也称为子Chart ) 
├── Chart.yaml                        # 当前Chart的描述信息 , YAML格式的文件
├── templates                         # 模板文件专用目录 , 目录中的文件需要发送给模板引擎进行渲染
│   ├── deployment.yaml
│   ├── _helpers.tpl
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml                       # 默认的值文件 , 用于向模板中的参数提供默认值

尽管charts和templates目录均为可选 , 但至少应该存在一个子Chart或一个模板文件 , 这意味着两个目录至少要存在一个。另外 , Helm保留使用charts/和templates/目录以及上面列出的文件名称 , 其他文件都将被忽略。

Chart.yaml是包中一个必备的重要文件 , 用于提供Chart相关的各种元数据 , 例如名称、版本、关键词、维护者信息、使用的模板引擎等。Helm版本v2与版本v3的Chart.yaml文件格式有所不同 , 版本v3主要包含以下字段。

apiVersion <String>          # Chart的API版本 , 必选字段
name <String>                # 当前Chart的名称 , 必选字段
version <String>             # 遵循语义化版本规范第2版的版本号 , 必选字段
kubeVersion <String>         # 兼容的Kubernetes版本的版本号范围
description <String>         # 当前项目的单语句描述信息
type <String>                # Chart所属的类型
keywords <[]String>          # 当前项目的关键词列表
home <String>                # 当前项目的主页URL
sources <[]String>           # 当前项目用到的源码来源url列表
dependencies <[]Object>      # 依赖的Chart的列表对象
- name <String>              # Chart名称
  version <String>           # Chart的版本
  repository <String>        # 仓库的URL或以@开头的别名
  conditions <String>        # 一个或多个YAML路径格式的条件字段 , 彼此间以逗号分隔
  tags <[]Tag>               # YAML标签列表 , 用于将依赖的Chart进行分组并按组启用或禁用
  enabled <Bool>             # 是否启用该Chart
  import-values <[]String>   # 导入子Chart中的值
  alias <String>             # Chart的别名
maintainers <[]Object>       # 项目维护者信息
- name <String>              # 项目维护人员的名字
  email <String>             # 维护人员的Email
  url <String>               # 项目维护人员的相关URL
icon <String>                # URL , 指向当前项目的图标 , SVG或PNG格式的图片
appVersion <String>          # 本项目用到的应用程序的版本号 , 且不必为语义化版本
deprecated <Bool>            # 当前Chart是否已废弃 , 布尔型值
annotations <map[String]String>  # 注解信息

上面Chart.yaml文件格式中 , 仅apiVersion、name、version为必选字段 , 余下的字段均可按需进行添加或移除。例如 , 下面的示例是 demoapp Chart中使用的Chart.yaml的内容。

apiVersion: v2
name: demoapp
description: A kubernetes-natvie application demo.
type: application
version: 0.1.0
appVersion: 1.0.0
maintainers:
  - name: test
    email: test@test.com
    url: http://www.test.com

更多Chart文件说明参考官网文档

3.2. Chart中的依赖关系

应用程序可能存在对其他应用的依赖关系 , 这类应用若对等制作为独立的Chart , 则它们反映到Helm中依然存在类似的依赖关系。这种依赖关系可通过Chart.yaml中的dependencies字段进行动态链接 , 也可直接将依赖的其他Chart存储在当前Chart的charts/目录中进行手动管理。显然 , 即便手动管理依赖关系对个别管理场景有着些许优势 , 动态管理方式依然是推荐的首选方式。

3.2.1. dependencies 字段

Chart.yaml中的dependencies字段本质上只是一个简单的依赖关系列表 , 它有类似如下配置格式中的几个可用字段。

dependencies <[]Object>    # 依赖的Chart的列表对象
- name <String>            # Chart名称
  version <String>         # Chart的版本
  repository <String>      # 仓库的URL或以@开头的别名
  conditions <String>      # 一个或多个YAML路径格式的条件字段 , 彼此间以逗号分隔
  tags <[]Tag>             # YAML标签列表 , 用于将依赖的Chart进行分组并按组启用或禁用
  enabled <Bool>           # 是否启用该Chart
  import-values <[]String> # 导入子Chart中的值
  alias <String>           # Chart的别名

定义依赖项时 , 仅name、version和repository几个为必选字段 , 且指定的各依赖项必然会被当前Chart所加载。若希望基于某种条件来决定是否启用依赖项 , 例如当前Chart是否在某个参数项启用了某项特性或某种功能等 , 则可以通过为依赖项添加conditions字段来实现。 该字段值是包含一到多个YAML格式的路径 , 但仅有第一个有效 , 它通常代表当前Chart的某个可配置的参数 , 该参数应该被解析为布尔型值 , 其结果决定了依赖项中的Chart启用与否。

另外 , 当依赖较多的Chart时 , tags字段能够将这些被依赖的 Chart分成逻辑组 , 而后在当前Chart的值文件values.yaml中根据标签统一启用或禁用。但存在冲突时 , conditions字段中的设定会覆盖tags中的定义。

示例如下 :

dependencies:
  - name: apache
    version: 1.2.3
    repository: https://example.com/charts
  - name: mysql
    version: 3.2.1
    repository: https://another.example.com/charts

一旦依赖关系文件配置完成 , 即可使用helm dependency update 命令更新依赖关系 , 并自动下载被依赖的Chart至当前包的charts/目录中。

3.2.2. charts 目录

若需要对依赖关系进行更多的控制 , 也可以手动将所有被依赖的Chart直接复制到charts目录中。被依赖的Chart既可以是归档格式 , 又可以是展开的目录格式 , 不过其名称不能以下划线 ( _ ) 或点号 ( . ) 开头 , 因为此类文件会被Chart装载器自动忽略。例如 , bitnami/harbor定义的依赖关系在其charts目录中生成的文件列表类似如下所示。

charts/
├── common
│   ├── Chart.yaml
│   ├── README.md
│   ├── templates
│   ├── ......
├── postgresql
│   ├── Chart.lock
│   ├── charts
│   │   └── common
│   │       ├── Chart.yaml
│   │       ├── README.md
│   ├── ......
└── redis
    ├── Chart.lock
    ├── charts
    │   └── common
    │       ├── Chart.yaml
    │       ├── README.md
    ├── ......

被依赖的每个Chart可直接由helm pull命令下载 , 它将下载的文件默认保存在当前的工作目录下。

4. 自定义Chart示例

典型的服务类容器化应用通常会由工作负载控制器 ( 例如Deployment资源 ) 、Service、ServiceAccount、ConfigMap、 Secret、Ingress、HPA和PersistentVolumeClaim等资源对象组成 , 一般来说 , 工作负载控制器和Service通常是必备的资源 , 其他资源可按需进行定义或添加。

4.1. 生成Chart配置示例

helm create命令能为Deployment、Ingress、HPA、 ServiceAccount和Service这5种类型资源各自提供一个示例模板以及其他几个必要的文件 , 以方便用户快速创建出所需要的自定义Chart。 例如 , 下面的命令便会在命令执行的当前目录中创建一个名为demoapp 的子目录作为Chart存储路径 , 并在该目录中为几种常见的资源类型生成基础的框架模板文件。

# helm create demoapp 
Creating demoapp

# tree demoapp/
demoapp/
├── charts
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   ├── _helpers.tpl
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

上述命令还会成一个示例性的Chart.yaml文件及values.yaml文件 , Helm默认将该Chart项目的目录名称作为项目名称 , 而版本号则为 " 0.1.0" , 如下面由该命令生成的Chart.yaml文件的内容所示。

# cat demoapp/Chart.yaml 
apiVersion: v2
name: demoapp
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.16.0"

另外 , 该命令默认生成的值文件中提供了应用副本数 ( replicaCount ) 、应用镜像 ( image ) 、服务账户 ( serviceAccount ) 、安全上下文 ( podSecurityContext和 securityContext ) 、服务和Ingress ( service和ingress ) 、容器资源约束 ( resources ) 、自动缩放 ( autoscaling ) 以及影响调度器调度决策 ( nodeSelector、tolerations和affinity ) 等各方面的参数值。事实上 , 我们仅需要在各文件现有框架的基础上按需进行简单修改即可定义出所需的Chart来。

4.2. 定制Chart

假设 , 我们的目标是将容器应用vvoo/demoapp:v1.0构建为一个完整的Helm应用 , 而且Deployment控制器就能满足基本的应用编排需求 , 则简单修改默认生成的values.yaml文件以下部分的内容值就能基本满足需求。

# vim demoapp/values.yaml
replicaCount: 1               # 应用副本数

image:                        # 定义应用镜像
  repository: vvoo/demoapp    # 仓库名称
  pullPolicy: IfNotPresent    # 镜像下载策略
  tag: "v1.0"                 # 镜像标签

imagePullSecrets: []          # 私有镜像仓库的Secret
nameOverride: ""              # 应用名称
fullnameOverride: ""          # 完成格式的应用名称

serviceAccount:               # 专用的ServiceAccount
  create: true                # 是否创建专用的SA
  annotations: {}
  name: ""                    # 专用SA名称
......
service:                      # 相关Service的定义
  type: ClusterIP
  port: 80

ingress:                      # 相关ingress的定义
  enabled: false
  className: ""
  annotations:
    kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: www.ikubernetes.io
      paths:
        - path: /
         # pathType: ImplementationSpecific
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local
......

值文件修订完成后 , 可使用helm template命令测试模板引擎基于值文件对模板进行渲染的结果 , 该命令会按照指定的Chart名称进行所有模板的渲染测试 , 如下面的命令所示。

# helm template demoapp
---
# Source: demoapp/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: release-name-demoapp
  labels:
    helm.sh/chart: demoapp-0.1.0
    app.kubernetes.io/name: demoapp
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
---
# Source: demoapp/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: release-name-demoapp
  labels:
    helm.sh/chart: demoapp-0.1.0
    app.kubernetes.io/name: demoapp
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
......

接着 , 再简单定制出demoapp/Chart.yaml文件的内容 , 确保其程序版本、Chart版本、维护者信息等能映射出项目的实际情况 , 一个自定义的Chart就已然初具雏形。下面给出了一个Chart.yaml文件的基础内容示例。

# vim demoapp/Chart.yaml
apiVersion: v2
name: demoapp
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.16.0"
maintainers:
  - name: test
    email: test@test.com
    url: http://www.test.com

另外 , 若有必要 , 还可以修改demoapp/templates/NOTE.txt文件的内容 , 以帮助用户快速获取到该应用的简要使用说明。随后 , 我们便可通过专用于发现Chart中存在问题的helm lint命令对自定义Chart 进行合规性校验 , 以确保自定义Chart能够遵循最佳实践且有着良好的模板格式。

# helm lint demoapp
==> Linting demoapp
[INFO] Chart.yaml: icon is recommended   # 提示类日志信息 , 建议为Chart添加icon的定义

1 chart(s) linted, 0 chart(s) failed

大多数情况下 , 该命令的错误提示及其标识的行号信息即能定位到问题所在。待确保关键问题都得到解决之后 , 即可通过helm install命令调试运行该Chart , 以确认其定义的容器化应用是否能以期望的方式完成部署并成功运行。为了说明如何通过命令行选项向模板传递参数 , 这里特地通过service.type来设定相关Service的类型为NodePort。

# helm install demoapp --debug ./demoapp/ --dry-run -n default --set service.type=NodePort
install.go:178: [debug] Original chart version: ""
install.go:195: [debug] CHART PATH: /root/yaml/helm/demoapp

NAME: demoapp
LAST DEPLOYED: Tue Jul  5 17:02:01 2022
NAMESPACE: default
STATUS: pending-install
REVISION: 1
USER-SUPPLIED VALUES:    # 用户自定义值
service:
  type: NodePort         # Service的类型为NodePort , 而不再是默认的ClusterIP
......

确认上述命令输出信息无误后 , 移除命令中的--dry-run选项后再次运行命令即可完成应用部署 , 如下面的命令及结果所示。

# helm install demoapp ./demoapp/ -n default --set service.type=NodePort
NAME: demoapp
LAST DEPLOYED: Tue Jul  5 17:04:09 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:    # 简要使用说明
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services demoapp)
  export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT

而后 , 根据上述NOTES中的命令提示 , 运行相关的命令获取访问端点后即可通过浏览器访问相应的服务。

# export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services demoapp)

# export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")

# echo http://$NODE_IP:$NODE_PORT
http://192.168.102.101:31179

# curl http://192.168.102.101:31179
vvoo demoapp v1.0 !! ClientIP: 192.168.102.101, ServerName: demoapp-668c968558-f2gxq, ServerIP: 10.244.2.47!

上面命令返回了在集群外部通过NodePort访问该Release 部署的相关服务的可用端点之一。当然 , 我们也可以启用Ingress资源 , 并借助Ingress控制器来发布该服务。下面是一个自定义的值文件 , 它仅指明了要启用的Ingress资源 , 并给出了适用的Ingress控制器类别及HTTP路由相关的配置。

# vim demoapp-values.yaml
ingress:
  enabled: true
  className: ""
  annotations:
    kubernetes.io/ingress.class: nginx
  hosts:
    - host: www.ikubernetes.io
      paths:
        - path: /

通过-f选项将该文件附加于helm install命令即可将其合并到Chart默认的值文件上 , 例如下面的命令基于自定义的demoapp Chart 创建了第二个实例 , 它通过Ingress将服务发布到集群之外。

# helm install demoapp-2 ./demoapp -n default -f ./demoapp-values.yaml 
NAME: demoapp-2
LAST DEPLOYED: Tue Jul  5 17:17:58 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  http://www.ikubernetes.io/

访问测试

// 获取Ingress Nginx控制器Service的信息
# kubectl get service ingress-nginx-controller -n ingress-nginx 
NAME                       TYPE       CLUSTER-IP    EXTERNAL-IP       PORT(S)                      AGE
ingress-nginx-controller   NodePort   10.96.25.44   192.168.102.101   80:30696/TCP,443:32340/TCP   9d

// 访问测试
# curl -H "Host: www.ikubernetes.io" 192.168.102.101:30696
vvoo demoapp v1.0 !! ClientIP: 10.244.1.49, ServerName: demoapp-2-84f96bc849-z65lv, ServerIP: 10.244.1.52!

类似地 , 若需要自定义模板文件支持的其他配置 , 修改值文件中的配置项即可。而需要用到其他非默认支持的资源 , 例如Role和 RoleBinding , 甚至是其他通过CRD定义的CR资源等 , 就需要用户自行开发并添加相应的模板文件 , 以及在值文件中添加相关参数的默认值等。

4.3. 打包及分享

测试完成的自定义Chart可打包后存储在目标仓库仅供自己或有限范围内的用户使用 , 也可按需公开回馈到社区之中。helm package命令基于众多选项提供了灵活的打包机制 , 如下的命令就会先更新依赖关系再进行打包操作。

# helm package -u --debug demoapp
Successfully packaged chart and saved it to: /root/yaml/helm/demoapp-0.1.0.tgz

该命令通过Chart.yaml文件中定义的版本等信息生成打包后的文件名称 , 且默认将打包后的文件存储在当前工作目录中 , 但也允许用户通过-d选项指定目标路径 , 随后即可将打包完成的Chart上传到仓库中。