Devops CI/CD
Nos adentraremos en un conjunto de herramientas y prácticas orientadas hacia uno de los aspectos más críticos en la producción de software: el desarrollo del código del producto.
Nos concentramos principalmente en las fases de codificación y construcción en DevOps, enfatizando las herramientas de automatización para minimizar ineficiencias y errores. Hablaremos sobre la importancia de la coherencia del código, las buenas prácticas y la revisión de código para un desarrollo eficiente.
laboratorio: laboratorio de CI/CD
Fuente: https://nub8.net/es/que-es-devops/
Exploraremos cómo las herramientas de automatización y la inspección continua pueden mejorar la calidad del código. Discutiremos el uso de herramientas como SonarQube y linters para garantizar la seguridad y las buenas prácticas de código.
También conectaremos la fase de construcción con las de liberación y despliegue, subrayando cómo cada etapa afecta a las otras. Un código bien construido puede facilitar un despliegue eficiente. Examinaremos estos temas en profundidad en la sección de lanzamiento y configuración. Nuestro objetivo es ofrecer una visión completa de estas etapas vitales en DevOps para un desarrollo de software de alta calidad.
De la planificación al desarrollo
Organizar el trabajo a realizar es un aspecto esencial. Una correcta organización y especificación marcarán cómo abordamos un desafío de desarrollo. Si logramos dividir el trabajo en grandes funcionalidades o épicas, y éstas a su vez en incrementos menores como historias de usuario, el desarrollo será más sostenible, más sencillo de distribuir entre los integrantes del equipo y generará menos errores.
Imaginemos que el equipo de desarrollo ya ha priorizado y organizado el trabajo para el próximo periodo de tiempo disponible, que podría ser una semana, un sprint (en la metodología Scrum) o una liberación completa. Por medio de las estimaciones, el equipo adquiere un compromiso con los stakeholders de entregar un conjunto de funcionalidades de forma incremental al término de dicho periodo.
Es habitual que para hacer las estimaciones, el equipo divida el trabajo en partes más pequeñas, plantee un enfoque técnico para superar desafíos tecnológicos e incluso desarrolle alguna prueba de concepto o prototipo para validar la viabilidad de lo que se pretende desarrollar. Todo esto se convertirá en la base para el desarrollo.
Una vez que se tiene toda esta información y los criterios de aceptación para el trabajo a desarrollar, lo único que queda es empezar a generar el código que permitirá que el producto cumpla con los nuevos requisitos. En esta sección no se tocarán temas de desarrollo específicos como los patrones de diseño o conceptos como la programación en pareja, ya que no son exclusivos de la temática.
El código que se generará lo llamaremos código fuente. Este será modificado por uno o varios equipos para implementar las funcionalidades y los cambios que demande el negocio. En las secciones siguientes, veremos cómo se abordan los desafíos que implica tener un solo código fuente y uno o varios equipos haciendo cambios simultáneamente sobre ese código. También analizaremos las herramientas que permiten el desarrollo en paralelo.
Control de versiones y teoría de ramas
El desarrollo de software es a menudo incremental, con el código fuente creciendo a medida que se añaden nuevas funcionalidades al producto. Este proceso puede llevar a errores, por lo que es esencial tener un registro de los cambios realizados para poder revertir cualquier cambio si es necesario. Esta trazabilidad se logra con el control de versiones.
El control de versiones es un sistema que realiza un seguimiento de los cambios en el código y registra quién los ha hecho. Este seguimiento se realiza en el repositorio de código, que es donde se almacena y se distribuye el código de la aplicación. Los equipos de desarrollo se conectan a este repositorio para obtener la última versión y hacer sus cambios y mejoras.
Fuente: https://davidinformatico.com/apuntes-git/
En el desarrollo con Git como sistema de control de versiones, los desarrolladores tienen una copia local del repositorio en la que van desarrollando sus cambios. El proceso comienza con la conexión del programa de control de versiones a la URL del repositorio remoto.
Desarrollo local
El primer paso es el comando fetch, que descarga commits, archivos y referencias del repositorio remoto a nuestro repositorio local. Este comando nos permite ver en qué han estado trabajando nuestros compañeros y obtener los últimos cambios para desarrollar nuestro código sobre la versión más reciente. El comando fetch no sobrescribe los cambios remotos obtenidos en nuestro repositorio local, permitiéndonos revisar los cambios sin perder cualquier trabajo que hayamos hecho en nuestra copia local del repositorio.
El comando pull es similar al fetch, pero los cambios descargados del repositorio remoto se fusionan con el repositorio local, lo que podría causar la pérdida de los cambios que hemos hecho. Esta operación se llama merge, y puede conducir a conflictos de código que necesitarán ser resueltos.
El comando commit es el que guarda los cambios realizados en los archivos en el repositorio local, mientras que el comando push carga el historial de commits del repositorio local al repositorio remoto, haciendo que los cambios locales sean visibles para el resto del equipo.
El desarrollo en paralelo es común, con varios desarrolladores trabajando en el mismo código. Para minimizar el riesgo de conflictos con los cambios del resto del equipo, se utilizan las ramas. Las ramas son copias virtuales del repositorio de código en las que realizamos nuestros cambios sin que nuestros compañeros los vean.
Fuente: https://enmilocalfunciona.io/git-como-gestionar-y-cuidar-nuestro-codigo/
El código fuente tiene una rama inicial o raíz llamada master que contiene la aplicación en su estado final. Todas las demás ramas son derivaciones de esta. Cuando el equipo inicia el desarrollo de nuevas funcionalidades, se crean ramas a partir de la rama master. En estas ramas se desarrollan y prueban todos los cambios. Una vez que las funcionalidades están terminadas y aprobadas, se fusiona la rama con la rama padre mediante el comando merge.
Fuente: https://www.atlassian.com/es/git/tutorials/comparing-workflows/gitflow-workflow
La fusión de una rama con su rama raíz puede ser compleja, especialmente si dos desarrolladores han estado trabajando en la misma parte de la aplicación. Si surgen conflictos, hay diferentes formas de resolverlos. Si el conflicto es el resultado de ambos desarrolladores modificando las mismas líneas de código, habrá que hacer un merge commit y resolver el conflicto manualmente. Si el conflicto surge porque estamos modificando un archivo desactualizado, Git intentará resolver el conflicto automáticamente al hacer el merge
Una vez que el desarrollo de las nuevas funcionalidades ha concluido, los conflictos se han resuelto y la rama se ha fusionado con la rama padre, es hora de cerrar la rama. En este punto, ya no es posible hacer commit de más código en ella. La siguiente figura es un resumen de las operaciones, y en el desarrollo real las ramas y los procesos pueden ser más complejos.
Consistencia y calidad del código
Esencialmente, el mantenimiento de la consistencia y calidad del código es crucial en el desarrollo de software, especialmente en entornos donde múltiples desarrolladores colaboran en un mismo proyecto. Este método de desarrollo distribuido y paralelo requiere la implementación de ciertas medidas para garantizar que el código generado es coherente, sigue las mejores prácticas y no introduce fallos o brechas de seguridad.
La revisión de código por parte de los compañeros de equipo, también conocida como “peer review”, es un procedimiento crucial en este proceso. Antes de que se publiquen los cambios en una rama de código, deben ser revisados por uno o más colegas. Este proceso tiene dos ventajas principales: ofrece una retroalimentación valiosa sobre el código generado y promueve la difusión del conocimiento sobre las nuevas funcionalidades entre los miembros del equipo.
Este proceso de revisión puede realizarse fácilmente mediante herramientas de control de versiones, que permiten visualizar los cambios en los archivos y discutirlos de manera interactiva. El proceso de revisión se inicia con una solicitud de pull request, que es una notificación de que un desarrollador ha completado una característica y solicita que se revisen los cambios antes de fusionarse en la rama principal.
Además de la revisión manual de código, es importante implementar herramientas de inspección continua de código para asegurar que no se introduzcan fallos de seguridad, y para mantener la escalabilidad y mantenibilidad del código. Estas herramientas son esenciales para el desarrollo ágil, la reducción de costos y la mejora de la calidad del código.
Por último, los tres principales tipos de problemas que se pueden encontrar son los bugs, las vulnerabilidades de seguridad y las violaciones de las mejores prácticas (también conocidas como “code smells”). Las herramientas automáticas, como SonarQube, pueden generar informes de los problemas de seguridad encontrados tras compilar la aplicación. En cuanto a los “code smells”, existen herramientas llamadas linters que ayudan a identificar y resolver estos problemas, promoviendo así el desarrollo de código limpio y mantenible.
Get Gomez M’s stories in your inbox
Join Medium for free to get updates from this writer.
Proceso de Integración Continua
Las secciones previas han discutido los procedimientos que preceden la unión del nuevo código con la totalidad de la aplicación. En el transcurso del desarrollo, resulta crítico que el equipo se asegure que el código no solo satisface las expectativas comerciales, sino que también se integra sin problemas con el resto de la aplicación, evitando la introducción de errores. Este es el punto en el que nos topamos con una de las prácticas esenciales de la cultura DevOps: la integración continua. Pero antes, veamos cómo se realizaba la integración antes de la adopción de esta práctica.
Integración Convencional
Anteriormente, era común que los programadores trabajaran de forma aislada durante un periodo extenso y luego intentaran incorporar sus modificaciones a la rama principal al terminar su trabajo. Esto resultaba en un proceso de integración prolongado, que involucraba código conflictivo o desactualizado y generaba una larga lista de errores. Profundicemos en cómo era este proceso y las consecuencias que tenía.
En la integración convencional, cada programador comenzaba con una copia del repositorio central. Todos los programadores partían del mismo lugar. Cada uno progresaba de manera independiente o en equipo. Incorporaban o modificaban clases, métodos, funciones y archivos, adaptando el código a sus requerimientos y las tareas asignadas.
Mientras tanto, otros equipos realizaban lo mismo, trabajando en sus tareas, añadiendo nuevas partes de código, modificándolo o corrigiendo errores asignados. Si observamos la situación global del proyecto, notamos que los programadores están modificando el contexto para otros programadores con cada cambio que hacen. Además, estos problemas se incrementan notablemente si los equipos son grandes y si los intervalos entre integraciones son extensos.
Integración Continua
La práctica de la integración continua implica la fusión o integración de cada modificación de código realizada por los programadores en un repositorio central de manera regular, y ejecutar una serie de pruebas automáticas sobre esta versión.
La frecuencia de esta integración regular será determinada por varios factores: tipo de aplicación, tiempo de compilación, tiempo de despliegue, tiempo de ejecución de las pruebas, tamaño del equipo, etc. El equipo deberá decidir la frecuencia que le proporciona el máximo beneficio. Probablemente, el caso más común es la integración continua diaria, en la que todos los commits realizados durante el día son compilados y desplegados durante la noche para ser probados el día siguiente.
Beneficios
Esta práctica aporta muchos beneficios al desarrollo:
• Reducción del riesgo durante la integración. Es común que el código producido dependa de otros cambios y partes de la aplicación, y que las modificaciones de otros entren en conflicto con las nuestras. Si integramos de manera frecuente, es mucho más fácil resolver estos problemas y conflictos.
• Mejoramiento de la calidad del código. Ayuda a minimizar los problemas en los sistemas por errores de código, proporcionando un código más robusto que mejora la calidad del software.
• Detección de errores más rápida y fácil. Al integrar de manera continua y regular, es más fácil detectar los errores justo cuando ocurren, facilitando su resolución. Esto facilita mucho el trabajo del equipo de QA.
• Reducción de tareas repetitivas y manuales. Los procesos repetitivos y manuales pueden llevar a errores, al automatizarlos eliminamos esa posibilidad.
• Posibilidad de crear versiones de prueba en cualquier momento. Al realizar una integración constante del código, podemos liberar una versión del software en cualquier momento y sin los errores que ya se detectaron y corrigieron previamente. Esto es muy útil si nos encontramos en una situación en la que necesitamos lanzar un parche de la solución de forma urgente.
• Visibilidad completa del proyecto. Permite tener una visión muy específica del avance del proyecto y con métricas sobre la calidad del código producido. Esto nos permitirá tomar mejores decisiones sobre cambios y mejoras.
• Mayor confianza y seguridad del equipo de trabajo. La integración continua permite tener un software probado y funcional en todo momento, lo que contribuye a la confianza del equipo pudiendo ver y probar los avances del software durante su desarrollo. Además, ya no es necesario esperar hasta el final del proyecto para verificar que todo se ha realizado de la forma correcta y cumpliendo con las expectativas comerciales.
Proceso
En la práctica, la integración continua (CI o continuous integration) requiere un servidor que está constantemente monitoreando los commits del equipo de desarrollo. Este servidor fusiona el último commit con el resto de la aplicación. Luego, toma el código entero y ejecuta el proceso de compilación con los parámetros que tenga configurados. Un equipo puede contar con varios servidores de CI si su aplicación se despliega en varios tipos de entornos (por ejemplo, un software que tiene versión para Windows, Mac y Linux), de ahí la parametrización del compilado y despliegue. Si se produce un error durante la compilación, el servidor notifica automáticamente al equipo. En este momento, el equipo debería comenzar a solucionar los errores antes de seguir generando nuevo código.
El entorno de CI
Como hemos visto en la sección anterior, la integración continua se basa en un servidor de CI que está atento a los cambios de código y realiza una serie de operaciones de forma automática. Este servidor deberá tener una serie de requisitos y herramientas para llevar a cabo su tarea. Veamos los principales componentes que debe tener este entorno:
• Sistema de control de versiones. En secciones previas de este documento, hemos hablado del control de versiones y del repositorio de la aplicación. Este sistema hace posible tener un proceso seguro y fiable para centralizar y guardar todos los cambios realizados en el proyecto durante su vida.
• Máquinas virtuales, contenedores o servidores. La mayoría de las aplicaciones se despliegan o instalan en algún entorno; ya sea entornos de movilidad emulados, aplicaciones web que requieren de un servidor, software que se instala directamente en el ordenador, etc. Un entorno de CI es básicamente una representación del entorno final donde se desplegará la aplicación. Esta representación puede ser en forma de máquina virtual (alojada en un servidor o en la nube), un contenedor o un servidor dedicado.
• Herramientas de automatización. Dependiendo de la naturaleza de nuestro proyecto, vamos a necesitar ciertas herramientas en este entorno, como por ejemplo, Jenkins, Bamboo, TestRails, Cypress, etc. En el segundo tema de este módulo vamos a ver algunos ejemplos de estas herramientas.
Diferencias basadas en la característica del producto
Existen varias especialidades en el desarrollo de software hoy en día. Consideremos algunas de ellas y los desafíos que presentan para la infraestructura de integración continua:
Aplicaciones tradicionales para computadoras
Estas no representan ningún desafío adicional ya que son programas que se instalan en un sistema operativo. Simplemente virtualizando el entorno final e instalándolas debería ser suficiente.
Aplicaciones móviles
Con el auge de los dispositivos móviles hace unos años y la gran cantidad de sistemas operativos disponibles, el desarrollo de aplicaciones móviles se ha vuelto considerablemente más complejo. Existen frameworks como Xamarin que simplifican parte de la complejidad de desarrollar para múltiples sistemas operativos y proporcionan herramientas para convertir tu código fuente en código nativo para los sistemas operativos más populares. Sin embargo, garantizar que una aplicación funciona en la mayoría de los teléfonos del mercado y con las últimas versiones de los sistemas operativos puede ser una tarea colosal.
Este tipo de desarrollo plantea el desafío de automatizar la verificación de calidad en tantos dispositivos como sea posible para cubrir la mayor cantidad de casos. Esto es posible mediante la emulación del hardware de cada teléfono junto con la emulación del sistema operativo. Hay empresas que proporcionan a los desarrolladores imágenes virtuales de los teléfonos más populares del mercado para que puedan instalar y probar sus aplicaciones de forma automática.
Además de las aplicaciones móviles, recientemente el número de pantallas donde una aplicación está disponible ha crecido exponencialmente (SmartTVs, smartwatches, electrodomésticos conectados, dispositivos domóticos como Alexa o Google Home, etc.). Esto plantea el desafío de no solo simular el hardware en el que se ejecutará la aplicación, sino que también exige una interfaz de usuario fluida y adaptable a casi cualquier tamaño de pantalla.
Aplicaciones web
En el pasado, las páginas web consistían en contenidos estáticos que se presentaban con un formato visual definido por el creador de la página. Hace tiempo que las páginas web se han vuelto más parecidas a las aplicaciones tradicionales y se han convertido en servicios que contienen una lógica de negocio capaz de realizar operaciones muy complejas. Estas aplicaciones presentan desafíos significativos al crear entornos de CI ya que requieren de una interfaz web o frontend y un servidor que contenga la lógica de la aplicación o backend.
Esta dualidad implica que necesitaremos entornos de CI que representen ambos lados y, en muchos casos, eso requerirá despliegues, pruebas y otros procesos automáticos completamente distintos e independientes.
Desarrollo de firmware
Este tipo de desarrollo requiere una emulación del hardware sobre el que se instala el firmware. Muchas veces, el desarrollo de un simulador se incluye en el propio proyecto. En algunos casos, el entorno de CI será una máquina física. Por ejemplo, en el caso del firmware desarrollado para una impresora 3D, puede requerir un simulador de las cabezas que inyectan el plástico fundido para crear la pieza, o bien una impresora real sobre la que se instala automáticamente la nueva versión del firmware. En ambos casos, el entorno de CI tiene requisitos adicionales específicos.
https://www.canva.com/design/DAFmTjAy_LQ/t527v0-rfSCKIdS40TXysg/view#9







