En muchos proyectos de desarrollo de software basados en la nube, se diseña y se mantiene un proceso de CI/CD para desplegar aplicaciones de forma eficiente, segura y productiva en entornos cloud.
El foco del proceso de CI/CD suele estar puesto en el lado cloud/remoto, pero rara vez se habla de la fase que viene justo antes: el desarrollo y testing local que ocurre en la laptop del developer.
En este artículo te voy a mostrar por qué muchos de los métodos comunes para el desarrollo local están lejos de ser ideales, y te voy a presentar una demo de un enfoque alternativo, cloud-native, que va a mejorar tu productividad (o la de tus developers). 🌤
Foto de Christina @ wocintechchat.com en Unsplash
👨🏼💻 Desarrollo local
Si bien no existe una buena práctica única que sirva para todos los casos —y varía según los requisitos y necesidades de cada organización o proyecto—, diría que el flujo típico de CI/CD se ve más o menos así:

Veamos qué pasa justo antes del paso #1: el desarrollo local.
En la mayoría de los casos, esto sucede en la máquina del developer. Es decir, planificar, diseñar, escribir código, escribir tests, ejecutarlos y, eventualmente, armar un POC; y, en algunos casos, verificar manualmente que todo funcione como se espera.
Muchos proyectos de desarrollo invierten muchísimo en setups locales que imitan el entorno cloud para probar si la nueva funcionalidad o fix que vas a introducir funciona bien, sin causar regresiones en otras funcionalidades y lógica.
Este tipo de setup se usa principalmente para correr ciertos tipos de tests automatizados (de sistema, de integración o end-to-end). Y, oye, si quieres, hasta puedes hacer pruebas manuales 😏.
Además, los entornos locales suelen usarse para depurar tu código. Algunos developers son fieles seguidores de lo que yo llamo "desarrollo orientado al debug", aunque la cosa va más allá. Depurar resulta útil cuando quieres investigar un bug complicado o entender mejor un flujo complejo.
Métodos comunes para el desarrollo local
Imagina que estás trabajando en un proyecto con un montón de microservicios desplegados en Kubernetes. Estos son algunos métodos habituales en la industria que me ha tocado ver:
- Método con docker-compose: escribir y mantener manifiestos de docker-compose (que son una especie de duplicado de los manifiestos YAML de K8s ya existentes) que levantan tus servicios y dependencias de terceros para hacer pruebas locales.
- Método con K8s local: levantar un cluster local de Kubernetes con minikube/k3s/kind/u otro, y desplegar tus servicios localmente para hacer pruebas, combinado con un puñado de scripts.
- Método con suites de tests: Makefiles o scripts bash que arrancan suites de tests de integración por servicio, donde las dependencias de terceros se levantan localmente como contenedores vía Docker (dockertest, por ejemplo, es una opción popular para este tipo de setup).
Todas son opciones válidas para correr y probar tu aplicación localmente.
Desventajas de los métodos comunes de desarrollo local
Sin embargo, los métodos mencionados arriba traen algunas desventajas, entre ellas:
Falta de soporte local de terceros
Hay dependencias cloud de terceros que no ofrecen una manera de correr localmente. En algunos casos puedes emularlas haciendo "mocking/stubbing" de las APIs, pero eso requiere mucho mantenimiento y no representa de verdad la realidad. Es una especie de truco. ¿Y si, por ejemplo, una de tus consultas a la base de datos tiene un error? ¿Cómo te darías cuenta vía testing/debug si no estás "hablando" con una instancia real de DB?
Problemas con uso intensivo de recursos
Si tu aplicación consume muchos recursos, puede que tu laptop no dé el ancho. Por ejemplo, corres un test de estrés para reproducir un memory leak. O un bug que solo se reproduce cuando hay 20 réplicas de tu aplicación corriendo en simultáneo. Al final, te das cuenta de que tu laptop se queda colgada 😅. Eso golpea la productividad.
Apagones mientras depuras
Si quieres probar o depurar un bug difícil o esquivo, que aparece solo cada varios días, tu máquina local podría apagarse, dormirse o hibernar, y obligarte a empezar de cero.
Sin una única fuente de verdad
Con el método con docker-compose, terminas violando el principio de "única fuente de verdad" al mantener múltiples representaciones de tus entornos.
Correr tests de integración ≠ integración real
Con el método con suites de tests, aunque correr tests de integración es una buena práctica con muchos beneficios (incluso al hacerlo localmente), no es lo mismo que integrar tu cambio en un entorno cloud real. En algunos casos, hay bugs y fallas de integración o despliegue que podrían pasar desapercibidos (problemas de conectividad de red con otros servicios, problemas de configuración, crashes en el arranque, etc.).
Las desventajas descritas afectan tu experiencia de desarrollo y te hacen menos productivo. Y, en muchos casos, no reflejan la realidad (un caso clásico de "¡Funciona en mi máquina!"), por lo que aportan poco valor.
¿Existe una manera mejor y más moderna de encarar la fase de desarrollo local, que mejore la productividad y la usabilidad? Sí, claro que sí.
☁️ **_Entornos de desarrollo cloud-native_**
Ahora vamos a explorar los entornos remotos, cloud-native.
Al transformar tu entorno local en un entorno remoto, los problemas mencionados arriba dejan de existir. Y, además, llevas tu experiencia de desarrollo a otro nivel: una experiencia moderna, confiable y cloud-native.
¿Cómo funcionan los entornos de desarrollo cloud-native?
Asignas un entorno cloud independiente a cada developer, que es una réplica (no idéntica, pero bastante cercana) de tus entornos de desarrollo/producción.
Con un enfoque de entorno personal cloud-native, cada developer del equipo tiene la autonomía de usar su entorno como mejor le parezca. Ya sea para probar nuevas funcionalidades y bug fixes, reproducir bugs o, incluso, experimentar para aprender. Todo en un entorno muy parecido a producción. ✌️
Después puedes adaptar tu tooling para sacarles más provecho a estos entornos, por ejemplo, ejecutando pruebas manuales/automatizadas, "espejando" tráfico desde producción hacia los entornos personales, etc.
Otra cosa valiosa que se me ocurre es hacer debugging remoto en vivo en estos entornos, en aislamiento total, sin interferir con otros entornos. Puedes aprovechar herramientas off-the-shelf hechas justo para esto (veremos un ejemplo en el próximo capítulo).
Puntos a favor en productividad, mantenibilidad y confiabilidad
Hay muchas razones por las que deberías pasarte a entornos de desarrollo cloud-native, pero te comparto mis ocho favoritas.
Vas a:
- Reutilizar tus manifiestos YAML de K8s existentes, sin tener que mantener configuraciones de despliegue duplicadas. (Manteniéndolo DRY 🌵)
- Desplegar a un entorno cloud de la misma forma que despliegas a dev/prod, pero con aislamiento adicional.
- Dejar de depender del hardware de tu laptop. La infraestructura cloud puede escalar fácilmente hacia arriba si quieres correr tests que demandan muchos recursos, y reducirse el fin de semana.
- Lanzar tests de larga duración en tu entorno sin miedo a que tu laptop se apague o entre en hibernación.
- Detectar problemas de integración rápidamente, ya que estás usando APIs reales de servicios cloud, y no mocks (por ejemplo, puedes comunicarte con PubSub, Cloud SQL, Datastore y muchos otros servicios cloud).
- Aprovechar la infraestructura integrada que ofrece la nube para monitoreo, alertas, profiling, tracing y agregación de logs, igual que la aprovechas en producción.
- Ahorrar tiempo al no tener que correr scripts de setup elaborados todo el tiempo. La nube es rápida. Tu entorno está listo cuando lo necesites.
- Usar estos entornos para aprender Kubernetes en la práctica (si recién empiezas), sin afectar otros entornos.
Por último —además de los developers—, los engineers de DevOps/SRE también pueden beneficiarse de un entorno personal corriendo cambios de configuración de infraestructura en el suyo, antes de tocar entornos compartidos.
🙋♂️ ¡Hola! ¡Tengo una pregunta!
¿Mi factura de la nube se va a disparar?
La idea inicial es aprovisionar una réplica completamente separada e independiente de tus instancias cloud para cada developer, aisladas por proyectos, donde cada proyecto contiene una réplica de tu infraestructura.
En general, es una buena práctica. Sobre todo desde el punto de vista de seguridad, ya que da un buen aislamiento. Sin embargo, el costo de este enfoque puede convertirse en un problema, especialmente a medida que tu empresa o equipo crece.
Como estamos hablando de entornos de desarrollo, que en muchos casos están aislados del entorno de producción, puedes hacer un tradeoff entre costos y seguridad.
Nota al margen: este tradeoff entre seguridad y costos varía entre organizaciones y requisitos; asegúrate de entender las implicancias. Esta guía de seguridad de GKE te puede ayudar.
Entonces, ¿cómo hacemos ese tradeoff? Puedes aprovechar las funcionalidades de multi-tenancy de tus servicios cloud para ahorrar costos compartiendo recursos entre entornos.
A esto se le suele llamar "Soft Multi-Tenancy", donde lo "Soft" limita el riesgo al permitir que varios usuarios de la misma organización "de confianza" compartan los recursos.
Por ejemplo, todos los developers de un equipo que trabajan en una aplicación pueden compartir un cluster de K8s, donde cada developer tiene todo el stack de la aplicación replicado en su propio namespace de K8s.
Otro ejemplo: todos los developers comparten una única instancia de DB, donde cada uno tiene un usuario, schema y tablas dedicados.
Este es el enfoque que vamos a mostrar en el próximo capítulo.
¿Es complicado configurarlo y mantenerlo? 🤕🥸
¡La verdad que no! Puedes (y deberías) automatizarlo y hacerlo fácilmente reproducible con principios de Infrastructure as Code y GitOps.
Una vez que toda esa configuración está descrita como código, crear o destruir un entorno personal es cuestión de unos pocos clicks. Además, hay menos margen para el error humano, ya que el proceso está automatizado.
¿Por qué no entra dentro del alcance del entorno de desarrollo? 🤔
El entorno de desarrollo clásico no está aislado. Es compartido entre todos los developers. Tú haces merge de tu código y, al mismo tiempo, tu compañero hace merge del suyo, así que es básicamente un cóctel. No es viable depurar y experimentar en un entorno compartido cuando tu equipo crece.
Además, la etapa del entorno de desarrollo llega un poco tarde. Ya creaste un PR, pasaste el code review, hiciste merge del PR. Y ahora descubres que tus cambios hacen crashear el servicio de inmediato. Eso ya se considera un blocker y requiere un revert/rollback/hotfix. Te quita tiempo a ti y a tus compañeros developers.
¿Entornos personales o entornos efímeros? 👀
La respuesta corta es: depende de ti.
Básicamente, con los entornos personales le dedicas un entorno a una persona específica con un propósito a largo plazo. En cambio, con los entornos efímeros creas un entorno para un cambio específico (que se destruye después de hacer merge de ese cambio).
Mi colega
escribió un excelente artículo sobre entornos efímeros que también te recomiendo leer.
Definitivamente puedes hacer ambos, aunque podría ser excesivo para tus necesidades.
En la Parte 2 voy a ilustrar un setup así con una arquitectura de ejemplo, usando Terraform, ArgoCD y Telepresence. ¡Ve a la Parte 2!