背景

在 KubeBlocks 中,一个 Cluster 由若干个 Component 组成,一个 Component 最终管理若干 Pod 和其它对象。

在 0.9 版本之前,这些 Pod 是从同一个 PodTemplate 渲染出来的(该 PodTemplate 在 ClusterDefinition 或 ComponentDefinition 中定义)。这样的设计不能满足如下需求:

  1. 对于从同一个 Add-on 中渲染出来的 Cluster,为其设置单独的 NodeName、NodeSelector 或 Tolerations 等调度相关配置
  2. 对于从同一个 Add-on 中渲染出来的 Component,为它所管理的 Pod 添加自定义 Annotation、Label 或 ENV
  3. 对于被同一个 Component 管理的 Pod,为它们配置不同的 CPU、Memory 等 Resources Requests 和 Limits

类似的需求还有很多,所以从 0.9 版本开始,Cluster API 中增加了实例模板(Instance Template)特性,以满足上述需求。

什么是实例模板

实例(Instance)是 KubeBlocks 中的基本单元,它由一个 Pod 和若干其它辅助对象组成。为了容易理解,你可以先把它简化为一个 Pod,下文中将统一使用实例这个名字。

从 0.9 开始,我们可以为一个 Cluster 中的某个 Component 设置若干实例模板(Instance Template),实例模板中包含 Name、Replicas、Annotations、Labels、Env、Tolerations、NodeSelector等多个字段(Field),这些字段最终会覆盖(Override)默认模板(也就是在 ClusterDefinition 和 ComponentDefinition 中定义的 PodTemplate)中相应的字段,并生成最终的模板以便用来渲染实例。

如何使用实例模板

RisingWave 为例 ,KubeBlocks 中支持管理 RisingWave 集群,RisingWave Add-on 由 RisingWave 官方贡献。RisingWave 需要一个外部存储来作为自己的存储后端(state backend),这个外部存储可以是 AWS S3、阿里云 OSS 等。RisingWave 集群在创建时需要配置外部存储的 Credential 等信息,以便能够正常工作,而这些信息对每个集群来说可能都不同。

在 RisingWave 的官方镜像(Image)中,这些信息可以通过环境变量(Env)方式注入,所以在 KubeBlocks 0.9 中,我们可以通过在实例模板中配置相应的环境变量,在每次创建集群时设置这些环境变量的值,以便将 Credential 等信息注入到 RisingWave 的容器中。

比如在 RisingWave Add-on 的默认模板中,环境变量相关配置如下:

apiVersion: apps.kubeblocks.io/v1alpha1
kind: ComponentDefinition
metadata:
  name: risingwave
# ...
spec:
#...
  runtime:
    containers:
      - name: compute
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
          privileged: false
        command:
        - /risingwave/bin/risingwave
        - compute-node
        env:
        - name: RUST_BACKTRACE
          value: "1"
        - name: RW_CONFIG_PATH
          value: /risingwave/config/risingwave.toml
        - name: RW_LISTEN_ADDR
          value: 0.0.0.0:5688
        - name: RW_ADVERTISE_ADDR
          value: $(KB_POD_FQDN):5688
        - name: RW_META_ADDR
          value: load-balance+http://$(metaSvc)-headless:5690
        - name: RW_METRICS_LEVEL
          value: "1"
        - name: RW_CONNECTOR_RPC_ENDPOINT
          value: $(connectorSvc):50051
        - name: RW_PROMETHEUS_LISTENER_ADDR
          value: 0.0.0.0:1222
# ...

Cluster 资源中添加实例模板后:

apiVersion: apps.kubeblocks.io/v1alpha1
kind: Cluster
metadata:
  name: {{ include "risingwave-cluster.name" . }}
  namespace: {{ .Release.Namespace }}
# ...
spec:
  componentSpecs:
  - componentDef: compute
    name: compute
    replicas: {{ .Values.risingwave.compute.replicas }}
    instances:
    - name: instance
      replicas: {{ .Values.risingwave.compute.replicas }}
      env:
      - name: RW_STATE_STORE
        value: "hummock+s3://{{ .Values.risingwave.stateStore.s3.bucket }}"
      - name: AWS_REGION
        value: "{{ .Values.risingwave.stateStore.s3.region }}"
      {{- if eq .Values.risingwave.stateStore.s3.authentication.serviceAccountName "" }}
      - name: AWS_ACCESS_KEY_ID
        value: "{{ .Values.risingwave.stateStore.s3.authentication.accessKey }}"
      - name: AWS_SECRET_ACCESS_KEY
        value: "{{ .Values.risingwave.stateStore.s3.authentication.secretAccessKey }}"
      {{- end }}
      - name: RW_DATA_DIRECTORY
        value: "{{ .Values.risingwave.stateStore.dataDirectory }}"
      {{- if .Values.risingwave.stateStore.s3.endpoint }}
      - name: RW_S3_ENDPOINT
        value: "{{ .Values.risingwave.stateStore.s3.endpoint }}"
      {{- end }}
      {{- if .Values.risingwave.metaStore.etcd.authentication.enabled }}
      - name: RW_ETCD_USERNAME
        value: "{{ .Values.risingwave.metaStore.etcd.authentication.username }}"
      - name: RW_ETCD_PASSWORD
        value: "{{ .Values.risingwave.metaStore.etcd.authentication.password }}"
      {{- end }}
      - name: RW_ETCD_ENDPOINTS
        value: "{{ .Values.risingwave.metaStore.etcd.endpoints }}"
      - name: RW_ETCD_AUTH
        value: "{{ .Values.risingwave.metaStore.etcd.authentication.enabled}}"
# ...

在上面的例子中,我们通过 instances 字段新增了一个实例模板,该实例模板的名字为 instance。模板中定义了 RW_STATE_STOREAWS_REGION 等若干环境变量,这些环境变量会被 KubeBlocks append 到默认模板中定义的环境变量列表后面,最终渲染的实例中将包含默认模板和在该实例模板中定义的所有环境变量。

另外,实例模板中 replicascomponentSpec 中相同(都为{{ .Values.risingwave.compute.replicas }}),意味着在覆盖默认模板后,该实例模板将用来渲染该 Component 中的所有实例。

实例模板详细说明

每个 Component 中可以定义多个实例模板,每个模板都需要通过 Name 字段设置模板名称,同一个 Component 中的实例模板名字必须保持唯一。

每个模板可以通过 Replicas 字段设置基于该模板渲染的实例数量,Replicas 默认为 1。同一个 Component 中的所有实例模板的 Replicas 相加后必须小于或等于 Component 的 Replicas 值。若基于实例模板渲染的实例数量小于 Component 需要的总的实例数量,剩余的实例将使用默认模板进行渲染。

基于实例模板渲染的实例名称的模式(Pattern)为 $(cluster name)-$(component name)-$(instance template name)-ordinal。比如在上文 RisingWave 示例中, Cluster 名字为 risingwave,Component 名字为 compute,实例模板名称为 instance,实例数量 Replicas 为 3。那么最终渲染出的实例名称为:risingwave-compute-instance-0, risingwave-compute-instance-1, risingwave-compute-instance-2

实例模板在集群创建阶段可以使用,并可以在后续运维中对实例模板进行更新,具体包括添加实例模板、删除实例模板或更新实例模板。实例模板更新可能会引起实例的更新、删除或重建,在更新前建议仔细分析最终的变化是否符合预期。

Annotations

实例模板中的 Annotations 用于覆盖默认模板中的 Annotations 字段,若实例模板 Annotations 中的某个 Key 在默认模板中已存在,该Key 对应的值(Value)将使用实例模板中的值;若该 Key 在默认模板中不存在,该 Key 和 Value 将被添加到最终的 Annotations 中。

比如默认模板中 Annotations 为:

annotations:
  "foo0": "bar0"
  "foo1": "bar"

实例模板中 Annotations 为:

annotations:
  "foo1": "bar1"
  "foo2": "bar2"

则最终被渲染出的实例的 Annotations 为:

annotations:
  "foo0": "bar0"
  "foo1": "bar1"
  "foo2": "bar2"

注意,KubeBlocks 会添加一些系统 Annotations,需要避免对这些 Annotations 造成覆盖。

Labels

0.9 版本之前,在默认模板、Cluster 级别、Component 级别都可以直接设置 Label,同时 KubeBlocks 也会设置一些系统默认 Label,这些 Label 联合起来组成最终的 Labels 列表。

从 0.9 开始,新增通过实例模板设置 Labels 方式。

Annotations 类似,实例模板中的 Labels 采用相同的覆盖逻辑应用到已有的 Labels 上,并形成最终的 Labels

注意,实例模版中的 Labels 具有最高优先级,需要避免覆盖 KubeBlocks 系统 Labels

Image

实例模板中的 Image 用于覆盖默认模板中第一个 Container 的 Image 字段。

该字段需慎用:在数据库等有状态应用中,Image 改变通常涉及数据格式等的兼容性,使用该字段时请确认实例模板的镜像版本与默认模板中的完全兼容。

同时 KubeBlocks 从 0.9 版本开始,通过 ComponentVersion 对镜像版本进行了详细设计,建议通过 ComponentVersion 进行版本管理。

SchedulingPolicy.SchedulerName

用于覆盖默认模板中的 SchedulerName 字段。

SchedulingPolicy.NodeName

用于覆盖默认模板中的 NodeName 字段。

SchedulingPolicy.NodeSelector

用于覆盖默认模板中的 NodeSelector 字段,覆盖逻辑与 AnnotationsLabels 相同。

SchedulingPolicy.Tolerations

用于覆盖默认模板中的 Tolerations 字段。

若实例模板中的 Toleration 与默认模板中的某个 Toleration 完全相同(KeyOperatorValueEffectTolerationSeconds 都相同),则该 Toleration 会被忽略;否则,追加到默认模板中的 Tolerations 列表中。

SchedulingPolicy.Affinity

用于覆盖默认模板中的 Affinity 字段。

SchedulingPolicy.TopologySpreadConstraints

用于覆盖默认模板中的 TopologySpreadConstraints 字段。

Resources

KubeBlocks 在 0.9 版本之前,在默认模板、Cluster 级别、Component 级别都可以设置 Resources 的值。最终的值通过逐级覆盖产生,其中 Component 级别最高。 从 0.9 开始,在实例模板中可以进一步覆盖 Resources 的值,其优先级高于 Component。

Env

0.9 版本之前,在默认模板、Component 级别都可以直接或间接设置 Env,同时 KubeBlocks 也会以 EnvVarSource 方式通过 ConfigMap 设置一些系统默认 Env,这些 Env 联合起来组成最终的 Env 列表。

从 0.9 开始,新增通过实例模板设置 Env 的方式。实例模板中定义的 Env 将覆盖除 KubeBlocks 系统默认 Env 外的其他 Env。覆盖逻辑与 AnnotaionsLabels 类似,即若 Env Name 相同,则用实例模板中的 ValueValueFrom;不同,则添加为新的 Env。

Volumes

用于覆盖默认模板第一个 Container 的 Volumes 字段。覆盖逻辑与 Env 类似,即若 Volume Name 相同,则用实例模板中的 VolumeSource;否则,添加为新的 Volume。

VolumeMounts

用于覆盖默认模板第一个 Container 的 VolumeMounts 字段。覆盖逻辑与 Volumes 类似,即若 VolumeMount Name 相同,则用实例模板中的 MountPath 等值;否则,添加为新的 VolumeMount。

VolumeClaimTemplates

用于覆盖 Component 中通过 ClusterComponentVolumeClaimTemplate 生成的 VolumeClaimTemplates。覆盖逻辑与 Volumes 类似,即若 PersistentVolumeClaim Name 相同,则用实例模板中的 PersistentVolumeClaimSpec 值;否则,添加为新的 PersistentVolumeClaim。

End

KubeBlocks 已发布 v0.9.0!KubeBlocks v0.9.0 全面升级了 API,构建一个 Cluster 更像是在用 Component “搭积木”!新增 topologies 字段,支持多种部署形态。InstanceSet 代替了 StatefulSet 来管理 Pods,支持将指定的 Pod 下线、Pod 原地更新,同时也支持数据库主从架构里主库和从库采用不同的 Pod spec。v0.9.0 还新增了 Reids 集群模式(分片模式),系统的容量、性能以及可用性显著提升!还支持了 MySQL 主备,资源的要求更少,数据复制的开销也更小!快来试试看!

小猿姐诚邀各位体验 KubeBlocks,也欢迎您成为产品的使用者和项目的贡献者。跟我们一起构建云原生数据基础设施吧!

💻 官网: www.kubeblocks.io

🌟 GitHub: https://github.com/apecloud/kubeblocks

🚀 Get started: https://cn.kubeblocks.io/docs/preview/user-docs/try-out-on-playground/try-kubeblocks-on-local-host

☁️ Cloud 试用:https://console.apecloud.cn/

关注小猿姐,一起学习更多云原生技术干货。