Kustomize: робота з маніфестами Kubernetes – огляд, приклади

Автор |  15/01/2023

Kustomizeсистема управління конфігураціями (configuration management tool) для Kubernetes, що дозволяє використовувати загальні набори маніфестів, які можуть бути змінені для кожного конкретного оточення/кластера, і може бути альтернативою шаблонам Helm (або доповнювати його).

Загальна концепція Kustomize – “where, what, and how” – “де, що і як”:

  • “де” – це наш базовий маніфест, наприклад deployment.yaml
  • “що” – що саме в маніфесті мінятимемо, наприклад кількість подів (replicas) у цьому деплойменті
  • “як” – файли конфігуарації Kustomize – kustomization.yaml

Огляд Kustomize

Для простого прикладу візьмемо файл kustomization.yaml з таким змістом:

resources:
- deployment.yaml
- service.yaml
namePrefix: dev-
namespace: development
commonLabels:
  environment: development

Тут описується, що потрібно взяти ресурси описані у файлах deployment.yaml та service.yaml, до імені кожного створюваного ресурсу додати префікс dev- ( namePrefix), деплоїти в namespace development , і додати labels environment: development.

Всі опції в Kustomize Feature List.

Крім того, Kustomize зручний для створення конфігурацій із загальних файлів, але для різних оточень.

У такому випадку використовується каталог overlays зі своїм набором kustomization.yaml:

Починаючи з версії 1.14, Kustomize вбудований у kubectl:

[simterm]

$ kubectl kustomize --help
Build a set of KRM resources using a 'kustomization.yaml' file. The DIR argument must be a path to a directory
containing 'kustomization.yaml', or a git repository URL with a path suffix specifying same with respect to the
repository root. If DIR is omitted, '.' is assumed.

Examples:
  # Build the current working directory
  kubectl kustomize
...

[/simterm]

І може використовуватися при apply, щоб спочатку зібрати (build) необхідний маніфест, і відразу відправити його в Kubernetes:

[simterm]

$ kubectl apply --help
...
  # Apply resources from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml
  kubectl apply -k dir/
...

[/simterm]

А з версії 1.16 доступний і в kubeadm.

Крім kuberctl apply, Kustomize можна використовувати для:

  • kubectl get -k отримати ресурс із Kubernetes кластера
  • kubectl describe -k опис ресурсу в Kubernetes кластері
  • kubectl diff -k – порівняти локально згенерований маніфест із ресурсом у кластері
  • kubectl delete -k видалити ресурс із кластера

Деплой з Kustomize

Створюємо тестову директорію:

[simterm]

$ mkdir -p kustomize_example/base
$ cd kustomize_example/

[/simterm]

У каталозі base створимо два файли – в одному опишемо Deployment, в іншому Service:

[simterm]

$ vim -p base/deployment.yaml base/service.yaml

[/simterm]

У файлі deployment.yaml робимо запуск поду з контейнером nginxdemo :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginxdemo
spec:
  selector:
    matchLabels:
      app: nginxdemo
  template:
    metadata:
      labels:
        app: nginxdemo
    spec:
      containers:
        - name: nginxdemo
          image: nginxdemos/hello
          ports:
          - name: http
            containerPort: 80
            protocol: TCP

І файл service.yaml із Сервісом для нього:

apiVersion: v1
kind: Service
metadata:
  name: nginxdemo
spec:
  selector:
    app: nginxdemo
  ports:
  - name: http
    port: 80

Далі, в тому ж каталозі base створюємо kustomization.yaml, в якому описуємо resources з чого ми збиратимемо наш майбутній маніфест для деплою:

resources:
  - deployment.yaml
  - service.yaml

І виконуємо сборку маніфесту:

[simterm]

$ kubectl kustomize base/
apiVersion: v1
kind: Service
metadata:
  name: nginxdemo
spec:
  ports:
  - name: http
    port: 80
  selector:
    app: nginxdemo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginxdemo
spec:
  selector:
    matchLabels:
      app: nginxdemo
  template:
    metadata:
      labels:
        app: nginxdemo
    spec:
      containers:
      - image: nginxdemos/hello
        name: nginxdemo
        ports:
        - containerPort: 80
          name: http
          protocol: TCP

[/simterm]

Або через сам kustomize:

[simterm]

$ kustomize build base/
apiVersion: v1
kind: Service
metadata:
  name: nginxdemo
spec:
...

[/simterm]

Або виконуємо сборку і відразу деплоїмо:

[simterm]

$ kubectl apply -k base/
service/nginxdemo created
deployment.apps/nginxdemo created

[/simterm]

Перевіряємо:

[simterm]

$ kubectl get all -l app=nginxdemo
NAME                             READY   STATUS    RESTARTS   AGE
pod/nginxdemo-7f8f587c74-kbczf   1/1     Running   0          26s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/nginxdemo-7f8f587c74   1         1         1       26s

[/simterm]

Тепер подивимося, як налаштувати ці Deployment та Service для двох оточень – Dev та Prod.

Kustomize Overlays

Створюємо каталоги overlays/dev та overlays/prod:

[simterm]

$ mkdir -p overlays/{dev,prod}

[/simterm]

Отримуємо таку структуру:

[simterm]

$ tree .
.
|-- base
|   |-- deployment.yaml
|   |-- kustomization.yaml
|   `-- service.yaml
`-- overlays
    |-- dev
    `-- prod

[/simterm]

У каталогах dev та prod створюємо окремі kustomization.yaml, в яких описуємо bases:

bases:
- ../../base

Якщо зараз виконати kustomize build overlays/dev/, то отримаємо маніфест аналогічний до того, який створювали раніше.

Можливості Kustomize

namePrefix

Що б змінити цей маніфест – у файлах kustomization.yaml для Dev і Prod додамо, наприклад, namePrefix:

bases:
- ../../base

namePrefix: dev-

Перевіряємо:

[simterm]

$ kustomize build overlays/dev/
apiVersion: v1
kind: Service
metadata:
  name: dev-nginxdemo
spec:
  ports:
  - name: http
    port: 80
  selector:
    app: nginxdemo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dev-nginxdemo
...

[/simterm]

У полях name з’явився префікс dev .

patchesStrategicMerge

Далі, припустимо, ми хочемо на Dev мати 1 под, а на Prod – 3, тобто мати різні значення для поля replicas.

Використовуємо patchesStrategicMerge.

Створюємо файл патчу – overlays/dev/replicas.yaml. Тип та Ім’я ресурсу, які будемо патчити, повинні збігатися з ресурсом з base:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginxdemo
spec:
  replicas: 1

Аналогічно для Prod – файл overlays/prod/replicas.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginxdemo
spec:
  replicas: 3

У файлах overlays/dev/kustomization.yaml та overlays/prod/kustomization.yaml додаємо patchesStrategicMerge:

bases:
- ../../base

namePrefix: dev-

patchesStrategicMerge:
- replicas.yaml

Запускаємо:

[simterm]

$ kustomize build overlays/dev/
apiVersion: v1
kind: Service
metadata:
  name: dev-nginxdemo
spec:
  ports:
  - name: http
    port: 80
  selector:
    app: nginxdemo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dev-nginxdemo
spec:
  replicas: 1
...

[/simterm]

Деплоїмо:

[simterm]

$ kubectl apply -k overlays/dev/
service/dev-nginxdemo created
deployment.apps/dev-nginxdemo created

$ kubectl apply -k overlays/prod/
service/prod-nginxdemo created
deployment.apps/prod-nginxdemo created

[/simterm]

Перевіряємо:

[simterm]

$ kubectl get all -l app=nginxdemo
NAME                                  READY   STATUS    RESTARTS   AGE
pod/dev-nginxdemo-7f8f587c74-vh2gn    1/1     Running   0          37s
pod/nginxdemo-7f8f587c74-kbczf        1/1     Running   0          104m
pod/prod-nginxdemo-7f8f587c74-dpc76   1/1     Running   0          33s
pod/prod-nginxdemo-7f8f587c74-f5j4f   1/1     Running   0          33s
pod/prod-nginxdemo-7f8f587c74-zqg8z   1/1     Running   0          33s

NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/dev-nginxdemo-7f8f587c74    1         1         1       37s
replicaset.apps/nginxdemo-7f8f587c74        1         1         1       104m
replicaset.apps/prod-nginxdemo-7f8f587c74   3         3         3       33s

[/simterm]

configMapGenerator та secretGenerator

Kustomize також вміє генерувати нові ресурси із шаблонів.

Наприклад візьмемо ConfgiMap для алертів Grafana Loki .

Так як алерти однакові і для Dev, і для Prod – то описуємо configMapGenerator в base/kustomization.yaml:

resources:
  - deployment.yaml
  - service.yaml

configMapGenerator:
- name: loki-ruler-alerts
  files:
  - loki-ruler-alerts.yaml

У каталозі base створюємо сам файл loki-ruler-alers.yaml з контентом майбутнього ConfigMap:

groups:
  - name: systemd-alerts
    rules:
      - alert: Pod killed by OOM Killer
        expr: |
          sum(rate({job="systemd-journal"} |~ ".*OOM-killed.*" | regexp `pod=".*/(?P<pod>[a-zA-Z].*)".*` | pod!="" [15m])) by (pod, hostname) > 0.1
        for: 1s
        labels:
          severity: warning
        annotations:
          description: |-
            *OOM Killer detected in the WorkerNode's systemd-journal logs*
            WorkerNode: {{`{{ $labels.hostname }}`}}

Перевіряємо:

[simterm]

$ kustomize build base/
apiVersion: v1
data:
  loki-ruler-alerts.yaml: |
    groups:
      - name: systemd-alerts
        rules:
          - alert: Pod killed by OOM Killer
            expr: |
              sum(rate({job="systemd-journal"} |~ ".*OOM-killed.*" | regexp `pod=".*/(?P<pod>[a-zA-Z].*)".*` | pod!="" [15m])) by (pod, hostname) > 0.1
            for: 1s
            labels:
              severity: warning
            annotations:
              description: |-
                *OOM Killer detected in the WorkerNode's systemd-journal logs*
                WorkerNode: {{`{{ $labels.hostname }}`}}
kind: ConfigMap
metadata:
  name: loki-ruler-alerts-47678t7d89
---
apiVersion: v1
kind: Service
metadata:
  name: nginxdemo
...

[/simterm]

Крім того, можна згенерувати дані прямо з консолі.

Наприклад, щоб додати у файл base/kustomization.yaml новий Secret – виконуємо kustomize edit add secret:

[simterm]

$ cd base/
$ kustomize edit add secret nginx-password --from-literal=password=12345678

[/simterm]

Перевіряємо:

$ cat kustomization.yaml 
resources:
- deployment.yaml
- service.yaml

configMapGenerator:
- files:
  - loki-ruler-alerts.yaml
  name: loki-ruler-alerts
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
secretGenerator:
- literals:
  - password=12345678
  name: nginx-password
  type: Opaque

generatorOptions

Якщо ми застосовуємо base/kustomization.yaml, то до імен ConfigMap і Secret будуть додані постфікси:

[simterm]

$ kubectl apply -k base/
configmap/loki-ruler-alerts-47678t7d89 created
secret/nginx-password-72mh6dg77t created
service/nginxdemo unchanged
deployment.apps/nginxdemo unchanged

[/simterm]

47678t7d89 и 72mh6dg77t.

Щоб змінити цю поведінку – додаємо generatorOptions з опцією disableNameSuffixHash:

resources:
- deployment.yaml
- service.yaml
  
configMapGenerator:
- files:
  - loki-ruler-alerts.yaml
  name: loki-ruler-alerts

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
secretGenerator:
- literals:
  - password=12345678
  name: nginx-password
  type: Opaque

generatorOptions:
  disableNameSuffixHash: true

Деплоїмо:

[simterm]

$ kubectl apply -k base/
configmap/loki-ruler-alerts created
secret/nginx-password created
service/nginxdemo unchanged
deployment.apps/nginxdemo unchanged

[/simterm]

Тепер у нас такі імена, як ми їх вказали в шаблоні.

Helm && Kustomize

І приклад того, як можемо використовувати разом Helm && Kustomize, наприклад коли у вас є форк чарта, і ви не хочете змінювати дані.

Створюємо каталог хельм-чарту:

[simterm]

$ mkdir -p kustomize-helm

[/simterm]

Генеруємо в ньому чарт:

[simterm]

$ helm create kustomize-helm
Creating kustomize-helm

[/simterm]

Отримуємо структуру стандартного чарту:

[simterm]

$ tree . 
.
|-- kustomize-helm
|   |-- Chart.yaml
|   |-- charts
|   |-- templates
|   |   |-- NOTES.txt
|   |   |-- _helpers.tpl
|   |   |-- deployment.yaml
|   |   |-- hpa.yaml
|   |   |-- ingress.yaml
|   |   |-- service.yaml
|   |   |-- serviceaccount.yaml
|   |   `-- tests
|   |       `-- test-connection.yaml
|   `-- values.yaml
`-- templates

[/simterm]

Якщо виконаємо helm template kustomize-helm, то отримаємо згенеровані шаблони чарту:

[simterm]

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

[/simterm]

Тепер, щоб не міняти чарт, але створити свій власний Secret – в каталозі kustomize-helm створюємо файл kustomization.yaml, в якому використовуємо resources з файлом helm-all.yaml який згенеруємо за допомогою helm template:

resources:
- helm-all.yaml

secretGenerator:
- literals:
  - password=12345678
  name: nginx-password
  type: Opaque

Запускаємо

[simterm]

$ cd kustomize-helm/
$ helm template . > helm-all.yaml && kustomize build .              
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: kustomize-helm
    app.kubernetes.io/version: 1.16.0
    helm.sh/chart: kustomize-helm-0.1.0
  name: release-name-kustomize-helm
---
apiVersion: v1
data:
  password: MTIzNDU2Nzg=
kind: Secret
metadata:
  name: nginx-password-72mh6dg77t
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/instance: release-name
...

[/simterm]

Готово.