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
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.
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 archivoindex.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>
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>
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>
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
- https://kustomize.io/
- https://github.com/kubernetes-sigs/kustomize/
- https://learnk8s.io/templating-yaml-with-code
- https://kubectl.docs.kubernetes.io/guides/introduction/kustomize/
- https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md
- https://gitlab.com/dvdcr/kustomize-basic-sample