Skip to main content

Lanzar aplicaciones nativas de Kubernetes con confianza

Escrito por
Headshot of Amir Moualem

Amir Moualem

kubernetes tumb

14 de noviembre de 2019

0 minutos de lectura

Hace unos meses, nuestro equipo comenzó a desarrollar un nuevo producto.

Este nuevo producto tiene algunas características que lo diferencian de otros proyectos de software que hemos desarrollado hasta ahora:

  1. Es nativo de Kubernetes, lo que significa que está estrechamente vinculado a la API de Kubernetes y requiere esa API específica para ejecutarse (o incluso para probarse… más sobre esto adelante).

  2. Requiere aplicar ciertas configuraciones para que funcione correctamente. Esto significa que debemos distribuir esta imagen con los archivos de configuración .yaml de Kubernetes para que nuestros usuarios puedan instalarla fácilmente. Esto se complica aún más porque queremos simplificar al máximo la incorporación al producto, por lo que admitimos la instalación tanto mediante charts de Helm como mediante archivos .yaml “vanilla”; ambos deben probarse y publicarse.

  3. Es una aplicación del lado del cliente; perdemos el control sobre ella en cuanto se publica, y no hay forma de recuperarla. Publicar hotfixes para nuestros microservicios puede hacerse en minutos, pero pedirles repetidamente a nuestros usuarios que actualicen su instalación solo complicaría su incorporación, en el mejor de los casos.

Esto nos llevó a dedicar un poco más de tiempo a planificar una canalización de CI y CD que nos permitiera lanzar un producto así con confianza, sin ralentizarnos demasiado.

La canalización de CI/CD existente de Snyk para microservicios gira en torno a la interacción de nuestros desarrolladores con GitHub:

  1. Al abrir pull requests, se instala, ejecuta y prueba el servicio en un entorno de pruebas.

  2. Al combinar pull requests, el proceso comienza de la misma manera y luego se realiza una versión semántica del servicio, se crea una imagen de Docker, se ejecuta y se implementa en el clúster de Kubernetes de nuestro entorno de producción.

Buscábamos interacciones similares para que el proceso se sintiera lo más natural posible.

Entonces, ¿cómo abordamos este proyecto?

Pruebas de integración de Kubernetes con Kind

La primera incógnita para nosotros, que teníamos poca experiencia, era cómo probar software que depende tanto de la API de Kubernetes. Simular todos los flujos relacionados con la API de Kubernetes sería increíblemente difícil de escribir y mantener, y probablemente dejaría fuera algunos de los flujos críticos que deberían probarse. Un enfoque ingenuo probablemente usaría un clúster real de Kubernetes, ya sea configurando uno para cada fase de pruebas o manteniendo un entorno en ejecución permanente. Aunque sin duda funcionaría, implicaría más complicaciones de las que estábamos dispuestos a afrontar en esta etapa inicial del desarrollo del software:

  • Crear nuevos entornos de Kubernetes cada vez tomaría tiempo de desarrollo y aumentaría considerablemente el tiempo de configuración de las pruebas.

  • Mantener un entorno en ejecución permanente implica encargarse de la limpieza y puede introducir mucha inestabilidad en las pruebas.

Por suerte, nos recomendaron elproyecto Kind. Kind es una herramienta basada en Docker para ejecutar clústeres locales de Kubernetes que cumplen con la API de Kubernetes. Se ajustó perfectamente a nuestras necesidades, ya que combina las ventajas de tener un entorno limpio para cada prueba con la de una configuración muy rápida.

Ahora, nuestra fase de pruebas, ya sea en nuestro entorno de CI o incluso de forma local, simplemente crea un clúster de Kind, carga en él la imagen que acabamos de crear y verifica que nuestro contenedor realice el trabajo asignado.

Prueba lo que entregas; entrega lo que pruebas

El siguiente desafío era asegurarnos de no probar solo nuestro código, sino también el producto completo que entregamos.

El código (y el servicio) se instala en una imagen de Docker. La imagen de Docker puede implementarse en clústeres de Kubernetes mediante charts de Helm o archivos .yaml “vanilla”, y los clústeres de Kubernetes pueden admitir distintas versiones de las API de Kubernetes.

Nuestro objetivo es probar y admitir esta amplia variedad de escenarios.

Desde el momento en que creamos una imagen, le asignamos una etiqueta para poder identificarla durante sus etapas de prueba. Esa imagen se instala con charts de Helm y archivos .yaml “vanilla” en clústeres de Kubernetes con distintas versiones de API, antes de recibir la etiqueta “approved”.

En esta etapa también usamos semantic-release para asignar versiones a nuestras imágenes. En lugar de llevar un registro de nuestros productos mediante sus SHA de Git o versiones arbitrarias asignadas manualmente, optamos por el versionado semántico. Así ofrecemos versiones sencillas y fáciles de entender, con notas de la versión asignadas automáticamente y publicadas en GitHub. El resultado final es una imagen con una etiqueta versionada, como 1.2.3.

En esta etapa, lo único que debemos hacer es apuntar nuestros archivos .yaml de implementación a esa nueva versión...

Publicar fácilmente con charts de Helm y GitHub Pages

Helm es el administrador de paquetes para Kubernetes. Una breve investigación reveló dos enfoques principales para publicar charts de Helm:

  1. Alojar el chart de Helm en elrepositorio público de charts seleccionados.

  2. Alojar los charts por nuestra cuenta con GitHub Pages en nuestro repositorio.

Al menos al principio, el segundo enfoque nos pareció más conveniente, porque es independiente y no nos hace depender de terceros. A medida que nuestro producto y nuestra canalización de CI y CD maduren, quizá decidamos contribuir nuestro chart al repositorio público también.

Para terminar, GitHub

Parece que hemos resuelto la mayoría de nuestros problemas: ejecutamos pruebas de integración con Kind para simular entornos reales de Kubernetes en distintos escenarios y usamos el versionado semántico para etiquetar nuestros productos, que publicamos en GitHub Pages para simplificar el proceso.

Entonces, ¿cómo encaja todo esto en nuestro proceso de desarrollo?

Acordamos usar dos ramas principales en nuestro repositorio de Git:

  1. staging, nuestrapredeterminada rama, es el punto de ramificación y combinación de todos los cambios al producto (funcionalidades, correcciones de errores, tareas de mantenimiento, etc.).

  2. master es el punto de decisión para publicar nuestro producto probado.

La canalización de CI y CD tiene cuatro etapas en total, que abarcan estas dos ramas y las dos interacciones principales que nuestros desarrolladores tienen con las ramas de GitHub (abrir un pull request y combinar un pull request). Cada una busca aumentar nuestra confianza en el producto que creamos y acercarnos a su lanzamiento para nuestros usuarios.

Vale la pena mencionar que la orquestación está a cargo de Travis CI (la migración a CircleCI está en curso), pero, para todos los efectos, podría haberse hecho con un breve script interno de Python.

La siguiente tabla resume estas cuatro etapas y lo que logramos en cada una.

Desencadenante

Acción

Objetivo

Abrir un pull request desde una rama de funcionalidad hacia staging.

Lint, compilación y pruebas unitarias.
Crear una imagen de Docker (desechable). Usarla en las pruebas de integración con Kind.

Probar en un entorno limpio.

Combinar un pull request desde una rama de funcionalidad hacia staging.

Crear una imagen de Docker. Etiquetarla como candidate.Ejecutarla en nuestras pruebas de integración.Invocar Semantic Release para etiquetar la imagen probada como 1.2.3-approved.

Asegurarnos de que las ramas combinadas no tengan conflictos. Probar y compilar en un entorno limpio. Crear una sola vez el producto que planeamos publicar.

Abrir un pull request desde staging hacia master.

Nada.

Este es un punto de pausa para realizar cualquier paso manual que queramos antes de publicar el producto, como dogfooding.

Última oportunidad para recibir comentarios.

Recordatorio para inspeccionar manualmente ciertos entornos de prueba, si es necesario.

Combinar un pull request desde staging hacia master.

Volver a etiquetar la imagen con su versión pública, 1.2.3. Modificar la referencia de la etiqueta en nuestros archivos .yaml y charts de Helm para que apunte a la nueva versión. Publicar una nueva ramagh-pages con todos los cambios para que se pueda usar el producto.

Publicar el producto que ya se creó y probó: sabemos qué estamos publicando.

Aquí no encontrarás otro resumen aburrido de siempre

Es broma, es completamente aburrido. Pero seré breve.

Creamos una excelente canalización de CI y CD y aprendimos mucho en el camino. Equilibramos la automatización con la cantidad justa de clics manuales que nos resulta natural. Cada paso nos da más confianza en lo que lanzamos, y el último hace que nuestra entrega esté disponible públicamente.

Como en cualquier otro proyecto de software, todavía hay margen de mejora. Podríamos empezar a publicar en un repositorio público de charts de Helm. Podríamos ampliar nuestra infraestructura de pruebas para incluir más entornos de larga duración y probar las actualizaciones. Cuando tengamos suficiente confianza en nuestras pruebas, quizá incluso nos saltemos algunos pasos manuales antes del lanzamiento. ¿Quién sabe?

Seguridad de contenedores centrada en los desarrolladores

Snyk encuentra y corrige automáticamente vulnerabilidades en imágenes de contenedores y cargas de trabajo de Kubernetes.

Publicado en: