10 Anti-Patterns para desarrollos de Kubernetes

Programación 6 de nov. de 2020

Este es un pequeño resumen de prácticas que debemos evitar en nuestros desarrollos Kubernetes.

Listado de anti-patterns

  1. Poner el archivo de configuración dentro de la imagen de Docker
  2. No usar Helm u otro tipo de plantillas
  3. Implementar en un orden específico
  4. Implementar pods sin establecer límites de CPU o memoria
  5. Usar la etiqueta "más reciente" de contenedores en producción
  6. Implementar nuevas actualizaciones o correcciones eliminando los pods para que extraigan las nuevas imágenes de Docker durante el proceso de reinicio
  7. Mezcla de cargas de trabajo de producción y no producción en el mismo clúster
  8. No utilizar el patrón blue/green o el patrón Canary para despliegues críticos
  9. No utilizar métricas
  10. Depender de un servicio/proveedor de nube concreto y no poder cambiar de servicio o proveedor

1. Poner el archivo de configuración dentro de la imagen de Docker

Este es un anti-pattern relacionado con el anti-patern de Docker.

La idea es evitar añadir el fichero de configuración de la imagen dentro del contenedor para que distintas configuraciones, desarrollo, producción, etc... no tengan distintos ficheros de configuración. Si hicíeramos esto, en realidad no estaríamos probando lo mismo y eso es precisamente lo que queremos evitar.

Para ello, podemos ayudarnos de dos recursos: ConfigMaps y Secrets.

Tanto ConfigMaps como Secrets son recursos que podemos usar como volúmenes de nuestras imágenes y almacenar en ellos nuestra información de configuración (ConfigMaps) o nuestras claves o certificados (Secrets).

2. No usar Helm u otro tipo de plantillas

Podríamos administrar las implementaciones de Kubernetes actualizando directamente el fichero Yaml, pero si trabajamos con varios clusters y aplicamos las mismas modificaciones, esto puede volverse tedioso y abrimos la puerta a equivocarnos en cualquier momento.

Para evitar esto, debemos hacer un uso correcto de herramientas de gestión de plantillas como Helm.

3. Implementar en un orden específico

Nuestra aplicación no debería fallar porque una dependencia no esté lista. Normalmente hay un orden específico en el inicio de nuestras aplicaciones. Por ejemplo: Un WebServer necesita tener primero una base de datos funcionando.

Es importante no aplicar esta mentalidad a la orquestación de contenedores. Ya que los contenedores se iniciar todos a la vez y si alguno falla, nuestra aplicación fallará irremediablemente.

Lo que debemos hacer es aplicar patrones de reintento, para comprobar si un contenedor tiene que esperar a que otro contenedor esté listo y sino puede en un intervalo de tiempo, pararse.

Así nos beneficiamos también de la propiedad autocurativa de Kubernetes.

4. Implementar pods sin establecer límites de CPU o memoria

La asignación de recursos varía según el servicio y puede ser difícil predecir qué recursos puede necesitar un contenedor para un rendimiento óptimo sin probar la implementación. Un servicio podría requerir un perfil de consumo de memoria y CPU fijo, mientras que el perfil de consumo de otro servicio podría ser dinámico.

Pero si no asignamos ningún perfil de consumo por ejemplo, podríamos enfrentarnos a un crecimiento desmedido de consumo de recursos y/o fallo de nuestra aplicación por falta de recursos.

Para establecer los límites de memoria y CPU para un contenedor, debemos tener cuidado de no solicitar más recursos que el límite. Para los pods que tienen más de un contenedor, las solicitudes de recursos totales no deben exceder los límites establecidos; de lo contrario, el pod nunca se programará.

La solicitud de recursos no debe exceder el límite

Establecer las solicitudes de memoria y CPU por debajo de sus límites logra dos cosas:

  • El pod puede hacer uso de la memoria / CPU cuando está disponible, lo que genera ráfagas de actividad.
  • Durante una ráfaga, el pod está limitado a una cantidad razonable de memoria / CPU.

La mejor práctica es mantener la solicitud de CPU en un núcleo o menos, y luego usar ReplicaSets para escalarlo, lo que le da al sistema flexibilidad y confiabilidad.

5. Usar la etiqueta "más reciente" de contenedores en producción

Este es un clásico. Nunca se debe usar la etiqueta "more recent" en nuestros contenedores y menos en producción.

Cualquier cambio que tenga la imagen nos puede repercutir y/o cambiar el comportamiento de nuestra aplicación con lo que volvemos a perder el concepto de "probar lo mismo" en todos los escenarios.

6. Implementar nuevas actualizaciones o correcciones eliminando los pods para que extraigan las nuevas imágenes de Docker durante el proceso de reinicio

La idea detrás de este anti-patern es conservar una trazabilidad de nuestra aplicación al ir versionando nuestro código.

Una vez que se ha lanzado una versión en producción, nunca se debe sobrescribir. Si algo se rompe, entonces no sabremos dónde o cuándo las cosas salieron mal y cuánto tiempo atrás debemos ir cuando necesitemos revertir el código mientras soluciona el problema.

7. Mezcla de cargas de trabajo de producción y no producción en el mismo clúster

Aunque Kubernetes de permita hacer esto utilizando distintos espacios de nombres, no lo hagas!!!

Es verdad que podemos ahorrar recursos y dinero, pero hay muchos factores a considerar cuando hacemos esto: Límites de recursos, rendimiento, cuotas, seguridad, etc...

Sinceramente no merece la pena comprometer el entorno de Producción por ahorrar algunos recursos.

8. No utilizar el patrón blue/green o el patrón Canary para despliegues críticos

Básicamente viene a decir que si nuestro entorno de producción lo permite, debemos usar o intentar usar este tipo de patrones para realizar nuevos despliegues de nuestro software.

Aunque Kubernetes nos permite hacer despliegues continuos, y volver atrás, no es todo lo eficiente que debería y/o puede comprometer nuestro entorno. Por ejemplo podemos encontrar los siguientes problemas:

  • Bajada de rendimiento mientras se realiza el despliegue debido a que no todos los pods están actualizados.
  • Consumo de todos los recursos de nuestro cluster
  • Dificultad de realizar un seguimiento para volver atrás cuando hay docenas, cientos o miles de implementaciones que se actualizan simultáneamente.

Por ello, si nuestro entorno lo permite debemos usar estrategias y/o patrones Canary o Blue/Green. Así nos aseguramos tener nuestro nuevo despliegue realizado correctamente y podemos volver atrás fácilmente si algo no está bien.

9. No utilizar métricas

Es importante conocer si nuestra implementación tuvo éxito o no. Si somos incapaces de medir esto nunca sabremos el estado real de nuestra implementación.

Los aspectos más importantes a considerar son:

  • Controlar el consumo de recursos
  • Equilibrar la carga de diferentes instancias de aplicaciones entre un host y otro.
  • Autoreparación de contenedores si se bloquean.
  • Aprovechar automáticamente los nuevos recursos si se añade un nuevo host al cluster.

Las métricas recomendadas por la documentación de Kubernetes son:

  • Métricas de recursos: CPU, uso de memoria, E / S de disco
  • Métricas nativas del contenedor
  • Métricas de la aplicación

10. Depender de un servicio/proveedor de nube concreto y no poder cambiar de servicio o proveedor

Precisamente la fortaleza y el objetivo de los contenedores y Kubernetes es la flexibilidad. Si nos encerramos en una solución de la nube o en un proveedor concreto perdemos esta máxima.

Existen multitud de "trampas" para obligarnos o crear dependencias con un servicio de la nube que debemos evitar, como por ejemplo:

  • Usar base de datos o servicios propios de la nube no portables a otras nubes.
  • Usar scripts de implementación y aprovisionamiento específicos de la nube cuando se pueden usar soluciones como Puppet, Ansible, Chef, etc...
  • Usar herramientas de CI/CD no portables a otras nubes.

Este artículo está basado en un resumen del siguiente artículo publicado en Medium.
10 Anti-Patterns for Kubernetes Deployments

Para más detalle podeis acudir al artículo original.