Éviter les répétitions : manifestes Kubernetes avec Kustomize

Arrêtez de vous répéter lors de la création de manifestes Kubernetes grâce à Kustomize. Qu'est-ce que Kustomize, comment fonctionne-t-il et de quoi est-il capable ? Répondons à ces questions dans cet article.

Éviter les répétitions : manifestes Kubernetes avec Kustomize

Qu'est-ce que Kustomize

Kustomize est un outil en ligne de commande qui peut être utilisé pour transformer/personnaliser de manière « déclarative » les manifestes Kubernetes de manière à éviter les répétitions et à faciliter la maintenance et la réutilisation des manifestes existants.

Il fonctionne avec du YAML brut et des ressources Kubernetes natives. Il est disponible sous forme de binaire autonome et est également partiellement intégré à « kubectl » via « kubectl kustomize » et « kubectl apply -k ».

« Kustomize » est facile à apprendre et à utiliser par rapport aux outils incluant du templating comme helm et helmfile. Voici l'article de blog Kubernetes présentant « Kustomize » : Annonce de Kustomize.

Comment fonctionne Kustomize

  • « Kustomize » fonctionne avec des manifestes Kubernetes bruts qu'il prend en entrée
  • Il transforme ensuite les manifestes d'entrée en fonction de sa configuration et génère le résultat
  • Les configurations Kustomize sont définies dans le fichier « kustomization.yaml »
  • C'est là que nous indiquons à « Kustomize » quels fichiers manifestes Kubernetes il doit prendre en entrée et quelles transformations doivent leur être appliquées.
  • Vous trouverez des informations sur le format et la syntaxe du fichier 'kustomization.yaml' ainsi que des explications détaillées sur chaque champ qu'il prend en charge, dans la page du fichier de Kustomization.

Exemple : Kustomization des manifestes de déploiement Kubernetes

Dans cet exemple, nous allons créer deux variantes d’un manifeste de ressources de déploiement Kubernetes pour les environnements de staging et de production.

Les manifestes spécifiques aux environnements seront générés à partir d'une base commune via « Kustomize ». Cela évite de dupliquer les manifestes de ressources Kubernetes.

  • Exemple d'arborescence :
$ tree kustomize-example/
kustomize-example/
├── common
│   ├── deploy.yml
│   └── kustomization.yaml
├── prod
│   └── kustomization.yaml
└── staging
    └── kustomization.yaml
  • Contenu des fichiers :
# Common

$ cat common/deploy.yml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: ubuntu
        resources:
          limits:
            memory: "300Mi"
          requests:
            memory: "300Mi"
            
$ cat common/kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deploy.yml

# Staging

$ cat staging/kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../common/
commonLabels:
  env: staging

# Prod

$ cat prod/kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../common/
commonLabels:
  env: prod
replicas:
- count: 4
  name: myapp
  • Manifestes Kustomizés résultants
# Staging

$ kustomize build staging/
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    env: staging         # <= specific config for staging
  name: myapp
  namespace: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
      env: staging
  template:
    metadata:
      labels:
        app: myapp
        env: staging
    spec:
      containers:
      - image: ubuntu
        name: myapp
        resources:
          limits:
            memory: 300Mi
          requests:
            memory: 300Mi
            
# Prod

$ kustomize build prod/
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    env: prod         # <= specific config for prod
  name: myapp
  namespace: myapp
spec:
  replicas: 4         # <= specific config for prod
  selector:
    matchLabels:
      app: myapp
      env: prod
  template:
    metadata:
      labels:
        app: myapp
        env: prod
    spec:
      containers:
      - image: ubuntu
        name: myapp
        resources:
          limits:
            memory: 300Mi
          requests:
            memory: 300Mi

Plus d'informations sur l'installation de « Kustomize » et la personnalisation facile des manifestes de ressources Kubernetes dans les sections suivantes.

Installer Kustomize

  • Parfois, il peut être utile d'installer « Kustomize » pour le développement car certaines de ses fonctionnalités CLI utiles ne sont pas disponibles avec la commande « kubectl kustomize » au moment de la rédaction.
  • La liste des versions de « Kustomize » peut être trouvée ici
  • Pour installer « Kustomize » sous Linux, procédez comme suit :
$ kustomize_version=v5.4.2 # choose version
$ linux_arch=amd64 # choose OS arch

# Get Kustomize binary
$ wget https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${kustomize_version}/kustomize_${kustomize_version}_linux_${linux_arch}.tar.gz

# Install Kustomize binary
$ sudo tar xzvf kustomize_${kustomize_version}_linux_${linux_arch}.tar.gz -C /usr/local/bin/

# Verify
$ kustomize version

Créer et mettre à jour le fichier de kustomization

  • Pour obtenir de l'aide sur l'utilisation et des exemples pour n'importe quelle commande CLI « Kustomize », exécutez les commandes suivies de « -h » ou « --help »
  • Utilisez la CLI ou mettez à jour directement le fichier « kustomization.yaml » avec les paramètres de configuration souhaités

Initialiser le fichier de kustomization

Cela peut être utile pour initialiser rapidement un fichier « kustomization.yaml » avec des fichiers YAML autodétectés à partir du répertoire actuel, ajoutés en tant que ressources de configuration « Kustomize ».

$ tree k8s-resources/
k8s-resources/
├── deploy.yml
├── vpa.yml
├── ingress.yml
└── service.yml

$ kustomize create --autodetect

$ cat kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deploy.yml
- vpa.yml
- ingress.yml
- service.yml

Ajouter ou supprimer des ressources du fichier de Kustomization

# Remove resource
$ kustomize edit remove resource vpa.yml

$ cat kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deploy.yml
- service.yml
- ingress.ym

# Add resource after creating the hpa.yml manifest
$ kustomize edit add resource hpa.yml

$ cat kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deploy.yml
- service.yml
- ingress.yml
- hpa.yml

Ajouter ou supprimer des labels et annotations communs du fichier de Kustomization

# Add labels and annotations
# Multiple labels and annotations values are separeted by whitespace
$ kustomize edit add label app:myapp env:staging
$ kustomize edit add annotation myapp/version:0.0.1

$ cat kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deploy.yml
- service.yml
- ingress.yml
- hpa.yml
commonAnnotations:
  myapp/version: 0.0.1
commonLabels:
  app: myapp
  env: staging
  
# Remove labels and annotations
# Only use the keys to remove labels or annotations
# Multiple keys values are separeted by comma
$ kustomize edit remove label app,env
$ kustomize edit remove annotation myapp/version

$ cat kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deploy.yml
- service.yml
- ingress.yml
- hpa.yml

Ajouter ou supprimer des patches du fichier de kustomization

# Add patches
$ kustomize edit add patch --name myapp --kind Deployment --group apps --version v1 --path deploy-patch.yml

$ cat kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deploy.yml
- service.yml
- ingress.yml
- hpa.yml
patches:
- path: deploy-patch.yml
  target:
    group: apps
    kind: Deployment
    name: myapp
    version: v1
 
# Remove patches
$ kustomize edit remove patch --name myapp --kind Deployment --group apps --version v1 --path deploy-patch.yml

$ cat kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deploy.yml
- service.yml
- ingress.yml
- hpa.yml 

Comme indiqué précédemment, la configuration des patchs « Kustomize » comporte un champ « target » qui identifie de manière unique la ressource à patcher (en utilisant au moins un des éléments de ressources suivants : « group », « version », « kind », « name », « namespace », « labelSelector » et « annotationSelector ») et un champ « path » qui spécifie le chemin d'accès (par rapport au fichier « kustomization.yaml ») au fichier contenant les patchs. Le contenu des fichiers de patchs peut être écrit avec deux formats, présentés ci-dessous.

Patchs utilisant le standard json6902

Kustomize patchesJson6902

# Syntax
- op: <operation type> # Possible values: add, remove, replace
  path: <path to the field to modified on the resource>
  value: <value we want for the field selected with path>

# Examples

# Patching container image
# File: deploy-patch.yml
- op: replace
  path: /spec/template/spec/containers/0/image
  value: nginx:1.25.3 
Patchs utilisant le standard de fusion stratégique (strategic merge)

Kustomize patchesStrategicMerge

  • Dans ce cas, le champ « target » de la configuration des patchs « Kustomize » peut être omis
  • La ressource cible est mise en correspondance à l'aide des champs « apiVersion », « kind » et « metadata.name » des fichiers de correctifs
# Syntax

apiVersion: <apiVersion of the resource>
kind: <kind of the resource>
metadata:
  name: <name of the resource>
spec:
  <patches>

# Examples

# Patching container name and image on a Deployment named nginx
# File: deploy-patch.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  template:
    spec:
      containers:
        - name: nginx
          image: nginx:1.25.3

Définir le préfixe et le suffixe pour les noms de ressources

$ kustomize edit set nameprefix -- prefix-
$ kustomize edit set namesuffix -- -suffix

$ cat kustomization.yml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
(...)
- deploy.yml
(...)
namePrefix: prefix-
nameSuffix: -suffix

# Resulting Kubernetes resources
$ kustomize build
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prefix-myapp-suffix
(...)

Définir l'espace de noms des ressources

$ kustomize edit set namespace myapp-namespace

$ cat kustomization.yml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
(...)
- deploy.yml
(...)
namespace: myapp-namespace

# Resulting Kubernetes resources
$ kustomize build
apiVersion: apps/v1
kind: Deployment
metadata:
  (...)
  namespace: myapp-namespace
(...)

Configurer le nombre de replicas

$ kustomize edit set replicas myapp=3

$ cat kustomization.yml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
(...)
- deploy.yml
(...)
replicas:
- count: 3
  name: myapp
  
# Resulting Kubernetes resources
$ kustomize build
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  (...)
spec:
  replicas: 3

Définir des images de conteneurs

# Original manifest
$ cat deploy.yml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  (...)
spec:
  (...)
    spec:
      containers:
      - name: myapp
        image: ubuntu
        (...)
        
# Edit images matching ubuntu as original name
# Set the new image name to debian and new tag to latest
$ kustomize edit set image ubuntu=debian:latest

$ cat kustomization.yml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deploy.yml
(...)
images:
- name: ubuntu
  newName: debian
  newTag: latest
  
# Resulting Kubernetes resources
$ kustomize build
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  (...)
spec:
  (...)
    spec:
      containers:
      - image: debian:latest
        name: myapp
        (...)

Ajoutez des configmaps dans le fichier de kustomization

# From literals
$ kustomize edit add configmap myapp-literals --from-literal key1=value1 --from-literal key2=value2

# From file
$ cat config.yml 
hello
$ kustomize edit add configmap myapp-files --from-file=config.yml

# From env file
$ cat .env
db_host=127.0.0.1
db_user=myapp
db_name=myapp
$ kustomize edit add configmap myapp-envs --from-env-file=.env

# Resulting kustomization file
$ cat kustomization.yml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
(...)
configMapGenerator:
- literals:
  - key1=value1
  - key2=value2
  name: myapp-literals
- files:
  - config.yml
  name: myapp-files
- envs:
  - .env
  name: myapp-envs
  
# Resulting Kubernetes resources
$ kustomize build
apiVersion: v1
data:
  db_host: 127.0.0.1
  db_name: myapp
  db_user: myapp
kind: ConfigMap
metadata:
  name: myapp-envs-kgh2b6hhdc
---
apiVersion: v1
data:
  config.yml: |
    hello
kind: ConfigMap
metadata:
  name: myapp-files-k472b254hd
---
apiVersion: v1
data:
  key1: value1
  key2: value2
kind: ConfigMap
metadata:
  name: myapp-literals-ch29b7t2m8
  • Notez qu'un hash a été rajouté comme suffixe au nom des ressources Kubernetes résultantes. Pour éviter cet ajout, vous pouvez utiliser l'option CLI « --disableNameSuffixHash ». Voici un exemple :
$ kustomize edit add configmap myapp-envs-no-suffix --from-env-file=.env --disableNameSuffixHash

$ cat kustomization.yml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
(...)
- envs:
  - .env
  name: myapp-envs-no-suffix
  options:
    disableNameSuffixHash: true
    
$ kustomize build
(...)
---
apiVersion: v1
data:
  db_host: 127.0.0.1
  db_name: myapp
  db_user: myapp
kind: ConfigMap
metadata:
  name: myapp-envs-no-suffix

Ajoutez des secrets dans le fichier de kustomization

# From literals
$ kustomize edit add secret myapp-literals --from-literal key1=value1 --from-literal key2=value2

# From files
$ kustomize edit add secret myapp-files-from-dir --from-file=secret-files/*

# From env file
$ cat .env
db_password=superpassword
$ kustomize edit add secret myapp-envs --from-env-file=.env

# Resulting kustomization file
$ cat kustomization.yml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
(...)
secretGenerator:
- literals:
  - key1=value1
  - key2=value2
  name: myapp-literals
  type: Opaque
- files:
  - secret-files/secret1
  - secret-files/secret2
  name: myapp-files-from-dir
  type: Opaque
- envs:
  - .env
  name: myapp-envs
  options:
    disableNameSuffixHash: true
  type: Opaque

# Resulting Kubernetes resources
apiVersion: v1
data:
  db_password: c3VwZXJwYXNzd29yZA==
kind: Secret
metadata:
  name: myapp-envs
type: Opaque
---
apiVersion: v1
data:
  secret1: c2VjcmV0MQo=
  secret2: c2VjcmV0Mgo=
kind: Secret
metadata:
  name: myapp-files-from-dir-52tfht2h62
type: Opaque
---
apiVersion: v1
data:
  key1: dmFsdWUx
  key2: dmFsdWUy
kind: Secret
metadata:
  name: myapp-literals-kk658chggc
type: Opaque

Injecter dynamiquement des données dans les configmaps et les secrets de l'environnement

Dans les projets du monde réel, nos sources de manifestes Kubernetes « Kustomisées » seront placées dans un système de gestion de versions de code source (comme Git) afin de faciliter le travail d'équipe et de suivre les modifications.

Dans le système de gestion de versions, nous ne souhaitons pas mettre de valeurs secrètes comme le mot de passe de la base de données de l'application. Supposons que notre application ait besoin de variables d'environnement spécifiques pour se connecter à sa base de données (hôte, nom d'utilisateur, mot de passe...).

Voici comment nous pourrions générer la ressource « configmap » ou « secret » Kubernetes contenant les variables d'environnement d'application requises sans définir de valeurs de variables secrètes dans les fichiers sources des manifestes :

  • Définissez les paires de variables non secrètes « clé = valeur » dans un fichier et définissez uniquement la « clé » pour les secrets :
$ cat .env
db_host=127.0.0.1
db_name=myapp
db_user=myapp
db_password
  • Créez une configuration de générateur de secret ou de configmap « Kustomize » en utilisant ce fichier comme fichier de variables d'environnement :
$ kustomize edit add configmap myapp-envs --from-env-file=.env
  • Renseignez la variable d'environnement db_password=<password_value> avant la construction des manifestes « Kustomizés » :
$ export db_password=superpassword

Cela sera de préférence réalisé dans un pipeline CI/CD après avoir récupéré le secret à partir d'un gestionnaire de secrets (comme Vault) ou d'autres emplacements appropriés.

  • Après avoir créé les manifestes « Kustomizés », les données « secret » ou « configmap » seront automatiquement renseignées avec la variable d'environnement « db_password » précédemment exportée :
$ kustomize build
apiVersion: v1
data:
  db_host: 127.0.0.1
  db_name: myapp
  db_password: superpassword
  db_user: myapp
kind: ConfigMap
(...)

Déployer des ressources gérées avec Kustomize

Voici comment nous pouvons déployer des ressources Kubernetes à partir de manifestes générés avec « Kustomize » :

# Deploy all resources
$ kubectl apply -k /path/to/kustomized/dir

# Deploy only resources with specific labels
$ kubectl apply -k /path/to/kustomized/dir -l app=myapp

Il est également possible de le faire comme ceci :

$ kustomize build | kubectl apply -f -

Créer des variantes à l'aide des superpositions (overlays)

Pour un aperçu de l'idée « Kustomize » des « superpositions » (overlays), jetez un œil à Exemple de superpositions (overlays) Kustomize.