2/5 Continuous Delivery

8 buenas prácticas de Continuous Delivery presentes en las empresas líderes de desarrollo de Software.

2/5 Continuous Delivery
💡
Las siguientes 8 capacidades de Continuous Delivery, es parte de la serie de 24 capacidades presentes en las empresas líderes en desarrollo de software.

6. Control de versiones

Si bien hoy en día la gran mayoría de las empresas utiliza herramientas para control de versiones como GIT, SVN o Mercurial, GIT es sin duda la más utilizada. Y en cuanto a las plataformas de gestión de versiones, destacan Gitlab, Github y  Bitbucket entre otras.

Ahora bien, seguro usted está ahora pensado... —nosotros ya usamos GIT para gestionar las versiones de nuestra aplicación—. Sin embargo, debe recordar que el control de versiones no sólo se refiere al código de la aplicación o servicio, sino también para la configuración del sistema, configuración de la aplicación, scripts de automatización y despliegue, y últimamente, infraestructura como código.

En la mayoría de las empresas no se suele versionar la configuración del sistema, o los scripts que ejecutan, sin embargo, se ha visto que las empresas líderes sí llevan el registro versionado de todos los elementos que componen sus servicios. De esta manera, tienen la historia e información completa de sus proyectos, haciendo que el seguimiento y gestión sea más sencillo.

7. Automatizar los pasos a producción

Esta práctica se refiere a la implementación de un proceso de despliegue automatizado a los ambientes productivos. Cualquier cambio en la aplicación, ya sea la incorporación de nueva funcionalidad, un cambio en la configuración, un corrección de un error, o un experimento, debe ser llevado a los ambientes productivos de manera segura, rápida y confiable, sin la intervención manual de una persona.

Para ello, el proceso, debe ser transparente para el usuario final, en la medida de que no exista una interrupción del servicio en ningún instante. Además, el proceso debe tener un bajo costo de ejecución, lo que habilita o permite que se realicen iteraciones de forma frecuente a lo largo del día. Al final y al cabo, son las herramientas y los computadores los ejecutan este tipo de tareas, liberando el tiempo de las personas.

8. Integración Continua (CI)

A veces es difícil entender un proceso sin ver lo que hace. La integración continua es de esos procesos que al verlo funcionar uno dice: guauuu, porque es ahí, donde “las papas queman”, donde se dimensiona lo importante y crítico de este proceso. Sobre todo cuando uno ha tenido que lidiar con engorrosos procesos de integración y pruebas de código.

Para poner el proceso de integración de código en perspectiva, es bueno conocer los dolores y problemas cuando el proceso es deficiente, es decir, el método antiguo de integración.

Durante las distintas etapas del desarrollo de software, los ingenieros al finalizar una nueva funcionalidad, deben mezclar —merge— el nuevo código con el código de la rama principal. Este proceso es lento y a veces complejo, sobre todo cuando la rama principal también ha sido modificada por otros profesionales. Cuanto más tiempo pasa, más complejo suele ser. El proceso puede tomar horas o incluso un par de días a una persona.

Una vez mezclado el nuevo código a la rama principal, el desarrollador deberá compilar el código para construir la aplicación. Este proceso de construcción es generalmente rápido, aunque dependerá del lenguaje y del tamaño del repositorio. Una vez compilado el código, la persona deberá ejecutar una serie de tests o pruebas, para validar que el nuevo código no afecte otras áreas del software.

Esta etapa de testing puede durar días o semanas. Sí, semanas; incluso con el apoyo de un equipo de 4 a 6 personas. Lo he visto en varios proyectos. Cuando la aplicación es muy grande, también lo es la cantidad de flujos y funcionalidades. Si a eso le sumamos la inexistencia de pruebas automatizadas, tenemos el escenario perfecto de ineficiencia. Sólo se validará el nuevo código implementado mediante la ejecución de un sin fin de pruebas funcionales. Este set de pruebas lo ejecuta entonces un pool de usuarios que realiza uno a uno los flujos o historias de usuario. Y así, después de una par de semanas, —y sólo cuando el resultado de las pruebas haya sido exitoso—, podremos estar seguros de que la aplicación funciona correctamente. Cualquier error implicará suspender las pruebas y devolver el problema al desarrollador para que sea corregido.

Todo el proceso de integración, construcción y testing que acabamos de ver, se puede empaquetar y automatizar. Eso es Continuous Integration o CI. Una vez implementado, el proceso de CI comienza a ser una rutina que también conlleva un componente cultural: los desarrolladores aprender a integrar con mayor frecuencia, lo que les permite encontrar y corregir errores más rápidamente. También mejoran la calidad del software y reducen el tiempo necesario para validar y publicar nuevas actualizaciones.

Por último, y para cerrar este punto, no siempre es factible implementar un proceso automatizado de CI. Se requiere tiempo. Tiempo para la construcción de pruebas unitarias y de integración, es decir, tiempo de calidad. Pero también este tiempo lo debe dedicar, idealmente, la misma persona que escribió el código. Es mucho más difícil escribir las pruebas de testing cuando ya ha pasado el tiempo.

Además, cuando no existen pruebas de testing, o éstas son muy pocas, tiene poco valor escribir una prueba que sólo cubrirá un bajo porcentaje del software. Aquí entra el concepto de teoría de la ventana rota.

En definitiva, la implementación de la Integración Continua debe darse desde un comienzo, y debe ser una práctica necesaria y obligatoria dentro el proceso de desarrollo, de tal forma que las pruebas tengan un porcentaje de cobertura mayor al 70%. Es un camino que se debe recorrer a través del aprendizaje e iteración. Por eso es que en software legado o en proyectos de desarrollo en que la práctica de CI nunca se ha ejercido, es que prácticamente imposible de implementar.

9. Desarrollo en rama troncal (master)

GIT es una herramienta para control de versiones de código, y hoy en día es indispensable. Todo el mundo la utiliza —o al menos debería, es lo mínimo. Pero, existen múltiples formas de trabajar las “ramas” con GIT. En algunas empresas se utiliza el estándar gitflow, en otras se crean nuevas ramas por cada nueva funcionalidad —“feature branch”—, pero las empresas líderes utilizan la opción de “trunk-based” o rama troncal.

Tanto el estándar de gitflow como feature branch requieren de un engorroso proceso de revisión o code review, ya que el incorporar las nuevas ramas en la rama principal muchas, a veces requiere de un buen tiempo de un ingeniero para resolver los conflictos entre la distintas versiones del código.

Por el contrario, el estándar de utilizar una única rama troncal o trunk-based, es la preferida por las empresas líderes. Cada profesional trabaja en ramas de corta duración, —de 1 o 2 días— que luego es incorporada en la rama principal. Este proceso sin embargo, requiere “pasar” un extenso proceso de testing automatizado, de tal forma que ante cualquier error, el desarrollador lo puede arreglar al momento.

En todo caso, vale la pena destacar que para pequeños equipos de desarrollo, —5 personas o menos— no importa mucho qué estándar estén utilizando. El estándar de trunk-based será realmente útil y eficiente cuando sean muchas las personas trabajando en un mismo código.

10. Automatización de pruebas (tests)

Dentro del flujo de integración contínua, o CI, existe un especial foco en la automatización de pruebas unitarias y pruebas de integración.

Es importante distinguir entre pruebas manuales y automatizadas. Las pruebas manuales las realiza una persona, haciendo clicks en la aplicación o interactuando con el software y las APIs. Las pruebas manuales son de alto costo, ya que requiere que alguien configure un ambiente y ejecute las pruebas por sí mismo. Esto queda expuesta a errores humanos, ya que la persona puede cometer errores tipográficos u omitir pasos en el flujo de prueba.

Las pruebas automatizadas, en cambio, las realiza un servicio o proceso que ejecuta un script de prueba escrito en el código. Estas pruebas pueden variar en complejidad, desde la comprobación de un único método en una clase hasta asegurarse de que la ejecución de una secuencia de acciones complejas en la interfaz de usuario retorne los mismos resultados. Las pruebas automatizadas son mucho más robustas y confiables que las pruebas manuales, pero la calidad de las pruebas automatizadas depende de lo bien que se hayan escrito los scripts de prueba.

A veces el conjunto de pruebas automatizadas no son confiables, ya que generan falsos positivos o negativos. Así, no sirven las pruebas automatizadas. Las pruebas deben ser confiables, y, por lo tanto, se requiere invertir tiempo de forma constante para mejorar y optimizar el conjunto de pruebas.

Cuando el conjunto de pruebas es confiable, y las pruebas pasan todos los controles, el equipo estará tranquilo y confiado de que pueden liberar la nueva versión del software a producción. Y al mismo tiempo, si la prueba falla, es porque existe un defecto real que debe ser corregido.

Como recomendación, si las pruebas no son confiables, deben sacarse del repositorio. Las buenas prácticas sugieren y fomentan que la construcción de las pruebas de aceptación las realicen las mismas personas que escribieron el código y no personas externas o de QA. Quienes desarrollan, conocen el código, y, por tanto, sabrán qué funciones o métodos son importantes de validar. Esto genera dos efectos. Por una lado se crea un sentido de responsabilidad, que hará que el desarrollador invierta tiempo en la mantención y corrección de las pruebas. Y por otra parte, el código tendrá una mayor cobertura de las pruebas realmente importantes.

Vale la pena mencionar que muchos jefes de proyecto, product owners, e incluso los mismos desarrolladores, desdeñan y minimizan esta práctica, considerándola muchas veces una pérdida de tiempo. Esto se da generalmente cuando las personas no conocen los beneficios y ventajas de las pruebas automatizadas, por lo que será importante capacitar y educar al equipo respecto de esta.

11. Test Data Management

Otro elemento que puede parecer burdo pero que en ocasiones es complejo, es la utilización de buenos datos para las pruebas automatizadas. Un buen conjunto de pruebas, requiere de un buen conjunto de datos. Aquí nos estamos refiriendo a los valores de entrada o “inputs” que nuestras funciones o módulos deberán testear.

La mayoría de las empresas utilizan los mismos datos del ambiente productivo, pero esto puede tener varias desventajas como el manejo de información sensible o pruebas acotadas.

Un buen conjunto de datos será mejor construirlo, y debe incluir no sólo valores de entrada, sino también condiciones y escenarios útiles que permitan validar el funcionamiento de nuestro código.

Además, los datos de pruebas no sólo se construyen para validar los escenarios positivos de nuestro software, sino también se deben testear posibles valores erróneos para probar el comportamiento de las excepciones de la aplicación. Por ejemplo, se suelen probar valores vacíos o en la blanco, o bien distintos a lo que solicita. Si en un campo se le pide al usuario ingresar su teléfono, ¿cómo se comporta si ingresamos sólo texto? También existen los casos de borde, como por ejemplo, la edad de un usuario de más de 100 años.

En definitiva, los datos deben ser de calidad y representativos, de tal forma de evaluar casos de uso real. ¿Tiene usted un buen conjunto de datos de prueba?

12. Seguridad

El tema de seguridad puede ser inagotable, tanto por la cantidad de elementos, como por la profundidad de cada uno de ellos. Dado que son muchas áreas, sólo se abordarán algunos conceptos básicos.

Otras áreas de seguridad que no abordaremos acá son: Cloud (redes, accesos, permisos), infraestructura (hardware, sistemas operativos, Privileged Access Management, etc).

Cyber amenazas

Los riesgos en materia de seguridad son diversos y evolucionan constantemente. Pueden ser muy básicos, o muy sofisticados. Los siguientes amenazas son bien comunes —casi diarias en algunos casos—:

  • Ataques de phishing
  • Malware
  • Fuerza bruta
  • Packet sniffing
  • DDoS o  Denegación de Servicio Distribuido

El phishing consiste en engañar a las personas para que revelen información sensible haciéndose pasar por una entidad de confianza. Por ejemplo, enviando un email para que respondas una encuesta del ambiente laboral de la empresa en que trabajas. Un link al dominio “encuesta-laboral.tuempresachile.com” muestra una página con los colores corporativos y logo de tu empresa. Parece real. Pero al iniciar la encuesta te piden tu login y password corporativo para validar tu identidad. Sin embargo, lo que busca es recolectar tus datos para luego usarlos para acceder a los sistemas de tu empresa.

Las personas son típicamente el eslabón más débil de la cadena. Para empresas que tienen miles de usuarios, es altamente probable que varios de ellos “caigan” en la trampa. Por eso, para protegerse del phishing se sugiere:

  • Capacitar a los empleados para que sean escépticos con los emails no solicitados. Si pide información sensible, lo mejor será desconfiar.
  • Capacitar a los empleados para que verifiquen la legitimidad de los enlaces antes de hacer clic comprobando la URL.
  • La empresa debe implementar filtros avanzados de email para identificar y filtrar posibles intentos de phishing.
  • La empresa debe realizar pruebas de phishing, para validar la preparación de los usuarios.

Respecto del malware, este suele ser un virus que se infiltra en los sistemas para comprometer los datos o exigir un rescate por su liberación. El más famoso es el randomware, que ha afectado a cientos de miles de usuarios. Estos virus se suelen esconder bajo la apariencia de un archivo normal, por lo que se les conoce como “troyanos”. Básicamente se camufla como archivo adjunto o descargable, esperando ser ejecutado por el usuario. Para prevenir, las empresas suelen:

  • Implementar soluciones de antivirus corporativos en todos los equipos.
  • Actualizar frecuentemente los sistemas para parchear vulnerabilidades.
  • Capacitar a los usuarios para evitar descargar archivos o hacer clic en enlaces de fuentes desconocidas.

Los ataques de fuerza bruta son repetidos intentos por ingresar a un sistema o aplicación probando distintas combinaciones de password. Para protegerse, las empresas deben obligar a sus empleados a:

  • Utilizar contraseñas complejas y únicas para cada cuenta.
  • Habilitar la autenticación de dos factores (2FA).
  • Requerir la actualización de la contraseña cada cierto tiempo.

El packet sniffing es un método de detección y evaluación de paquetes de datos enviados a través de una red. Cuando nos conectamos a un red Wifi por ejemplo —en un cowork, en el metro, etc—, estamos transmitiendo nuestra información por el aire. Si dicha información no está encriptada, entonces cualquier persona podría ver dichos paquetes y acceder a información sensible. Básicamente nos estarían espiando y accederían a toda la información que transmitimos. Se recomienda:

  • Utilizar conexiones seguras y cifradas (HTTPS).
  • Evitar el uso de Wifi públicas para transacciones sensibles. Utilizar VPN.

Por último, el ataque de denegación de servicio distribuido (DDoS) es un intento malicioso de interrumpir el tráfico normal de un servidor, servicio o red con una avalancha de tráfico de internet. Los ataques DDoS logran su eficacia utilizando múltiples sistemas informáticos infectados como fuentes del tráfico de ataque. Desde un alto nivel, un ataque DDoS es como un gran taco vehicular que obstruye la autopista, impidiendo que el tráfico normal llegue a su destino. Estos ataques son dirigidos para botar un servicio en particular. Existen múltiples ataques famosos a empresas como AWS, Google y Github. Se sugiere:

  • Habilitar herramientas de mitigación de DDoS.
Application Security — AppSec

Antes que nada, la seguridad de una aplicación no puede ser una actividad que se realice al final de un proyecto de desarrollo, sino que debe ser una parte integral durante el proceso de construcción y diseño.

Ahora bien, ¿que hacen los ingenieros de seguridad para prevenir las amenazas? O más bien, ¿que hacen para construir y desarrollar soluciones y aplicaciones seguras? Típicamente el diseño y construcción se evalúa de acuerdo a estos términos:

  • Vulnerabilidad: Debilidad explotable en el diseño de un sistema.
  • Amenaza: La posibilidad de que un agente explote una vulnerabilidad.
  • Riesgo: Pérdida o daño que puede producirse cuando se materializa una amenaza.

El responsable de la seguridad de un software debe encargarse de:

  • Prevenir y detectar de riesgos: Defiende los activos de la empresa: sus datos, aplicaciones, código, infraestructura, etc.
  • Respuesta y recuperación: Reacciona rápidamente ante las amenazas y remediar los ataques.

Por último, una práctica muy común en aplicaciones de uso masivo, es contar con un programa de incentivos para la detección de errores o brechas de seguridad. Comúnmente se les conoce como “Bug bounty program for getting bugs”.

Zero Trust

La ideal del modelo Zero Trust no es otra que tratar a los usuarios internos como si fuesen usuarios externos. No se les entregan privilegios de accesos a los usuarios internos, ni tampoco éstos podrán saltarse los controles de seguridad. Bueno, claramente el diablo está en el detalles, pero en pocas palabras de eso se trata.

13. Continuous Delivery (CD)

El Continuous Delivery, o CD, es el conjunto de características que permiten que los cambios de una aplicación o software en su ambiente productivo sean de manera rápida, segura y confiable. Visto de otra forma, cualquier cambio al software, ya sea en su configuración, corrección de un error o en la incorporación de una nueva funcionalidad, que no interrumpa del servicio en ningún momento. Y por lo mismo, el CD es algo que puede ser realizado dentro del horario normal de trabajo, como parte de las rutinas diarias. Esto reduce tanto los dolores de los pasos a producción, como el burnout —agotamiento— de los equipos.

En el pasado era común reiniciar los servicios, o esperar que la nueva versión del software terminara de compilar, lo que inevitablemente implicaba una interrupción del servicio. Y por lo mismo, el proceso se realizaba en horarios no hábiles y se comunicaba a los usuarios respecto de la indisponibilidad de la aplicación en una “ventana de mantención” programada. Era una especie de evento tipo “big-bang”.

Como dijo W. Edward Deming en su popular libro “Out the Crisis” sobre los principios de gestión eficiente, “elimina la dependencia de la inspección para lograr la calidad”. Esto, llevado al concepto de CD, implica que a través de la inversión en la construcción de herramientas de testing —continuous integration—, es posible encontrar los errores rápidamente, y así éstos pueden se resueltos inmediatamente.

Por otra parte, se busca iterar en bloques pequeños, cambios de uno o dos días trabajo que puedan ser incorporados lo antes posible en la rama troncal —rama “master” típicamente—. Esto, que parece sin importancia, es vital para obtener feedback rápido de los usuarios. Así, evitamos un desvío, como realizar trabajo que no aporta valor para los usuarios, y podemos corregir el curso lo antes posible.

Todo este proceso de construcción, pruebas y despliegue a producción, debe ser un proceso automatizado que “vive” en el repositorio. Cualquier cambio en el repositorio gatillará el ciclo completo de revisión y testing, y ante una falla, el desarrollador lo podrá resolver inmediatamente.

Como las pruebas o testing son esenciales, deben trabajarse siempre como parte integral del proceso de desarrollo. La automatización de las pruebas unitarias y de aceptación deben ejecutarse cada vez que se realiza un nuevo commit (cambio) al repositorio.

La implementación del Continuous Delivery implica crear múltiples loops de revisión para asegurar una alta calidad del software que se está distribuyendo frecuentemente a los usuarios. Cuando se implementa correctamente, pasa a ser un rutina. Por lo demás, los procesos de pruebas son tareas que pueden y deben realizar los computadores por sí mismos, y así liberan de tiempo a los ingenieros, que podrán aportar valor en el diseño y construcción.

Mirado desde la perspectiva de los equipos de ingeniería, el Continuous Delivery debe entregar a los desarrolladores las herramientas para encontrar los problemas al momento, además del tiempo y recursos para invertir en la calidad del desarrollo, y la autoridad para corregir los problemas inmediatamente. Se crea un ambiente en que el profesional acepta la responsabilidad de su trabajo reflejada en la calidad y estabilidad de la solución.

Continuación ...

Puedes seguir leyendo esta serie de posts relacionados con la incorporación de las capacidades para un mejor desarrollo de Software. El siguiente post habla de las capacidades de arquitectura:

3/5 Arquitectura
Capacidades necesarias en torno a la arquitectura para un mejor desarrollo de software.