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 API:

[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]

Теперь посмотрим, как настроить это приложение для двух окружений – 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.

Используем 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]

Готово.