工程化 k8s 资源编排之 Kustomize
使用 Kustomize 工具对 kubernetes 资源编排实现工程化治理

对于 Kubernetes 下复杂服务组件的部署编排,通常会需要考虑到资源组织的工程化管理。本文将主要对作为两种最主流编排方式之一的 Kustomize 进行基本的使用介绍。

Kustomize vs Helm

对于 k8s 环境中的服务资源编排,当前最主流的仍然是 Kustomize 与 Helm 这两种。两种方案基于不同的理念实现,但目标都是为了实现资源编排的工程化治理。

Kustomize Helm

编排理念

Base + Overlays 合并

模板渲染

扩展自由度

开放,允许 Overlay 完全自定义,自由度高

封闭,仅限于定义的行为,自由度低

工作流

直接编排

定义 chart → 填充 → 发布

依赖复杂度

轻量,kubectl 原生集成

依赖外部二进制

模板化

弱,原生仅能通过 transformer / replacement,插件扩展较复杂

强,所有属性均可模板化替换,且可扩展 helper 方法提高复用能力

从实际应用场景来看,Helm 适合将成熟的服务编排打包封装后进行分发,发布链路相对更长。而 Kustomize 更适合提供给专业的运维人员,用于管理 SaaS / 内部服务的编排,通过快速简洁的发布链路,能够适应配置灵活调整的需求。Kustomize 的设计理念,也更贴近于面向对象程序设计的理念。

不过相对的,对于非标准化结构的参数化处理上,Kustomize 的灵活性就不如 Helm,比如对于私有的配置文件中参数进行模板替换的场景。

基础功能

资源定义管理

传统上基于 yaml 文件实现的 k8s 资源编排,在使用上存在诸多不便,最显著的一点就是缺乏将同一个服务下,散落在不同文件中的资源定义统一生效。Kustomize 提供的最基础能力便是可以对有关联的多文件建立统一的索引组织关系,整合成工程目录进行管理,而不是在单一 yaml 文件中以子文档的形式治理。从工程治理角度,提供了基本的资源组织能力,带来可维护性的提升。

./
├── deployment.yaml
├── service.yaml
└── kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: foo

resources:
  - deployment.yaml
  - service.yaml

images:
  - name: nginx
    newTag: latest

Kustomize 中对于资源编排定义提供了 Resource 与 Component 两种方式。虽然 Kustomize 本质上都是通过 overlay 对 base 层的资源定义执行修改与扩展,但 Component 在配置复用场景下进行了部分差异化设计,会有部分行为上的区别,相对更适合对基础资源定义进行组件化声明。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

---
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component

配置生成器 (Generator)

针对配置生成与引用的场景,Kustomize 还提供了 ConfigMap 与 Secret 的生成器功能,能够通过引用配置文件或直接声明的方式,生成需要使用的 ConfigMap 或 Secrect 提供给 Pod 挂载使用。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

configMapGenerator:
  - name: config
    files:
      - config.yaml=config/config.yaml
    options:
      disableNameSuffixHash: true

默认情况下,Kustomize 会对生成的 ConfigMap 或 Secrect 按照资源定义的内容补充上 hash 后缀。同时,对于引用相同名称 ConfigMap 或 Secrect 的配置挂载,Kustomze 会自动关联上补全后缀之后的名称应用,保证其关联性。

通过这一设计,可以确保在配置内容发生变更的情况下,生成新的资源声明,从而避免潜在热更新能力,对正在运行的原有服务造成影响。如果需要使用配置热更新特性,或者不希望在环境中出现带有预期外后缀的资源出现,可以通过 disableNameSuffixHash 参数来控制这一行为。

转换器 (Transformer)

为了更加方便的对分层覆盖过程中,上游资源的部分属性进行变更修改,Kustomize 中对于常见需要被修改的资源属性,提供了 Transformer 功能进行简单的修改。

预定义的转换器 (Transformer) 包含以下常见属性的转换能力:

  • Images transformer

  • Prefix/suffix transformer

  • Labels transformer

  • Annotations transformer

  • Namespace transformer

  • Name reference transformer

典型的使用场景比如可以对容器镜像执行修改替换。

images:
  - name: nginx
    newTag: 1.8.0

在预定义行为之外,Kustomize 还提供了 Transformer 扩展能力,允许自定义其他的变换行为,包括变更 CRD 资源等。不过由于自定义扩展具有很高的自由度,且需要自行实现其处理逻辑,容易带来长期维护成本与可靠性风险,需要谨慎选择。

进阶使用

补丁修改 (Patch)

对于需要在 Overlay 层对特定资源的特定属性进行修改的场景,Kustomize 提供了打补丁 (Patch) 的能力,跟据指定的资源选择规则,对 Base 中的资源定义属性进行定制化修改。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

patches:
  - path: patch.yaml
    target:
      group: apps
      version: v1
      kind: Deployment
      name: deploy.*
      labelSelector: "env=dev"
      annotationSelector: "zone=west"

  - patch: |-
      - op: replace
        path: /some/existing/path
        value: new value
    target:
      kind: MyKind
      labelSelector: "env=dev"

Patch 内容支持「策略合并」与「JSON6902」两种方式,可以选择在 kustomize 定义中内嵌声明,也可以通过独立的 json / yaml 文件进行定义管理。

  • 策略合并 (Strategic Merge)

    通过类似资源结构申明的方式进行定义需要修改/替换/删除的属性。对于属性/资源对象删除的场景,可以配合 $patch: delete 来实现。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: dummy-app
      labels:
        app.kubernetes.io/version: 1.21.0
  • JSON6902

    通过 JSON 属性路径规则,配合操作的行为,控制最终要执行的修改。

    - op: replace
      path: /spec/template/spec/containers/0/image
      value: nginx:1.21.0

属性替换 (Replacement)

在资源定义复用的编排场景下,多套不同部署存在差异的场景,为了保持关联性,会需要进行不同资源/组件的统一标记与修改。一般在 helm 下会很直接的选择模板变量来实现,而 Kustomize 的设计思想摈弃了模板变量替换的思路,为了能适应这一类场景,提供了 Replacement 作为某种程度上的补偿。

通过 Replacement,可以从上下文某个特定的资源属性上,获取到需要替换的属性内容 (source),并按照指定的规则,应用到需要替换的目标属性上 (target)。目标属性的个数没有限制,可以是多个。此外,还能通过指定字符串分隔符 (delimiter) 的形式,替换目标上指定部分的字符串内容。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

replacements:
- source:
    kind: Deployment
    fieldPath: metadata.name
  targets:
  - select:
      name: my-resource

早前版本的 Kustomize 提供了变量定义 (vars) 配置能力,但由于这一方向违背了此项目非模板化处理的设计初衷,在新版本中已经弃用了模板变量的路线,转而提供了属性替换 (Replacement) 的能力,apiVersion v1 正式版中将会移除变量定义功能。

资源定义复用

跨部署环境资源复用

对于应用服务的部署,通常会遇到将同一套资源编排在不同环境下部署的需求,同时有希望尽可能保持其结构一致的需求,以降低长期维护成本,以及配置一致性管理的风险。而在不同环境下,也会存在配置差异化的现实需求,比如在测试验证环境与生产环境部署的资源规模不同、引用的镜像标签/仓库存在区别等等问题。

此类场景下,可以通过在 overlay 层定义不同环境的配置扩展,复用 base 层的资源定义来实现。

./
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
└── overlays/
    ├── test
    │   └── kustomization.yaml
    └── production
        └── kustomization.yaml

跨命名空间(namespace)场景资源复用

相比于最终只生成一份配置实例的单一引用,对于需要将组件在不同 namespace 下重用的场景,通常会遇到 xxx may not add resource with an already registered id 的错误提示,即相同的资源不能以同样的 ID 注册到全局资源引用关系中。此时就可以有两种解决思路:

  1. 可以通过在各自的复用层,添加不同的 namePrefix/nameSuffix 名称前后缀的方式来规避,但带来的副作用是所有资源的 metadata.name 都会被全局补对应充前后缀规则,导致最终生成的资源整体可读性出现影响,也会影响到组件之间一些隐藏的关联引用关系。

    > 前后缀 Transformer 在配置上,仅支持对被修改后缀的字段进行规定,而不能配置其影响的资源类型筛选规则。默认会影响领域下所有资源的 `metadata.name` 属性。
  2. 当名称的重复的资源分布在不同 namespace 下时,通常不存在实际冲突的情况,如果想要更为灵活的处理差异,可以通过自定义 Replacement 替换规则来实现对于资源属性的修改。

    > 早前版本的 kustomize 提供了变量定义 (vars) 的功能,能够在全局上下文定义可替换的模板变量被用于特定的引用场景,但后续计划中,官方已准备弃用这一方案,而交由 Replacement 来实现。

示例:

./
├── base/                       # 被复用的基础资源编排
│   ├── rbac.yaml
│   └── kustomization.yaml
└── overlay/
    ├── foo/                    # 命名空间 foo 下的资源编排
    │   └── kustomization.yaml
    ├── bar/                    # 命名空间 bar 下的资源编排
    │   └── kustomization.yaml
    ├── replacements/           # 替换规则
    │   └── replace.yml
    └── kustomization.yaml      # 跨命名空间的全局资源编排引用
Base Overlay
cat << EOF > ./base/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-role-binding-<NAMESPACE>
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: test-role
subjects:
  - kind: ServiceAccount
    name: test-account
    namespace: test

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-account
  namespace: <NAMESPACE>
automountServiceAccountToken: true
EOF


cat << EOF > ./base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component

resources:
  - rbac.yaml
EOF
cat << EOF > ./overlay/replacements/replace.yaml
source:
  kind: ServiceAccount
  name: test-account
  fieldPath: metadata.namespace
targets:
  - select:
      kind: ClusterRoleBinding
    fieldPaths:
      - metadata.name
    options:
      delimiter: "-"
      index: 3
  - select:
      kind: ClusterRoleBinding
    fieldPaths:
      - subjects.0.namespace
EOF

cat << EOF > ./overlay/foo/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: foo

components:
  - ../../base

replacements:
  - path: ../replacements/replace.yaml
EOF

cat << EOF > ./overlay/bar/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: bar

components:
  - ../../base

replacements:
  - path: ../replacements/replace.yaml
EOF

cat << EOF > ./overlay/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ./foo
  - ./bar
EOF

ℹ️ 值得注意的一个问题是,在被 Component 嵌套引用的情况下,对于属性替换 (Replacement) 场景,同一资源将会被重复执行替换多次,并且由最后一次替换后的组件生成所有最终的资源定义。

例如,在上面的示例中,如果同时将 overlay 与 base 层的资源编排,均定义成 Component 后便会产生冲突。导致 ServiceAccount 出现 namespace transformation produces ID conflict 错误提示,因为其 namespace 将会被替换为最后一次执行 replace 的结果。


参考内容


最后修改于 2024-11-11