Kustomize 管理 Kubernetes 集群

对比

在维护 Kubernetes 过程中,一开始是直接使用 kubectl 编辑集群资源,但毕竟需要归档编排,那么就需要一个配置管理工具。一开始尝试使用 Helm,但尝试过后发现我的使用场景并不适合 Helm。后来发现 Kustomize,那么先对比一下两款工具:

  • Helm 依赖服务端(tiller),Kustomize 专注于本地渲染
  • Helm 的配置可读性差(go-template),但大部分用户不需要关心 template,只需要更改 values 即可
  • Helm 通过 tiller 有完整声明周期管理,Kustomize 只能通过 Git 追踪变更
  • Helm 可以进行依赖管理
  • Kustomize 本质是 Kubernetes 的 Yaml 语法,并且增加 Patch、Generator、Transformer 等功能
  • Helm 打包的 Chart 易于分发,可做私有 Repository
  • Helm的其他问题:参考

如果是需要对外发布应用,Helm Chart 可以帮助用户远离复杂性,只需要关心能够配置的参数即可,这种模式非常利于分发。

而如果是业务应用,特别是自运维场景下,更为轻量级的 Kustomize 可能更为实用,与 Kubernetes Yaml 一致的语法能够让研发人员快速编排,与 Git 结合可以快速变更应用或者 Fork 出一个新版本,overlay 则能够有效地管理不同环境。

kustomize

使用

在 Kubernetes v1.14 之后,kustomize 成为 kubectl 的子命令,但这里还是推荐安装完整 kustomize,比 kubectl 提供更完善的特性支持。

# 如 brew
brew install kustomize

环境管理

Domain 划分

首先初始化 Git 仓库:git init,第一层根据 Namesapce 以及通用资源划分目录(比如业务服务、基础服务、K8S集群资源),第二层根据资源以文件夹区分。

.
├── app
│   ├── app1
│   └── app2
├── base
│   ├── postgres
│   ├── rabbitmq
│   └── redis
├── ingress
├── monitering
│   ├── fluent-bit
│   └── prometheus
├── secrets
│   ├── docker-registry
└── storage
    └── rook-ceph

应用编排

比如现在有一个以 Deployment 形式部署的服务,按照 Kustomize 建议划分 base 和 overlays 层。

  • base 层定义基础配置
  • overlays 层根据不同环境 override 基础配置
.
├── README.md
├── base
│   ├── deployment.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays
    ├── production
    │   ├── deployment.yaml
    │   └── kustomization.yaml
    └── staging
        ├── deployment.yaml
        └── kustomization.yaml

定义一个基础 Deployment:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: app1
spec:
  replicas: 1  # production 中 override 到合适副本
  selector:
    matchLabels:
      app: app1
  template:
    metadata:
      labels:
        app: app1
    spec:
      containers:
      - name: api
        image: registry.com/app1:latest
        ports:
        - containerPort: 80
        env:
        - name: DEBUG
          value: "false"

在 overlay 中定义一个对应的 patch,并根据迭代在不同环境配置对应 image 版本号:

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

bases:
- ../../base

patchesStrategicMerge:
  - deployment.yaml

images:
- name: registry.com/app1
  newTag: v1.0.0 # v1.2.3 不同环境配置不同版本号

多次继承

这里用一个定时任务来举例说明:现在我们有一个 CLI 应用,我们需要利用 Kubernetes 的 CronJob 去定时驱动,并且这个应用可根据参数执行不同的任务。如果只是按照上面的简单分两层,那我们需要定义多个独立的 CronJob。那如何解决这个问题,这里利用到了 kustomization 配置是不限层级继承的特性。

.
├── base
│   ├── cronjobs
│   │   ├── job1
│   │   │   ├── kustomization.yaml
│   │   │   └── patch.yaml
│   │   ├── job2
│   │   │   ├── kustomization.yaml
│   │   │   └── patch.yaml
│   │   └── kustomization.yaml
│   └── kustomization.yaml
├── cronjobs
│   ├── base.yaml
│   └── kustomization.yaml
└── overlays
    ├── production
    │   ├── configmap.yaml
    │   └── kustomization.yaml
    └── staging

首先定义这类定时任务的基本定义(cronjobs/base.yaml):

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: schedule # 任务名称
spec:
  concurrencyPolicy: Forbid
  schedule: "0 0 * * *"
  successfulJobsHistoryLimit: 0
  failedJobsHistoryLimit: 2
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: worker
            image: registry.com/cli:latest
            args:
            - run
            - echo
            volumeMounts:
            - mountPath: /app/job.yaml
              subPath: job.yaml
              name: config
          volumes:
          - name: config
            configMap:
              name: job-config

然后在 base/cronjobs/ 下定义多个独立任务。如果不 patch 资源的名称,则注意需要使用 nameSuffixnamePrefix 为每个独立任务配置独立的 name

# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

nameSuffix: -my-job1 # 最终生成的名称为 schedule-my-job1

bases:
- ../../../cronjobs

patchesJson6902:
- target:
    group: batch
    version: v1beta1
    kind: CronJob
    name: schedule
  path: patch.yaml

# patch.yaml
- op: replace # patch cron
  path: /spec/schedule
  value: "0 1 */1 * *"
- op: replace # patch args
  path: /spec/jobTemplate/spec/template/spec/containers/0/args
  value:
  - run
  - job1

由于每个独立任务还需要更改容器启动 Args 与定时时间,与上一个应用编排的例子不一样,由于本例需要 Patch 的内容更少,且层级更深,如果使用 patchesStrategicMerge 描述资源 override 则非常冗长,所以使用 JSON Patch(可以用 Yaml 格式)。

到此就可以生成多个独立 CronJob,然后再加上 overlay 根据不同环境定义 ConfigMap。

Generator

以 Secret Generator 从明文生成密码为例:

generatorOptions:
  disableNameSuffixHash: true

secretGenerator:
- name: mysecret
  namespace: ns1
  literals: &secret
  - user=the-user-name
- name: rabbitmq
  namespace: ns2
  literals:
    *secret

如果需要固定生成资源的名称则需要将 disableNameSuffixHash 设置为 true,否则生成资源的名称会如下带有 hash 后缀。目前 Generator 仍有一个问题,就是同时需要生成同样的资源在不同 Namespace 的场景,语法上并没有给出解决方案,目前只能通过 Yaml 锚点解决。

secret/mysecret-dddghtt9b5 created

你甚至可以定义 Generator 来对 Helm Chart 进行修改

配置继承

由于 Kustomize 这种配置分层结构,是可以继承外部配置,然后作出自己的定制修改。思路就是使用 Git Submodule 作为 base 层,overlays 做自己的定制层,随时跟踪上游的变更。

git submodule add https://example.com/repo.git base
mkdir -p overlays/customization-one
mkdir -p overlays/customization-two

检查配置 && 应用

# 检查
kustomize build app1/overlays/staging | less
# 应用
kustomize build app1/overlays/staging | kubectl apply -f -

另外一个选择

如果项目是需要部署到多种云上,并且可能需要交付的,那么还有另外一种选择:Terraform,可以对接多种云提供商(Provider),将云端资源代码化(Infrastructure as Code)。而且 Terraform 有自己的 DSL(HCL),用户编写时甚至不需要关心了解 Kubernetes 许多概念(configmap、secret)。

Reference