Introducción a Kustomize. Personalización de recursos de Kubernetes.

La gestión de recursos en kubernetes se realiza principalmente a través de definiciones en lenguaje YAML. Para mejorar la gestión y reutilizar definiciones, algunas de las implementaciones específicas de kubernetes como OpenShift permiten el uso de plantillas o templates, mientras que en otros casos se utilizan herramientas de terceros que permiten definir plantillas, procesarlas y generar recursos en base a parámetros.

kustomize es una herramienta para conseguir el mismo objetivo con un enfoque ligeramente diferente. Permite personalizar una definición inicial que sirve como base, especificando parches concretos por entorno, proyecto o grupo de recursos. Las personalizaciones o parches incluyen sólo aquellas partes que difieren de la definición base. A diferencia de una plantilla, el documento de partida es una definición real y perfectamente utilizable de manera individual, no un documento con una sintaxis de marcado que requiere un procesado previo que reemplace las variables por valores reales para su utilización.

 

Contenido

 

Instalación

Kustomize viene incluido en el cliente de kubernetes kubectl desde la versión 1.14. Utilizamos kustomize con este cliente con el comando kubectl apply -k <archivo.yaml> en lugar de utilizar el habitual parámetro -f que se utiliza al aplicar las definiciones de un archivo. También existe la posibilidad de utilizarlo como una herramienta standalone. Es una herramienta creada en Go por lo que consta de un sólo ejecutable que depende del sistema operativo. Basta con descargar el archivo comprimido de la página de versiones, descomprimirlo y copiarlo a algún directorio que se encuentre dentro del path en nuestro sistema operativo.

También se puede instalar con la instrucción:

curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" \
 | bash

que detectará el sistema operativo y se descargará el binario que corresponda. Este sistema no funciona para arquitectura ARM.

 

Definición de los recursos de una aplicación sin kustomize

Para entender el funcionamiento de kustomize vamos a crear una aplicación de ejemplo. Nos basaremos en el cluster de microk8s, llamado ocm-hub que configuramos en el artículo Instalar Open Cluster Management en un equipo personal

Arrancamos la máquina virtual y el cluster:

multipass start ocm-hub
multipass exec ocm-hub microk8s start

Partimos de un namespace llamado portales donde vamos a crear nuestro proyecto:

kubectl --kubeconfig=$HOME/.kube/ocm-hub.config create namespace portales

La definición inicial de la aplicación será para un entorno de desarrollo y tendrá los siguientes elementos:

  • Un config map llamado contenido con el código de la página index.html.
  • Un deployment llamado portal que crea un pod de nginx y monta el config map como archivo index.html en el directorio de publicación de ngnix.
  • Un service portal para exponer los pods de forma que sean accesibles desde fuera del cluster.

Vamos a crear un directorio llamado base donde van a existir un archivo por cada uno de estos elementos:

webapp
└── base
    ├── configmap.yaml
    ├── deployment.yaml
    └── service.yaml

Podemos crear todos los objetos necesarios para esta aplicación situándonos en el directorio donde están los archivos YAML de definición y ejecutando:

kubectl --kubeconfig=$HOME/.kube/ocm-hub.config apply -f .
configmap/contenido created
deployment.apps/portal created
service/portal created

Comprobamos que se han creado el pod y el servicio:

kubectl --kubeconfig=$HOME/.kube/ocm-hub.config get pods -n portales
NAME                     READY   STATUS    RESTARTS   AGE
portal-87bdbf6c8-8m7cf   1/1     Running   0          5m5s
kubectl --kubeconfig=$HOME/.kube/ocm-hub.config get service -n portales
NAME     TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
portal   NodePort   10.152.183.147   <none>        8080:30001/TCP   5m17s

Podemos comprobar que funciona haciendo una petición al puerto 30001 de nuestro cluster. Para ello obtenemos la dirección IP de nuestra máquina virtual:

multipass info ocm-hub | grep IPv4
IPv4:           10.89.130.31

e introducimos la dirección en el navegador o probamos con curl:

curl 10.89.130.31:30001
<html>
  <style>
    body {
      background-color: #fff;
    }
  </style>
  <body>
     <h1>Portal corporativo</h1>
     <h2>Entorno: Desarrollo</h2>
  </body>
</html>

 

Definición de los recursos de una aplicación con customize para un entorno de desarrollo

Ahora supongamos que esta aplicación corresponde a un entorno de desarrollo, pero queremos ahora desplegarla además en un entorno de preproducción. La configuración en lo esencial será la misma, pero habrá algunos parámetros diferentes, como las etiquetas, el número de pods y los límites de recursos por pod. Podríamos simplemente repetir todos los archivos YAML y modificarlos en aquellos puntos que difieren, pero aquí es donde interviene kustomize para hacer esto un poco más sencillo.

Kustomize permite partir de una configuración válida, como en este caso la de desarrollo, y definir varias configuraciones alternativas con cambios a partir de configuración base. Esto permitiría por ejemplo partir de un repositorio de git sobre el que no tengamos control, personalizando las partes que difieren y adaptar más fácilmente los cambios que surjan en el repositorio original.

Vamos a seguir una de las estrategias posibles que es especificar sólo los atributos del archivo YAML que difieren y aquellos que conforman la jerarquía completa de la que dependen hasta la raíz para que el resultado sea un archivo correcto.

Primero vamos a eliminar todos los objetos que hemos creado:

kubectl --kubeconfig=$HOME/.kube/ocm-hub.config delete -f .

Vamos a revisar la configuración base, que estará compuesta por los archivos que ya teníamos con alguna leve modificación.

base/configmap.yaml

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: contenido
  namespace: portales
data:
  entorno: Base
  index: >
    <html>
      <style>
        body {
          background-color: #fff;
        }
      </style>
      <body>
         <h1>Portal corporativo</h1>
         <h2>Entorno base</h2>
      </body>
    </html>    

 

base/deployment.yaml

----
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: portales
  name: portal
  labels:
    app: portal
spec:
  replicas: 0
  selector:
    matchLabels:
      app: portal
  template:
    metadata:
      labels:
        app: portal
    spec:
      containers:
      - name: nginx
        image: nginx:1.21.5
        ports:
        - containerPort: 8080
          protocol: TCP
        volumeMounts:
          - name: paginas
            mountPath: /usr/share/nginx/html
        resources:
          requests:
            cpu: 100m
            memory: "128M"
          limits:
            cpu: 100m
            memory: "256M"
        env:
        - name: LOG_LEVEL
          value: "DEBUG"
        - name: ENTORNO
          valueFrom:
            configMapKeyRef:
              name: contenido
              key: entorno
      volumes:
      - name: paginas
        configMap:
          name: contenido
          items:
          - key: index
            path: index.html

 

base/service.yaml

---
apiVersion: v1
kind: Service
metadata:
  namespace: portales
  name: portal
  labels:
    app: portal
spec:
  type: NodePort
  ports:
  - name: acceso-externo
    nodePort: 30000
    port: 8080
    protocol: TCP
    targetPort: 80
  selector:
    app: portal

 

Aparte de los archivos de definición propiamente dichos tendremos que añadir un archivo de configuración para kustomize. Tiene que estar en el directorio donde tenemos nuestros archivos YAML y su nombre debe ser kustomization.yaml:

base/kustomization.yaml

---
resources:
- configmap.yaml
- deployment.yaml
- service.yaml

Este archivo contiene la lista de los archivos que conforman la configuración base, lo que se indica bajo la etiqueta resources.

Ahora crearemos el directorio overlays donde añadiremos las personalizaciones sobre la base para cada uno de los entornos. Comenzamos creando una personalización para el entorno de desarrollo, en un directorio que llamaremos dev:

mkdir -p overlays/dev

Respecto al configmap, tendremos un archivo completo. Difiere en la variable entorno y que en el código html mostrará en un encabezado el texto Entorno desarrollo. Con respecto a este elemento no hay ningún ahorro en código y reutilización que nos aporte kustomization. El contenido principal es el código HTML, que es contenido estático. Si se tratara de otro tipo de archivo que no fuera estático podríamos incluir contenido en base a variables de entorno y no sería necesario repetirlo al completo:

overlays/dev/configmap.yaml

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: contenido
  namespace: portales
data:
  entorno: Desarrollo
  index: >
    <html>
      <style>
        body {
          background-color: #fff;
        }
      </style>
      <body>
         <h1>Portal corporativo</h1>
         <h2>Entorno desarrollo</h2>
      </body>
    </html>    

Respecto al deployment, vamos a especificar el número de pods, que será 1 (en la configuración base era 0), para lo que crearemos el archivo overlays/dev/deployment-replicas.yaml:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: portales
  name: portal
spec:
  replicas: 1

En este caso sólo incluimos los datos necesarios para la identificación del objeto (metadata.namespace y metadata.name) y el dato concreto que queremos modificar (spec.replicas:1)

También sobre el deployment vamos a modificar las etiquetas asignadas al deployment y a los pods que contiene, para que sean diferentes de las de otros entornos, en este caso las etiquetas van a ser app: portal-dev. El archivo lo llamaremos overlays/dev/deployment-labels.yaml:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: portales
  name: portal
  labels:
    app: portal-dev
spec:
  selector:
    matchLabels:
      app: portal-dev
  template:
    metadata:
      labels:
        app: portal-dev

En el service, será necesario modificar las etiquetas de selección de pods para que se adecúen al entorno de desarrollo, etiquetado con app: portal-dev. También habrá que variar el puerto. Estos cambios estarán en el archivo overlays/dev/service.yaml:

---
apiVersion: v1
kind: Service
metadata:
  namespace: portales
  name: portal
spec:
  ports:
  - name: acceso-externo
    nodePort: 30001
    port: 8080
    protocol: TCP
    targetPort: 80
  selector:
    app: portal-dev

A todo lo enterior añadiremos un archivo de configuración para kustomize. Tiene que estar en el directorio donde tenemos nuestros archivos YAML y se llamará kustomization.yaml:

---
nameSuffix:
  -dev
bases:
- ../../base
patchesStrategicMerge:
- configmap.yaml
- service.yaml
- deployment-labels.yaml
- deployment-replicas.yaml

Este archivo refleja cuáles son los archivos de definición a procesar, indicando cuál es el directorio o directorios que contienen la configuración base (parámetro bases), cuales son los archivos que modificarán esa configuración base (parámetro patchesStrategicMerge) y además que sobre los nombres de los objetos que se creen se aplicará el sufijo -dev (parámetro nameSuffix).

Kustomize soporta diferentes mecanismos de parcheo o personalización de la configuración base, como patchesStrategicMerge y patchesJson6902. El primero de ellos, que es el que estamos utilizando en este ejemplo, es una lista de archivos, cada uno de los cuales debe incluir el nombre de un recurso (Deployment, Pod, Service, etc.) procedente de la base. Lo más recomendable es hacer pequeños parches que hacen un cambio en concreto.

La estructura de archivos y directorios que queda tras la configuración es la siguiente:

webapp
├── base
│   ├── configmap.yaml
│   ├── deployment.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays
    └── dev
        ├── configmap.yaml
        ├── deployment-labels.yaml
        ├── deployment-replicas.yaml
        ├── kustomization.yaml
        └── service.yaml

Volvemos a desplegar nuestra aplicación, pero en este caso especificando el parámetro -k para indicar que se debe utilizar kustomize para procesar los archivos de definición y el directorio donde se encuentran las definiciones. Al utilizar este parámetro kustomize buscará un archivo kustomization.yaml y aplicará las instrucciones ahí definidas. Si nos situamos en el directorio webapp la forma de aplicar la configuración para el entorno de desarrollo sería:

kubectl --kubeconfig=$HOME/.kube/ocm-hub.config apply -k overlays/dev
configmap/contenido-dev created
service/portal-dev created
deployment.apps/portal-dev created

Podemos ver que los elementos se han creado con el nombre previsto y el sufijo -dev.

kubectl --kubeconfig=$HOME/.kube/ocm-hub.config get pods,deployments,services -n portales
NAME                             READY   STATUS    RESTARTS   AGE
pod/portal-dev-d6f58dc49-zb47c   1/1     Running   0          27s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/portal-dev   1/1     1            1           27s

NAME                 TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/portal-dev   NodePort   10.152.183.50   <none>        8080:30001/TCP   27s

y que la web muestra su contenido a través del puerto 30001:

curl 10.89.130.31:30001
<html>
  <style>
    body {
      background-color: #fff;
    }
  </style>
  <body>
     <h1>Portal corporativo</h1>
     <h2>Entorno desarrollo</h2>
  </body>
</html>

 

Definición de los recursos de una aplicación con customize para un entorno de preproducción

Ahora crearemos dentro del directorio overlays un directorio que llamaremos pre para almacenar la personalización para el entorno de preproducción:

mkdir -p overlays/pre

El config map será idéntico salvo por la variable entorno y la descripción del encabezado h2 del código html:

overlays/dev/configmap.yaml

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: contenido
  namespace: portales
data:
  entorno: Preproducción
  index: >
    <html>
      <style>
        body {
          background-color: #fff;
        }
      </style>
      <body>
         <h1>Portal corporativo</h1>
         <h2>Entorno preproducción</h2>
      </body>
    </html>    

Respecto al deployment, vamos a especificar el número de pods para el, que será 2, para lo que crearemos el archivo overlays/dev/deployment-replicas.yaml:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: portales
  name: portal
spec:
  replicas: 2

Sobre el deployment vamos a modificar las etiquetas asignadas al deployment y a los pods que contiene, en este caso app: portal-pre. El archivo lo llamaremos overlays/dev/deployment-labels.yaml:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: portales
  name: portal
  labels:
    app: portal-pre
spec:
  selector:
    matchLabels:
      app: portal-pre
  template:
    metadata:
      labels:
        app: portal-pre

En el service, cambiarán las etiquetas de selección de pods, etiquetados con app: portal-pre. También habrá que variar el puerto. Estos cambios estarán en el archivo overlays/dev/service.yaml:

---
apiVersion: v1
kind: Service
metadata:
  namespace: portales
  name: portal
spec:
  ports:
  - name: acceso-externo
    nodePort: 30002
    port: 8080
    protocol: TCP
    targetPort: 80
  selector:
    app: portal-pre

Por último añadimos el archivo kustomization.yaml:

---
nameSuffix:
  -pre
bases:
- ../../base
patchesStrategicMerge:
- configmap.yaml
- service.yaml
- deployment-labels.yaml
- deployment-replicas.yaml

La estructura de archivos y directorios que tenemos es la siguiente:

webapp
├── base
│   ├── configmap.yaml
│   ├── deployment.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays
    ├── dev
    │   ├── configmap.yaml
    │   ├── deployment-labels.yaml
    │   ├── deployment-replicas.yaml
    │   ├── kustomization.yaml
    │   └── service.yaml
    └── pre
        ├── configmap.yaml
        ├── deployment-labels.yaml
        ├── deployment-replicas.yaml
        ├── kustomization.yaml
        └── service.yaml

Volvemos a desplegar nuestra aplicación, pero en este lugar indicando tras el parámetro -k la ruta de los archivos del entorno de preproducción:

kubectl --kubeconfig=$HOME/.kube/ocm-hub.config apply -k overlays/pre
configmap/contenido-pre created
service/portal-pre created
deployment.apps/portal-pre created

Podemos ver que los elementos se han creado con el nombre previsto y el sufijo -pre.

kubectl --kubeconfig=$HOME/.kube/ocm-hub.config get pods,deployments,services -n portales
NAME                             READY   STATUS    RESTARTS   AGE
pod/portal-dev-d6f58dc49-zb47c   1/1     Running   0          21m
pod/portal-pre-5756bdc5f-j8xsx   1/1     Running   0          26s
pod/portal-pre-5756bdc5f-d8cjv   1/1     Running   0          26s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/portal-dev   1/1     1            1           21m
deployment.apps/portal-pre   2/2     2            2           26s

NAME                 TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/portal-dev   NodePort   10.152.183.50   <none>        8080:30001/TCP   21m
service/portal-pre   NodePort   10.152.183.47   <none>        8080:30002/TCP   26s

En este caso vemos que se han desplegado dos pods para el entorno de preproducción. También podemos ver que la web muestra su contenido, en este caso a través del puerto 30002:

curl 10.89.130.31:30002
<html>
  <style>
    body {
      background-color: #fff;
    }
  </style>
  <body>
     <h1>Portal corporativo</h1>
     <h2>Entorno preproducción</h2>
  </body>
</html>

 

Resumen

Hemos visto la definición de recursos para desplegar en kubernetes una aplicación básica de la forma habitual.

A continuación hemos adaptado la estructura de este despliegue para crear una configuración base apta para kustomize.

Se ha definido un parche con kustomize sobre la configuración base para el despliegue de la aplicación en un supuesto entorno de desarrollo.

Se ha definido un segundo parche con kustomize para el despliegue en un supuesto entorno de producción.

Este esquema sería replicable para otros entornos y variantes de configuración, que podríamos desplegar de forma individual o en conjunto de forma simultánea.

Los archivos de recursos utilizados en este artículo se encuentran en https://gitlab.com/dvdcr/kustomize-basic-sample

 

Referencias