CaaS Cloud

12 Consejos para usar todo el potencial de un entorno de contenedores

12/04/21 18 min. de lectura

Hace unos meses hablaba en este post sobre todos los tipos de servicios de Nube así que si aún no los tienes claros, puedes leerlo aquí

¿Qué es un entorno de contenedores?

Como explica Google, los contenedores ofrecen un mecanismo de empaquetado lógico en el que las aplicaciones se pueden abstraer del entorno en el que se ejecutan. Esta separación permite que las aplicaciones basadas en contenedores se implementen de manera fácil y uniforme.

Uno de los problemas más comunes es que no aprovechamos todo el potencial de estos entornos.

Por eso, en este post te voy a hablar de las operativas que afectan al uso de Contenedores como Servicios (CaaS, del inglés: Container as a Service), conceptos, detalles y 12 consejos basados en mi experiencia que no puedes olvidar al utilizar un entorno de estas características en entornos productivos si quieres aprovechar todo su potencial.

Lo primero a tener en cuenta es que en entornos productivos hay muchas configuraciones que requieren de conocimientos más afinados que en los entornos previos de desarrollo o pre producción.

Trabajando con Docker 🐳

El uso de la tecnología docker requiere de múltiples configuraciones y expertise técnico, sumado al conocimiento de las arquitecturas y sistemas sobre los que se apoyan.

Esto hace que el disponibilizar y mantener un alto número de servicios de forma manual o estanca sobre los S.O tradicionales sea bastante complejo.

Por ello, el mejor consejo es apoyarse en un orquestador de contenedores, que nos dará de caja las siguientes características o funcionalidades ya implementadas, disponibles para utilizar o configurar.

Esto no quita que debamos aprender muy bien qué significan y qué potencia nos dan antes de utilizarlas, ya que existen múltiples configuraciones que se ajustarán a las necesidades dependiendo de un gran número de factores externos. Pero bueno, esto es otra historia.

Un orquestador de contenedores nos ofrece:

  • Configuración automática
  • Despliegue y arranque automático de contenedores
  • Balanceado de carga
  • Capacidad de Auto-escalado
  • Capacidad de auto-reinicio
  • Healthchecks o Control de la «salud» de cada contenedor
  • Control del Intercambio de datos y networking
  • Mantenimiento de secrets y configuraciones, p.ej config-maps

12 Consejos para usar todo el potencial de un entorno de contenedores

12 Consejos para usar todo el potencial de un Entorno de Contenedores

👉 1º Definir correctamente la arquitectura de la aplicación

Saber discernir qué elementos de la misma son susceptibles y cuáles no de ser contenedores, realizar un assessment si se quiere llevar la arquitectura actual a contenedores, o realizar una nueva definición acorde a esto en caso de ser un producto nuevo.

No es igual de sencillo trabajar con Java/Maven en  microservicios, que con Bases de Datos (BBDD) o productos dockerizados en cluster. Para ayudar a tomar esta decisión deberíamos basarnos en el concepto de Cloud Readiness.

Para el caso del software a desarrollar – que será el que formará parte de nuestros contenedores ya que el contenido de este software se desplegará y compilará en el interior de los mismos -,  tendríamos que tener en cuenta los siguientes criterios:

  • Diseño o uso de microservicios.

https://www.redhat.com/es/topics/microservices

  • Desacoplar los datos lo máximo posible.

Los datos deben encontrarse en sistemas externos, si es posible con mayor seguridad o cifrado.

Además hay que tener en cuenta que los contenedores son efímeros y los datos almacenados dentro se pierden tras un reinicio, y solamente el propio contenedor es capaz de acceder a ellos, por lo que hay que tratar de utilizar para soportar el uso de datos compartidos, sistemas de datos y protocolos externos (JDBC, SFTP, S3).

El uso de sistemas de cacheo también puede ser muy importante, pero siempre hay que analizar tanto la necesidad como la eficiencia de los mismos, y que cumplan a su vez el criterio de resiliencia explicado en el siguiente punto. (p.ej Redis o Datagrid son sistemas de uso habitual)

  • Hacer la aplicación lo más resiliente posible

Es necesario que el software y su configuración esté optimizado al máximo para resistir cualquier tipo de fallo provocado por problemas externos.

«Estos fallos normalmente están asociados a las comunicaciones con otros sistemas o con otras piezas de la arquitectura»

Para ello hay que trabajar en el buen uso y configuración de pooles de conexiones, con parámetros muy afinados, así como con la capacidad de reconectar ante fallos sin la necesidad de reincios ni ninguna actuación manual o humana.

  • Rendimiento y Escalabilidad

Es muy importante la optimización también de los recursos utilizados, clases, librerías etc… Se necesitan que sean los más rápidas y eficientes posibles.

Adicionalmente es muy importante tener en cuenta la posibilidad de que el SW se ejecute de forma standalone y sea compatible con la funcionalidad de Autoescalado que proveen estos sistemas , para funcionar en modo alta disponibilidad del mismo modo permitiendo una experiencia fluida al usuario final y sin fallos.

  • Incluir la seguridad en el código desde el primer momento

Cada vez es más importante, y sobre todo en sistemas cloud, que nuestros sistemas sean seguros, y no solo los sistemas, se puede empezar desde el código.

Para ello existen también sistemas de control de código que pueden analizar e indicarnos los problemas de seguridad desde el punto del desarrollador. Si queréis conocer más detalles sobre estos conceptos os dejo otros post de interés.

👉 2º Revisar el Entorno de Despliegue

Para que la experiencia sea completa, vamos a necesitar disponer de un entorno preparado para poder integrar la compilación del SW, con la compilación de nuestras imágenes docker, así como la integracion y despliegue de las mismas sobre el entorno final de orquestación.

Este grupo de herramientas se suele definir con la nomenclatura ALM (Application Lifecycle Management), y consta normalmente de:

  • Compiladores
  • Herramientas de Gestion y Control de Versiones (p.ej Git)
  • Orquestadores de tareas (p.ej Jenkins)
  • Repositorios de Binarios (p.ej. Nexus)
  • Herramientas de QA o análisis de código, tanto técnico, como funcional o de seguridad. (SonarQube, Kiuwan, Fortify, etc…)
Fuente: Miro Medium

Y para completar este “sencillo” stack de herramientas vamos a necesitar utilizar un repositorio de imágenes docker y disponibilizar a su vez de unas plantillas o templates asociadas a cada una de las imágenes, que permitan a nuestro entorno final entender que recursos, configuraciones y variables vamos a necesitar utilizar cada vez que desplegamos uno de estos contenedores.

👉 3º Minimizar el contenido del contenedor

Existen en el mercado diversas versiones de S.O Ligeras preparadas para correr de forma más eficiente sobre contenedores. Éstos contienen algunas restricciones de diseño específicas: un sistema de archivos de solo lectura, un conjunto mínimo de paquetes y un solo comando para administrar actualizaciones.

Lo que nos interesa es que el contenedor arranque lo más rápido posible asi como que consuma los mínimos recursos del host que lo contenga.

Esto nos permitirá albergar un mayor número de dockers sobre la misma infraestructura base del cluster.

👉 4º Gestionar y estandarizar el catálogo de imágenes disponibles

En entornos grandes o empresariales lo más aconsejable es disponer de un catálogo de imágenes base predeterminados que puedan cumplir con el 80/90% de los requisitos arquitecturales. Esto nos permitirá además de disponer de imágenes optimizadas y homologadas en cuanto a todo tipo de requisitos (especialmente los de seguridad).

A su vez disponer de un entorno standard de monitorización y uso de los mismos, evitando tener infinidad de imágenes diferentes, cuyo comportamiento o análisis en cuanto a soporte se complicaría infinitamente.

👉 5º La seguridad es muy importante

Simplemente como punto a tener en cuenta, no es la finalidad de este post hacer zoom en la parte de seguridad, pero debe estar presente en todo momento, más si vamos a exponer a internet algunos de los servicios desplegados. Siempre recomendar el uso de WAF’s tipo Imperva si queremos aprovechar las ventajas del cloud.

Además no olvidarnos de tener todo el tráfico seguro mediante certificados SSL con los últimos algoritmos de cifrado así como no permitir la entrada de protocolos inseguros (p.ej TLS 1.0 o 1.1 actualmente).

En cuanto a parámetros genéricos como fuga de información o cabeceras de seguridad, debemos preocuparnos de que todo nuestro catálogo de imágenes base cumpla con todo esto de caja.

Además de contener los sistemas operativos últimas versiones de parcheo etc… debemos preocuparnos de tener también las últimas versiones de los productos o servidores de aplicaciones desplegados y configurados de modo que evitemos cualquier tipo de fuga información que permita a un atacante conocer a que se enfrenta.

👉 6º Uso de Healthchecks

Openshift nos ofrece dos tipos de healthchecks para cada grupo de contenedores o pods.

  • Readiness

Nos permite indicar cuando, una vez levantado y arrancados los procesos del contenedor, éste pasa a estar disponible por el servicio (básicamente decide si añadirlo a la lista de contenedores ya existentes para permitir que empiece a entrar tráfico también a esta nueva instancia a partir del momento de cumplimiento del check).

Inform Service: Readiness Probe Passed
Fuente: Blogger.com

Es muy importante ya que es posible que el orquestador detecte que el contenedor ya está running, pero los procesos que corren dentro y que hacen que el aplicativo desplegado funcione pueden tardar más tiempo en llegar a estar disponibles.

Por lo que una mala o nula configuración de este health, hará que se produzcan fallos durante el arranque del mismo si hay peticiones que se desvían a esta instancia sin haber llegado a arrancar completamente.

  • Liveness

Nos permite revisar periódicamente el correcto comportamiento de nuestro contenedor, pudiendo revisarlo via url, o revisando la conexión con un puerto concreto (también se puede revisar la ejecución de la salida de un script interno que haga las revisiones pertinentes).

Openshift nos permite indicar cada cuanto tiempo vamos a realizar esta comprobación, asi como cuantas veces fallidas es realmente un KO para nosotros, haciendo a partir de ese momento que, tras un KO, el contenedor se reinicie y vuelva a su estado original (además no volverá a dar servicio hasta que no cumpla el primer check de Readiness comentado antes).

Este health nos permite tener una herramienta rápida de recuperación ante un mal funcionamiento evidente de la funcionalidad principal desplegada.

Liveness PASS
Fuente: Blogger.com

Tan importante es realizar estas configuraciones como realizarlas bien, y eso significa que una configuración estándar no es válida en ningún caso para todos ellos y que nos toca hacer zoom en el comportamiento del contenedor para poder ajustar los valores a unos tiempos que nos permitan recuperar el servicio lo antes posible, pero sin llegar a pasarnos, ya que podemos generar un buen problema de pérdida de servicio teniendo una mala configuración, o una no lo suficientemente trabajada.

El uso de url’s como check, solamente permite reconocer como KO, códigos de error diferentes a 200 en el rango de tiempo que le hemos indicado (como si fuera un timeout), no permite reconocer patrones en el código de vuelta o errores visuales que contengan 200 como código de salida.

En el caso de springboot, y haciendo uso de springcloudconfig, el mejor uso para este tipo de checks es la url /info , que simplemente nos da información sobre nosotros mismos, haciendo uso de otro tipo de healths como /health que supuestamente es el status de salud de nuestra aplicación java, podemos caer en el fallo de que dicho /health valide dependencias o sistemas de terceros, pudiendo llegar a dar KO’s por timeout a la espera de la información de health de un tercero, pero el sistema acabaría tirando nuestro pod en ejecución, aunque nuestro estado realmente fuera correcto.

👉 7º Autoescalado, criterios y mejor uso

Debemos conocer el comportamiento de la aplicación para hacer el mejor uso posible sobre los HPA, entendiendo si nos puede afectar un alto consumo de CPU , Memoria o cualquier otro criterio de monitorización.

Esto nos hará eficientar las métricas a configurar en cuanto a escalado Horizontal, evitando que se creen un montón de réplicas innecesariamente ante un pico puntual que al final no serán utilizadas haciendo el entorno más ineficiente, sobre todo durante unas pruebas de carga controladas.

👉 8º Uso de límites e implicaciones

Estas configuraciones son sumamente importantes no solo para el buen funcionamiento del contenedor, sino para la salud global de los hosts donde viven, ya que si no limitamos el consumo, un solo pod podría ocupar los recursos completos de su host, y dejar al resto de contenedores que conviven con el sin capacidad de procesamiento o memoria, generando un problema en el entorno.

Importante es saber que debemos controlar y vigilar detenidamente tanto la memoria y cpu consumida por el contenedor en si (a modo S.O) que es la que afecta al host, como saber detallar que procesos o servicios internos al pod son los que hacen que esa memoria o CPU llegue a sus limites.

A veces , y ante fallos de outofmemory parece lógico ir subiendo los limites del pod, y nos olvidamos de revisar que problema dentro de él es el que lo está generando y porqué… ¿es el software?, ¿es algún problema o configuración del servidor de aplicaciones?, ¿algún otro proceso que corre en el docker que no funciona correctamente?.

Si conseguimos hacer este análisis detallado, probablemente podamos corregir el problema sin subir la memoria del contenedor, e incluso optimizarlo para que se pueda reducir sin afectar al rendimiento de la aplicación.

La CPU es igual de importante controlarla y monitorizarla, los microservicios no debieran consumir con su lógica demasiada CPU ni ser muy pesados.

controlar y monitorizar la CPU

Un ejemplo de host sería una máquina de 64GB con 8CPU’s , teniendo en cuenta que los microservicios pudieran correr con 512MB de RAM, tendríamos una capacidad de más de 100 pods corriendo en el mismo host.

Si hacemos los cálculos, y contando con las CPU’s que reserva el orquestador, tendríamos una situación donde el reparto de CPU por microservicio sería del orden de 6 CPU/100 pods , por lo que de media deberían consumir unos 60 millicores (¡¡prácticamente nada!!).

👉 9º Pruebas de carga, comprueba tus limites

Es algo obvio que se necesitan realizar este tipo de pruebas para asegurar un correcto funcionamiento del entorno, y capacidad del mismo. Sobre todo para revisar que las configuraciones de cpu/memoria, y Autoescalado funcionan y permiten que el entorno cambie según las necesidades de carga, dando unos tiempos de respuesta razonables o esperados.

Ahora bien, como todo, es muy importante hacer estas pruebas con casos de uso lo más completos posibles, ya que podemos caer en un engaño, pensando que el sistema aguanta si probamos ciertas operativas, pero otras (más pesadas) no.

Es recomendable disponer de un capacity amplio para tener en todo momento la posibilidad de escalar ante un pico , sin el riesgo de quedarse sin recursos. El Autoescalado de nodos del cluster es una opción muy interesante aunque más complejo.

👉 10º Entorno completo de Monitorizacion/observabilidad

Es muy recomendable tener herramientas y exportar datos que generan los orquestadores y que de forma agrupada nos permitan tener acceso también a los siguientes datos:

  • Gestion de logs de contenedores
  • Gestion de entrada/salida de trafico HTTP a través de los routers.
  • Gestion de Eventos (Incluidos healthchecks)
  • Gestion de auditoria (quien hace que)
  • Informacion de infraestructura, tanto status de nodos, como de contenedores a nivel de CPU/RAM/Procesos.

👉 11º Explorar Procesos Chaos Monkey 

La idea es poder hacer pruebas de resiliencia sobre el entorno o proyectos concretos, configurando caídas y errores de forma aleatoria o controlada, para poder comprobar que los sistemas son lo suficientemente robustos y resilientes como para soportar este tipo de “ruido”.

👉 12º Contenedores “autocontenidos” , ¿es la mejor opción?

Para terminar me gustaría hacer  una reflexión , y hacer ver que nada es tan bueno, ni tan malo como lo pintan.

«Lo más importante es elegir el sistema que más se adapte a las necesidades del resto del entorno, y sobre todo, al modelo de trabajo y metodología del equipo, que también influye mucho en tomar este tipo de decisiones»


El modelo autocontenido, donde el SW va junto a la imagen base del contenedor y se compilan conjuntamente en Integracion Continua, es un modelo que tiene múltiples ventajas (como la seguridad de que la imagen es exactamente la misma en todos los entornos con una buena gestión del versionado) o no depender de sistemas externos en el arranque de los pods (p.ej nexus para traerse el SW, o un git para los ficheros de configuración etc…)

Son puntos de fallo que nos evitamos y suelen fallar en el momento menos oportuno pero requieren de un equipo sumamente ágil que gestione los cambios y problemas, ya que cualquier cambio que se lleve a producción debe estar definido previo a la compilación.

Además también requieren un sistema robusto de integracion continua que asegure la disponibilidad del entorno de integracion y despliegue continuo para poder resolver un problema o bug de forma rápida.

Esta sería en la mayoría de los casos la mejor opción… sobre todo hoy en dia si contamos con equipos DevOps y procedimientos Agile.

Por otra parte, el tener desacoplados los elementos de Dockers/SW/Configuración, posibilita que sean diferentes equipos los que trabajen en cada uno de los ámbitos, pudiendo delegar más fácilmente el soporte a un tercero, y muy importante, facilitando los cambios masivos derivados de cambios requeridos en las imágenes base, ya sea por obsolescencia, mal funcionamiento o posibles riesgos de seguridad.

Imaginemos un entorno con 10.000 microservicios java springboot, que hacen uso de una imagen base java V1 y se encuentran en produccion. A dicha imagen se le detecta un problema critico de vulnerabilidad nuevo que es necesario corregir y desplegar urgentemente pero dada una nueva imagen V1.1…

Con el modelo autocontenido, necesitamos 10.000 compilaciones, y 30.000 despliegues de SW para tener seguro el entorno productivo. Con este modelo se podría cambiar un parámetro de forma automatizada en 10.000 ficheros yml (de forma controlada por supuesto para no saturar el entorno J), pero se podría hacer desde el equipo de operaciones sin necesidad de contar con desarrollo ni con equipos de implantación o despliegue y solo en 1 paso!!!.

Esto mismo se aplica a las otras piezas, ya que se podría modificar la versión del SW para corregir un fix de forma instantánea sin necesidad de compilar un nuevo docker, o se podría hacer un cambio urgente de configuración para corregir un parámetro crítico en solo segundos.

Miguel Angel Salas

Miguel Ángel Salas

Santander Global Tech

Cloud & Transactional Manager, actualmente gestiono equipos multidisciplinares, especialmente de la capa middleware y asociados a las nuevas tecnologías. Estoy especializado en APIs, Sistemas Cloud, Contenedores y Metodologías DevOps y Agile; y disfruto estando a la última y aportando mi pequeño grano de arena. Siempre orientado al impulso del cambio y sobre todo, a las personas.

 

👉 Mi perfil de LinkedIn

 

Otros posts